前置知识
AOP是基于代理模式中动态代理实现的,而动态代理是基于反射实现的。
AOP
AOP (Aspect Orient Programming),直译过来就是 面向切面编程。AOP 是一种编程思想,是面向对象编程(OOP)的一种补充。面向对象编程将程序抽象成各个层次的对象,而面向切面编程是将程序抽象成各个切面。
为什么需要 AOP
想象下面的场景,开发中在多个模块间有某段重复的代码,我们通常是怎么处理的?显然,没有人会靠“复制粘贴”吧。在传统的面向过程编程中,我们也会将这段代码,抽象成一个方法,然后在需要的地方分别调用这个方法,这样当这段代码需要修改时,我们只需要改变这个方法就可以了。然而需求总是变化的,有一天,新增了一个需求,需要再多出做修改,我们需要再抽象出一个方法,然后再在需要的地方分别调用这个方法,又或者我们不需要这个方法了,我们还是得删除掉每一处调用该方法的地方。实际上涉及到多个地方具有相同的修改的问题我们都可以通过 AOP 来解决。
AOP 实现分类
AOP 要达到的效果是,保证开发者不修改源代码的前提下,去为系统中的业务组件添加某种通用功能。AOP 的本质是由 AOP 框架修改业务组件的多个方法的源代码,看到这其实应该明白了,AOP 其实就是前面一篇文章讲的代理模式的典型应用。
按照 AOP 框架修改源代码的时机,可以将其分为两类:
- 静态 AOP 实现, AOP 框架在编译阶段对程序源代码进行修改,生成了静态的 AOP 代理类(生成的 *.class 文件已经被改掉了,需要使用特定的编译器),比如 AspectJ。
- 动态 AOP 实现, AOP 框架在运行阶段对动态生成代理对象(在内存中以 JDK 动态代理,或 CGlib 动态地生成 AOP 代理类),如 SpringAOP。
类别 |
机制 |
原理 |
优点 |
缺点 |
静态AOP |
静态织入 |
在编译起,切面直接以字节码的形式编译到目标字节码文件中 |
对系统无性能影响 |
灵活性不够 |
动态AOP |
JDK动态代理 |
在运行期,目标类加载后,为接口动态生成代理类,将切面织入到代理类中 |
相对静态AOP更加灵活 |
切入的关注点需要实现接口。对系统一点性能影响 |
动态字节码生成 |
GCLIB |
在运行期,目标类加载后,动态生成目标类的子类,将切面逻辑加入到子类中 |
没有接口也可以织入 |
扩展类的实例方法用final修饰时,无法织入 |
自定义类加载器 |
|
在运行期,目标类加载前,将切面逻辑加到目标字节码里 |
可以对绝大部分类进行织入 |
代码中如果使用了其他类记载其,则这些类将不会织入 |
字节码转换 |
|
在运行期,所有类加载器加载字节码前进行拦截 |
可以对所有类进行记载 |
麻烦而已 |
AOP术语
- 通知(Advice):AOP框架中的增强处理。通知描述了切面何时执行以及如何执行增强处理。
- 连接点(JoinPoint):连接点表示应用执行过程中能够插入切面的一个点,这个点可以是方法的调用、异常的抛出。在springAOP中,连接点总是方法的调用。
- 切点(PointCut):可以插入增强处理的连接点。
- 切面(Aspect):切面是通知和切点的结合。
- 引入(Introduction):引入允许我们向现有的类添加新的方法或者属性。
- 织入(Weaving):将增强处理添加到目标对象中,并创建一个被增强的对象,这个过程就是织入。
springAOP中,通过Advice定义横切逻辑,spring中支持5中类型的Advice:
|通知类型|连接点|实现接口|
|-|-|-|
|前置通知|方法前|org.springframework.aop.MethodBeforeAdvice|
|后置通知|方法后|org.springframework.aop.AfterReturningAdvice|
|环绕通知|方法前后|org.aopalliance.intercept.MethodInterceptor|
|异常抛出通知|方法抛出异常|org.springframework.aop.ThrowsAdvice|
|引介通知|类中增加新的方法属性|org.springframework.aop.IntroductionInterceptor|
springAOP
springAOP特点
spring的AOP是通过动态代理实现的,默认是JDK动态代理,也支持CGLIB。springAOP不能拦截对对象字段的修改,也不支持构造器连接点,无法在Bean创建时应用通知。
代码示例
这里用maven进行构建,依赖如下。
1 2 3 4 5 6 7 8 9 10 11 12
| <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>5.3.6</version> </dependency>
<dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9.6</version> </dependency>
|
业务接口
1 2 3 4 5 6
| public interface UserService { public void add(); public void delete(); public void update(); public void select(); }
|
业务实现类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| public class UserServiceImpl implements UserService { @Override public void add() { System.out.println("add a User"); }
@Override public void delete() { System.out.println("delete a User"); }
@Override public void update() { System.out.println("update a User"); }
@Override public void select() { System.out.println("select a User"); } }
|
使用spring的API接口
实现类继承spring api
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| public class Log implements MethodBeforeAdvice,AfterReturningAdvice {
@Override
public void before(Method method, Object[] objects, Object o) throws Throwable { System.out.println(o.getClass().getName()+"的"+method.getName()+"被执行了!"); }
@Override
public void afterReturning(Object o, Method method, Object[] objects, Object o1) throws Throwable { System.out.println(o1.getClass().getName()+"的"+method.getName()+"被执行了!并返回了:"+o); } }
|
配置文件,配置aop
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| <?xml version="1.0" encoding="UTF-8"?> <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" 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">
<bean id="userService" class="com.snmlm.service.impl.UserServiceImpl"/> <bean id="log" class="com.snmlm.log.Log"/>
<aop:config> <aop:pointcut id="pointcut" expression="execution(* com.snmlm.service.impl.UserServiceImpl.*(..))"/> <aop:advisor advice-ref="log" pointcut-ref="pointcut"/> </aop:config> </beans>
|
测试
1 2 3 4 5 6 7
| public class ApiTest { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("apicontext.xml"); UserService userService = (UserService)context.getBean("userService"); userService.add(); } }
|
结果输出
com.snmlm.service.impl.UserServiceImpl的add被执行了!
add a User
com.snmlm.service.impl.UserServiceImpl的add被执行了!并返回了:null
自定义切面实现AOP
自定义切面方法
1 2 3 4 5 6 7 8 9 10 11 12 13
| public class DiyPointCut { public void before(){ System.out.println("方法前执行"); } public void after(){ System.out.println("方法后执行"); } public void around(ProceedingJoinPoint jp) throws Throwable { System.out.println("环绕前"); Object result = jp.proceed(); System.out.println("环绕后"); } }
|
配置切面和切点
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| <?xml version="1.0" encoding="UTF-8"?> <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" 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">
<bean id="userService" class="com.snmlm.service.impl.UserServiceImpl"/> <bean id="diy" class="com.snmlm.diy.DiyPointCut"/>
<aop:config> <aop:aspect ref="diy"> <aop:pointcut id="point" expression="execution(* com.snmlm.service.impl.*.*(..))"/> <aop:before method="before" pointcut-ref="point"/> <aop:after method="after" pointcut-ref="point" /> <aop:around method="around" pointcut-ref="point"/> </aop:aspect> </aop:config> </beans>
|
测试类
1 2 3 4 5 6 7
| public class DiyTest { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("diycontext.xml"); UserService userService = (UserService)context.getBean("userService"); userService.add(); } }
|
结果
方法前执行
环绕前
add a User
环绕后
方法后执行
使用注解实现AOP
定义切面类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| @Aspect public class AnnotationPointCut { @Before("execution(* com.snmlm.service.impl.*.*(..))") public void before(){ System.out.println("方法前执行"); } @After("execution(* com.snmlm.service.impl.*.*(..))") public void after(){ System.out.println("方法后执行"); } @Around("execution(* com.snmlm.service.impl.*.*(..))") public void around(ProceedingJoinPoint jp) throws Throwable { System.out.println("环绕前"); Object result = jp.proceed(); System.out.println("环绕后"); } }
|
配置切面,开启注解
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| <?xml version="1.0" encoding="UTF-8"?> <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" 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">
<bean id="userService" class="com.snmlm.service.impl.UserServiceImpl"/> <bean id="annotationPointCut" class="com.snmlm.annotation.AnnotationPointCut"/>
<aop:aspectj-autoproxy expose-proxy="false"/>
</beans>
|
测试类
1 2 3 4 5 6 7
| public class AnnotationTest { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("annotationcontext.xml"); UserService userService = (UserService)context.getBean("userService"); userService.add(); } }
|
结果
环绕前
方法前执行
add a User
方法后执行
环绕后