JWT(json web tokens)是目前比较流行的跨域认证解决方案;说通俗点就是比较流行的token生成和校验的方案。碰巧公司有个app的项目的token采用了jwt方案,因此记录下后端项目集成jwt的过程,方便后续查阅。

一、jwt的简单介绍

    jwt生成的token是一种无状态的token,服务端不需要对该token进行保存;它一般由客户端保存。客户端访问请求服务时,服务端对token进行校验,然后进行各种控制。下面直接拿一个生成好的token来讲解
        
    通过上图我们可以发现jwt生成的token是非常长的字符串,并且字符串中有2个小点("."),通过这2个小点我们可以把这token分成3部分。
      • header:头部,是用来描述这个token是什么类型,采用了何种加密算法;token中header是经过base64编码的
      • payload:荷载,用来存放需要传递的数据。官方提供的几个标准字段,同时也可以自己往里面加自定义的字段和内容,用来存放一些不敏感的用户信息。可以简单的把它想像成一个Map集合;token中payload也是经过base64编码的
      • signature:签名,主要是将header和payload的base64编码后内容用点拼接在一起然后进行加密生成签名。服务端需要利用这签名来校验token是否被篡改(验签)
    所以通俗的来讲,token = base64(header) + "." + base64(payload) + "." + 签名
    网上很多博文对jwt的介绍都比较详细,因此本文就不再详细的介绍jwt相关细节,重点放在java代码该怎么写。jwt相关详细介绍可以参考如下链接:
    下面直接上代码

二、Maven依赖版本说明

    pom中部分重要jar包依赖版本如下:
    <!-- SpringBoot 版本 -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.4.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent> <!--jwt 依赖-->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
14
 
1
    <!-- SpringBoot 版本 -->
2
    <parent>
3
        <groupId>org.springframework.boot</groupId>
4
        <artifactId>spring-boot-starter-parent</artifactId>
5
        <version>2.1.4.RELEASE</version>
6
        <relativePath/> <!-- lookup parent from repository -->
7
    </parent>
8

9
        <!--jwt 依赖-->
10
        <dependency>
11
            <groupId>io.jsonwebtoken</groupId>
12
            <artifactId>jjwt</artifactId>
13
            <version>0.9.1</version>
14
        </dependency>

三、token生成和解析工具类及token认证拦截器的编写

(1)token生成和解析工具类编写

    该工具类需要具有如下功能
      • 生成jwt标准的token;生成token时支持把不敏感的用户信息放在token里面,后续解析token后可以直接使用这些用户信息
      • 解析token,校验token是否过期和篡改
        直接看下面代码
package com.psx.gqxy.web.jwt;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.exception.ExceptionUtils;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects; /**
* JwtUtils
* @author ZENG.XIAO.YAN
* @blog https://www.cnblogs.com/zeng1994/
* @version 1.0
*/
@Slf4j
public final class JwtUtils { /** 存放token的请求头对应的key的名字 */
private static String headerKey = "token";
/** 加密的secret */
private static String secret = "zxyTestSecret";
/** 过期时间,单位为秒 */
private static long expire = 1800L; static {
// TODO 上面变量的值应该从配置文件中读取,方便测试这里就不从配置文件中读取
// 利用配置文件中的值覆盖静态变量初始化的值
} /**
* 生成jwt token
*/
public static String generateToken(Map<String, Object> userInfoMap) {
if (Objects.isNull(userInfoMap)) {
userInfoMap = new HashMap<>();
}
// 过期时间
Date expireDate = new Date(System.currentTimeMillis() + expire * 1000);
return Jwts.builder()
.setHeaderParam("typ", "JWT") // 设置头部信息
.setClaims(userInfoMap) // 装入自定义的用户信息
.setExpiration(expireDate) // token过期时间
.signWith(SignatureAlgorithm.HS512, secret)
.compact();
} /**
* 校验token并解析token
* @param token
* @return Claims:它继承了Map,而且里面存放了生成token时放入的用户信息
*/
public static Claims verifyAndGetClaimsByToken(String token) {
try {
/* 如果过期或者是被篡改,则会抛异常.
注意点:只有在生成token设置了过期时间(setExpiration(expireDate))才会校验是否过期,
可以参考源码io.jsonwebtoken.impl.DefaultJwtParser的299行。
拓展:利用不设置过期时间就不校验token是否过期的这一特性,我们不设置Expiration;
而采用自定义的字段来存放过期时间放在Claims(可以简单的理解为map)中;
通过token获取到Claims后自己写代码校验是否过期。
通过这思路,可以去实现对过期token的手动刷新
*/
return Jwts.parser()
.setSigningKey(secret)
.parseClaimsJws(token)
.getBody();
}catch (Exception e){
log.debug("verify token error:[{}] ", ExceptionUtils.getStackTrace(e));
return null;
}
} public static String getHeaderKey() {
return headerKey;
} }
83
 
