1. aop
大家想象,有这样的一个情景,演员表演,观众鼓掌的情景,在表演开始之前,观众安静,在演员表演之后,鼓掌叫好,在演出失败的时候观众鼓励演员。
在生活中是这样,在程序中也有这样的情况,比如在JDBC访问数据库的时候,事务的提交总是很繁琐,一段很长很长的代码中,只有一点点的关于业务的,剩下的很多的代码都是关于数据库的访问的 比如下面的代码:
在每次之后都要自己手动的提交事务,在开始之前都要自己手动的开启事务,这样的列子很多,再比如,在一个系统中,我要记录操作,就是什么人在什么时候干了什么事情,那我就要在这个人操作以后,也就是对应的service层的方法执行之后要记录,如果每次都在业务层的方法中调用记录操作的方法,就很多余。
也就是在程序中有很多的交叉事务 如下图所示
会有很多的交叉事务,比如安全,事务,和一些其他的交叉事务贯穿在程序中的每个层面。为了解决这个问题,就引出了aop,面向切面编程,将一些交叉事务提取出来,将它引入到 需要的地方,就很好的解决了这个问题,主业务逻辑只关注自己的事情,并不需要关注其他的事情。
2. aop的专业名词
aop全称为(Aspect Oriented Programming) 详细请看 aop-百度百科
在aop中很一些专业名词,需要了解一下
- 通知(advice)
就是之前所讲的交叉事务,比如之前所讲的演员和观众的问题,观众的表演之前的安静,表演之后的鼓掌,表演失败之后的鼓励演员,就是通知,观众的这几个动作放到同一个类中,这个类就是通知类。 正规的解释就是 切面的工作叫做通知,虽然我不怎么喜欢这个解释,简单的讲,就是定义了切面要干啥事。 - 切点(point cut)
就是定义了通知类中的方法要作用到哪些方法上。上面的例子中,也就是观众的安静这个动作要作用到哪些方法上,显而易见,是作用在观众表演之前的方法上。 通知是定义要干什么,切点就是定义了去哪里干 - 连接点 (joint point)
这个不太好说,要是不太理解,到最后的程序中就能理解了,
连接点就是程序中潜在的对象,也就是程序宏所有方法都可能是连接点, - 切面(Aspect)
通知 + 切点 = 切面 - 织入(weaving)
织入就是把切面应用到目标对象并且创建代理对象的过程。
在织入的过程中有三种方式: - 编译期: 顾名思义,就是在目标类的编译期时候织入,这种方法需要特殊的编译器。AspectJ的织入就是这种
- 类加载期: 目标类被加载到JVM的时候织入,这种方法需要特殊的类加载器(classloader),在目标类在应用之前增强它的字节码。AspectJ5就这种
- 运行期: 在运行的时候织入,Aop容器会自动创建一个代理对象。AOP就是这种
实现动态代理的方式有两种
- 如果目标类没有实现任何的接口,cglib 字节码增强
- 如果实现了接口,JDK动态代理
3.一个aop的例子
将上面的例子实例化
aop的实现也有两种方式,一种是xml,一种是注解
在这里会结合之前的内容
步骤:
- 创建一个表演的接口
- 创建一个演员类实现表演接口
- 创建一个舞台类
- 在舞台类中注入一个演员
- 在舞台类中调用演员表演
- 创建一个观众的通知类
- 将通知类中的方法织入到舞台类中。
看代码
- xml
创建一个表演的接口
package second;
public interface Performance {
public void dance();
}
-----------------------------------------------------------------------------
创建一个演员类实现表演接口
package second;
import org.springframework.stereotype.Component;
@Component
public class Actor implements Performance{
public void dance() {
System.out.println("Actor.dance");
}
}
-----------------------------------------------------------------------------
创建一个舞台类
package second;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class Stage {
@Autowired
private Performance actor;
public void actorPerformance(){
actor.dance();
}
}
-----------------------------------------------------------------------------
创建一个观众的通知类
package second;
public class AudienceAdvice {
public void quite(){
System.out.println("安静");
}
public void applause(){
System.out.println("鼓掌");
}
public void refuel(){
System.out.println("你可以 加油!!!");
}
}
-----------------------------------------------------------------------------
配置文件(applicationContext.xml)
将通知类中的方法织入到舞台类中。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/cache"
xmlns:contex="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
<!---
注意:
所有的关于切面的配置都以 aop开头的
----->
<contex:component-scan base-package="second"/>
<bean id="advice" class="second.AudienceAdvice"></bean>
<aop:config>
/*
定义切点
expression 定义的是切点表达式 具体的语法在后面会有介绍
*/
<aop:pointcut id="pointcut" expression="execution(public * second.Stage.actorPerformance())"/>
<aop:aspect id="aspect" ref="advice">
/*
在目标方法之前调用
*/
<aop:before method="quite" pointcut-ref="pointcut"></aop:before>
/*
在目标方法之后调用
*/
<aop:after method="applause" pointcut-ref="pointcut"/>
/*
在目标方法出错之后调用
*/
<aop:after-throwing method="refuel" pointcut-ref="pointcut"/>
</aop:aspect>
</aop:config>
</beans>
- 注解
注解版和xml的差异就是 关于aop的配置在advice类中就写好了 那在这里就把 观众的advice类重写一下就好了,其他的不变
package second;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
@Aspect //表示是一个通知类
@Component //表示是一个组件
public class AudienceAdvice {
/*
切点的复用,这里定义切点,将切点变为方法,这样就可以通过类名.方法名来复用切点 ,要是在自己的类中
就不需要类名
*/
@Pointcut("execution(public * second.Stage.actorPerformance())")
public void pointcut1(){}
@Before("AudienceAdvice.pointcut1()")
public void quite(){
System.out.println("安静");
}
@After("AudienceAdvice.pointcut1()")
public void applause(){
System.out.println("鼓掌");
}
@AfterThrowing("AudienceAdvice.pointcut1()")
public void refuel(){
System.out.println("你可以 加油!!!");
}
}
-------------------------------------------------------------------------------
xml文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/cache"
xmlns:contex="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
<contex:component-scan base-package="second"/>
<!----
这个表示自动创建代理
------>
<aop:aspectj-autoproxy ></aop:aspectj-autoproxy>
</beans>
结果:
4.补充说明
- 通知的分类
通知分为下面几个类别- 前置通知
- 后置通知
- 返回通知
- 异常通知
- 环绕通知
在上面的几个通知中,比较烦的就是返回通知和环绕通知
但是需要记住的是 **在方法的参数的有JoinPoint对象的形参,就可以获取目标方法的具体的信息
**
关于这几个通知的差别在 Spring AOP–前置通知和后置通知 、返回通知,异常通知和环绕通知中有具体的介绍
- 切入点表达式