Spring中AOP基于Annotation的零配置(1)

时间:2021-11-22 23:45:49



一、AOP需要程序员参与的三个部分:

1、定义普通业务组件

2、定义切入点,一个切入点可能横切多个业务组件

3、定义曾强处理,增强处理就是AOP框架为普通业务组件织入的处理动作

一旦定义了合适的切入点和增强处理,AOP框架将会自动生成AOP代理,而AOP代理方法大致有如下公式:

代理对象的方法 = 增强处理 + 被代理对象的方法

建议使用AspectJ方式来定义切入点和增强处理,在这种公式下,Spring依然有如下两种选择来定义切入点和增强处理

①基于Annotation的“零配置”方式:使用@Aspect、@Pointcut等Annotation来标注切入点和增强处理

②基于XML配置文件的管理方式:使用SPring配置文件来定义切入点和增强处理

二、Spring依然采用运行时生成动态代理的方式来增强目标对象,所以它不需要增加额外的编译,也不需要AspectJ的织入器支持;而AspectJ在采用编译时增强,所以AspectJ需要使用自己的编译器来编译Java文件,还需要织入器。

为启动Spring对@AspectJ切面配置的支持,并保证Spring容器的目标Bean被一个或多个切面自动增强,必须Spring配置文件中配置如下片段:

bean.xml

<?xml version="1.0" encoding="UTF-8"?>

<span style="font-family: Arial, Helvetica, sans-serif; background-color: rgb(255, 255, 255);"></span><pre name="code" class="html"><!-- Spring配置文件的根元素,使用spring-beans-3.0.xsd语义约束 -->
<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"
	xmlns="http://www.springframework.org/schema/beans"
	xsi:schemaLocation="http://www.springframework.org/schema/beans
	http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
	http://www.springframework.org/schema/context
	http://www.springframework.org/schema/context/spring-context-3.0.xsd
	http://www.springframework.org/schema/aop
	http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">
	
	
	<!-- 指定自动搜索Bean组件、自动搜索切面类 -->
	<context:component-scan base-package="tju.chc.app.service.impl,tju.chc.app.service">
		<context:include-filter type="annotation" expression="org.aspectj.lang.annotation.Aspect"/>
	
	</context:component-scan>
	<!-- 启动!AspectJ支持 -->
	<aop:aspectj-autoproxy/>
	
	<bean id="korea" class="tju.chc.app.service.impl.Korea" />
</beans>

TxApect增强处理

public aspect TxAspect {
	//指定执行Hello.sayHello()方法时执行下面代码块
	void around():call(void Hello.sayHello()){
		System.out.println("开始事务");
		proceed();
		System.out.println("事物结束");
	}
}


BeforeAdvice增强处理
 
<span style="font-family: Arial, Helvetica, sans-serif; background-color: rgb(255, 255, 255);">
</span>
<span style="font-family: Arial, Helvetica, sans-serif; background-color: rgb(255, 255, 255);">定义Before增强处理</span>
package tju.chc.aspectJTest;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
@Aspect
public class BeforeAdviceTest {
	//匹配  包下所有的类的所有方法的执行  作为切入点
	@Before("execution(* tju.chc.app.service.impl.*.*(..))")
	public void authority(){
		System.out.println("before 切入");
	}
}

定义AfterReturning增强处理

package tju.chc.aspectJTest;

import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;


@Aspect
public class AfterReturningAdviceTest {
	//匹配tju.chc.app.service.impl包下所有类的所有方法的执行作为切入点
	@AfterReturning(returning="rvt",
			pointcut="execution(* tju.chc.app.service.impl.*.*(..))")
	public void log(Object rvt){
		System.out.println("获取目标方法返回值:" + rvt);
		System.out.println("模拟记录日志功能。。。");
	}
}

输出

开始事务
Hello AspectJ!
事物结束

before 切入
获取目标方法返回值:adfHello , SPring AOP
模拟记录日志功能。。。
adfHello , SPring AOP
before 切入
Korea eat 泡菜
获取目标方法返回值:null
模拟记录日志功能。。。

4AfterThrowing增强处理

package tju.chc.aspectJTest;

import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;

@Aspect
public class AfterThrowingAdviceTest {
	//匹配  包下所有类的所有方法的执行作为切入点
	@AfterThrowing(throwing ="ex"
			,pointcut="execution(* tju.chc.app.service.impl.*.*(..))")
	public void doRecoveryActions(Throwable ex){
		System.out.println("目标方法中抛出的异常:"+ ex);
		System.out.println("模拟抛出异常增强处理");
	}
}
Korea类增加的两个方法
	//定义一个sayHi方法
	public String sayHi(String name){
		try{
			System.out.println("start running");
			new FileInputStream("a.txt");
		}catch(Exception ex){
			System.out.println("目标异常处理" + ex.getMessage());
		}
		return name + ", Hi,Spring AOP";
	}
	public void divide(){
		int a = 5 / 0;
		System.out.println("divide compelete");
	}

