概述
AOP是aspect-oriented programming的缩写,译为面向切面编程。利用 AOP 可以对业务逻辑的各个部分进行隔离,从而使得 业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。简单来说,AOP就是不修改源代码在主干功能里面添加新功能。
底层原理
AOP底层使用了动态代理:在有接口的时候使用JDK 动态代理、在没有接口的时候使用CGLIB字节码动态代理。
JDK动态代理
简介
使用JDK 动态代理需要用到JDK中的Proxy类里面的newProxyInstance方法创建代理对象。方法如下:
1 | static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h); |
newProxyInstance方法的三个参数说明:
- loader 定义代理类的类加载器
- interfaces 代理类要实现的接口列表(可以多个)
- h 指派方法调用的处理程序(要增强的功能在这里面实现)
newProxyInstance方法返回一个指定接口的代理类实例。
InvocationHandler接口中有个invoke方法,用于实现增强的功能:
1 | public Object invoke(Object proxy, Method method, Object[] args) |
invoke方法的三个参数说明:
- proxy表示代理对象
- method表示被增强的方法
- args是方法的参数 若没有则为null
示例代码
代码结构如下:
1 | └─src |
UserDao接口:
1 | public interface UserDao { |
UserDaoImpl类:
1 | public class UserDaoImpl implements UserDao { |
JDKProxy类:
1 | public class JDKProxy { |
运行结果:
方法之前执行…add :传递的参数…[1, 2]
I am add…
方法之后执行…com.spring5.UserDaoImpl@355da254
result:3
CGLIB字节码动态代理
简介
使用CGLIB字节码动态代理不受代理类必须实现接口的限制,其底层采用ASM字节码生成框架。CGLIB动态代理的优缺点:
- 使用字节码技术生产代理类比JAVA反射效率高
- 不能对声明为final的类和方法进行代理,因为其原理是动态生成被代理类的子类
需要实现接口MethodInterceptor,然后重写intercept方法:
1 | Object intercept(Object proxy, Method method, Object[] args, MethodProxy arg3) throws Throwable; |
intercept方法的参数说明:
- proxy CGLIB生成的代理类实例,也是目标对象的子类
相当于重写父类方法 - method 被代理方法
- args 方法参数
- 为生成的代理类对方法的代理引用
intercept方法返回
参考链接
另外用到了Enhancer类,它是Cglib中的一个字节码增强器,先调它的setSuperclass()将被代理类设置成父类、再调setCallback函数执行intercept方法,最后调create()生成代理类。
示例代码
代码结构如下:
1 | └─src |
User类:
1 | public class User { |
CglibProxy类:
1 | public class CglibProxy { |
运行结果:
睡觉前脱衣服
我想睡觉…
起床穿衣服
AOP操作
概述
AOP相关的几个术语:
- 连接点
类里面哪些方法可以被增强,这些方法称为连接点 - 切入点
实际被真正增强的方法称为切入点 - 通知
实际增强的逻辑部分称为通知,分为前置通知、后置通知、环绕通知、异常通知和最终通知五种类型,其中最终通知相当于JAVA的finally。 - 切面
把通知应用到切入点过程
AspectJ
AspectJ不是 Spring 组成部分,是一个独立的AOP 框架,一般把 AspectJ 和 Spirng 框架一起使 用,进行 AOP操作。增强就是代理的意思。
准备工作
在进行AOP操作的时候需要先引入下面四个Jar包
1 | com.springsource.net.sf.cglib-2.2.0.jar |
引入包后的所有包如下:
- com.springsource.net.sf.cglib-2.2.0.jar
- com.springsource.org.aopalliance-1.0.0.jar
- com.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar
- spring-aspects-5.2.6.RELEASE.jar
- commons-logging-1.1.1.jar
- spring-aop-5.2.6.RELEASE.jar
- spring-beans-5.2.6.RELEASE.jar
- spring-context-5.2.6.RELEASE.jar
- spring-core-5.2.6.RELEASE.jar
- spring-expression-5.2.6.RELEASE.jar
AspectJ的切入点表达式语法说明
- 语法
1 | execution([权限修饰符] [返回类型] [类全路径] [方法名称]([参数列表]) ) |
- 示例语法详细说明
1 | execution (* com.sample.service.impl..*. *(..)) |
1、execution(): 表达式主体。
2、第一个*号:表示返回类型, 号表示所有的类型。
3、包名:表示需要拦截的包名,后面的两个句点表示当前包和当前包的所有子包,com.sample.service.impl包、子孙包下所有类的方法。
4、第二个号:表示类名,号表示所有的类。
5、(…):最后这个星号表示方法名,*号表示所有的方法,后面括弧里面表示方法的参数,两个句点表示任何参数
- 其他示例
1 | //例1 对com.dao.BookDao 类里面的 add 进行增强 |
基于注解实现
主要步骤
主要步骤如下:
-
在Spring配置文件中,开启注解扫描
需要在XML中引入context和aop的上下文空间。 -
使用注解创建 User 和 UserProxy 对象
-
在增强类上面添加注解 @Aspect
-
在Spring配置文件中开启生成代理对象
1
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
开启Aspectj生成对象后,会去代码中扫描@aspect注解
-
配置不同类型的通知
在通知方法上面使用@Before、@AfterReturning、@Around、@AfterThrowing和@After注解,结合切入点表达式配置。
@after是在方法执行之后执行(有异常也执行),@afterReturning是在返回值之后执行(有异常不执行)。
示例代码
代码结构:
1 | └─src |
User类:
1 |
|
UserProxy类:
1 |
|
Test类:
1 | public class Test { |
无异常返回结果:
Around 环绕之前
前置通知 before
I am add
Around 环绕之后
最终通知 after
后置通知(返回通知)afterReturning
有异常时返回结果:
Around 环绕之前
前置通知 before
最终通知 after
异常通知 afterThrowing
java.lang.ArithmeticException: / by zero
相同切入点提取
用@Pointcut标签
1 | //相同切入点抽取 |
多个增强类对同一个方法进行增强
用@Order注解设置增强类优先级,数字类型值越小表示优先级越高。
1 |
|
完全注解开发
在启动配置类中添加@EnableAspectJAutoProxy注解:
1 |
|
基于配置文件实现
具体步骤
- 创建增强类和被增强类,创建相关方法
- 在Spring配置文件中配置两个类对象
- 在Spring配置文件中配置AOP
示例代码
代码结构如下:
1 | └─src |
Student类:
1 | public class Student { |
StudentProxy类:
1 | public class StudentProxy { |
Test类:
1 | public class Test { |
bean.xml:
1 |
|
运行结果:
I am before…
I want to buy a book…
I am afterReturn…
AspectJ获取方法参数和返回值
示例代码:
1 | // 获取方法参数 |
只使用返回值时,joinPoint可以省略。
AOP失效场景
1、类内部调用被代理方法,此时被代理方法并不会被增强。
2、被代理方法非public修饰 (未验证,最好改为public)。
示例代码:
1 |
|
由于addPropositionList方法是在PropositionManageServiceImpl类内部调用的,所以AOP代理会失效。不会执行这个切面方法。
1 |
|
解决方法:使用service调用需要代理的方法。
1 |
|
Spring AOP切面方法出现异常,会影响主程序运行的解决方法
- 采用@AfterReturning,在主程序走完后执行
- try catch住异常代码,并且不要抛出新异常
第二步是必须的。
Spring AOP切面中启用新事务 (可以用@After注解代替)
Spring AOP切面中启用新事务,解决不管主程序是否回滚,都会执行切面方法(比如记录日志)。
因为@Transactional也是声明式事务,本身就是AOP实现的,在AOP的代码中使用不起作用。
所以就只能使用spring的编程式事务了,需要引入TransactionTemplate。
示例代码如下:
1 |
|
这种方式只适合于"主程序异常但需要切面正常执行"的场景。完全可以使用@After注解代替
参考链接