面向方面的编程(Aspect-oriented Programming,简称AOP)通过提供另一种思考程序结构的方式来补充面向对象的编程(OOP)。这是官方文档对AOP定义的翻译,看不懂没关系,先实现一个简单的Demo,再回过头思考什么是AOP会容易很多。
Name | Description |
---|---|
通知(Advice) | 切面的工作内容 |
连接点(Joint point) | 切点通知的时机 |
切点(Point cut) | 在何处通知 |
切面(Aspect) | 通知和切点的结合(切点是什么,在何时和何处完成通知) |
引入(Introduction) | 向现有的类添加新方法或新属性 |
织入(Weaving) | 把切面应用到目标对象并创建新的代理对象的过程 |
同样的,暂时看不懂没关系,通过一个简单的Demo可以很容易理解!
这里用的例子是参考《Spring实战(第四版)》里面的例子,我模仿书里面的代码后不能运行,所以做了一些修改。
设计一个表演的场景。表演的接口类和表演的实现类。
package com.kai.service;
public interface Performance {
public void perform();
}
package com.kai.service;
public class PerformanceImpl implements Performance {
public void perform(){
System.out.println("Performance接口实现!");
}
}
测试代码:
import com.kai.service.Performance;
import com.kai.service.PerformanceImpl;
public class MyTest {
public static void main(String[] args) {
PerformanceImpl performance = new PerformanceImpl();
performance.perform
}
}
这样就实现了一个表演的场景,但是现实中的表演场景明显会更加复杂,在表演开始前,为表示礼貌手机需要静音、然后搬个板凳,表演结束后要鼓掌。但是现在表演类已经写好了,怎么把这些功能加进去而不影响原来的代码呢?这就用到面向切面编程(AOP)了。
AOP的实现方法有三种:①Spring API实现;②XML定义切面;③注解定义切面。因为很多书还有网上教程都没说明是用哪种方法,所以相互参考的时候搞的有点懵,走了一些弯路。
注解实现应该是最简单的方法了。
<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
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<bean id="performance" class="com.kai.service.PerformanceImpl"/>
<bean id="audience" class="com.kai.log.Audience"/>
<aop:aspectj-autoproxy/>
beans>
基于注解的AOP实现配置XML非常简单,只需要加入
自动代理支持就行了。
package com.kai.log;
import org.aspectj.lang.annotation.*;
@Aspect
public class Audience {
@Pointcut("execution(* com.kai.service.PerformanceImpl.perform(..))")
public void performance(){}
@Before("performance()") // 表演之前
public void silenceCellPhone() {
System.out.println("Silence cell phones.");
}
@Before("performance()") // 表演之前
public void takeSeat() {
System.out.println("Taking Seats.");
}
@AfterReturning("performance()") // 表演之后
public void applause() {
System.out.println("CLAP CLAP CLAP!!!");
}
@AfterThrowing("performance()") // 表演失败之后
public void demandfund() {
System.out.println("Demanding a refund.");
}
}
import com.kai.service.Performance;
import com.kai.service.PerformanceImpl;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MyTest {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
Performance performance = (Performance) context.getBean("performance");
performance.perform();
}
}
从运行结果可以看出,加入Audience类之后,在perform()
方法之前先执行了silenceCellPhone()
和takeSeat()
方法,在perform()
方法执行结束之后又执行了applause
方法。看到这应该可以理解面向切面编程是什么意思了吧。通俗地讲就是在原来的代码基础上切入一些功能。
再来看下Audience类,@Aspect
注解声明这个类是一个切面,@Pointcut
注解声明了切点的位置,这里既Perform()
方法,我们需要在Perform()
方法前后添加功能,所以它就是切点。@Before
、@AfterReturning
、@AfterThrowing
这三个注解则定义了添加的功能的内容,这个内容就是通知,连接点则是定义的Performace
接口。几个术语一下子就搞定了。
顾名思义,上一种方式是通过注解来定义切面,因此在XML中只需要加入自动代理即可。这种方法则需要在XML手动定义切面。还是以上面那个例子来介绍。
表演的接口和实现类不变,修改下Audience类:
package com.kai.log;
import org.aspectj.lang.annotation.*;
public class Audience {
public void silenceCellPhone() {
System.out.println("Silence cell phones.");
}
public void takeSeat() {
System.out.println("Taking Seats.");
}
public void applause() {
System.out.println("CLAP CLAP CLAP!!!");
}
public void demandfund() {
System.out.println("Demanding a refund.");
}
}
也就是将注解删掉,将切面的定义转移到XML文件中。XML配置文件:
<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
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<bean id="performance" class="com.kai.service.PerformanceImpl"/>
<bean id="audience" class="com.kai.log.Audience"/>
<aop:config>
<aop:aspect ref="audience">
<aop:pointcut id="pointcut" expression="execution(* com.kai.service.PerformanceImpl.perform(..))"/>
<aop:before method="silenceCellPhone" pointcut-ref="pointcut"/>
<aop:before method="takeSeat" pointcut-ref="pointcut"/>
<aop:after-returning method="applause" pointcut-ref="pointcut"/>
<aop:after-throwing method="demandfund" pointcut-ref="pointcut"/>
aop:aspect>
aop:config>
beans>
可以看到相比于注解的方式,这种方式使得配置文件的内容多了很多。
这种方法比较复杂,现在已经渐渐被淘汰了,这里就不多做介绍,有兴趣可以去B站看秦疆老师的视频。
主要参考材料
Tips:
本人是刚开始学习Sping,如有错误或者不足之处还望不吝赐教,欢迎共同交流学习!