输出

----------------------sayHi
before 切入
start running
目标异常处理a.txt (系统找不到指定的文件。)
获取目标方法返回值:mashang6, Hi,Spring AOP
模拟记录日志功能。。。
----------------------divide
before 切入
目标方法中抛出的异常:java.lang.ArithmeticException: / by zero
模拟抛出异常增强处理
Exception in thread "main" java.lang.ArithmeticException: / by zero
at tju.chc.app.service.impl.Korea.divide(Korea.java:36)
at tju.chc.aspectJTest.Hello.main(Hello.java:25)

5、After增强处理

与AfterReturning增强处理优点类似,但也有区别:

①AfterReturning增强处理只有在目标方法成功完成后才会被织入

②After增强处理不管目标方法如何结束(包括成功完成和遇到异常终止两种情况),它都会被织入

因此After增强处理必须准备处理正常返回和异常返回两种情况,这种增强处理通常用于释放资源。

package tju.chc.aspectJTest;

import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;

@Aspect
public class AfterAdviceTest {
	//匹配包下所有类的所有方法的执行作为切入点
	@After("execution(* tju.chc.app.service.impl.*.*(..))")
	public void release(){
		System.out.println("模拟方法结束后的释放资源。。。");
	}
}


输出

目标方法中抛出的异常:java.lang.ArithmeticException: / by zero
模拟抛出异常增强处理
模拟方法结束后的释放资源。。。
Exception in thread "main" java.lang.ArithmeticException: / by zero
	at tju.chc.app.service.impl.Korea.divide(Korea.java:36)
	at tju.chc.aspectJTest.Hello.main(Hello.java:25)

6.Around增强处理

Around 增强处理既可以在执行目标方法之前织入增强动作,也可以执行目标方法之后织入增强动作

Around增强处理可以决定目标方法在什么时候执行,如何执行,甚至可以完全阻止方法的执行

可以改变执行目标方法的参数值,也可以改变执行目标方法之后的返回值

(通常需要在线程安全的环境下使用,因此如果普通的Before增强处理和AfterReturning 增强处理就能解觉得问题,就没必要使用Around)

当定义一个Around增强处理方法时,该方法的第一个形参必须是ProceedingJoinPoint类型(至少包含一个形参),在增强处理方法体内,调用ProceedingJoinPoint的proceed()方法才会执行目标方法(这就是Around可以完全控制目标方法执行时机。如何执行的关键;如果程序没有调用ProceedingJoinPoint的proceed()方法,则目标方法不会执行)

调用ProceedingJoinPoint的proceed()方法时,还可以传入一个Object【】对象,该数组中的值将被出入目标方法作为执行方法的实参。(用于改变目标方法的的参数)

切面定义如下

package tju.chc.aspectJTest;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;

@Aspect
public class AroundAdviceTest {
	//匹配包下的所有类的所有方法的执行作为切入点
	
	@Around("execution(* tju.chc.app.service.impl.*.*(..))")
	public Object processTx(ProceedingJoinPoint pjp) throws Throwable{
		
		System.out.println("z执行目标方法之前,模拟开始事务");
		//执行目标方法,并保存目标方法执行后的返回值
		Object ret = pjp.proceed(new String[]{"被改变的参数"});
		System.out.println("z执行目标方法之后,模拟结束事务。。。");
		//该变目标方法的返回值
		return ret + " new added content"; 
	}
}

输出

