spring aop 的花式应用


前言

在Spring框架中,Filter(过滤器)和Interceptor(拦截器)都扮演着拦截和处理HTTP请求和响应的重要角色,但它们工作的层次和触发链路有所不同。了解它们的区别和触发链路对于设计和优化Web应用非常重要
链路图:

在这里插入图片描述

一、Filter(过滤器)

1、 触发层次

Filter是Servlet规范的一部分,它工作在Servlet容器级别,对所有请求和响应进行拦截。这意味着Filter的触发是早于Spring MVC的DispatcherServlet的。Filter能够修改请求和响应头、记录日志、处理跨域问题、安全认证等。

2、 配置方式

Filter通常在web.xml中配置,或者在Servlet 3.0及以上版本中,可以使用**@WebFilter**注解并注册为Servlet容器的监听器或启动类中的ServletContextInitializer。

3、 触发链路

当Web应用接收到一个HTTP请求时,首先会通过配置的Filter链。如果Filter链中没有任何异常,请求最终会被发送到Servlet(如Spring MVC的DispatcherServlet)。Servlet处理完请求后,响应也会经过Filter链返回给客户端。

4、具体应用

4.1、Filter

校验token信息

@Slf4j
public class AuthFilter implements Filter {

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        String tokenKey = "x-token";
        HttpServletRequest req = (HttpServletRequest) request;
        HttpServletResponse res = (HttpServletResponse) response;
        String token = req.getParameter(tokenKey);
        if (StringUtils.isEmpty(token)) {
            Cookie[] cookies = req.getCookies();
            if (cookies != null) {
                for (Cookie cookie : cookies) {
                    if (cookie.getName().equals(tokenKey)) {
                        token = cookie.getValue();
                    }
                }
            }
        }
        if (StringUtils.isEmpty(token)) {
            res.setContentType("application/json;charset=utf-8");
            res.getWriter().println("不存在相关token");
            return;
        }
        chain.doFilter(request, response);
    }
    
    @Override
    public void destroy() {
        
    }

}

注册到拦截器链

@Configuration
public class ExternalWebConfig {

    private static final String custSysUrl = "/sys/*,/user/*";

    @Bean
    public FilterRegistrationBean<CustSysAuthFilter> sysAuthFilterRegistration() {
        FilterRegistrationBean<CustSysAuthFilter> registration = new FilterRegistrationBean<>();
        registration.setName("authFilter");
        registration.setOrder(1);
        String[] urlPatterns = custSysUrl.split(",");
        registration.addUrlPatterns(urlPatterns);
        registration.setFilter(new AuthFilter());
        return registration;
    }

}

4.2、OncePerRequestFilter

处理跨域问题

@Component
@WebFilter(urlPatterns = "/*", filterName = "crossDomainFilter")
public class CrossDomainFilter extends OncePerRequestFilter implements Ordered {


    @Override
    protected void doFilterInternal(HttpServletRequest httpRequest, HttpServletResponse httpResponse, FilterChain filterChain) throws ServletException, IOException {
        String originHeads = httpRequest.getHeader("Origin");
        httpResponse.setHeader("Access-Control-Allow-Origin", originHeads);
        httpResponse.setHeader("Access-Control-Allow-Credentials", "true");
        httpResponse.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS, PATCH");
        httpResponse.setHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Token, Accept , Connection, User-Agent, Cookie");
        // 在使用CORS方式跨域时,浏览器只会返回 默认的头部 Header,认情况下可用的响应头包括:Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma
        // name:Access-Control-Expose-Headers 响应报头  value:可以暴露出去的响应头,多个响应头以","隔开
        httpResponse.setHeader("Access-Control-Expose-Headers", "Content-Disposition");
        httpResponse.setHeader("Access-Control-Max-Age", "3628800");
        httpResponse.setHeader("P3P", "CP=\"CAO PSA OUR\"");
        filterChain.doFilter(httpRequest, httpResponse);
    }


    @Override
    public int getOrder() {
        return -1;
    }
}

OncePerRequestFilter
Spring Framework 中的一个抽象类,它扩展自 javax.servlet.Filter 接口,用于在 Spring 的 Web 应用中提供请求过滤的功能。这个类的主要目的是确保在单个请求的处理过程中,过滤器链中的每一个过滤器只被调用一次,即使该过滤器被映射到多个 URL 模式上。

二、Interceptor(拦截器)

1、触发层次

Interceptor是Spring MVC框架提供的一个功能,它工作在Spring MVC的DispatcherServlet级别,主要对Controller的请求和响应进行拦截。它用于实现一些通用的功能,如日志记录、权限检查、数据预处理和转换等。

2、配置方式

Interceptor需要在Spring MVC的配置中注册,这可以通过实现HandlerInterceptor接口并在配置类中通过addInterceptors方法添加到拦截器链中来实现。

3、触发链路

当一个HTTP请求被DispatcherServlet捕获后,它会根据请求的信息(如URL)找到对应的Controller处理器。在处理Controller之前、之后或渲染视图之后,DispatcherServlet会触发配置的Interceptor链。这意味着Interceptor是在Spring MVC处理请求/响应流程的特定阶段被调用的。

4、具体应用

4.1、HandlerInterceptor

@Component
public class ThreadLocalInterceptor implements HandlerInterceptor {

    private ThreadLocal<String> threadLocal = new ThreadLocal<>();

