SpringBoot整合AOP
AOP简介
最核心的两个功能就是AOP(面向切面)和IOC(控制反转)。Spring框架的AOP机制可以让开发者把业务流程中的通用功能抽取出来,单独编写功能代码。在业务流程执行过程中,Spring框架会根据业务流程要求,自动把独立编写的功能代码切入到流程的合适位置。
例如,在一个业务系统中,用户登录是基础功能,凡是涉及到用户的业务流程都要求用户进行系统登录。如果把用户登录功能代码写入到每个业务流程中,会造成代码冗余,维护也非常麻烦,当需要修改用户登录功能时,就需要修改每个业务流程的用户登录代码,这种处理方式显然是不可取的。比较好的做法是把用户登录功能抽取出来,形成独立的模块,当业务流程需要用户登录时,系统自动把登录功能切入到业务流程中。下图是用户登录功能切入到业务流程示意图。
基于SpringBoot编写的一个简单的Spring AOPDemo。
1.引入AOP依赖
<!--引入AOP依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
2.创建Service
package com.example.aop.service;
import org.springframework.stereotype.Service;
@Service
public class UserService {
public String getUserNameById(Integer id){
System.out.println("getUserNameById");
return "zby";
}
public void deleteUserById(Integer id){
System.out.println("deleteUserById");
}
}
3.实现一个简单的web请求入口
@RestController
public class UserController {
@Autowired
UserService userService;
@GetMapping("/test1")
public String getUserNameById(Integer id){
return userService.getUserNameById(id);
}
@GetMapping("/test2")
public void deleteUserById(Integer id){
userService.deleteUserById(id);
}
}
在完成了引入AOP依赖包后,一般来说并不需要去做其他配置。因为在AOP的默认配置属性中,spring.aop.auto属性默认是开启的,也就是说只要引入了AOP依赖后,默认已经增加了@EnableAspectJAutoProxy。
3、定义切面类,实现web层的切面。要想把一个类变成切面类,需要两步,
① 在类上使用 @Component 注解 把切面类加入到IOC容器中
② 在类上使用 @Aspect 注解 使之成为切面类
@Component
@Aspect//表示这是一个切面
public class LogComponent {
/**
* 定义切入点,切入点为com.example.aop.service下的所有函数
*/
@Pointcut("execution(* com.example.aop.service.*.*(..))")
public void pc1(){
}
/**
* 前置通知:在连接点之前执行的通知
* @param jp
* @throws Throwable
*/
@Before(value = "pc1()")
public void before(JoinPoint jp){
String name = jp.getSignature().getName();
System.out.println("before--"+name);
}
/**
* 后置通知:在连接点之后执行的通知
* @param jp
* @throws Throwable
*/
@After(value = "pc1()")
public void after(JoinPoint jp){
String name = jp.getSignature().getName();
System.out.println("after--"+name);
}
/**
* 后置返回通知:在某连接点之后执行的通知,通常在一个匹配的方法返回的时候执行(可以在后置通知中绑定返回值)
* 如果参数中的第一个参数为JoinPoint,则第二个参数为返回值的信息
* 如果参数中的第一个参数不为JoinPoint,则第一个参数为returning中对应的参数
* returning:限定了只有目标方法返回值与通知方法相应参数类型时才能执行后置返回通知,否则不执行,
* 对于returning对应的通知方法参数为Object类型将匹配任何目标返回值
* @param jp
* @param result
*/
@AfterReturning(value = "pc1()",returning = "result")
public void afterReturning(JoinPoint jp,Object result){
String name = jp.getSignature().getName();
System.out.println("afterReturning--"+name+"--"+result);
}
/**
* 后置异常通知
* 定义一个名字,该名字用于匹配通知实现方法的一个参数名,当目标方法抛出异常返回后,将把目标方法抛出的异常传给通知方法;
* throwing:限定了只有目标方法抛出的异常与通知方法相应参数异常类型时才能执行后置异常通知,否则不执行,
* 对于throwing对应的通知方法参数为Throwable类型将匹配任何异常。
* @param jp
* @param e
*/
@AfterThrowing(value = "pc1()" ,throwing = "e")
public void afterThrowing(JoinPoint jp,Exception e){
String name = jp.getSignature().getName();
System.out.println("afterThrowing--"+name+"--"+e.getMessage());
}
/**
* 环绕通知:
*环绕通知非常强大,可以决定目标方法是否执行,什么时候执行,执行时是否需要替换方法参数,执行完毕是否需要替换返回值。
*环绕通知第一个参数必须是org.aspectj.lang.ProceedingJoinPoint类型
*/
@Around("pc1()")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
Object proceed = pjp.proceed();
return "proceed";
}
}
任何通知方法都可以将第一个参数定义为org.aspectj.lang.JoinPoint类型(环绕通知需要定义第一个参数为ProceedingJoinPoint类型,它是JoinPoint的一个子类)。JoinPoint接口提供了一系列有用的方法,比如 getArgs()(返回方法参数)、getThis()(返回代理对象)、getTarget()(返回目标)、getSignature()(返回正在被通知的方法相关信息)和 toString()(打印出正在被通知的方法的有用信息)。
切入点表达式
定义切入点的时候需要一个包含名字和任意参数的签名,还有一个切入点表达式,如execution(public * com.example.aop...(..))
切入点表达式的格式:execution([可见性]返回类型[声明类型].方法名(参数)[异常])
其中[]内的是可选的,其它的还支持通配符的使用:
- *:匹配所有字符
- ..:一般用于匹配多个包,多个参数
- +:表示类及其子类
4)运算符有:&&,||,!
切入点表达式关键词用例:
1)execution:用于匹配子表达式。
//匹配com.cjm.model包及其子包中所有类中的所有方法,返回类型任意,方法参数任意
@Pointcut(“execution(* com.cjm.model...(..))”)
public void before(){}
2)within:用于匹配连接点所在的Java类或者包。
//匹配Person类中的所有方法
@Pointcut(“within(com.cjm.model.Person)”)
public void before(){}
//匹配com.cjm包及其子包中所有类中的所有方法
@Pointcut(“within(com.cjm..*)”)
public void before(){}
3) this:用于向通知方法中传入代理对象的引用。
@Before(“before() && this(proxy)”)
public void beforeAdvide(JoinPoint point, Object proxy){
//处理逻辑
}
4)target:用于向通知方法中传入目标对象的引用。
@Before(“before() && target(target)
public void beforeAdvide(JoinPoint point, Object proxy){
//处理逻辑
}
5)args:用于将参数传入到通知方法中。
@Before(“before() && args(age,username)”)
public void beforeAdvide(JoinPoint point, int age, String username){
//处理逻辑
}
6)@within :用于匹配在类一级使用了参数确定的注解的类,其所有方法都将被匹配。
@Pointcut(“@within(com.cjm.annotation.AdviceAnnotation)”)
- 所有被@AdviceAnnotation标注的类都将匹配
public void before(){}
7)@target :和@within的功能类似,但必须要指定注解接口的保留策略为RUNTIME。
@Pointcut(“@target(com.cjm.annotation.AdviceAnnotation)”)
public void before(){}
8)@args :传入连接点的对象对应的Java类必须被@args指定的Annotation注解标注。
@Before(“@args(com.cjm.annotation.AdviceAnnotation)”)
public void beforeAdvide(JoinPoint point){
//处理逻辑
}
9)@annotation :匹配连接点被它参数指定的Annotation注解的方法。也就是说,所有被指定注解标注的方法都将匹配。
@Pointcut(“@annotation(com.cjm.annotation.AdviceAnnotation)”)
public void before(){}
10)bean:通过受管Bean的名字来限定连接点所在的Bean。该关键词是Spring2.5新增的。
@Pointcut(“bean(person)”)
public void before(){}
网友评论