1
package com.psx.gqxy.web.jwt;
2
import io.jsonwebtoken.Claims;
3
import io.jsonwebtoken.Jwts;
4
import io.jsonwebtoken.SignatureAlgorithm;
5
import lombok.extern.slf4j.Slf4j;
6
import org.apache.commons.lang3.exception.ExceptionUtils;
7
import java.util.Date;
8
import java.util.HashMap;
9
import java.util.Map;
10
import java.util.Objects;
11

12
/**
13
 * JwtUtils
14
 * @author ZENG.XIAO.YAN
15
 * @blog https://www.cnblogs.com/zeng1994/
16
 * @version 1.0
17
 */
18
@Slf4j
19
public final class JwtUtils {
20

21
    /** 存放token的请求头对应的key的名字 */
22
    private static String headerKey = "token";
23
    /** 加密的secret */
24
    private static String secret = "zxyTestSecret";
25
    /** 过期时间,单位为秒 */
26
    private static long expire = 1800L;
27

28
    static {
29
        // TODO 上面变量的值应该从配置文件中读取,方便测试这里就不从配置文件中读取
30
        // 利用配置文件中的值覆盖静态变量初始化的值
31
    }
32

33

34
    /**
35
     * 生成jwt token
36
     */
37
    public static String generateToken(Map<String, Object> userInfoMap) {
38
        if (Objects.isNull(userInfoMap)) {
39
            userInfoMap = new HashMap<>();
40
        }
41
        //  过期时间
42
        Date expireDate = new Date(System.currentTimeMillis() + expire * 1000);
43
        return Jwts.builder()
44
                .setHeaderParam("typ", "JWT")   // 设置头部信息
45
                .setClaims(userInfoMap)               // 装入自定义的用户信息
46
                .setExpiration(expireDate)            // token过期时间
47
                .signWith(SignatureAlgorithm.HS512, secret)
48
                .compact();
49
    }
50

51
    /**
52
     * 校验token并解析token
53
     * @param token
54
     * @return Claims:它继承了Map,而且里面存放了生成token时放入的用户信息
55
     */
56
    public static Claims verifyAndGetClaimsByToken(String token) {
57
        try {
58
            /* 如果过期或者是被篡改,则会抛异常.
59
                注意点:只有在生成token设置了过期时间(setExpiration(expireDate))才会校验是否过期,
60
                可以参考源码io.jsonwebtoken.impl.DefaultJwtParser的299行。
61
                拓展:利用不设置过期时间就不校验token是否过期的这一特性,我们不设置Expiration;
62
                      而采用自定义的字段来存放过期时间放在Claims(可以简单的理解为map)中;
63
                      通过token获取到Claims后自己写代码校验是否过期。
64
                      通过这思路,可以去实现对过期token的手动刷新
65
            */
66
            return Jwts.parser()
67
                    .setSigningKey(secret)
68
                    .parseClaimsJws(token)
69
                    .getBody();
70
        }catch (Exception e){
71
            log.debug("verify token error:[{}] ", ExceptionUtils.getStackTrace(e));
72
            return null;
73
        }
74
    }
75

76
    public static String getHeaderKey() {
77
        return headerKey;
78
    }
79

80

81
}
82

83

