Author | ychhh_ |
---|
Spring AOP介绍
-
基本概念
AOP 为Aspect Oriented Programming的缩写,意思为面向切面编程,是通过预编译方式和运行期间的动态代理实现程序功能的统一维护的技术,为Spring的核心技术之一
-
AOP的作用及其优势
- 在程序运行期间,在不修改源码的情况下对方法的增强
- 减少代码重复性,提高开发效率且便于维护
底层实现技术
-
aop底层通过Spring的动态代理技术进行实现,即动态生成代理对象
-
常用的动态代理技术
-
JDK代理
- 基于接口的动态代理
- 缺陷:目标对象必须实现相应的接口
public class ProxyJDKTest { @Test public void test1(){ final TargetImpl target = new TargetImpl(); final Advice advice = new Advice(); TargetInterface proxy = (TargetInterface)Proxy.newProxyInstance( TargetImpl.class.getClassLoader(),// 获取目标对象的类加载器 TargetImpl.class.getInterfaces(), // 获取目标对象的接口字节码 new InvocationHandler(){ // 代理执行的任何方法,均质性invoke函数 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { advice.before(); method.invoke(target,args); advice.after(); return null; } } ); proxy.save(); } }
-
cglib代理
- 基于父类的动态代理技术
早期cglib需要为第三方依赖需要进行导入
在spring5.0之后的版本,已经集成导spring-core的库中,导入spring即可
-
STEP
- 生成目标对象和代理增强对象
- 创建增强器
- 设置父类(目标对象)
- 设置回调
- 创建代理对象
-
public class ProxyCGLIBTest { @Test public void test1(){ TargetImpl target = new TargetImpl(); Advice advice = new Advice(); Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(TargetImpl.class); enhancer.setCallback(new MethodInterceptor() { @Override public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { advice.before(); Object obj = method.invoke(target,args); advice.after(); return obj; } }); TargetImpl proxy = (TargetImpl)enhancer.create(); proxy.save(); } }
-
相关术语
- target:代理对象
- proxy:增强后的代理类
- joinpoint(连接点):连接点指被拦截到的点,在spring中指的是方法,因为spring只支持方法类型的连接点,目标对象的方法都是连接点
- pointcut(切入点):指对哪些方法进行增强,被增强的方法即为切入点,即当连接点被增强后,连接点 ==> 切入点
- advice(通知):指对拦截后的方法进行增强的部分
- aspect(切面):指的是通知和切入点的结合
- weaving(织入):即增强和切入点结合的过程
-
AOP开发明确事项
- 需要编写的内容
- 编写业务的核心代码(目标类的目标方法)
- 编写切面类,切面类中有通知(增强功能方法)
- 在配置文件中,配置织入的关系,即将哪些通知和哪些连接点结合
- AOP技术实现的内容
- Spring框架监控切入点方法的执行。一旦监控到切入点的方法被执行,使用代理机制,动态创建目标对象的代理对象,根据通知类别,在代理对象的对应位置,将通知对应的功能织入,完成代码的逻辑运行
- AOP底层使用哪种代理方式
- 在spring中,框架会根据目标类是否实现了接口来决定采用哪种动态代理方式
AOP开发
基于XML方式
-
在配置时可以使用spring的原生的aop配置也可使用aspectj的aop封装进行配置,建议使用aspectj进行配置
<dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.8.4</version> </dependency>
-
基本步骤
-
导入AOP相关的坐标
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.0.5.RELEASE</version> </dependency>
<dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.8.4</version> </dependency>
-
创建目标接口和目标类(内部有切入点)
public class Target { public void save(){ System.out.println("6666666666"); }; }
-
创建切面类(内部有增强方法)
public class MyAspect { public void before(){ System.out.println("before..."); } public void after(){ System.out.println("after..."); } }
-
将目标类和切面类的创建权交给spring
<!-- 目标对象 --> <bean id="target" class="aop.Target"></bean> <!-- 配置切面 --> <bean id="aspect" class="aop.MyAspect"></bean>
-
在applicationContext.xml中配置织入关系
<aop:config> <aop:aspect ref="aspect"> <aop:before method="before" pointcut="execution(public void aop.Target.save())"></aop:before> </aop:aspect> </aop:config>
-
代码测试
-
-
切点表达式的写法
-
表达式语法
execution([修饰符]返回值类型 包名.类名.方法名(参数))
- 访问修饰符可以不写
- 返回值类型、包名、类名、方法名可以使用 * 代替代表任意
- 包名与类名之间一个点 . 代表当前包下的类,两个点…代表当前包下及其子包下的类
- 参数列表使用两个点 … 表示任意个数,任意类型的参数
-
通知的类型
名称 标签 说明 前置通知 aop:before 用于配置前置通知,在切入点执行前执行 后置通知 aop:after-returning 用于配置后置通知,在切入点执行之后执行 环绕通知 aop:around 用于配置环绕通知,在切入点执行前后均执行 异常抛出通知 aop:throwing 用于配置异常抛出通知,指定方法抛出异常时执行 最终通知 aop:after 用于配置最终通知,无论增强方法是否执行均会执行通知 <aop:config> <aop:aspect ref="aspect"> <aop:before method="before" pointcut="execution(public void aop.Target.save())"></aop:before> <aop:after-returning method="after" pointcut="execution(public void aop.Target.save())"></aop:after-returning> </aop:aspect> </aop:config>
-
一个切入点可以配置多个通知
-
若配置环绕通知,需要对切入点方法配置指定的参数
public void around(ProceedingJoinPoint pjp) throws Throwable { System.out.println(555); pjp.proceed(); System.out.println(444); }
-
-
-
切点表达式的抽取
<aop:pointcut id="myPointcut" expression="execution(public void aop.Target.save())"/> <aop:before method="before" pointcut-ref="myPointcut"></aop:before>
- 完成了对切入点的抽取,方便后期的维护,和同一的增强
基于注解方式
-
基本步骤
-
创建目标接口和目标类(内部有切点)
-
创建切面类(内有增强方法)
-
将目标类和切面类的创建权交给Spring
@Component("target1") public class Target { public void save(){ System.out.println("6666666666"); }; public void save1(){ System.out.println(222); } }
@Component("aspect1") @Aspect public class MyAspect { @Before("execution(public void AnnotationAop.Target.save1())") public void before(){ System.out.println("before..."); } public void after(){ System.out.println("after..."); } public void around(ProceedingJoinPoint pjp) throws Throwable { System.out.println(555); pjp.proceed(); System.out.println(444); } }
-
在切面类中使用注解配置织入关系
-
在配置文件中开启组件扫描和AOP的自动代理
<context:component-scan base-package="AnnotationAop"></context:component-scan> <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
-
-
通过注解方式实现切入点抽取
@Pointcut("execution(public void AnnotationAop.Target.save())") public void pointCut(){}; @AfterReturning("pointCut()") public void after(){ System.out.println("after..."); }