AOP(Aspect-Oriented Programming)学前知识点
- 什么是横切关注点:横切关注点可以描述为影响多个业务模块的功能。
- 切面来源:横切关注点可以被模块为切面(aspect)。
- 将横切关注点模块化为切面的好处:1.使横切关注点的代码集中在一个地方。2.使每个业务模块只包含它们需要实现的主要功能,次要功能被转移到切面中。
- AOP功能:AOP有助于横切关注点与它们所影响的对象之间的解耦。
- AOP用法过程:横切关注点从多个类中抽取出来模块化成一个切面后,再将它的方法影响到这多个类执行的过程中。
- AOP使用到的设计模式:代理模式。
- 因为Spring基于动态代理,所以Spring只支持方法级别的连接点。
- Spring的通知是Java编写的
- 运行期,在ApplicationContext下的BeanFactory中加载所有bean,为有@Aspect注解的bean创建一个代理,这个代理会围绕着所有该切面的切点所匹配的bean,当有方法调用时,代理类拦截方法调用,先执行切面逻辑,再调用目标bean的方法。
- 通知(Adivce):就是在目标对象方法的调用前后调用通知,为对象增加新的行为。
Spring切面的5种类型的通知:
- 前置通知(Before):在目标方法被调用之前调用通知功能。
- 后置通知(After):在目标方法完成之后调用通知,此时不会关心方法的输出是什么。
- 返回通知(After-returning):在目标方法成功执行之后调用通知。
- 异常通知(After-throwing):在目标方法抛出异常后调用通知。
- 环绕通知(Around):通知包裹了被通知的方法,在被通知的方法调用之前和调用之后执行自定义的行为。
- 连接点(Join point):应用执行时能插入切面的点(Spring中类的各种方法都是连接点)。
- 切点(Point cut):缩小切面通知的连接点范围。用切点表达式匹配到切面要插入的1个或多个连接点。
- 切面(Aspect):切面是通知和切点的结合。是横切关注点模块化出来的一个类。
- 引入(Introduction):引入允许我们向现有的类添加新方法或属性。
- 织入(Weaving):切面在指定连接点被织入到目标对象中。织入是把切面应用到目标对象并创建新的代理的过程。
切面织入到类的三个时期:
- 编译期:在目标编译时织入切面。需要如AspectJ一样的特殊编译器。
- 类加载期:在目标类加载到JVM时被织入。这种方式需要特殊的类加载器,对字节码添加新的行为。
- 运行期:切面在应用运行的某个时刻被织入。在织入时,Spring AOP容器会为目标对象动态的创建一个代理对象。
- 基于代理的经典Spring AOP:编程模型很复杂很笨重。
- 纯POJO切面:运用的XML配置将POJO转化为切面。
- @AspectJ注解驱动的切面:Spring借鉴了AspectJ的切面,用注解将POJO转化为切面。
- 注入式AspectJ切面:需要拦截构造器或属性的时候。
需要加入的依赖:
<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver --> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.8.13</version> </dependency> <!-- https://mvnrepository.com/artifact/org.springframework/spring-test --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>4.3.14.RELEASE</version> </dependency> <!-- https://mvnrepository.com/artifact/org.springframework/spring-context --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>4.3.14.RELEASE</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.11</version> <scope>test</scope> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjrt</artifactId> <version>1.8.10</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>RELEASE</version> </dependency>
表演接口:
public interface Performance { // 表演接口 void perform(); }
音乐剧类实现表演接口:
public class Musical implements Performance {// 音乐剧类 @Override public void perform() { System.out.println("开始演奏——啦啦啦啦——演奏完毕"); } }
观众切面:
import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.*; // 定义一个切面 @Aspect public class Audience {// 观众类 @Pointcut("execution(* com.qsh.concert.Musical.perform(..))")// 将perform()方法定义为切点,并依附于perform()这个空方法上 public void perform() {// 用作切点依附的空方法 } @Before("perform()")// 前置通知 public void closePhone() {// 关闭手机 System.out.println("Before—Turn off the phone"); } @Before("perform()")// 前置通知 public void takeSeat() {// 请坐 System.out.println("Before—Take a seat"); } @After("perform()")// 后置通知 public void standUp() {// 站起来 System.out.println("After—stand up"); } @AfterReturning("perform()")// 返回通知 public void handclap() {// 鼓掌 System.out.println("AfterReturning—CLAP~CLAP~CLAP!"); } @AfterThrowing("perform()")// 异常通知 public void demandRefund() {// 要求退款 System.out.println("AfterThrowing—Demand a refund"); } @Around("perform()")// 环绕通知 public void doEverything(ProceedingJoinPoint jp) {// 实现上面所有通知 try { System.out.println("Around—Turn off the phone"); System.out.println("Around—Take a seat"); jp.proceed(); System.out.println("Around—stand up"); System.out.println("Around—CLAP~CLAP~CLAP!"); } catch (Throwable e) { System.out.println("Around—Demand a refund"); } } }
配置类:
import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.EnableAspectJAutoProxy; @Configuration @EnableAspectJAutoProxy //AspectJ自动代理会为使用了@Aspect注解的bean创建一个代理 public class ConcertConfig { @Bean public Audience audience() { return new Audience(); } @Bean public Musical musical() { return new Musical(); } }
测试方法:
import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes = ConcertConfig.class) public class test { @Autowired private Performance performance; @Test public void test() { performance.perform(); } }
测试结果:
注:可以看出环绕通知先开始先结束,后置通知在返回通知前先调用。
表演接口:
public interface Performance { // 表演接口 void perform(); }
音乐剧类实现表演接口:
public class Musical implements Performance {// 音乐剧类 @Override public void perform() { System.out.println("开始演奏——啦啦啦啦——演奏完毕"); } }
观众类(无注解,一会用XML将这个类声明为切面):
import org.aspectj.lang.ProceedingJoinPoint; public class Audience {// 观众类 public void closePhone() {// 关闭手机 System.out.println("Before—Turn off the phone"); } public void takeSeat() {// 请坐 System.out.println("Before—Take a seat"); } public void standUp() {// 站起来 System.out.println("After—stand up"); } public void handclap() {// 鼓掌 System.out.println("AfterReturning—CLAP~CLAP~CLAP!"); } public void demandRefund() {// 要求退款 System.out.println("AfterThrowing—Demand a refund"); } public void doEverything(ProceedingJoinPoint jp) {// 实现上面所有通知 try { System.out.println("Around—Turn off the phone"); System.out.println("Around—Take a seat"); jp.proceed();// 调用被通知的方法 System.out.println("Around—stand up"); System.out.println("Around—CLAP~CLAP~CLAP!"); } catch (Throwable e) { System.out.println("Around—Demand a refund"); } } }
XML配置:
<?xml version="1.0" encoding="utf-8" ?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:aop="http://www.springframework.org/schema/aop" 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.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.2.xsd"> <!--将观众类装配为bean--> <bean id="audience" class="com.qsh.concert.Audience"/> <!--将音乐会类装配为bean--> <bean id="musical" class="com.qsh.concert.Musical"/> <!--将观众类声明为切面--> <aop:config> <aop:aspect ref="audience"> <aop:pointcut id="perform" expression="execution(* com.qsh.concert.Performance.perform(..))"/> <aop:before pointcut-ref="perform" method="closePhone"/> <aop:before pointcut-ref="perform" method="takeSeat"/> <aop:after pointcut-ref="perform" method="standUp"/> <aop:after-returning pointcut-ref="perform" method="handclap"/> <aop:after-throwing pointcut-ref="perform" method="demandRefund"/> <aop:around pointcut-ref="perform" method="doEverything"/> </aop:aspect> </aop:config> </beans>
测试类:
import org.springframework.context.support.ClassPathXmlApplicationContext; public class test { public static void main(String[] args) { ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("concertconfig.xml"); Performance performance = context.getBean(Performance.class); performance.perform(); } }