Ioc和Aop是spring的两大重要思想,前者指的是控制反转(Invers of control)后者指的是面向切面编程(Aspect oriented programing)。aop的一大作用就是能将很多重复的功能点抽取出来,而用注解或者配置的方式统一给需要此功能的方法进行一个增强
因此,aop的一大用途就是用来简化重复而一致的一些功能比如(日志打印、登录校验、性能监控、事务管理等)。这篇文章讲的是aop在登录校验方面的一种实现
实现步骤:
1.编写拦截器,实现HandlerIntercepter接口(注意,HandlerInterceptor是springMVC提供的接口,需要引入spring mvc依赖)
2.在实现类中重写方法(preHandle、postHandle、afterCompletion),在方法中实现切面逻辑
3.编写配置类,确定使用哪些拦截器,拦截器作用于哪些地方,多个拦截器之间执行的先后顺序
实现拦截器
编写拦截器&重写HandlerInterceptor中的默认方法
package com.dianping.utils;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.StrUtil;
import com.dianping.dto.UserDTO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import static com.dianping.utils.RedisConstants.LOGIN_USER_KEY;
@Slf4j
public class RefreshTokenInterceptor implements HandlerInterceptor {
private StringRedisTemplate stringRedisTemplate;
public RefreshTokenInterceptor(StringRedisTemplate stringRedisTemplate) {
this.stringRedisTemplate = stringRedisTemplate;
}
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// TODO 1.获取请求头中的 token
String token = request.getHeader("authorization");
if (StrUtil.isBlank(token)) {
// 没有 token 无法刷新,直接结束
return true;
}
// TODO 2.基于 token 获取 redis 中的用户
String tokenKey = LOGIN_USER_KEY + token;
Map<Object, Object> userMap = stringRedisTemplate.opsForHash().entries(tokenKey);
// 3.判断用户是否存在
if (userMap.isEmpty()) {
// redis中没有用户信息,无法刷新,直接结束
return true;
}
// TODO 5.将查询到的 Hash 数据转为 UserDTO对象
UserDTO userDTO = BeanUtil.fillBeanWithMap(userMap, new UserDTO(), false);
// TODO 6.存在,保存用户信息到 ThreadLocal
UserHolder.saveUser((UserDTO) userDTO);
// log.info("刷新有效期");
// TODO 7.刷新 token 有效期
stringRedisTemplate.expire(tokenKey,30, TimeUnit.MINUTES);
// 8.放行
return true;
}
}
package com.dianping.utils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Slf4j
public class LoginInterceptor implements HandlerInterceptor {
/**
* Intercept the execution of a handler. Called after HandlerMapping determined
* an appropriate handler object, but before HandlerAdapter invokes the handler.
* <p>DispatcherServlet processes a handler in an execution chain, consisting
* of any number of interceptors, with the handler itself at the end.
* With this method, each interceptor can decide to abort the execution chain,
* typically sending an HTTP error or writing a custom response.
* <p><strong>Note:</strong> special considerations apply for asynchronous
* request processing. For more details see
* {@link org.springframework.web.servlet.AsyncHandlerInterceptor}.
* <p>The default implementation returns {@code true}.
* @param request current HTTP request
* @param response current HTTP response
* @param handler chosen handler to execute, for type and/or instance evaluation
* @return {@code true} if the execution chain should proceed with the
* next interceptor or the handler itself. Else, DispatcherServlet assumes
* that this interceptor has already dealt with the response itself.
* @throws Exception in case of errors
* preHandle方法:该方法在请求处理之前被调用,可以在该方法中进行一些预处理操作
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 1.判断是否需要拦截(ThreadLocal中是否有用户)
if (UserHolder.getUser() == null) {
// 没有,需要拦截,设置状态码
log.info("用户未登录");
response.setStatus(401);
// 拦截
return false;
}
// 有用户则放行
return true;
// // 1.获取session
// HttpSession session = request.getSession();
// // 2.获取session中的用户
// Object user = session.getAttribute("user");
// // 3.判断用户是否存在
// if (user == null) {
// // 4.不存在,拦截
// response.setStatus(401);
// return false;
// }
// // 5.存在,保存到ThreadLocal
// UserHolder.saveUser((UserDTO) user);
// // 6.放行
// return true;
}
/**
* Intercept the execution of a handler. Called after HandlerAdapter actually
* invoked the handler, but before the DispatcherServlet renders the view.
* Can expose additional model objects to the view via the given ModelAndView.
* <p>DispatcherServlet processes a handler in an execution chain, consisting
* of any number of interceptors, with the handler itself at the end.
* With this method, each interceptor can post-process an execution,
* getting applied in inverse order of the execution chain.
* <p><strong>Note:</strong> special considerations apply for asynchronous
* request processing. For more details see
* {@link org.springframework.web.servlet.AsyncHandlerInterceptor}.
* <p>The default implementation is empty.
* @param request current HTTP request
* @param response current HTTP response
* @param handler the handler (or {@link HandlerMethod}) that started asynchronous
* execution, for type and/or instance examination
* @param modelAndView the {@code ModelAndView} that the handler returned
* (can also be {@code null})
* @throws Exception in case of errors
* postHandle方法:该方法在请求处理之后、视图渲染之前被调用,可以在该方法中对请求处理结果进行一些处理,例如修改ModelAndView中的数据、记录日志等。
*/
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
@Nullable ModelAndView modelAndView) throws Exception {
}
/**
* Callback after completion of request processing, that is, after rendering
* the view. Will be called on any outcome of handler execution, thus allows
* for proper resource cleanup.
* <p>Note: Will only be called if this interceptor's {@code preHandle}
* method has successfully completed and returned {@code true}!
* <p>As with the {@code postHandle} method, the method will be invoked on each
* interceptor in the chain in reverse order, so the first interceptor will be
* the last to be invoked.
* <p><strong>Note:</strong> special considerations apply for asynchronous
* request processing. For more details see
* {@link org.springframework.web.servlet.AsyncHandlerInterceptor}.
* <p>The default implementation is empty.
* @param request current HTTP request
* @param response current HTTP response
* @param handler the handler (or {@link HandlerMethod}) that started asynchronous
* execution, for type and/or instance examination
* @param ex any exception thrown on handler execution, if any; this does not
* include exceptions that have been handled through an exception resolver
* @throws Exception in case of errors
*afterCompletion方法:该方法在请求处理完成后、视图渲染完成后被调用,可以在该方法中进行一些资源清理操作,例如释放数据库连接、删除临时文件等。此时,无法修改响应结果的内容和格式,因为响应已经被发送到客户端了。
*/
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
}
}
编写配置类,实现WebMvnConfigurer,重写addInterceptor接口
package com.dianping.config;
import com.dianping.utils.LoginInterceptor;
import com.dianping.utils.RefreshTokenInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class MvcConfig implements WebMvcConfigurer {
@Autowired
StringRedisTemplate stringRedisTemplate;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginInterceptor())
.excludePathPatterns(
"/user/code",
"/user/login"
)
.order(1);
// order 指定不同拦截器的执行顺序,order数字越小的越先执行。
// 如果没有用 order 指定执行顺序,按照注册的先后顺序执行(先注册先执行)
registry.addInterceptor(new RefreshTokenInterceptor(stringRedisTemplate))
.addPathPatterns("/**")
.order(0);
}
}