(2)token身份认证拦截器的编写

    拦截器主要作用如下:
      • 1)拦截器拦截到请求后,拿请求头中的token,如果不存在只直接response输出token不能为空
      • 2)拿到token后,进行token的解析,校验是否篡改或者过期。如果被篡改或者过期只直接response输出token已失效
      • 3)如果校验都通过了,则把token中解析出的用户信息放在request请求域中,方便后续Controller方法取用户信息
        直接看参考下面代码
package com.psx.gqxy.web.jwt;
import com.alibaba.fastjson.JSON;
import com.psx.gqxy.common.base.CommonConstant;
import com.psx.gqxy.common.base.ModelResult;
import io.jsonwebtoken.Claims;
import org.apache.commons.lang3.StringUtils;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException; /**
* jwtToken校验拦截器
* @author ZENG.XIAO.YAN
* @blog https://www.cnblogs.com/zeng1994/
* @version 1.0
*/
public class JwtInterceptor extends HandlerInterceptorAdapter { public static final String USER_INFO_KEY = "user_info_key"; @Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 获取用户 token
String token = request.getHeader(JwtUtils.getHeaderKey());
if (StringUtils.isBlank(token)) {
token = request.getParameter(JwtUtils.getHeaderKey());
}
// token为空
if(StringUtils.isBlank(token)) {
this.writerErrorMsg(CommonConstant.UNAUTHORIZED,
JwtUtils.getHeaderKey() + " can not be blank",
response);
return false;
}
// 校验并解析token,如果token过期或者篡改,则会返回null
Claims claims = JwtUtils.verifyAndGetClaimsByToken(token);
if(null == claims){
this.writerErrorMsg(CommonConstant.UNAUTHORIZED,
JwtUtils.getHeaderKey() + "失效,请重新登录",
response);
return false;
}
// 校验通过后,设置用户信息到request里,在Controller中从Request域中获取用户信息
request.setAttribute(USER_INFO_KEY, claims);
return true;
} /**
* 利用response直接输出错误信息
* @param code
* @param msg
* @param response
* @throws IOException
*/
private void writerErrorMsg (String code, String msg, HttpServletResponse response) throws IOException {
ModelResult<Void> result = new ModelResult<>();
result.setCode(code);
result.setMsg(msg);
response.setContentType("application/json;charset=UTF-8");
response.getWriter().write(JSON.toJSONString(result));
} }
65
 
1
package com.psx.gqxy.web.jwt;
2
import com.alibaba.fastjson.JSON;
3
import com.psx.gqxy.common.base.CommonConstant;
4
import com.psx.gqxy.common.base.ModelResult;
5
import io.jsonwebtoken.Claims;
6
import org.apache.commons.lang3.StringUtils;
7
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
8
import javax.servlet.http.HttpServletRequest;
9
import javax.servlet.http.HttpServletResponse;
10
import java.io.IOException;
11

12
/**
13
 * jwtToken校验拦截器
14
 * @author ZENG.XIAO.YAN
15
 * @blog https://www.cnblogs.com/zeng1994/
16
 * @version 1.0
17
 */
18
public class JwtInterceptor extends HandlerInterceptorAdapter {
19

20
    public static final String USER_INFO_KEY = "user_info_key";
21

22
    @Override
23
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
24
        //  获取用户 token
25
        String token = request.getHeader(JwtUtils.getHeaderKey());
26
        if (StringUtils.isBlank(token)) {
27
            token = request.getParameter(JwtUtils.getHeaderKey());
28
        }
29
        //  token为空
30
        if(StringUtils.isBlank(token)) {
31
            this.writerErrorMsg(CommonConstant.UNAUTHORIZED,
32
                    JwtUtils.getHeaderKey() + " can not be blank",
33
                    response);
34
            return false;
35
        }
36
        //  校验并解析token,如果token过期或者篡改,则会返回null
37
        Claims claims = JwtUtils.verifyAndGetClaimsByToken(token);
38
        if(null == claims){
39
            this.writerErrorMsg(CommonConstant.UNAUTHORIZED,
40
                    JwtUtils.getHeaderKey() + "失效,请重新登录",
41
                    response);
42
            return false;
43
        }
44
        //  校验通过后,设置用户信息到request里,在Controller中从Request域中获取用户信息
45
        request.setAttribute(USER_INFO_KEY, claims);
46
        return true;
47
    }