	// 此方法的作用是在请求进入到Controller进行拦截,有返回值。(返回true则将请求放行进入Controller控制层,false则请求结束返回错误信息)
	// 前置处理,类似于切面的before
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        threadLocal.set("进入了preHandle,可以处理相应业务");
        return true;
    }

 	// 该方法是在Controller控制器执行完成但是还没有返回模板进行渲染拦截。没有返回值。就是Controller----->拦截------>ModelAndView。
 	// 可以进一步包装 ModelAndView
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {

    }

 	// 该方法是在ModelAndView返回给前端渲染后执行
 	// 后置处理,类似于切面的after
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        threadLocal.remove();
    }
}

4.2、WebMvcConfigurer

添加拦截规则

@Configuration
public class WebConfigurer implements WebMvcConfigurer {
    public WebConfigurer() {
    }
	
	// 添加到拦截器链
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new ThreadLocalInterceptor ()).addPathPatterns("/**").excludePathPatterns("/**/th","/**/ch");
    }

    @Bean(name = "multipartResolver")
    public MultipartResolver multipartResolver(Environment environment) {
        CommonsMultipartResolver resolver = new CommonsMultipartResolver();
        resolver.setDefaultEncoding("UTF-8");
        // resolveLazily属性启用是为了推迟文件解析,以在在UploadAction中捕获文件大小异常
        resolver.setResolveLazily(true);
        resolver.setMaxInMemorySize(40960);
        // 上传文件大小50M 50*1024*1024
        resolver.setMaxUploadSize(5 * 1024 * 1024 * 1024L);
        return resolver;
    }
}

三、ControllerAdvice、RestControllerAdvice

@ControllerAdvice和@RestControllerAdvice 是Spring MVC框架中的一个注解,它提供了一种全局的控制器增强机制。通过该注解,开发者可以声明一些全局的处理器、数据绑定器和模型处理器,这些处理器可以在Controller的方法执行前后或者异常发生时进行干预,从而实现一些通用的逻辑处理。

1、全局异常处理

结合@ExceptionHandler注解使用,可以捕获Controller中抛出的异常,并进行统一的异常处理。这有助于减少模板代码,提升代码的可读性和可维护性。
示例:定义一个全局异常处理器类,在该类上使用@ControllerAdvice注解,并定义多个带有@ExceptionHandler注解的方法,每个方法处理不同类型的异常。

2、全局数据绑定

结合@ModelAttribute注解使用,可以在Controller的方法执行之前,将一些公共的数据添加到模型中,以便在视图层中使用。
示例:定义一个带有@ModelAttribute注解的方法,该方法返回一个Map或自定义对象,Spring MVC会将该方法的返回值添加到模型中。

3、全局数据预处理

结合@InitBinder注解使用,可以对请求中的参数进行预处理,如自定义参数的解析方式、注册自定义的编辑器等。
示例:定义一个带有@InitBinder注解的方法,该方法接收一个WebDataBinder参数,可以在该方法中对WebDataBinder进行配置,以实现自定义的参数解析逻辑。

4、具体应用

@RestControllerAdvice  
public class RestControllerAdviceHandler {  
  
    // 处理所有运行时异常  
    @ExceptionHandler(RuntimeException.class)  
    public ResponseEntity<Object> handleRuntimeException(RuntimeException ex) {  
        // 这里可以记录日志、返回自定义的错误信息等  
        Map<String, Object> body = new HashMap<>();  
        body.put("timestamp", LocalDateTime.now());  
        body.put("status", HttpStatus.INTERNAL_SERVER_ERROR.value());  
        body.put("error", "Internal Server Error");  
        body.put("message", ex.getMessage());  
  
        return new ResponseEntity<>(body, HttpStatus.INTERNAL_SERVER_ERROR);  
    }  
  
    // 处理特定类型的异常,例如自定义的 BusinessException  
    @ExceptionHandler(BusinessException.class)  
    public ResponseEntity<Object> handleBusinessException(BusinessException ex) {  
        Map<String, Object> body = new HashMap<>();  
        body.put("timestamp", LocalDateTime.now());  
        body.put("status", HttpStatus.BAD_REQUEST.value());  
        body.put("error", "Bad Request");  
        body.put("message", ex.getMessage());  
  
        return new ResponseEntity<>(body, HttpStatus.BAD_REQUEST);  
    }  
     
    // @ModelAttribute 没有指定值,且被修饰的方法返回值为void,
    // 则需要借助map或者model将数据模型放入request域
    @ModelAttribute
    public void addCommonAttributes(HttpServletRequest request) {
        request.setAttribute("appName", "My Application");
    }
	
	//  在数据绑定到模型之前对请求参数进行预处理,提高了数据的准确性和应用程序的健壮性
    @InitBinder  
    public void initBinder(WebDataBinder binder) {  
        // 注册自定义的日期格式化器  
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");  
        dateFormat.setLenient(false);  
        binder.registerCustomEditor(Date.class, new CustomDateEditor(dateFormat, true));  
    }
    
}
@RestController
@RequestMapping("/test")
public class TestController {

	//  @ExceptionHandler(RuntimeException.class)  
    @GetMapping("/get1")
    public String event1() {
        int i = 1/0;
        return "success";
    }
    
    // @ModelAttribute
    @GetMapping("/get2")
    public String event2(HttpServletRequest request) {
        System.out.println(request.getAttribute("appName"));
        // 成功转换
        return "success";
    }
    
    //  @InitBinder
    @GetMapping("/get3")
    public String event3(Date date) {
        // 成功转换
        System.out.println(date);
        return "success";
    }
}

总结

Spring AOP(面向切面编程)是一种编程思想,可以通过很多种方式实现,找到符合业务的需求,去针对性实现,实现解耦,更好维护

  • 16
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值