ThreadLocal+Filter+SpringBoot 保存当前登录用户信息
1、线程封闭
1.1 线程封闭
当访问共享的可变数据时,通常需要同步。一种避免同步的方式就是不共享数据。如果仅在单线程内访问数据,就不需要同步,这种技术称为线程封闭(thread confinement).
线程封闭技术一个常见的应用就是JDBC的Connection对象,JDBC规范并没有要求Connection对象必须是线程安全的,在服务器应用程序中,线程从连接池获取一个Connection对象,使用完之后将对象返还给连接池。下面介绍几种线程封闭技术:
1.2 ThreadLocal 类
维持线程封闭性的一种更加规范方法是使用ThreadLocal类,这个类能使线程中某个值与保存值的对象关联起来。ThreadLocal类提供了 get 和 set 等访问接口或者方法,这些方法为每个使用该变量的线程都存在一份独立的副本,因此 get 总是放回当前执行线程在调用set设置的最新值。
扩展知识:
ThreadLocalMap对象是以this指向的ThreadLocal对象为键进行查找的,这当然和前面set()方法的代码是相呼应的。
进一步地,我们可以创建不同的ThreadLocal实例来实现多个变量在不同线程间的访问隔离,为什么可以这么做?因为不同的ThreadLocal对象作为不同键,当然也可以在线程的ThreadLocalMap对象中设置不同的值了。通过ThreadLocal对象,在多线程中共享一个值和多个值的区别,就像你在一个HashMap对象中存储一个键值对和多个键值对一样,仅此而已。
也就说,每个线程都有一个ThreadLocalMap,该线程访问到某个局部变量,且该局部变量是用ThreadLocal类进行声明时,该线程就会new ThreadLocal(),然后将该ThreadLocal类的对象作为key值,所对应的局部变量作为value值保存到ThreadLocalMap中。当线程访问多个ThreadLocal类进行声明局部变量时,在ThreadLocalMap中就有多个键值对。而每个线程都有自己的ThreadLocalMap,从而达到隔离的目的了。
当某个线程终止后,该线程里的ThreadLocalMap也被回收了,所以完全不用担心内存泄漏的问题。
假如多线程访问的对象实例是单例的,或者说只能创建一个,那就老老实实的使用同步机制(synchronized)了.
2、ThreadLocal 使用实例
ThreadLocal+Filter+SpringBoot 保存当前登录用户信息
package com.icmc.icao.util;
import com.icmc.icao.entity.sys.IcaoUser;
import javax.servlet.http.HttpServletRequest;
/**
* @author
* @title: RequestHolder
* @description: TODO
* @date 2020/4/13 10:35
*/
public class RequestHolder {
private final static ThreadLocal<IcaoUser> userHoldert =
new ThreadLocal<IcaoUser>();
private static final ThreadLocal<HttpServletRequest> requestHolder =
new ThreadLocal<HttpServletRequest>();
public static void add (IcaoUseruser) {
userHoldert.set(user);
}
public static void add(HttpServletRequest request) {
requestHolder.set(request);
}
public static IcaoUseruser getIcaoUser() {
return userHoldert.get();
}
public static HttpServletRequest getCurrentRequest() {
return requestHolder.get();
}
public static void remove() {
userHoldert.remove();
requestHolder.remove();
}
}
Interceptor 拦截器代码
package com.icmc.icao.handler;
import com.icmc.icao.util.Log4JUtil;
import com.icmc.icao.util.RequestHolder;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* @author
* @title: HttpInterceptor
* @description: TODO
* @date 2020/4/13 10:50
*/
public class HttpInterceptor extends HandlerInterceptorAdapter {
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
Log4JUtil.getLogger().info("preHandle");
return true;
}
@Override
public void afterCompletion(HttpServletRequest request,
HttpServletResponse response,
Object handler, Exception ex) throws Exception {
RequestHolder.remove();
Log4JUtil.getLogger().info("afterCompletion");
return;
}
}
启动类配置
package com.icmc.icao;
import com.icmc.icao.handler.HttpInterceptor;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@SpringBootApplication
public class LcaoApplication implements WebMvcConfigurer {
public static void main(String[] args) {
SpringApplication.run(LcaoApplication.class, args);
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new HttpInterceptor()).addPathPatterns("/**");
}
}
Filter 过滤器代码
package com.icmc.icao;
import com.icmc.icao.util.RequestHolder;
import com.icmc.icao.entity.sys.IcaoUser;
import org.springframework.stereotype.Component;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;
@Component
@WebFilter(urlPatterns = "/*", filterName = "authFilter")
public class AuthFilter implements Filter {
public void init(FilterConfig filterConfig) throws ServletException {
}
public void doFilter(ServletRequest servletRequest,
ServletResponse servletResponse,
FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) servletRequest;
HttpServletResponse resp = (HttpServletResponse) servletResponse;
// 这里的实现思路是,先获取 session 中的 IcaoUser,然后放到 ThreadLocal中。
// 所以在登录接口中,登录成功后,必须将 IcaoUser 用户对象放入session中
if (req.getSession() != null
&& req.getSession().getAttribute("user") != null)
IcaoUser icaoUser = (IcaoUser)req.getSession().getAttribute("user");
if (icaoUser== null) {
String path = "/login.jsp";
resp.sendRedirect(path);
return;
}
RequestHolder.add(icaoUser);
RequestHolder.add(req);
filterChain.doFilter(servletRequest, servletResponse);
return;
}
public void destroy() {
}
}
用户登录控制层代码
@RequestMapping(value = {
"login" }, method = RequestMethod.POST)
@ResponseBody
public String login(HttpServletRequest req,
String username,
String password,
String checkcode) {
// 业务层实现 参数非空验证,以及验证验证码等操作 todo……
// 验证账号密码等,如果用户名和密码正确,将用户信息放入session
IcaoUser icaoUser = 业务层的相关查询……
req.getSession().setAttribute("user", icaoUser);
// 根据业务需求返回相关信息
return "success";
}
业务层需要获取当前用户信息实例
在其他业务层需要获取到用户信息
@Service
public class IcaoOrgQualiServiceImpl implements IcaoOrgQualiService {
@Autowired
private IcaoOrgQualiMapper icaoOrgQualiMapper;
@Override
public void insertSelective(IcaoOrgQuali icaoOrgQualin) {
// 获取当前用户信息
IcaoUser icaoUser = RequestHolder.getIcaoUser();
icaoOrgQualin.setCreateUser(icaoUser.getUserName());
icaoOrgQualiMapper.insertSelective(icaoOrgQualin);
}
}
.