before 切入
z执行目标方法之前,模拟开始事务
获取目标方法返回值:被改变的参数Hello , SPring AOP
模拟记录日志功能。。。
z执行目标方法之后,模拟结束事务。。。
模拟方法结束后的释放资源。。。
被改变的参数Hello , SPring AOPnew added content
before 切入
z执行目标方法之前,模拟开始事务
Korea eat 被改变的参数
获取目标方法返回值:null
模拟记录日志功能。。。
z执行目标方法之后,模拟结束事务。。。
模拟方法结束后的释放资源。。。
----------------------sayHi
before 切入
z执行目标方法之前,模拟开始事务
start running
目标异常处理a.txt (系统找不到指定的文件。)
获取目标方法返回值:被改变的参数, Hi,Spring AOP
模拟记录日志功能。。。
z执行目标方法之后,模拟结束事务。。。
模拟方法结束后的释放资源。。。
----------------------divide
before 切入
z执行目标方法之前,模拟开始事务
目标方法中抛出的异常:java.lang.ClassCastException: java.lang.String cannot be cast to org.aspectj.lang.JoinPoint
模拟抛出异常增强处理
模拟方法结束后的释放资源。。。
Exception in thread "main" java.lang.ClassCastException: java.lang.String cannot be cast to org.aspectj.lang.JoinPoint
	at tju.chc.app.service.impl.Korea$AjcClosure9.run(Korea.java:1)
	at org.aspectj.runtime.reflect.JoinPointImpl.proceed(JoinPointImpl.java:221)
	at tju.chc.aspectJTest.AroundAdviceTest.processTx(AroundAdviceTest.java:16)
	at tju.chc.app.service.impl.Korea.divide(Korea.java:35)
	at tju.chc.aspectJTest.Hello.main(Hello.java:25)


7、访问目标函数的参数

在定义增强处理方法时将第一个参数定义为JoinPoint类型,当该类型增强处理方法被调用时,该JoinPoint参数就代表了织入曾强处理的连接点。JoinPoint中几个常用的方法

Object[]  getArgs():返回执行目标方法时的参数

Signature getSignature()  放回被增强的方法的相关信息

Object getTarget() :返回被织入增强处理的目标对象

Object getThis()  : 返回AOP框架为目标对象生成的代理对象

@Aspect
public class FourAdviceTest {
	// 匹配包下的所有类的所有方法的执行作为切入点
	@Around("execution(* tju.chc.app.service.impl.*.*(..))")
	public Object processTx(ProceedingJoinPoint pjp) throws Throwable {

		System.out.println("Around增强:z执行目标方法之前,模拟开始事务");
		//访问执行目标方法的参数
		Object[] args = pjp.getArgs();
		
		//当执行目标方法的参数存在且第一个参数是字符串参数
		if(args != null && args.length > 0 && args[0].getClass() == String.class){
			//改变第一个目标方法的第一个参数
			args[0] = "被改变参数4";
		}
		// 执行目标方法,并保存目标方法执行后的返回值
		Object ret = pjp.proceed(args);
		System.out.println("Around增强:z执行目标方法之后,模拟结束事务。。。");
		// 该变目标方法的返回值
		return ret + " new added content";
	}
	
	//Before增强处理 匹配  包下所有的类的所有方法的执行  作为切入点
	@Before("execution(* tju.chc.app.service.impl.*.*(..))")
	public void authority(JoinPoint jp){
		System.out.println("Before 增强处理:模拟执行权限检查!");
		//返回被织入增强处理目标方法
		System.out.println("Before增强:被织入增强处理目标方法为:" + jp.getSignature().getName());
		//访问执行目标方法的参数
		System.out.println("Before增强:目标方法的参数为:" + Arrays.toString(jp.getArgs()));
		//访问被增强处理的目标对象
		System.out.println("Before增强:被织入增强处理的目标对象为:" + jp.getTarget());
		
	}

	// After 增强处理执行 匹配 包下所有类的所有方法的执行作为切入点
	@After("execution(* tju.chc.app.service.impl.*.*(..))")
	public void release(JoinPoint jp) {
		System.out.println("After 增强处理:模拟方法结束后的释放资源。。。");
		//返回被织入增强处理目标方法
		System.out.println("After 增强:被织入增强处理目标方法为:" + jp.getSignature().getName());
		//访问执行目标方法的参数
		System.out.println("After 增强:目标方法的参数为:" + Arrays.toString(jp.getArgs()));
		//访问被增强处理的目标对象
		System.out.println("Atfer 增强:被织入增强处理的目标对象为:" + jp.getTarget());
	
	}
	
	//定义AfterReturning增强处理执行  匹配tju.chc.app.service.impl包下所有类的所有方法的执行作为切入点
	@AfterReturning(returning="rvt",
			pointcut="execution(* tju.chc.app.service.impl.*.*(..))")
	public void log(JoinPoint jp, Object rvt){
		System.out.println("AfterReturning 增强处理:模拟记录日志功能。。。");
		//返回被织入增强处理目标方法
		System.out.println("AfterReturning 增强:被织入增强处理目标方法为:" + jp.getSignature().getName());
		//访问执行目标方法的参数
		System.out.println("AfterReturning 增强:目标方法的参数为:" + Arrays.toString(jp.getArgs()));
		//访问被增强处理的目标对象
		System.out.println("AfterReturning 增强:被织入增强处理的目标对象为:" + jp.getTarget());
	
	
	}
}