48

49
    /**
50
     * 利用response直接输出错误信息
51
     * @param code
52
     * @param msg
53
     * @param response
54
     * @throws IOException
55
     */
56
    private void writerErrorMsg (String code, String msg, HttpServletResponse response) throws IOException {
57
        ModelResult<Void> result = new ModelResult<>();
58
        result.setCode(code);
59
        result.setMsg(msg);
60
        response.setContentType("application/json;charset=UTF-8");
61
        response.getWriter().write(JSON.toJSONString(result));
62
    }
63

64
}
65

四、拦截器的配置和功能测试

(1)编写一个Controller

    写一个Controller,里面包含一登录方法和一个test方法
      • 登录方法用来实现登录,登录成功后返回token
      • test方法,主要通过拦截器拦截该方法的请求,当用户带有效的token访问时才允许访问该方法
        代码如下:
package com.psx.gqxy.web.controller;
import com.psx.gqxy.common.base.CommonConstant;
import com.psx.gqxy.common.base.ModelResult;
import com.psx.gqxy.domain.dto.UserLoginDTO;
import com.psx.gqxy.web.jwt.JwtInterceptor;
import com.psx.gqxy.web.jwt.JwtUtils;
import io.jsonwebtoken.Claims;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map; /**
* TestJwtController
* @author ZENG.XIAO.YAN
* @blog https://www.cnblogs.com/zeng1994/
* @Date 2019-07-14
* @version 1.0
*/ @RestController
@RequestMapping("jwt")
public class TestJwtController { @PostMapping("login")
public ModelResult<String> login(@RequestBody UserLoginDTO dto) {
ModelResult<String> result = new ModelResult<>();
// 这里登录就简单的模拟下
if ("root".equals(dto.getUserName()) && "123456".equals(dto.getPassword())) {
Map<String, Object> userInfoMap = new HashMap<>();
userInfoMap.put("userName", "隔壁老王");
String token = JwtUtils.generateToken(userInfoMap);
result.setData(token);
} else {
result.setCode(CommonConstant.FAIL);
result.setMsg("用户名或密码错误");
}
return result;
} @GetMapping("test")
public String test(HttpServletRequest request) {
// 登录成功后,从request中获取用户信息
Claims claims = (Claims) request.getAttribute(JwtInterceptor.USER_INFO_KEY);
if (null != claims) {
return (String) claims.get("userName");
} else {
return "fail";
}
} }
53
 
1
package com.psx.gqxy.web.controller;
2
import com.psx.gqxy.common.base.CommonConstant;
3
import com.psx.gqxy.common.base.ModelResult;
4
import com.psx.gqxy.domain.dto.UserLoginDTO;
5
import com.psx.gqxy.web.jwt.JwtInterceptor;
6
import com.psx.gqxy.web.jwt.JwtUtils;
7
import io.jsonwebtoken.Claims;
8
import org.springframework.web.bind.annotation.*;
9
import javax.servlet.http.HttpServletRequest;
10
import java.util.HashMap;
11
import java.util.Map;
12

13
/**
14
 * TestJwtController
15
 * @author ZENG.XIAO.YAN
16
 * @blog https://www.cnblogs.com/zeng1994/
17
 * @Date 2019-07-14
18
 * @version 1.0
19
 */
20

21
@RestController
22
@RequestMapping("jwt")
23
public class TestJwtController {
24

25
    @PostMapping("login")
26
    public ModelResult<String> login(@RequestBody UserLoginDTO dto) {
27
        ModelResult<String> result = new ModelResult<>();
28
        // 这里登录就简单的模拟下
29
        if ("root".equals(dto.getUserName()) && "123456".equals(dto.getPassword())) {
30
            Map<String, Object> userInfoMap = new HashMap<>();
31
            userInfoMap.put("userName", "隔壁老王");
32
            String token = JwtUtils.generateToken(userInfoMap);
33
            result.setData(token);
34
        } else {
35
            result.setCode(CommonConstant.FAIL);
36
            result.setMsg("用户名或密码错误");
37
        }
38
        return result;
39
    }
40

41
    @GetMapping("test")
42
    public String test(HttpServletRequest request) {
43
        // 登录成功后,从request中获取用户信息
44
        Claims claims = (Claims) request.getAttribute(JwtInterceptor.USER_INFO_KEY);
45
        if (null != claims) {
46
            return (String) claims.get("userName");
47
        } else {
48
            return "fail";
49
        }
50
    }
51

52
}
53

