前言
Aspect Oriented Programming ,面向切面编程
1、AOP概念
Aspect Oriented Programming ,面向切面编程
1.1、目前流行的AOP框架
AOP 框架 | 说明 |
SpringAOP | 是一款基于 AOP 编程的框架,它能够有效的减少系统间的重复代码,达到松耦合的目的。Spring AOP 使用纯 Java 实现,不需要专门的编译过程和类加载器,在运行期间通过代理方式向目标类植入增强的代码。Spring AOP 支持 2 种代理方式,分别是基于接口的 JDK 动态代理和基于继承的 CGLIB 动态代理。 |
AspectJ | 是一个基于 Java 语言的 AOP 框架,从 Spring 2.0 开始,Spring AOP 引入了对 AspectJ 的支持。AspectJ 扩展了 Java 语言,提供了一个专门的编译器,在编译时提供横向代码的植入。 |
1.2、AOP术语
名称 | 说明 |
Joinpoint(连接点) | AOP 的核心概念,指的是程序执行期间明确定义的一个点,例如方法的调用、类初始化、对象实例化等。在 Spring 中,连接点则指可以被动态代理拦截目标类的方法。 |
Pointcut(切入点) | 又称切点,指要对哪些 Joinpoint 进行拦截,即被拦截的连接点。 |
Advice(通知) | 指拦截到 Joinpoint 之后要执行的代码,即对切入点增强的内容。 |
Target(目标) | 指代理的目标对象,通常也被称为被通知(advised)对象。 |
Weaving(织入) | 指把增强代码应用到目标对象上,生成代理对象的过程。 |
Proxy(代理) | 指生成的代理对象。 |
Aspect(切面) | 切面是切入点(Pointcut)和通知(Advice)的结合。 |
1.3、Advice(增强处理)
通知 | 说明 |
before(前置通知) | 通知方法在目标方法调用之前执行 |
after(后置通知) | 通知方法在目标方法返回或异常后调用 |
after-returning(返回后通知) | 通知方法会在目标方法返回后调用 |
after-throwing(抛出异常通知) | 通知方法会在目标方法抛出异常后调用 |
around(环绕通知) | 通知方法会将目标方法封装起来 |
1.4、AOP类型
1.4.1、动态 AOP
动态 AOP 的织入过程是在运行时动态执行的。其中最具代表性的动态 AOP 实现就是 Spring AOP,它会为所有被通知的对象创建代理对象,并通过代理对象对被原对象进行增强。
相较于静态 AOP 而言,动态 AOP 的性能通常较差,但随着技术的不断发展,它的性能也在不断的稳步提升。
动态 AOP 的优点是它可以轻松地对应用程序的所有切面进行修改,而无须对主程序代码进行重新编译。
1.4.2、静态 AOP
静态 AOP 是通过修改应用程序的实际 Java 字节码,根据需要修改和扩展程序代码来实现织入过程的。最具代表性的静态 AOP 实现是 AspectJ。
相较于动态 AOP 来说,性能较好。但它也有一个明显的缺点,那就是对切面的任何修改都需要重新编译整个应用程序。
1.5、在 Spring 框架中使用 AOP的优势
1、提供声明式企业服务;
2、允许用户实现自定义切面。在某些不适合用 OOP 编程的场景中,采用 AOP 来补充。
3、可以对业务逻辑的各个部分进行隔离,从而使业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时也提高了开发效率。
2、Spring AOP
代理技术 | 描述 |
JDK 动态代理 | Spring AOP 默认的动态代理方式,若目标对象实现了若干接口,Spring 使用 JDK 的 java.lang.reflect.Proxy 类进行代理。 |
CGLIB 动态代理 | 若目标对象没有实现任何接口,Spring 则使用 CGLIB 库生成目标对象的子类,以实现对目标对象的代理。 |
2.1、连接点
Spring AOP 只支持一种连接点类型:方法调用
2.2、通知类型
通知类型 | 接口 | 描述 |
前置通知 | org.springframework.aop.MethodBeforeAdvice | 在目标方法执行前实施增强。 |
后置通知 | org.springframework.aop.AfterAdvice | 在目标方法执行后实施增强。 |
后置返回通知 | org.springframework.aop.AfterReturningAdvice | 在目标方法执行完成,并返回一个返回值后实施增强。 |
环绕通知 | org.aopalliance.intercept.MethodInterceptor | 在目标方法执行前后实施增强。 |
异常通知 | org.springframework.aop.ThrowsAdvice | 在方法抛出异常后实施增强。 |
引入通知 | org.springframework.aop.IntroductionInterceptor | 在目标类中添加一些新的方法和属性。 |
2.3、切面类型
在 Spring AOP 中,切面可以分为三类:一般切面、切点切面和引介切面。
切面类型 | 接口 | 描述 |
一般切面 | org.springframework.aop.Advisor | Spring AOP 默认的切面类型。 由于 Advisor 接口仅包含一个 Advice(通知)类型的属性,而没有定义 PointCut(切入点),因此它表示一个不带切点的简单切面。 这样的切面会对目标对象(Target)中的所有方法进行拦截并织入增强代码。由于这个切面太过宽泛,因此我们一般不会直接使用。 |
切点切面 | org.springframework.aop.PointcutAdvisor | Advisor 的子接口,用来表示带切点的切面,该接口在 Advisor 的基础上还维护了一个 PointCut(切点)类型的属性。使用它,我们可以通过包名、类名、方法名等信息更加灵活的定义切面中的切入点,提供更具有适用性的切面。 |
引介切面 | org.springframework.aop.IntroductionAdvisor | Advisor 的子接口,用来代表引介切面,引介切面是对应引介增强的特殊的切面,它应用于类层面上,所以引介切面适用 ClassFilter 进行定义。 |
2.3.1、 一般切面开发例子
package com.xxxx.spring.dao; public interface NewsDAO { public void insert(); public void delete(); public void update(); public void select(); }
package com.xxxx.spring.dao.impl; import com.xxxx.spring.dao.NewsDAO; public class UserDAOImpl implements NewsDAO { @Override public void insert() { System.out.println("执行UserDAOImpl的insert()方法"); } @Override public void delete() { System.out.println("执行UserDAOImpl的delete()方法"); } @Override public void update() { System.out.println("执行UserDAOImpl的update()方法"); } @Override public void select() { System.out.println("执行UserDAOImpl的select()方法"); } }
package com.xxxx.spring.advice; import org.springframework.aop.MethodBeforeAdvice; import java.lang.reflect.Method; public class NewsDAOAdvice implements MethodBeforeAdvice { @Override public void before(Method method, Object[] args, Object target) throws Throwable { System.out.println("执行前置操作....."); } }
<?xml version="1.0" encoding="utf-8" ?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd"> <!--代理目标--> <bean id="newsDAO" class="com.xxxx.spring.dao.impl.UserDAOImpl"/> <!--定义前置增强--> <bean id="newsDAOAdvice" class="com.xxxx.spring.advice.NewsDAOAdvice"/> <!--生成代理对象--> <bean id="newsDAOProxy" class="org.springframework.aop.framework.ProxyFactoryBean"> <!--设置代理目标--> <property name="target" ref="newsDAO"/> <!--实现接口--> <property name="proxyInterfaces" value="com.xxxx.spring.dao.NewsDAO"/> <!--增强bean名称--> <property name="interceptorNames" value="newsDAOAdvice"/> </bean> </beans>
Java测试代码:
ClassPathXmlApplicationContext classPathXmlApplicationContext = new ClassPathXmlApplicationContext("newsdao.xml"); NewsDAO newsDAOProxy = classPathXmlApplicationContext.getBean("newsDAOProxy", NewsDAO.class); newsDAOProxy.delete(); newsDAOProxy.insert();
2.3.2、 PointcutAdvisor 的 AOP 开发(带切点的切面)
Spring 提供了多个 PointCutAdvisor 的实现,其中常用实现类如如下。
- NameMatchMethodPointcutAdvisor:指定 Advice 所要应用到的目标方法名称,例如 hello* 代表所有以 hello 开头的所有方法。
- RegExpMethodPointcutAdvisor:使用正则表达式来定义切点(PointCut),RegExpMethodPointcutAdvisor 包含一个 pattern 属性,该属性使用正则表达式描述需要拦截的方法。
package com.xxxx.spring.dao.impl; import com.xxxx.spring.dao.NewsDAO; public class NewsDAOImpl implements NewsDAO { @Override public void insert() { System.out.println("执行UserDAOImpl的insert()方法"); } @Override public void inserts() { System.out.println("执行UserDAOImpl的inserts()方法"); } @Override public void delete() { System.out.println("执行UserDAOImpl的delete()方法"); } @Override public void update() { System.out.println("执行UserDAOImpl的update()方法"); } @Override public void select() { System.out.println("执行UserDAOImpl的select()方法"); } }
package com.xxxx.spring.advice; import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; public class NewsDAOAroundAdvice implements MethodInterceptor { @Override public Object invoke(MethodInvocation invocation) throws Throwable { System.out.println("before"); Object proceed = invocation.proceed(); System.out.println("after"); return proceed; } }
<?xml version="1.0" encoding="utf-8" ?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd"> <!--代理目标--> <bean id="newsDAO" class="com.xxxx.spring.dao.impl.NewsDAOImpl"/> <!--定义前置增强--> <bean id="newsDAOAdvice" class="com.xxxx.spring.advice.NewsDAOAroundAdvice"/> <!--定义切面--> <bean id="pointCutAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor"> <!--<property name="patterns" value=".*"/>--> <property name="patterns" value="com.xxxx.spring.dao.impl.NewsDAOImpl.insert.*"/> <property name="advice" ref="newsDAOAdvice"/> </bean> <!--生成代理对象--> <bean id="newsDAOProxy" class="org.springframework.aop.framework.ProxyFactoryBean"> <!--设置代理目标--> <property name="target" ref="newsDAO"/> <!--false JDK true CGLIB--> <property name="proxyTargetClass" value="true"/> <!--增强bean名称--> <property name="interceptorNames" value="pointCutAdvisor"/> </bean> </beans>