写点什么

AOP 注解详解

作者:武师叔
  • 2022 年 7 月 12 日
  • 本文字数:4077 字

    阅读完需:约 13 分钟

AOP 注解详解

AOP 注解详解

配置

对负责扫描组件的配置文件类(@Configuration) 添加 @EnableAspectJAutoProxy 注解,启用 AOP 功能。


**默认通过 JDK 动态代理方式进行织入。**但必须代理一个实现接口的类,否则会抛出异常。


注解改为 @EnableAspectJAutoProxy(proxyTargetClass = true)


通过 cglib 的动态代理方式进行织入。但如果拓展类的方法被 final 修饰,则织入无效。


@Configuration@ComponentScan(basePackageClasses = {com.company.project.service.Meal.class})@EnableAspectJAutoProxy(proxyTargetClass = true)public class AppConfig {}Copy to clipboardErrorCopied
复制代码

切面

对组件类(@component) 添加 @Aspect 注解,表示该类为切面类。

增强类型

前置通知


切面方法注解 @Before 表示目标方法调用前,执行该切面方法。


@Before("execution(* com.company.project.service.Meal.eat(..))")public void cook() {    System.out.println("cook");}Copy to clipboardErrorCopied
复制代码


后置通知


  • 切面方法注解 @After 表示目标方法返回或抛出异常后,执行该切面方法。

  • 切面方法注解 @AfterReturning 只在目标方法返回后,执行该切面方法。

  • 切面方法注解 @AfterThrowing 只在目标方法抛出异常后,执行该切面方法。


@AfterReturning("execution(* com.company.project.service.Meal.eat(..))")public void clean() {    System.out.println("clean");}Copy to clipboardErrorCopied
复制代码


环绕通知


切面方法注解 @Around 表示切面方法执行过程中,执行目标方法。


传入参数为 ProceedingJoinPoint 类对象,表示目标方法。在切面方法中调用其 proceed 方法来执行。


@Around("execution(* com.company.project.service.Meal.eat(..))")public void party(ProceedingJoinPoint pj) {    try {        System.out.println("cook");        pj.proceed();        System.out.println("clean");    } catch (Throwable throwable) {        throwable.printStackTrace();    }}Copy to clipboardErrorCopied
复制代码

切点声明

在切面方法中需要声明切面方法要切入的目标方法,execution 指示器是我们定义切点时最主要使用的指示器。


格式为: execution(返回数据类型 路径.类.方法(传入参数类型))



还有一些其他指示器:



  • 多个匹配条件之间使用链接符连接: &&||!

  • within 指示器表示可以选择的包,bean 指示器可以在切点中选择 bean 。


如参数 execution(String com.company.project.service.test1.IBuy.buy(double)) && args(price) && bean(girl)


要求返回类型为 String ;参数类型为 double ;参数名为 price ;调用目标方法的 bean 名称为 girl 。


简化代码


对于类中要频繁要切入的目标方法,我们可以使用 @Pointcut 注解声明切点表达式,简化代码。


@Aspect@Componentpublic class EatPlus {
@Pointcut("execution(* com.company.project.service.Meal.eat(..))") public void point(){}
@Before("point()") public void cook() { System.out.println("cook"); }
@Around("point()") public void party(ProceedingJoinPoint pj) { try { System.out.println("cook"); pj.proceed(); System.out.println("clean"); } catch (Throwable throwable) { throwable.printStackTrace(); } }
@Pointcut("execution(String com.company.project.service.Meal.eat(double)) && args(price) && bean(people)") public void point2(double price) { }
@Around("point2(price)") public String pay(ProceedingJoinPoint pj, double price){ try { pj.proceed(); if (price > 100) { System.out.println("can not afford"); return "没有购买"; } } catch (Throwable throwable) { throwable.printStackTrace(); } return "购买"; }}Copy to clipboardErrorCopied
复制代码



常用 AOP

异常处理

  • @ControllerAdvice / @RestControllerAdvice: 标注当前类为所有 Controller 类服务

  • @ExceptionHandler: 标注当前方法处理异常(默认处理 RuntimeException) @ExceptionHandler(value = Exception.class): 处理所有异常