(2)拦截器的配置

    拦截器拦截需要身份认证的请求,同时放行登录接口
    代码如下:
/**
* web相关的定制化配置
* @author ZENG.XIAO.YAN
* @version 1.0
*/
@Configuration
public class WebConfig implements WebMvcConfigurer {
// WebMvcConfigurerAdapter 这个类在SpringBoot2.0已过时,官方推荐直接实现WebMvcConfigurer 这个接口 @Bean
public JwtInterceptor jwtInterceptor() {
return new JwtInterceptor();
} @Override
public void addInterceptors(InterceptorRegistry registry) {
InterceptorRegistration jwtInterceptorRegistration = registry.addInterceptor(jwtInterceptor());
// 配置拦截器的拦截规则和放行规则
jwtInterceptorRegistration.addPathPatterns("/jwt/**")
.excludePathPatterns("/jwt/login");
}
}
x
 
1
/**
2
 * web相关的定制化配置
3
 * @author ZENG.XIAO.YAN
4
 * @version 1.0
5
 */
6
@Configuration
7
public class WebConfig implements WebMvcConfigurer {
8
    // WebMvcConfigurerAdapter 这个类在SpringBoot2.0已过时,官方推荐直接实现WebMvcConfigurer 这个接口
9

10
    @Bean
11
    public JwtInterceptor jwtInterceptor() {
12
        return new JwtInterceptor();
13
    }
14

15
    @Override
16
    public void addInterceptors(InterceptorRegistry registry) {
17
        InterceptorRegistration jwtInterceptorRegistration = registry.addInterceptor(jwtInterceptor());
18
        // 配置拦截器的拦截规则和放行规则
19
        jwtInterceptorRegistration.addPathPatterns("/jwt/**")
20
                .excludePathPatterns("/jwt/login");
21
    }
22
}    

(3)相关测试

    • 不带token访问 /jwt/test接口,被拦截器拦截;返回token不能为空; 效果如下图
                

    • 访问登录接口,进行登录;登录成功,同时返回生成的token;效果如下图
                

    • 带上登录成功返回的token访问/jwt/test接口,拦截器放行了请求,成功请求到了test方法;效果如下图
                

    • 当token被篡改或者已过期时,访问/jwt/test接口,拦截器拦截了该请求,返回token已失效;效果如下图
                

    进行完上述测试后,说明jwt的集成已经大功告成了。

五、小结

    jwt集成不麻烦,但是也有很多不完善的地方,后续再想办法把它完善。
    在我的JwtUtils的verifyAndGetClaimsByToken方法里提到了相关扩展的思路,可以通过该思路来实现token的刷新及其他的骚操作。
    当然,jwt也有很多缺点,这里就不在赘述了。


