Java获取客户端真实IP地址经典写法详解

 更新时间:2026年04月13日 09:36:46   作者:爱码少年 00fly.online  
在Web开发中,获取客户端的真实IP地址是一个常见需求,本文将分别给出经典写法(基于工具类/条件判断)和Lambda写法(基于函数式接口 + 流式处理)的实现,并进行对比

在Web开发中,获取客户端的真实IP地址是一个常见需求。由于客户端可能经过代理、负载均衡或CDN,request.getRemoteAddr() 往往只能拿到代理服务器的IP。因此,需要从特定的HTTP头(如 X-Forwarded-ForProxy-Client-IPWL-Proxy-Client-IP)中解析出原始IP。

下面分别给出经典写法(基于工具类/条件判断)和Lambda写法(基于函数式接口 + 流式处理)的实现,并进行对比。

经典写法(传统工具类)

使用 if-else 链和循环逐层解析,逻辑清晰,易于理解和调试。

import javax.servlet.http.HttpServletRequest;
public class IpUtils {
    /**
     * 获取客户端真实IP地址
     * @param request HttpServletRequest
     * @return 真实IP,如果无法获取则返回 "unknown"
     */
    public static String getClientIp(HttpServletRequest request) {
        String ip = request.getHeader("X-Forwarded-For");
        if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("Proxy-Client-IP");
        }
        if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("WL-Proxy-Client-IP");
        }
        if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("HTTP_CLIENT_IP");
        }
        if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("HTTP_X_FORWARDED_FOR");
        }
        if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getRemoteAddr();
        }
        // 对于通过多级代理的情况,取第一个非 unknown 的IP
        if (ip != null && ip.contains(",")) {
            ip = ip.split(",")[0].trim();
        }
        return ip;
    }
}

Lambda写法(Java 8+)

利用 Stream 和 Optional 对头名称列表进行链式处理,代码更紧凑、函数式风格更明显。

import javax.servlet.http.HttpServletRequest;
import java.util.Arrays;
import java.util.Optional;
public class IpUtilsLambda {
    private static final String[] HEADERS_TO_TRY = {
        "X-Forwarded-For",
        "Proxy-Client-IP",
        "WL-Proxy-Client-IP",
        "HTTP_CLIENT_IP",
        "HTTP_X_FORWARDED_FOR"
    };
    /**
     * 获取客户端真实IP地址(Lambda风格)
     * @param request HttpServletRequest
     * @return 真实IP,默认返回 request.getRemoteAddr()
     */
    public static String getClientIp(HttpServletRequest request) {
        String ip = Arrays.stream(HEADERS_TO_TRY)
                .map(request::getHeader)
                .filter(header -> header != null && !header.isEmpty() && !"unknown".equalsIgnoreCase(header))
                .findFirst()
                .orElseGet(request::getRemoteAddr);
        // 处理多级代理情况
        if (ip != null && ip.contains(",")) {
            ip = ip.split(",")[0].trim();
        }
        return ip;
    }
}

或者更进一步,将分割逻辑也融入流中:

public static String getClientIpAdvanced(HttpServletRequest request) {
    return Arrays.stream(HEADERS_TO_TRY)
            .map(request::getHeader)
            .filter(header -> header != null && !header.isEmpty() && !"unknown".equalsIgnoreCase(header))
            .map(header -> header.contains(",") ? header.split(",")[0].trim() : header)
            .findFirst()
            .orElseGet(request::getRemoteAddr);
}

两种写法对比

对比维度经典写法Lambda写法
可读性直观,每个条件分支清晰可见,适合所有Java开发者代码紧凑,但对不熟悉函数式编程的开发者有一定阅读门槛
代码行数较多(约20行)较少(约10行)
扩展性添加新头需要增加一个 if 块只需在 HEADERS_TO_TRY 数组中增加一个元素
性能几乎无差异,都是 O(n) 遍历,且只在请求生命周期内执行一次同样 O(n),但 Stream 会引入少量额外开销(微乎其微)
调试便利性可在任意 if 处打断点,逐行跟踪流内调试相对困难,需借助 peek() 或 IDE 的流调试插件
错误处理可针对每个头单独处理异常或日志需要额外在 map 或 filter 中处理,不够直观
团队接受度高,任何Java程序员都能立即理解依赖于团队对函数式编程的熟悉程度

注意事项与最佳实践

  • 代理信任X-Forwarded-For 头可以被伪造,如果应用直接暴露在公网,不应完全信任该头。通常需要在反向代理(如Nginx)层面配置 proxy_set_header X-Forwarded-For $remote_addr,并限制内部网络才能传递该头。
  • IP格式:IPv4和IPv6均可能出现在头中,无需额外处理,直接取字符串即可。
  • 多级代理X-Forwarded-For 的值格式为 client, proxy1, proxy2,通常取第一个(最左侧)为真实客户端IP。
  • 空值处理:许多代理会返回 unknown 字符串,需要过滤掉。
  • 性能考虑:获取IP的操作发生在每个请求中,但遍历几个头字符串的性能开销可以忽略不计。
  • Spring Boot集成:如果使用Spring Boot,也可以直接使用内置工具类 RequestUtils.getRemoteAddress() 或 ServletWebRequest.getHeaderValues(),但原理与上述相同。

方法补充

下面对比了两种Java获取客户端IP的实现方式。经典写法使用for循环遍历IP头信息数组,逐个检查并返回第一个有效IP;Lambda写法则利用Stream API,通过链式操作完成相同的逻辑,代码更简洁。两种方法都考虑了多种代理头信息(X-Forwarded-For等)和IP格式处理(分割、去空、trim),最终回退到request.getRemoteAddr()。Lambda写法展现了函数式编程的优势,但两者在功能上完全等效。

