目录
4.4.1 AOP
4.4.1.1 AOP概述
4.4.1.1.1 AOP与OOP
OOP:Object-Oriented Programing,面向对象编程)
AOP:Aspect-Oriented Programming,面向方面编程,是通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。
OOP引入封装、继承和多态性等概念来建立一种对象层次结构,用以模拟公共行为的一个集合。当我们需要为分散的对象引入公共行为的时候,OOP则显得无能为力。也就是说,OOP允许你定义从上到下的关系,但并不适合定义从左到右的关系。例如日志功能。日志代码往往水平地散布在所有对象层次中,而与它所散布到的对象的核心功能毫无关系。对于其他类型的代码,如安全性、异常处理和透明的持续性也是如此。这种 散布在各处的无关的代码被称为横切(cross-cutting)代码,在OOP设计中,它导致了大量代码的重复,而不利于各个模块的重用。
而AOP技术则恰恰相反,它利用一种称为“横切”的技术,剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其名为 “Aspect”,即方面。所谓“方面”,简单地说,就是将那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低 模块间的耦合度,并有利于未来的可操作性和可维护性。
AOP的核心思想就是:将应用程序中的商业逻辑同对其提供支持的通用服务进行分离
4.4.1.1.2 概念
- Target(目标对象):代理的目标对象
- Proxy (代理):一个类被 AOP 织入增强后,就产生一个结果代理类
- Joinpoint(连接点):所谓连接点是指那些被拦截到的点。在spring中,这些点指的是方法,因为spring只支持方法类型的连接点
- Pointcut(切点):所谓切点是指我们要对哪些 Joinpoint 进行拦截的定义
- Advice(通知/ 增强):所谓通知是指拦截到 Joinpoint 之后所要做的事情就是通知
- Aspect(切面):是切点和通知(引介)的结合(切点表达式)
- Weaving(织入):是指把增强应用到目标对象来创建新的代理对象的过程。spring采用动态代理织入,而AspectJ采用编译期织入和类装载期织入
4.4.1.1.3 步骤
-
目标类的目标方法
-
编写切面类,切面类中有通知(增强功能方法)
-
在配置文件中,配置织入关系,也就是将哪些通知与哪些连接点进行结合
关键点
Pointcut(切点):被增强的方法
Advice(通知/ 增强):封装增强业务逻辑的方法
Aspect(切面):切点+通知
Weaving(织入):将切点与通知结合的过程
4.4.1.2 AOP动态代理技术
JDK 代理 : 基于接口的动态代理技术
cglib 代理:基于父类的动态代理技术
4.4.1.2.1 JDK代理
jdk代理基于接口
//创建接口A_interface
interface A_interface{
//A_interface中有fun()方法
public void fun();
}
//书写实现类 实现接口
class A implements A_interface{
@Override
public void fun() {
System.out.println("执行fun方法");
}
}
JDK代理就是创建新的接口实现类(具有原有实现类的方法),并对原有实现类的方法进行增强。
class Jdk_A implements Jdk_A_interface{
private A_interface a;//需要增强的类
public X(A_interface a) {//将需要增加的类传入
this.a = a;
}
@Override
public void fun() {//需要增强的方法
新方法;
a.fun();
}
}
4.4.1.2.2 cglib代理
cglib动态代理基于父类,可以看成创建一个代理类,继承指定的类
简单实现原理
class Notice{
public void content(){
System.out.println("开学");
}
}
class CglibNotice extends Notice{
//通过重写的方式实现增强
@Override
public void content() {
System.out.println("开学时间:下周一");
super.content();
}
}
-------------------------------------------------------------
@Test public void t2(){ Notice n=new Notice(); CGLIB cglib =new CGLIB(); //在获取时spring容器返回代理对象 进行使用 n=cglib; n.content(); } }
public class cglib {
@Test
public void t3() {
//如上代码虽然基于父类实现 但是不是动态代理
//cglib动态代理应该是根据 被代理对象进行创建指定的代理对象进行使用
A a = new A();
Enhancer enhancer = new Enhancer(); //创建增强器
enhancer.setSuperclass(A.class); //设置父类
final A finalA = a;
enhancer.setCallback(new MethodInterceptor() { //设置回调
@Override
public Object intercept(Object o, Method method, Object[] objects,
MethodProxy methodProxy) throws Throwable {
System.out.println("新增方法执行");
Object invoke = method.invoke(finalA, objects);
return invoke;
}
});
a = (A) enhancer.create(); //创建代理对象
a.a();
}
}
4.4.1.3 基于XML的AOP开发
4.4.1.3.1 步骤
- 导入 AOP 坐标
- 创建目标接口和目标对象(类)(内部有切点)
- 创建切面对象(类),添加增强方法
- 由spring创建目标对象类和切面对象类
- 在 applicationContext.xml 中配置织入。
4.4.1.3.2 切点表达式
指明切点
execution([修饰符] 返回值类型 包名.类名.方法名(参数))
-
修饰符可以省略
-
返回值类型、包名、类名、方法名可以使用* 代表任意
-
包名与类名之间 . 代表当前包下的类, .. 表示当前包及其子包下的类
-
参数列表可以使用 .. 表示任意个数,任意类型的参数列表
示例
修饰符为public 返回值为void com.bl.aop包下的Target类中的无参method方法
execution(public void com.bl.aop.Target.method())
任意修饰符 返回值为void com.bl.aop包下Target类的所有无参方法
execution(void om.bl.aop.Target.*())
任意修饰符 返回值为void com.bl.aop包下Target类的所有方法
execution(void om.bl.aop.Target.*(..))
任意修饰符 任意返回值 com.bl.aop包下所有类的所有方法(不包含子包)
execution(* com.bl.aop.*.*(..))
任意修饰符 任意返回值 com.bl.aop包下所有类的所有方法(包含子包)
包含子包 的所有方法
execution(* com.bl.aop..*.*(..))
任意修饰符 任意返回值 com.bl.aop包下所有类 包含子包 名包含s的所有方法
execution(* com.bl.aop..*.*s*(..))
由spring管理所有类的所有方法
execution(* *..*.*(..))
4.4.1.3.2 通知
<aop:通知类型 method=“切面类中方法名” pointcut=“切点表达式"></aop:通知类型>
通知类型:
- 前置通知
- 后置通知
- 环绕通知
- 异常通知
- 返回通知
1、前置通知
在连接点方法执行之前执行
<aop:config>
<aop:aspect ref="切面类">
<aop:before method="切面类里的增强方法" pointcut="execution(public void 修饰符 返回值类型 包名.方法名)"></aop:before>
</aop:aspect>
</aop:config>
TargetInterface接口、Target实现类
public interface TargetInterface {
public void method();
}
public class Target implements TargetInterface{
@Override
public void method() {
System.out.println("方法执行");
}
}
切面类
public class MyAspect {
//前置增强方法
public void before_Mathod(){
System.out.println("前置方法");
}
}
applicationContext_AOP.xml
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="
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
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--配置目标类-->
<bean id="target" class="com.bl.aop.Target"></bean>
<!--配置切面类-->
<bean id="myAspect" class="com.bl.aop.MyAspect"></bean>
<aop:config>
<!--引用myAspect的Bean为切面对象-->
<aop:aspect ref="myAspect">
<!--配置Target的method方法执行时要进行myAspect的before方法前置增强-->
<aop:before method="before_Mathod" pointcut="execution(public void com.bl.aop.Target.method())"></aop:before>
</aop:aspect>
</aop:config>
</beans>
测试类
@Test
public void test(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext_AOP.xml");
TargetInterface target= (TargetInterface ) applicationContext.getBean("target");
target.method();
}
2、后置通知
等价于finally
<aop:config>
<aop:aspect ref="切面类">
<aop:after method="切面类里的增强方法" pointcut="execution(修饰符 返回值类型 包名.方法名)"></aop:after>
</aop:aspect>
</aop:config>
@Component
public class MyAspect {
//前置增强方法
public void before_Method(){
System.out.println("前置方法");
}
//后置增强方法
public void after_Method(){
System.out.println("后置方法");
}
}
<aop:config>
<!--引用myAspect的Bean为切面对象-->
<aop:aspect ref="myAspect">
<!--配置Target的method方法执行时要进行myAspect的before方法前置增强-->
<aop:before method="before_Method" pointcut="execution(public void com.bl.aop.Target.method())"></aop:before>
<aop:after method="after_Method" pointcut="execution(public void com.bl.aop.Target.method())"></aop:after>
</aop:aspect>
</aop:config>
3、环绕通知
around 在连接点方法执行前后执行。
public Object 方法名(ProceedingJoinPoint joinPoint) throws Throwable { Object[] args = joinPoint.getArgs();//获取请求参数 Object proceed = joinPoint.proceed(args);//调用源方法 return proceed; }参数说明:
ProceedingJoinPoint 环绕通知中使用的获取原方法的参数
getArgs() 获取原方法请求执行时传入的参数
proceed(args) 调用执行原方法传入参数
@Component
public class MyAspect {
//前置增强方法
public void before_Method(){
System.out.println("前置方法");
}
//后置增强方法
public void after_Method(){
System.out.println("后置方法");
}
//环绕通知
public Object around_Method(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("环绕通知");
Object[] args = joinPoint.getArgs();//获取请求参数
Object proceed = joinPoint.proceed(args);//调用源方法
System.out.println("环绕通知");
return proceed;
}
}
<aop:config>
<!--引用myAspect的Bean为切面对象-->
<aop:aspect ref="myAspect">
<!--配置Target的method方法执行时要进行myAspect的before方法前置增强-->
<aop:before method="before_Method" pointcut="execution(public void com.bl.aop.Target.method())"></aop:before>
<aop:after method="after_Method" pointcut="execution(public void com.bl.aop.Target.method())"></aop:after>
<aop:around method="around_Method" pointcut="execution(public void com.bl.aop.Target.method())"></aop:around>
</aop:aspect>
</aop:config>
4、异常通知
异常通知,指定连接点发生异常时才会执行的通知。
throwing配置的是异常通知接受异常对象的参数名。
<aop:after-throwing method="" throwing=" " pointcut="execution()"></aop:after-throwing>
public class Target implements TargetInterface{
@Override
public void method() {
//模拟异常
System.out.println(1/0);
System.out.println("方法执行");
}
}
@Component
public class MyAspect {
//前置增强方法
public void before_Method(){
System.out.println("前置方法");
}
//后置增强方法
public void after_Method(){
System.out.println("后置方法");
}
//环绕通知
public Object around_Method(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("环绕通知");
Object[] args = joinPoint.getArgs();//获取请求参数
Object proceed = joinPoint.proceed(args);//调用源方法
System.out.println("环绕通知");
return proceed;
}
//异常通知
public void afterThrowing_Method(Exception e) {
System.out.println("异常信息:"+e);
}
}
<aop:config>
<!--引用myAspect的Bean为切面对象-->
<aop:aspect ref="myAspect">
<!--配置Target的method方法执行时要进行myAspect的before方法前置增强-->
<aop:before method="before_Method" pointcut="execution(public void com.bl.aop.Target.method())"></aop:before>
<aop:after method="after_Method" pointcut="execution(public void com.bl.aop.Target.method())"></aop:after>
<aop:around method="around_Method" pointcut="execution(public void com.bl.aop.Target.method())"></aop:around>
<aop:after-throwing method="afterThrowing_Method" throwing="e" pointcut="execution(public void com.bl.aop.Target.method())"></aop:after-throwing>
</aop:aspect>
</aop:config>
5、返回通知
返回通知 ,连接点执行成功返回结果时,执行的通知 ,出现异常后不会执行。
returning 配置的是方法执行结束后返回值对应的参数名
@Component
public class MyAspect {
//前置增强方法
public void before_Method(){
System.out.println("前置方法");
}
//后置增强方法
public void after_Method(){
System.out.println("后置方法");
}
public Object around_Method(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("环绕通知");
Object[] args = joinPoint.getArgs();//获取请求参数
Object proceed = joinPoint.proceed(args);//调用源方法
System.out.println("环绕通知");
return proceed;
}
//异常通知
public void afterThrowing_Method(Exception e) {
System.out.println("异常信息"+e);
}
//返回通知
public void returning_Method(JoinPoint joinPoint, Object result) throws Throwable {
System.out.println("参数:"+Arrays.toString(joinPoint.getArgs()));
System.out.println("结果:"+result);
}
}
<aop:config>
<!--引用myAspect的Bean为切面对象-->
<aop:aspect ref="myAspect">
<!--配置Target的method方法执行时要进行myAspect的before方法前置增强-->
<aop:before method="before_Method" pointcut="execution(public void com.bl.aop.Target.method())"></aop:before>
<aop:after method="after_Method" pointcut="execution(public void com.bl.aop.Target.method())"></aop:after>
<aop:around method="around_Method" pointcut="execution(public void com.bl.aop.Target.method())"></aop:around>
<aop:after-throwing method="afterThrowing_Method" throwing="e" pointcut="execution(public void com.bl.aop.Target.method())"></aop:after-throwing>
<aop:after-returning method=" returning_Method" pointcut="execution(public void com.bl.aop.Target.method())" returning="result"></aop:after-returning>
</aop:aspect>
</aop:config>
4.4.1.3.3 抽离切点表达式
仔细观察上节代码,会发现在applicationCntext_AOP.xml中的
pointcut="execution(public void com.bl.aop.Target.method())"
是大量重复的,我们可以将其抽离出来。
抽离出切点表达式 <aop:pointcut id="id名" expression="execution(* *..*.*(..))"/> 使用 pointcut-ref 指定使用的切点表达式 <aop:after method="after" pointcut-ref="id名"></aop:after>
<aop:pointcut id="e1" expression="execution(public void com.bl.aop.Target.method())"/>
<aop:pointcut id="e1" expression="execution(* *..*.*(..))"/>
<aop:after method="after" pointcut-ref="e1"></aop:after>
4.4.1.4 基于注解的AOP开发
4.4.1.4.1 @Aspect注解
标识当前类为切面类 ,等价于aop:aspect标签 。
4.4.1.4.2 @Before注解
前置通知
@Before("execution(public void com.bl.aop.Target.method())")
public void before_Method(){
System.out.println("前置方法");
}
4.4.1.4.3 @After注解
后置通知
//后置增强方法
@After("execution(public void com.bl.aop.Target.method())")
public void after_Method(){
System.out.println("后置方法");
}
4.4.1.4.4 @Around注解
环绕通知
@Around("execution(public void com.bl.aop.Target.method())")
public Object around_Method(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("环绕通知");
Object[] args = joinPoint.getArgs();//获取请求参数
Object proceed = joinPoint.proceed(args);//调用源方法
System.out.println("环绕通知");
return proceed;
}
4.4.1.4.5 @AfterThrowing注解
异常通知
需要注意的是,异常通知的value不能省略,并且后面要跟上throwing @AfterThrowing(value="execution()",throwing = " ")
//异常通知
@AfterThrowing(value="execution(public void com.bl.aop.Target.method())",throwing = "e")
public void afterThrowing_Method(Exception e) {
System.out.println("异常信息"+e);
}
4.4.1.4.6 @AfterReturning注解
返回通知
@AfterReturning(value="execution()",returning = "方法返回值参数名 ")
//返回通知
@AfterReturning(value="execution(public void com.bl.aop.Target.method())",returning = "result")
public void returning_Method(JoinPoint joinPoint, Object result) throws Throwable {
System.out.println("参数:"+Arrays.toString(joinPoint.getArgs()));
System.out.println("结果:"+result);
}
4.4.1.4.7 @Pointcut注解
同基于xml的AOP开发一样,基于注解的AOP开发中,切点表达式也可以通过@Pointcut注解 抽离出来。
创建方法使用Pointcut注解书写切点表达式,其他通知方法通过类名.方法名获取切点 。并且在配置文件中添加代理模式
1.默认使用jdk动态代理,没有接口则使用cglib代理
<aop:aspectj-autoproxy/>
2.可以通过以下方式强制使用cglib代理
<aop:aspectj-autoproxy proxy-target-class="true"/>3.有两种抽离方式:
1.在当前切面类中抽离
2.单独创建一个类抽离
方法一:直接在当前切面类中定义
public class MyAspect {
@Pointcut("execution(public void com.bl.aop.Target.method())")
//书写方法使用注解定义通用切点表达式
public void pointcut(){
}
//前置增强方法
@Before("MyAspect.pointcut()")
public void before_Method(){
System.out.println("前置方法");
}
}
方法二:单独创建一个类定义
public class MyPointcut {
@Pointcut("execution(public void com.bl.aop.Target.method())")
//书写方法使用注解定义通用切点表达式
public void pointcut(){
}
}
public class MyAspect {
//前置增强方法
@Before("MyPointcut .pointcut()")
public void before_Method(){
System.out.println("前置方法");
}
}
applicationContext_AOP.xml
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="
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
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--配置目标类-->
<bean id="target" class="com.bl.aop.Target"></bean>
<!--配置切面类-->
<bean id="myAspect" class="com.bl.aop.MyAspect"></bean>
<!--默认使用jdk动态代理,没有接口则使用cglib代理-->
<aop:aspectj-autoproxy/>
<!--可以通过以下方式强制使用cglib代理-->
<!--<aop:aspectj-autoproxy proxy-target-class="true"/>-->
</beans>