springboot+springsession+redis实现session共
* 1、springboot+springsession+redis
* 2、feign框架导致session共享失效
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-redisartifactId>
dependency>
<dependency>
<groupId>org.springframework.sessiongroupId>
<artifactId>spring-session-data-redisartifactId>
dependency>
该注解的作用,就是引入springsession管理,同时实现是采用redis管理session的方式。
@SpringBootApplication
@EnableRedisHttpSession//增加redissession缓存支持
public class ServiceOneApplication {
public static void main(String[] args) {
SpringApplication.run(ServiceOneApplication.class,args);
}
}
spring:
redis:
host: localhost
port: 6379
server:
port: 8080
/**
* session设置
* @param key
* @param value
* @param request
* @return
*/
@ResponseBody
@RequestMapping("/setSession/{key}/{value}")
public String setSession(@PathVariable String key , @PathVariable String value,
HttpServletRequest request){
request.getSession().setAttribute(key,value);
Enumeration headers = request.getHeaderNames();
while (headers.hasMoreElements()){
String name = headers.nextElement();
System.out.println(name + ":"+ request.getHeader(name));
}
System.out.println(request.getSession().getId());
return request.getSession().getId();
}
/**
* 读取session
* @param key
* @param request
* @return
*/
@ResponseBody
@RequestMapping("/getSession/{key}")
public String getSession(@PathVariable String key ,HttpServletRequest request){
return request.getSession().getAttribute(key) + "---- sessionId:" + request.getSession().getId() ;
}
启动两个实例,分别访问发现获取到的sessionid是一致的。
经过源码分析,springsession+redis关键是通过@EnableRedisHttpSession注解引入的,主要是通过SessionRepositoryFilter进行session的预处理,整个过程还是通过
原生的Cookie中获取SessionID实现。因此我们需要对session做一些特殊操作的时候,需要考虑SessionRepositoryFilter的级别和顺序。
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-feignartifactId>
dependency>
注意:这里feign接口声明只需要能够表达出来调用的url/path/params等信息就可以了,不要携带额外的信息,否则会报错。因为feign本身
是一个rpc框架,目的是为了实现rpc调用,与dubbo等不同,不是接口声明式的,它是restful风格的,因此它要模拟的是浏览器,不是javabean
//这里不要出现与调用无关的任何信息,哪怕这些信息是真正实现时要用到的。
@FeignClient(name = "session",url = "http://localhost:8090")
public interface ControllerInterface {
@RequestMapping("/setSession/{key}/{value}")
public String setSession(@PathVariable("key") String key , @PathVariable("value") String value);
@RequestMapping("/getSession/{key}")
public String getSession(@PathVariable(name = "key") String key );
}
@SpringBootApplication
@EnableRedisHttpSession//增加redissession缓存支持
@EnableFeignClients//增加feign支持,引入feign注解,feign扫描路径可以单独指定(basePackages = ),默认是spring的扫描路径
public class ServiceOneApplication {
public static void main(String[] args) {
SpringApplication.run(ServiceOneApplication.class,args);
}
}
@Autowired
private ControllerInterface controllerInterface;
/**
* 测试feign的session问题。
* @param key
* @return
*/
@ResponseBody
@RequestMapping("/testFeign/{key}")
public String testFeign(@PathVariable String key,HttpServletRequest request) {
return controllerInterface.getSession(key);
}
到这一步,一个普通的feign调用示例就结束了,因为它倾向于restful风格的,因此默认的是无状态的,不会向下游携带额外状态信息,实际开发中我们又需要携带如登录
信息一样的状态信息,该怎么办呢?Feign早就想到了,我们看下源码中的RequestInterceptor怎么说的
/**
* "#FF0000">
* Zero or more {@code RequestInterceptors} may be configured for purposes such as adding headers to
* all requests. No guarantees are give with regards to the order that interceptors are applied.
* Once interceptors are applied, {@link Target#apply(RequestTemplate)} is called to create the
* immutable http request sent via {@link Client#execute(Request, feign.Request.Options)}.
* For example:
*
* public void apply(RequestTemplate input) {
* input.replaceHeader("X-Auth", currentToken);
* }
*
*
*
* Zero or more {@code RequestInterceptors} may be configured for purposes such as adding headers to
* all requests. No guarantees are give with regards to the order that interceptors are applied.
* Once interceptors are applied, {@link Target#apply(RequestTemplate)} is called to create the
* immutable http request sent via {@link Client#execute(Request, feign.Request.Options)}.
* For example:
*
* public void apply(RequestTemplate input) {
* input.replaceHeader("X-Auth", currentToken);
* }
注意标红的部分,大意是我们有时候需要一个或者多个RequestInterceptor去配置诸如请求头信息,还给出了授权的头部配置。
到这里我们就明白了,我们需要实现一个RequestInterceptor,在里面将原来的请求头信息付给下游请求,实际上就是Cookie信息,
这样sessionId就传到下游了,也就实现了共享。
“`aidl
import javax.servlet.http.HttpServletRequest;
import java.util.Enumeration;
/**
* 实现RequestInterceptor,用于设置feign全局请求模板
*/
@Component
public class FeignRequestIntercepter implements RequestInterceptor {
@Override
public void apply(RequestTemplate requestTemplate) {
//通过RequestContextHolder获取本地请求
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
if (requestAttributes == null){
return;
}
//获取本地线程绑定的请求对象
HttpServletRequest request = ((ServletRequestAttributes)requestAttributes).getRequest();
//给请求模板附加本地线程头部信息,主要是cookie信息
Enumeration headerNames = request.getHeaderNames();
while (headerNames.hasMoreElements()){
String name = headerNames.nextElement();
requestTemplate.header(name,request.getHeader(name));
}
}
}
“`
在浏览器中分别输入
http://localhost:8080/setSession/key/hello
http://localhost:8090/getSession/key
http://localhost:8080/testFeign/key
可以看到,打出的sessionID是相同的,而且都可以获取到session属性。
到这一步就基本解决了session丢失问题。至于网上说的熔断的异步化,导致session信息获取不到的问题,我们这里不做熔断,因此可以不考虑。