GitHub 地址:
https://github.com/asd821300801/Spring-Boot.git
pom.xml文件中添加AOP支持
<!-- AOP -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
使用@Aspect注解将一个Java类定义为切面类
- @Pointcut定义一个切入点,可以是一个规则表达式,比如下例中某个package下的所有函数,也可以是一个注解等。
根据需要在切入点不同位置的切入内容
@Before在切入点开始处切入内容
@After在切入点结尾处切入内容
@AfterReturning在切入点return内容之后切入内容(可以用来对处理返回值做一些加工处理)
@Around在切入点前后切入内容,并自己控制何时执行切入点自身的内容
@AfterThrowing用来处理当切入内容部分抛出异常之后的处理逻辑
AOP切面类
- AspectAPI.java
包所在:com.example.aop
package com.example.aop;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
@Component
@Aspect
public class AspectAPI {
//两个..代表所有子目录,最后括号里的两个..代表所有参数
@Pointcut("execution(* com.example.service..*.*(..))")
private void pointCutMethod(){
}
//声明前置通知
@Before("pointCutMethod()")
public void doBefore(){
System.out.println("前置通知");
}
//后置通知,包括异常
@After("pointCutMethod()")
public void doAfter(){
System.out.println("后置通知,包括异常");
}
//声明例外通知
@AfterThrowing(pointcut="pointCutMethod()",throwing = "e")
public void doAfterThrowing(Exception e){
System.out.println("例外通知(异常)");
}
//声明后置通知
@AfterReturning(pointcut="pointCutMethod()",returning="result")
public void daAfterReturning(String result){
System.out.println("后置通知,连接点完成,不包括异常: " + result);
}
//声明环绕通知
@Around("pointCutMethod()")
public Object doAround(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("进入方法---环绕通知");
System.out.println(pjp.getTarget().getClass().getName());
Object o = pjp.proceed();
System.out.println("退出方法---环绕通知");
return o;
}
}
service层在:Spring Boot (教程十一: 集成Mybatis) 中已经编写过了,这里就不贴service层的代码了,需要的同学可以到GitHub下载
控制器
- UserController.java
包所在:com.example.controller
UserController.java
package com.example.controller;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.example.domain.User;
import com.example.service.UserService;
@RestController
@RequestMapping("/user")
public class UserController {
private Logger logger = LoggerFactory.getLogger(this.getClass());
@Autowired
private UserService service;
@RequestMapping("/all")
public List<User> getUser(){
List<User> list = service.getAll();
logger.info(list.toString());
return list;
}
}
测试
灵活使用注解进行拦截
自定义元注解
- LogAop.java
包所在:com.example.annotate
package com.example.annotate;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.METHOD)//字段注解 , 用于描述方法
@Retention(RetentionPolicy.RUNTIME)//在运行期保留注解信息
public @interface LogAop {
String name() default "Log";
}
AOP切面类
- LogAspect.java
包所在: com.example.aop
package com.example.aop;
import java.util.Arrays;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import com.example.annotate.LogAop;
@Component
@Aspect
public class LogAspect {
private static final Logger LOG = LoggerFactory.getLogger(LogAspect.class);
@Before("@annotation(log)")
public void beforeTest(JoinPoint joinPoint, LogAop log) throws Throwable {
LOG.info("进入:" + log.name());
LOG.info("CLASS_METHOD : " + joinPoint.getSignature().getDeclaringTypeName() + "."
+ joinPoint.getSignature().getName());
LOG.info("参数 : " + Arrays.toString(joinPoint.getArgs()));
}
@After("@annotation(log)")
public void afterTest(JoinPoint point, LogAop log) {
LOG.info("退出:" + log.name());
}
}
控制器
- AopController.java
包所在:com.example.controller
package com.example.controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.example.annotate.LogAop;
@RestController
@RequestMapping("/aop")
public class AopController {
@LogAop(name="/aop/aop.action") //添加了注解之后才会被拦截
@RequestMapping("/aop")
public String aop(){
return "hello world!";
}
@RequestMapping("/noAop") //这个方法是不会被拦截的
public String noAop(){
return "hello world!";
}
}
测试
- 使用@LogAop注解的方法会被AOP拦截
- 没有使用@LogAop注解的方法则不会被拦截
工程结构图
额外的内容
注意:
在application.properties中也不需要添加spring.aop.auto=true,因为这个默认就是true,值为true就是启用@EnableAspectJAutoProxy注解了。
你不需要手工添加 @EnableAspectJAutoProxy 注解。
当你需要使用CGLIB来实现AOP的时候,需要配置spring.aop.proxy-target-class=true,这个默认值是false,不然默认使用的是标准Java的实现。
其实aspectj的拦截器会被解析成AOP中的advice,最终被适配成MethodInterceptor,这些都是Spring自动完成的,如果你有兴趣,详细的过程请参考springAOP的实现。
关于集中拦截方法的区别总结:
HandlerInterceptoer拦截的是请求地址,所以针对请求地址做一些验证、预处理等操作比较合适。
当你需要统计请求的响应时间时MethodInterceptor将不太容易做到,因为它可能跨越很多方法或者只涉及到已经定义好的方法中一部分代码。
MethodInterceptor利用的是AOP的实现机制,在本文中只说明了使用方式,关于原理和机制方面介绍的比较少,因为要说清楚这些需要讲出AOP的相当一部分内容。
在对一些普通的方法上的拦截HandlerInterceptoer就无能为力了,这时候只能利用AOP的MethodInterceptor。
Filter是Servlet规范规定的,不属于spring框架,也是用于请求的拦截。但是它适合更粗粒度的拦截,在请求前后做一些编解码处理、日志记录等。