@RestControllerAdvicepublic class ControllerExceptionHandler {
@ExceptionHandler(Throwable.class) public ResultBean handleOtherException(Throwable e) { String message = String.format("错误=%s,位置=%s", e.toString(), e.getStackTrace()[0].toString()); return ResultBean.error(ErrorCode.UNKNOWN_ERROR.getErrorCode(), message); }
@ExceptionHandler(StreamPlatformException.class) public ResultBean handleVenusException(StreamPlatformException e) { return ResultBean.error(e.getErrorCode(), e.getMessageToUser()); }
@ExceptionHandler(FormValidationException.class) public ResultBean handleFormValidationException(FormValidationException e) { StringBuilder message = new StringBuilder(); e.getResult().getAllErrors().forEach(objectError -> { if (objectError instanceof FieldError) { FieldError fieldError = (FieldError) objectError; message.append("参数").append(fieldError.getField()) .append("错误值为").append(fieldError.getRejectedValue()) .append(fieldError.getDefaultMessage()); } else { message.append(objectError.getDefaultMessage()); } }); return ResultBean.error(ErrorCode.PARAMETER_VALIDATION_ERROR.getErrorCode(), String.format(ErrorCode.PARAMETER_VALIDATION_ERROR.getMessage(), message)); }}Copy to clipboardErrorCopied
复制代码

拦截器

  • 拦截器(Interceptor)


Java Web 中,在执行 Controller 方法前后对 Controller 请求进行拦截和处理。依赖于 web 框架,在 Spring 配置。在实现上基于 Java 的反射机制。


  • 过滤器(Filter)


Java Web 中,在 request/response 传入 Servlet 前,过滤信息或设置参数。依赖于 servlet 容器,在 web.xml 配置。在实现上基于函数回调。


两者常用于修改字符编码、删除无用参数、登录校验等。Spring 框架中优先使用拦截器:功能接近、使用更加灵活。


拦截器配置


// 在配置中引入拦截器对象(单独编写拦截器类)
@Overridepublic void addInterceptors(InterceptorRegistry registry) { // 导入拦截器对象,默认拦截全部 InterceptorRegistration addInterceptor = registry.addInterceptor(new myInterceptor());
// 排除配置 addInterceptor.excludePathPatterns("/error","/login","/user/login"); addInterceptor.excludePathPatterns("/asserts/**"); addInterceptor.excludePathPatterns("/webjars/**"); addInterceptor.excludePathPatterns("/public/**"); // 拦截配置 addInterceptor.addPathPatterns("/**");}Copy to clipboardErrorCopied
复制代码


拦截器类通过实现 HandlerInterceptor 接口或者继承 HandlerInterceptorAdapter 类。


// 定义拦截器public class myInterceptor extends HandlerInterceptorAdapter {
// Session key public final static String SESSION_KEY = "user";
// preHandle 预处理 @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 检查 session HttpSession session = request.getSession(); if (session.getAttribute(SESSION_KEY) != null) return true; // 重定向到登录页面 request.setAttribute("message","登录失败,请先输入用户名和密码。"); request.getRequestDispatcher("login").forward(request,response); return false; }
// postHandle 善后处理 @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) { System.out.println("INTERCEPTOR POSTHANDLE CALLED"); }
}Copy to clipboardErrorCopied
复制代码


过滤器类通过继承 Filter 类实现,直接添加注解即可。


@Component                                                                // 作为组件,交给容器处理@ServletComponentScan                                                     // 扫描组件@WebFilter(urlPatterns = "/login/*",filterName = "loginFilter")           // 设定过滤路径和名称@Order(1)                                                                 // 设定优先级(值小会优先执行)public class LoginFilter implements Filter{
@Override public void init(FilterConfig filterConfig) throws ServletException { }
@Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { // 过滤器前执行 System.out.println("before"); // 执行内部逻辑 filterChain.doFilter(servletRequest,servletResponse); // 过滤器后执行 System.out.println("after"); }
@Override public void destroy() { }}Copy to clipboardErrorCopied
复制代码


调用顺序



发布于: 刚刚阅读数: 3
用户头像

武师叔

关注

每天丰富自己,去过自己想要的生活! 2022.04.28 加入

一个喜欢最新技术,研发的人工智能专业的大二学生,用自己的代码做一些有意义的事情! 目前大二结束有去大厂研发岗实习的计划,每天丰富自己的技术,去过自己想要的实习生活。

评论

发布
暂无评论
AOP 注解详解_7月月更_武师叔_InfoQ写作社区