1. pom中引入使用AOP相关的依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
2. 定义切面类
① 使用 @Component 注解 把切面类加入到IOC容器中
② 在类上使用 @Aspect 注解 使之成为切面类
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* @Author:hk
* @Date: 2019/10/30 15:30
* @Description:
*/
@Aspect
@Component
public class TestAcpect {
private Logger logger=LoggerFactory.getLogger(TestAcpect.class);
@Pointcut("execution(public * com.example.demo.controller.UserController.*(..))")
public void Test(){
}
@Before("Test() && args(username)")
public void doBefore(String username){
logger.info("USERNAME"+username);
logger.info("before############");
}
@AfterReturning(returning = "ret",pointcut = "Test()")
public void doAfterReturning(Object ret){
logger.info("RETURNING "+ret);
}
@After("Test()")
public void doAfter(){
logger.info("after############");
}
}
@Pointcut定义的切入点为com.example.demo.controller.UserController类下的所有函数做切点,@Before实现切入点的前置通知,@After实现切入点的前置通知,@AfterReturning记录请求返回的对象。
3. 控制层UserController
import com.example.demo.entity.Result;
import com.example.demo.entity.User;
import com.example.demo.repository.UserReposity;
import com.example.demo.utils.ResultUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
/**
* @Author:
* @Date: 2019/10/23 11:33
* @Description:
*/
@RestController
@RequestMapping("user")
public class UserController {
private Logger logger=LoggerFactory.getLogger(UserController.class);
@Autowired
private UserReposity userReposity;
@GetMapping("/getOne")
public Result<User> getOne(@RequestParam String username){
User user=userReposity.findByUsername(username);
logger.info("getOne############");
return ResultUtil.success(user);
}
}
4. 控制台输出
5. AOP支持的通知
①前置通知@Before:在某连接点之前执行的通知,除非抛出一个异常,否则这个通知不能阻止连接点之前的执行流程。
② 后置通知@AfterReturning:在某连接点之后执行的通知,通常在一个匹配的方法返回的时候执行(可以在后置通知中绑定返回值)。
③ 后置异常通知@AfterThrowing:在方法抛出异常退出时执行的通知。
④ 后置最终通知@After:当某连接点退出时执行的通知(不论是正常返回还是异常退出)。
⑤环绕通知@Around:包围一个连接点的通知,如方法调用等。这是最强大的一种通知类型。环绕通知可以在方法调用前后完成自定义的行为,它也会选择是否继续执行连接点或者直接返回它自己的返回值或抛出异常来结束执行。
环绕通知最强大,也最麻烦,是一个对方法的环绕,具体方法会通过代理传递到切面中去,切面中可选择执行方法与否,执行几次方法等。环绕通知使用一个代理ProceedingJoinPoint类型的对象来管理目标对象,所以此通知的第一个参数必须是ProceedingJoinPoint类型。在通知体内调用ProceedingJoinPoint的proceed()方法会导致后台的连接点方法执行。proceed()方法也可能会被调用并且传入一个Object[]对象,该数组中的值将被作为方法执行时的入参。
/**
* 环绕通知:
* 环绕通知非常强大,可以决定目标方法是否执行,什么时候执行,执行时是否需要替换方法参数,执行完毕是否需要替换返回值。
* 环绕通知第一个参数必须是org.aspectj.lang.ProceedingJoinPoint类型
*/
@Around(value = POINT_CUT)
public Object doAroundAdvice(ProceedingJoinPoint proceedingJoinPoint){
logger.info("环绕通知的目标方法名:"+proceedingJoinPoint.getSignature().getName());
try {
Object obj = proceedingJoinPoint.proceed();
return obj;
} catch (Throwable throwable) {
throwable.printStackTrace();
}
return null;
}
⑥ 有时候我们定义切面的时候,切面中需要使用到目标对象的某个参数,如何使切面能得到目标对象的参数呢?可以使用args来绑定。如果在一个args表达式中应该使用类型名字的地方使用一个参数名字,那么当通知执行的时候对象的参数值将会被传递进来。
注意:任何通知方法都可以将第一个参数定义为org.aspectj.lang.JoinPoint类型(环绕通知需要定义第一个参数为ProceedingJoinPoint类型,它是 JoinPoint 的一个子类)。JoinPoint接口提供了一系列有用的方法,比如 getArgs()(返回方法参数)、getThis()(返回代理对象)、getTarget()(返回目标)、getSignature()(返回正在被通知的方法相关信息)和 toString()(打印出正在被通知的方法的有用信息)。
6. 切入点表达式
定义切入点的时候需要一个包含名字和任意参数的签名,还有一个切入点表达式,如execution(public * com.example.aop...(..))
切入点表达式的格式:execution([可见性]返回类型[声明类型].方法名(参数)[异常])
其中[]内的是可选的,其它的还支持通配符的使用:
1) *:匹配所有字符
2) ..:一般用于匹配多个包,多个参数
3) +:表示类及其子类
4)运算符有:&&,||,!
切入点表达式关键词用例:
① execution:用于匹配子表达式。
//匹配com.cjm.model包及其子包中所有类中的所有方法,返回类型任意,方法参数任意
@Pointcut(“execution(* com.cjm.model...(..))”)
public void before(){}
② within:用于匹配连接点所在的Java类或者包。
//匹配Person类中的所有方法
@Pointcut(“within(com.cjm.model.Person)”)
public void before(){}
//匹配com.cjm包及其子包中所有类中的所有方法
@Pointcut(“within(com.cjm..*)”)
public void before(){}
③ this:用于向通知方法中传入代理对象的引用。
@Before(“before() && this(proxy)”)
public void beforeAdvide(JoinPoint point, Object proxy){
//处理逻辑
}
④ target:用于向通知方法中传入目标对象的引用。
@Before(“before() && target(target)
public void beforeAdvide(JoinPoint point, Object proxy){
//处理逻辑
}
⑤ args:用于将参数传入到通知方法中。
@Before(“before() && args(age,username)”)
public void beforeAdvide(JoinPoint point, int age, String username){
//处理逻辑
}
⑥ @within :用于匹配在类一级使用了参数确定的注解的类,其所有方法都将被匹配。
@Pointcut(“@within(com.cjm.annotation.AdviceAnnotation)”)
- 所有被@AdviceAnnotation标注的类都将匹配
public void before(){}
⑦ @target :和@within的功能类似,但必须要指定注解接口的保留策略为RUNTIME。
@Pointcut(“@target(com.cjm.annotation.AdviceAnnotation)”)
public void before(){}
⑧ @args :传入连接点的对象对应的Java类必须被@args指定的Annotation注解标注。
@Before(“@args(com.cjm.annotation.AdviceAnnotation)”)
public void beforeAdvide(JoinPoint point){
//处理逻辑
}
⑨ @annotation :匹配连接点被它参数指定的Annotation注解的方法。也就是说,所有被指定注解标注的方法都将匹配。
@Pointcut(“@annotation(com.cjm.annotation.AdviceAnnotation)”)
public void before(){}
⑩ bean:通过受管Bean的名字来限定连接点所在的Bean。该关键词是Spring2.5新增的。
@Pointcut(“bean(person)”)
public void before(){}