SpringBoot集成JWT的更多相关文章

  1. SpringBoot集成JWT 实现接口权限认证

    JWT介绍 Json web token (JWT), 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准((RFC 7519).该token被设计为紧凑且安全的, 特别适用于分布式站点 ...

  2. SpringBoot集成JWT实现token验证

    原文:https://www.jianshu.com/p/e88d3f8151db JWT官网: https://jwt.io/ JWT(Java版)的github地址:https://github. ...

  3. SpringBoot集成JWT实现权限认证

    目录 一.JWT认证流程 二.SpringBoot整合JWT 三.测试 上一篇文章<一分钟带你了解JWT认证!>介绍了JWT的组成和认证原理,本文将介绍下SpringBoot整合JWT实现 ...

  4. springboot之Jwt验证

    简介 什么是JWT(Json Web Token) jwt是为了在网络应用环境间传递声明而执行的一种基于json的开放标准.该token被设计紧凑且安全的,特别适用于SSO场景. jwt的声明一般被用 ...

  5. SpringBoot系列 - 集成JWT实现接口权限认证

    会飞的污熊 2018-01-22 16173 阅读 spring jwt springboot RESTful API认证方式 一般来讲,对于RESTful API都会有认证(Authenticati ...

  6. SpringBoot 集成SpringSecurity JWT

    目录 1. 简介 1.1 SpringSecurity 1.2 OAuth2 1.3 JWT 2. SpringBoot 集成 SpringSecurity 2.1 导入Spring Security ...

  7. Springboot shiro JWT集成总结

    SpringBoot Shiro JWT 1.建表 DDL.sql CREATE TABLE `t_user` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, ` ...

  8. spring boot 2 集成JWT实现api接口认证

    JSON Web Token(JWT)是目前流行的跨域身份验证解决方案.官网:https://jwt.io/本文使用spring boot 2 集成JWT实现api接口验证. 一.JWT的数据结构 J ...

  9. 轻松上手SpringBoot+SpringSecurity+JWT实RESTfulAPI权限控制实战

    前言 我们知道在项目开发中,后台开发权限认证是非常重要的,springboot 中常用熟悉的权限认证框架有,shiro,还有就是springboot 全家桶的 security当然他们各有各的好处,但 ...

随机推荐

  1. SHELL脚本--变量

    环境变量 环境变量就是运行在"环境"上下文的,在这个上下文都可以引用.例如,常见的cd.ls等命令严格来说应该使用绝对路径如/bin/ls来执行,由于/bin目录加入到了PATH环 ...

  2. 283.移动零 关于列表list与remove原理*****(简单)

    题目: 给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序. 注意,该题目要求不开辟行的数组空间,在原数据上进行操作. 示例: 输入: [0,1,0,3,12 ...

  3. Python内容

    1.Python介绍.计算机硬件.网络.变量.数据类型:列表+元组+字典+布尔值+字符串+数字+集合.格式化输出.if判断.for循环.while循环. 2.三元运算.字符编码.文件处理:r/rb(读 ...

  4. zz图像、神经网络优化利器:了解Halide

    动图示例实在太好 图像.神经网络优化利器:了解Halide  Oldpan  2019年4月17日  0条评论  1,327次阅读  3人点赞 前言 Halide是用C++作为宿主语言的一个图像处理相 ...

  5. clickhouse 离线/在线 安装和java通过jdbc链接

    检查 需要确保是否x86_64处理器构架.Linux并且支持SSE 4.2指令集 grep -q sse4_2 /proc/cpuinfo && echo "SSE 4.2 ...

  6. Salesforce 版本控制 - VS Code + GitHub + Salesforce

    使用VS Code开发Salesforce有个很好的地方是可以联接GitHub进行代码版本控制,点击查看使用VS Code开发SalesForce 第一步:安装GIthub Desktop Githu ...

  7. Salesforce 开发整理(十一) 自定义放大镜查找效果

    有时候在自定义的visualforce页面上,需要实现系统标准的查找样式,当不能使用标准的style的时候,我们只能选择自定义实现,下面分享一个demo,预览效果如下: 实现代码,Visualforc ...

  8. 姿态角(RPY)的优化目标函数

    在Pose-Graph的过程中,如果使用G2O优化函数库,那么似乎是不用自己编写代价函数(也就是优化目标函数)的,因为G2O有封装好的SE3等格式,使得Pose-Graph的过程变得简单了,即只需要设 ...

  9. windows 排查javaWeb程序占用CPU过高问题(可追踪到问题代码所在行)

      1.情景展示 java虚拟机占用这么高的CPU,肯定不正常! 2.原因分析 第一个是tomcat,正在运行java项目: 第二个是eclipse,因为eclipse的运行依赖于java. 现在的问 ...

  10. java常量池-字符串常量池、class常量池和运行时常量池

    原文链接:http://tangxman.github.io/2015/07/27/the-difference-of-java-string-pool/ 在java的内存分配中,经常听到很多关于常量 ...