一、AOP简介
AOP (Aspect Oriented Programming) 面向切面编程
AOP 的主要编程对象是切面(aspect), 而切面模块化横切关注点
二、AOP示例
①在 Spring 应用中使用 AspectJ 注解, 必须依赖 AspectJ 类库: aopalliance.jar、 aspectj.weaver.jar 和 spring-aspects.jar
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.0.8.RELEASE</version>
</dependency>
<dependency>
<groupId>aopalliance</groupId>
<artifactId>aopalliance</artifactId>
<version>1.0</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.2</version>
</dependency>
②spring 的配置文件需要引用 aop 的命名空间
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.2.xsd">
</beans>
③在 Spring 的配置文件中加入< aop:aspectj-autoproxy >
<aop:aspectj-autoproxy/>
<context:component-scan base-package="cn.com.et"/>
④在实现类中增加注解,将其纳入到 spring 容器中
@Component
public class Add implements Calculator {
@Override
public double cal(double num1, double num2) {
return num1 + num2;
}
}
⑤编写切面
public class CalculatorAspect {
@Before("execution(* cn.com.et.impl.*.cal(..))")
public void before(){
System.out.println("执行前需要执行的内容");
}
}
⑥测试程序
public class AspectTest {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("META-INF/applicationContext.xml");
Calculator calculator = (Calculator) ctx.getBean("add");
double rst = calculator.cal(777, 111);
System.out.println(rst);
}
}
控制台打印:
执行前需要执行的内容
888.0
三、切入点表达式
1.完整表达式 例:
execution(public double cn.com.et.impl.Add.cal(double,double))
- execution:动作关键字,描述切入点的行为动作,例如execution表示执行到指定切入点
- public:访问修饰符,还可以是public,private等,可以省略
- double:返回值,写返回值类型
- cn.com.et.impl:包名,多级包使用点连接
- Add:类/接口名称
- cal:方法名
- double:参数,直接写参数的类型,多个类型用逗号隔开
- 异常名:方法定义中抛出指定异常,可以省略
2.通配符
*
:单个独立的任意符号,可以独立出现,也可以作为前缀或者后缀的匹配符出现
..
:多个连续的任意符号,可以独立出现,常用于简化参数的书写
例:execution(* *.cal(..))
四、JoinPoint参数
可以在通知方法中声明一个类型为 JoinPoint 的参数. 然后就能访问链接细节. 如方法 名称和参数值。
@Before("execution(* cn.com.et.impl.*.cal(..))")
public void before(JoinPoint joinPoint){
System.out.println("被通知对象名称" + joinPoint.getTarget().getClass().toString());
System.out.println("被指定的方法名称" + joinPoint.getSignature().getName());
System.out.println("方法的参数" + Arrays.toString(joinPoint.getArgs()));
System.out.println("执行前需要执行的内容");
}
五、AOP术语
- 切面(Aspect):描述通知与切入点的对应关系
加上注解@Aspect的类 - 通知(Advice):若干个方法的共性功能,在切入点处执行,最终体现为一个方法
在切面中加入注解@Before;@After 等等的方法 - 目标对象(Target):被代理的原始对象成为目标对象
要使用这个切面通知的类。JoinPoint.getTarget()方法来获取 - 代理(Proxy):SpringAOP的核心本质是采用代理模式实现的
JoinPoint.getThis()方法来获取 - 连接点(JoinPoint):在SpringAOP中,理解为任意方法的执行
可以通过通知方法的参数 JoinPoint 来获取 - 切入点(Pointcut):匹配连接点的式子,也是具有共性功能的方法描述
通过切点表达式所匹配到的方法
1.通知
@Before
前置通知。表示在业务方法执行之前去执行的通知方法。
@After
后置通知, 在业务方法执行之后执行
@AfterReturning
返回通知, 在方法返回结果之后执行。无论连接点是正常返回还是抛出异常, 后置通知 都会执行. 如果只想在连接点返回的时候记录日志, 应使用返回通知代替后置通知
@AfterThrowing
异常通知,只在连接点抛出异常时才执行异常通知
@Around:
环绕通知是所有通知类型中功能最为强大的, 能够全面地控制连接点. 甚至可以控制是 否执行连接点. 对于环绕通
@Component
@Aspect
public class CalculatorAspect {
@Before("execution(* cn.com.et.impl.*.cal(..))")
public void before(JoinPoint joinPoint){
System.out.println("被通知对象名称" + joinPoint.getTarget().getClass().toString());
System.out.println("被指定的方法名称" + joinPoint.getSignature().getName());
System.out.println("方法的参数" + Arrays.toString(joinPoint.getArgs()));
System.out.println("执行前需要执行的内容");
}
@After("execution(* cn.com.et.impl.*.cal(..))")
public void after(){
System.out.println("执行之后...");
}
@AfterReturning(pointcut = "execution(* cn.com.et.impl.*.cal(..))",returning = "obj")
public void returning(JoinPoint joinPoint,Object obj){
System.out.println("返回通知......方法返回值为" + obj);
}
@Around("execution(* *.cal(..))")
public Object around(ProceedingJoinPoint proceedingJoinPoint){
System.out.println("before");
Object obj = null;
try {
obj = proceedingJoinPoint.proceed();
System.out.println("after return");
} catch (Throwable e) {
System.out.println("Throwable");
}finally {
System.out.println("after");
}
return obj;
}
}
2.代理
遇到的小问题:(Add实现了Calculator接口)
Add add = (Add) ctx.getBean("add");
Exception in thread “main” java.lang.ClassCastException:
com.sun.proxy.$Proxy11 cannot be cast to cn.com.et.impl.Add
不能用接口的实现类(Add)来转换Proxy的实现类,它们是同级,应该用共同的接口(Calculator)来转换。
Calculator calculator = (Calculator) ctx.getBean("add");
查询得知:
Spring AOP实现方式有两种
1:使用JDK动态代理,如果被代理的目标实现了至少一个接口,则会使用JDK动态代理,所有该目标类型实现的接口都将被代理。
2:通过CGLIB来为目标对象创建代理,若该目标对象没有实现任何接口,则创建一个CGLIB代理,创建的代理类是目标类的子类。
参考文章:https://www.jianshu.com/p/a0c349e813eb
总结
本文对今天的AOP学习做了一个简单的总结,参考了多位大佬的资料,受益匪浅。