输出:

before 切入
z执行目标方法之前,模拟开始事务
Around增强:z执行目标方法之前,模拟开始事务
Before 增强处理:模拟执行权限检查!
Before增强:被织入增强处理目标方法为:sayHello
Before增强:目标方法的参数为:[adf]
Before增强:被织入增强处理的目标对象为:tju.chc.app.service.impl.Korea@55d96549
Around增强:z执行目标方法之后,模拟结束事务。。。
After 增强处理:模拟方法结束后的释放资源。。。
After 增强:被织入增强处理目标方法为:sayHello
After 增强:目标方法的参数为:[adf]
Atfer 增强:被织入增强处理的目标对象为:tju.chc.app.service.impl.Korea@55d96549
AfterReturning 增强处理:模拟记录日志功能。。。
AfterReturning 增强:被织入增强处理目标方法为:sayHello
AfterReturning 增强:目标方法的参数为:[adf]
AfterReturning 增强:被织入增强处理的目标对象为:tju.chc.app.service.impl.Korea@55d96549
获取目标方法返回值:被改变参数4Hello , SPring AOP new added content
模拟记录日志功能。。。
z执行目标方法之后,模拟结束事务。。。
模拟方法结束后的释放资源。。。
被改变参数4Hello , SPring AOP new added content new added content
before 切入
z执行目标方法之前,模拟开始事务
Around增强:z执行目标方法之前,模拟开始事务
Before 增强处理:模拟执行权限检查!
Before增强:被织入增强处理目标方法为:eat
Before增强:目标方法的参数为:[泡菜]
Before增强:被织入增强处理的目标对象为:tju.chc.app.service.impl.Korea@55d96549
Korea eat 被改变参数4
Around增强:z执行目标方法之后,模拟结束事务。。。
After 增强处理:模拟方法结束后的释放资源。。。
After 增强:被织入增强处理目标方法为:eat
After 增强:目标方法的参数为:[泡菜]
Atfer 增强:被织入增强处理的目标对象为:tju.chc.app.service.impl.Korea@55d96549
AfterReturning 增强处理:模拟记录日志功能。。。
AfterReturning 增强:被织入增强处理目标方法为:eat
AfterReturning 增强:目标方法的参数为:[泡菜]
AfterReturning 增强:被织入增强处理的目标对象为:tju.chc.app.service.impl.Korea@55d96549
获取目标方法返回值:null
模拟记录日志功能。。。
z执行目标方法之后,模拟结束事务。。。
模拟方法结束后的释放资源。。。
----------------------sayHi
before 切入
z执行目标方法之前,模拟开始事务
Around增强:z执行目标方法之前,模拟开始事务
Before 增强处理:模拟执行权限检查!
Before增强:被织入增强处理目标方法为:sayHi
Before增强:目标方法的参数为:[mashang6]
Before增强:被织入增强处理的目标对象为:tju.chc.app.service.impl.Korea@55d96549
start running
目标异常处理a.txt (系统找不到指定的文件。)
Around增强:z执行目标方法之后,模拟结束事务。。。
After 增强处理:模拟方法结束后的释放资源。。。
After 增强:被织入增强处理目标方法为:sayHi
After 增强:目标方法的参数为:[mashang6]
Atfer 增强:被织入增强处理的目标对象为:tju.chc.app.service.impl.Korea@55d96549
AfterReturning 增强处理:模拟记录日志功能。。。
AfterReturning 增强:被织入增强处理目标方法为:sayHi
AfterReturning 增强:目标方法的参数为:[mashang6]
AfterReturning 增强:被织入增强处理的目标对象为:tju.chc.app.service.impl.Korea@55d96549
获取目标方法返回值:被改变参数4, Hi,Spring AOP new added content
模拟记录日志功能。。。
z执行目标方法之后,模拟结束事务。。。
模拟方法结束后的释放资源。。。
----------------------divide
before 切入
z执行目标方法之前,模拟开始事务
模拟方法结束后的释放资源。。。
Exception in thread "main" java.lang.ClassCastException: java.lang.String cannot be cast to org.aspectj.lang.JoinPoint
	at tju.chc.app.service.impl.Korea$AjcClosure19.run(Korea.java:1)
	at org.aspectj.runtime.reflect.JoinPointImpl.proceed(JoinPointImpl.java:221)
	at tju.chc.aspectJTest.AroundAdviceTest.processTx(AroundAdviceTest.java:15)
	at tju.chc.app.service.impl.Korea.divide(Korea.java:35)
	at tju.chc.aspectJTest.Hello.main(Hello.java:25)