经典写法

    @Autowired
    HttpServletRequest request;
    
    String[] IP_HEADERS = {"X-Forwarded-For", "X-Real-IP", "Proxy-Client-IP", "WL-Proxy-Client-IP", "HTTP_X_FORWARDED_FOR", "HTTP_X_FORWARDED", "HTTP_X_CLUSTER_CLIENT_IP", "HTTP_CLIENT_IP", "HTTP_FORWARDED_FOR", "HTTP_FORWARDED", "HTTP_VIA", "REMOTE_ADDR"};
  
    private String getClientIpClassic()
    {
        log.info("##### Classic");
        for (String header : IP_HEADERS)
        {
            String ip = request.getHeader(header);
            if (StringUtils.isNotBlank(ip) && !StringUtils.equalsIgnoreCase("unknown", ip))
            {
                log.info("##### ip header: {}", header);
                String[] ips = ip.split(",");
                return ips[0].trim();
            }
        }
        log.info("##### request.getRemoteAddr");
        return request.getRemoteAddr();
    }

Lambda写法

    private String getClientIpLambda()
    {
        log.info("##### Lambda");
        return Arrays.stream(IP_HEADERS)
            .peek(log::info)
            .map(request::getHeader)
            .filter(ips -> StringUtils.isNotBlank(ips) && !"unknown".equalsIgnoreCase(ips))
            .flatMap(ips -> Arrays.stream(ips.split(",")))
            .filter(StringUtils::isNotBlank)
            .map(String::trim)
            .findFirst()
            .orElse(request.getRemoteAddr());
    }

总结

  • 经典写法:适合大多数项目,尤其是维护性优先、团队成员技术水平不一的场景。它直观、易于调试和扩展,没有额外的学习成本。
  • Lambda写法:适合追求代码简洁、熟悉函数式编程、且头名称固定不变的场景。它减少了样板代码,让意图更聚焦于“从一系列候选头中找出第一个非空的有效值”。

实际项目中,两种写法均可正确工作。如果项目已经使用 Java 8+ 并普遍采用 Stream API,Lambda写法可以提升代码的表达力;否则,经典写法更加稳妥。

到此这篇关于Java获取客户端真实IP地址经典写法详解的文章就介绍到这了,更多相关Java获取客户端IP地址内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Java并发编程之详解ConcurrentHashMap类

    Java并发编程之详解ConcurrentHashMap类

    在之前的文章中已经为大家介绍了java并发编程的工具:BlockingQueue接口、ArrayBlockingQueue、DelayQueue、LinkedBlockingQueue、PriorityBlockingQueue、SynchronousQueue、BlockingDeque接口,本文为系列文章第八篇.需要的朋友可以参考下
    2021-06-06
  • Java Web开发项目中中文乱码解决方法汇总

    Java Web开发项目中中文乱码解决方法汇总

    这篇文章主要为大家详细汇总了Java Web开发项目中中文乱码的解决方法,分析了5种Java Web中文乱码情况,感兴趣的小伙伴们可以参考一下
    2016-05-05
  • Java ConcurrentHashMap实现线程安全的代码示例

    Java ConcurrentHashMap实现线程安全的代码示例

    众所周知ConcurrentHashMap是HashMap的多线程版本,HashMap 在并发操作时会有各种问题,而这些问题,只要使用ConcurrentHashMap就可以完美解决了,本文将给详细介绍ConcurrentHashMap是如何保证线程安全的
    2023-05-05
  • Java实现单链表SingleLinkedList增删改查及反转 逆序等

    Java实现单链表SingleLinkedList增删改查及反转 逆序等

    单链表是链表的其中一种基本结构。一个最简单的结点结构如图所示,它是构成单链表的基本结点结构。在结点中数据域用来存储数据元素,指针域用于指向下一个具有相同结构的结点。 因为只有一个指针结点,称为单链表
    2021-10-10
  • 详解Java多线程编程中CountDownLatch阻塞线程的方法

    详解Java多线程编程中CountDownLatch阻塞线程的方法

    在Java中和ReadWriteLock.ReadLock一样,CountDownLatch的本质也是一个"共享锁",这里我们就来详解Java多线程编程中CountDownLatch阻塞线程的方法:
    2016-07-07
  • Java的Cglib动态代理实现方式详解

    Java的Cglib动态代理实现方式详解

    这篇文章主要介绍了Java的Cglib动态代理实现方式详解,CGLIB是强大的、高性能的代码生成库,被广泛应用于AOP框架,它底层使用ASM来操作字节码生成新的类,为对象引入间接级别,以控制对象的访问,需要的朋友可以参考下
    2023-11-11
  • Java基础之集合框架详解

    Java基础之集合框架详解

    这篇文章主要介绍了Java基础之集合框架详解,文中有非常详细的代码示例,对正在学习java的小伙伴们有非常好的帮助,需要的朋友可以参考下
    2021-04-04
  • SpringBoot_Cache自定义使用SimpleCacheManager方式

    SpringBoot_Cache自定义使用SimpleCacheManager方式

    这篇文章主要介绍了SpringBoot_Cache自定义使用SimpleCacheManager方式,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2023-07-07
  • Java8常用的新特性详解

    Java8常用的新特性详解

    这篇文章主要介绍了Java8常用的新特性详解,文中有非常详细的代码示例,对正在学习Java8新特性的小伙伴们有非常好的帮助,需要的朋友可以参考下
    2021-04-04
  • Spring MVC入门_动力节点Java学院整理

    Spring MVC入门_动力节点Java学院整理

    这篇文章主要介绍了Spring MVC入门,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-08-08

最新评论