什么是AOP
AOP(Aspect Oriented Programming) 翻译成中文意思就是面向切面编程,它和OOP(面向对象)一样都是一种编程思想,面向对象关注点是至上而下(纵向共同点),而我们的AOP关注的是从左到到右(横向共同点)。我说完这些你还是没有明白的化,不要着急继续听我慢慢讲。
我们来举一个例子来说明:如何把大象放入冰箱中。
POP 面向过程做发:
第一步,打开冰箱
第二步,把大象塞进去
第三步,关上冰箱
OOP 面向对象
我们换个角度将 大象 和 冰箱 归为单独的对象来处理。
冰箱就有三个行为:
- 开门
- 装大象
- 关门
大象有一个行为:
- 走进冰箱
现在的处理方式就是:冰箱开门 -> 冰箱装大象 -> 大象走进冰箱 -> 冰箱关门。这样做的好处是啥累? 如果大象儿子也要装入冰箱,或者 冰箱要升级。我们在java中只需要通过继承的方式就可以保证大象儿子和冰箱升级的行为不用再重新定义。
AOP 面向切面
面向切面 是在面向对象的基础一上来进行。在说AOP 面向切面之前我们在加点需求来说明:我们增加了2个冰箱分别来装水果和装蔬菜。
装大象:开门,装大象,关门
装水果:开门,装水果,关门
装蔬菜:门,装蔬菜,关门
我们发现每个冰箱都有共同的特性 那就是开门和关门。
我们将开门和关门进行抽取出来(暂且理解为可以抽取) 等装大象 或者 装水果的时候在调用。这样好处就是不用再为每个冰箱都定义一次开门和关门的行为了。
Spring AOP代码实现
接下来我们通过代码的方式来进行演示Spring是如何处理装大象 水果还有蔬菜的。
第一步需要引入Spring Aop相关的依赖:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.1.4.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.6.8</version>
</dependency>
查看pom.xml具体配置信息
如果不引入aspectjweaver会报如下错误:
16:23:26,524 WARN t.support.ClassPathXmlApplicationContext: 554 - Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.BeanCreationException: Error creating bean with name ‘refrigeratorA’ defined in class path resource [ioc-aop-helloworld.xml]: BeanPostProcessor before instantiation of bean failed; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name ‘org.springframework.aop.aspectj.AspectJPointcutAdvisor#0’: Cannot create inner bean ‘(inner bean)#7181ae3f’ of type [org.springframework.aop.aspectj.AspectJMethodBeforeAdvice] while setting constructor argument; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name ‘(inner bean)#7181ae3f’: Resolution of declared constructors on bean Class [org.springframework.aop.aspectj.AspectJMethodBeforeAdvice] from ClassLoader [[email protected]] failed; nested exception is java.lang.NoClassDefFoundError: org/aspectj/lang/JoinPoint
其他的依赖请参考源码:
第一步首先创建三个冰箱类用于分别装大象,装水果,装蔬菜。
public class RefrigeratorA {
public void into() {
System.out.println("装入大象...");
}
}
public class RefrigeratorB {
public void into() {
System.out.println("装入水果...");
}
}
public class RefrigeratorC {
public void into() {
System.out.println("装入蔬菜...");
}
}
开门行为抽取的通知类
public class OpenAdvice {
private static final Logger log = LoggerFactory.getLogger(OpenAdvice.class);
public void open() {
log.info("开门.....");
}
}
关门行为的抽取的通知类
public class CloseAdvice {
private static final Logger log = LoggerFactory.getLogger(OpenAdvice.class);
public void close() {
log.info("关门.....");
}
}
Spring xml配置文件进行配置切面:
<!-- 将冰箱ABC 声明称Spring Bean -->
<bean id="refrigeratorA" class="cn.zhuoqianmingyue.aop.helloword.RefrigeratorA"></bean>
<bean id="refrigeratorB" class="cn.zhuoqianmingyue.aop.helloword.RefrigeratorB"></bean>
<bean id="refrigeratorC" class="cn.zhuoqianmingyue.aop.helloword.RefrigeratorC"></bean>
<!-- 开门和关门通知类声明称Spring Bean -->
<bean id="closeAdvice" class="cn.zhuoqianmingyue.aop.helloword.CloseAdvice"></bean>
<bean id="openAdvice" class="cn.zhuoqianmingyue.aop.helloword.OpenAdvice"></bean>
<!-- aop 配置 -->
<aop:config>
<!-- aop的切面配置 ref 配置切面类(也叫通知类)的引用 -->
<aop:aspect ref="openAdvice">
<!-- aop:before 设置通知类别 method:指定我们通知(切入的方法) pointcut 指定我们的切入点(要切入目标类的那个方法)-->
<aop:before method="open" pointcut="execution(public void cn.zhuoqianmingyue.aop.helloword.RefrigeratorA.into())"/>
<aop:before method="open" pointcut="execution(public void cn.zhuoqianmingyue.aop.helloword.RefrigeratorB.into())"/>
<aop:before method="open" pointcut="execution(public void cn.zhuoqianmingyue.aop.helloword.RefrigeratorC.into())"/>
</aop:aspect>
<aop:aspect ref="closeAdvice">
<aop:after method="close" pointcut="execution(public void cn.zhuoqianmingyue.aop.helloword.RefrigeratorA.into())"/>
<aop:after method="close" pointcut="execution(public void cn.zhuoqianmingyue.aop.helloword.RefrigeratorB.into())"/>
<aop:after method="close" pointcut="execution(public void cn.zhuoqianmingyue.aop.helloword.RefrigeratorC.into())"/>
</aop:aspect>
</aop:config>
查看具体配置信息
进行测试:
测试类:
public class ElephantIntoRefrigeratorTest {
@Test
public void into() {
ApplicationContext appliction = new ClassPathXmlApplicationContext("ioc-aop-helloworld.xml");
RefrigeratorA refrigeratorA = (RefrigeratorA)appliction.getBean("refrigeratorA");
refrigeratorA.into();
RefrigeratorB refrigeratorB = (RefrigeratorB)appliction.getBean("refrigeratorB");
refrigeratorB.into();
RefrigeratorC refrigeratorC = (RefrigeratorC)appliction.getBean("refrigeratorC");
refrigeratorC.into();
}
}
Spring AOP 基本概念介绍
通过上面的代码介绍 你可能对Spring Aop有了一定的认识,接下里我们来具体说明一个Aop基本概念
目标类
包含切入点的类统称为目标类。 例如:上面案例的冰箱ABC就是目标类,因为他们都包含了 into方法 (也就是我们要将开门和关门行为动态引入的方法)。
连接点
目标类中的所有方法都是连接点,我们上面冰箱ABC三个类的所有方法都是连接点。
切入点
共性行为被抽取的方法就叫做切入点,例如:冰箱ABC into方法就是切入点
切面
配置切面类中的通知 和目标类的切入点的关联信息就是切面。
切面类
将共性行为抽取出来并形成独立的行为的类,例如:OpenAdvice 和CloseAdvice就是切面类
通知
抽取共性行为就是通知。例如:OpenAdvice 和CloseAdvice的open和close方法就是 通知
通知类别
在配置中aop:before 在into方法执行前执行 aop:after 在into方法执行后执行,指定执行的位置就是通知类别。