8.动态代理
AOP:面向切面编程
OOP:面向对象编程
面向切面编程:基于OOP基础上的新的编程思想
只在程序云运行期间,将某段代码动态的切入到指定方法的指定位置进行与运行的这种编程方式
动态代理的实现:
需求:在方法执行前/执行后/异常动态的打印日志
实现原理:使用动态代理方式(底层是反射)执行
1.接口:定义需要实现的方法
public interface Calculator {
/*加法*/
public int add(int i,int j);
/*减法*/
public int sub(int i,int j);
/*乘法*/
public int mul(int i,int j);
/*除法*/
public int div(int i,int j);
}
2.接口实现:定义方法的具体实现
public class CalculatorImp implements Calculator {
public int add(int i, int j) {
return i + j;
}
public int sub(int i, int j) {
return i - j;
}
public int mul(int i, int j) {
return i * j;
}
public int div(int i, int j) {
return i / j;
}
}
3.动态代理的实现:
public class CalculatorProxy {
/*
* 为传入的参数对象创建一个代理对象
* Calculator calculator:被代理的对象
* */
public static Calculator getProcy(final Calculator calculator) {
ClassLoader classLoader = calculator.getClass().getClassLoader();
Class<?>[] interfaces = calculator.getClass().getInterfaces();
InvocationHandler invocationHandler = new InvocationHandler() {
/*
* 利用反射执行方法
* Object proxy:代理对象,jdk使用,任何时候都不能使用
* Method method:当前对象要执行的目标方法
* Object[] args:入参
* */
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result = null;
try {
/**
* 参数1:执行目标方法的对象
* 参数2:执行参数的入参
*/
LogUtil.logBefore(method, args);--------------------->调用自定义的打印日志方法
result = method.invoke(calculator, args);
LogUtil.logEnd(method, result);--------------------->调用自定义的打印日志方法
} catch (Exception e) {
LogUtil.lodException(method, e);--------------------->调用自定义的打印日志方法
} finally {
//返回值必须返回回去,才能被外界拿到
return result;
}
}
};
//参数1:是需要执行的目标方法的类的类加载器
//参数2:是该类实现的接口
//参数3:需要new,里面实现的方法是具体调用被代理对象的哪个方法
Object instance = Proxy.newProxyInstance(classLoader, interfaces, invocationHandler);------------>调用的是jdk底层的方法
return (Calculator) instance;
}
}
4.测试代码的实现:
@Test
public void test1(){
Calculator calculator=new CalculatorImp();------------->利用向上转型:接口类型引用执行实现类对象
Calculator procy = CalculatorProxy.getProcy(calculator);
procy.add(1,2);
procy.div(1, 0);
}
5.输出:
执行:add 参数列表为:[1, 2]
执行:add 返回:3
执行:div 参数列表为:[1, 0]
方法:div执行异常!异常信息->java.lang.ArithmeticException: / by zero
实现了日志的动态打印
6.问题
6.1 Calculator procy = CalculatorProxy.getProcy(calculator);是否是Calculator 对象么
测试代码:
public void test1() {
Calculator calculator = new CalculatorImp();
System.out.println(calculator.getClass());-------------->此时输出:class cn.com.wmd.impl.CalculatorImp
Calculator procy = CalculatorProxy.getProcy(calculator);
System.out.println(procy.getClass());------------------->此时输出:class com.sun.proxy.$Proxy4
procy.add(1, 2);
}
发现通过CalculatorProxy.getProcy代理获取的对象根本不是Calculator对象,也不是CalculatorImp对象,那为什么可以调用Calculator 的方法呢
通过:
System.out.println(Arrays.asList(procy.getClass().getInterfaces()));
打印出:[interface cn.com.wmd.inter.Calculator]
发现:通过CalculatorProxy.getProcy代理获取的对象是实现了Calculator接口的,他是在何时实现的呢
![](https://img2022.cnblogs.com/blog/1349485/202205/1349485-20220506214455996-1786214118.png)
结论:
1.通过动态代理获取的对象和被代理对象唯一能产生关联的是实现了同一个接口
2.动态代理写起来麻烦
3.jdk默认的动态代理,如果目标没有实现任何接口,是无法为他创建代理对象的
spring可以一句代码都不写去创建动态代理,而且没有强制要求目标对象必须实现接口
![](https://img2022.cnblogs.com/blog/1349485/202205/1349485-20220506214605085-1344202916.png)
spring如何实现动态代理:
步骤:
1.导包:
spring基础包:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>3.0.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>3.0.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>3.0.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-expression</artifactId>
<version>3.0.6.RELEASE</version>
</dependency>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.1.1</version>
</dependency>
spring的动态代理包:
<!--基础包-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>3.0.6.RELEASE</version>
</dependency>
<!--加强包:即使类没有实现接口也可以创建该类的代理-->
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>2.2.2</version>
</dependency>
<dependency>
<groupId>aopalliance</groupId>
<artifactId>aopalliance</artifactId>
<version>1.0</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.6.8</version>
</dependency>
2.写配置
2.1将目标类和切面类(封装了通知方法(在方法前后执行的方法)))加载到容器中
目标类:
@Service-------->给目标类加上注解
public class CalculatorImp implements Calculator {
public int add(int i, int j) {
return i + j;
}
public int sub(int i, int j) {
return i - j;
}
public int mul(int i, int j) {
return i * j;
}
public int div(int i, int j) {
return i / j;
}
}
切面类:(封装了目标方法执行前后执行的方法)
@Component-------->给切面类加上注解
@Aspect----------->告诉spring这是个切面类
public class LogUtil {
/**
* 5个通知注解
*
* @Before:在目标方法之前运行 前置通知
* @After:在目标方法之后运行 后置通知
* @AfterReturning:在目标方法正常返回之后执行 返回通知
* @AfterThrowing:在目标方法抛出异常后执行 异常通知
* @Around:环绕 环绕通知
*/
//想在目标方法执行之前执行:写入切入点表达式
//格式为:@Before("execution(访问权限符 返回值类型 方法签名)")
//若为目标类的所有方法可以用*:@Before("execution(public int cn.com.wmd.impl.CalculatorImp.*(int,int ))")
@Before("execution(public int cn.com.wmd.impl.CalculatorImp.*(int,int ))")
public static void logBefore() {
System.out.println("执行:xxx方法 参数列表为:xxx");
}
@After("execution(public int cn.com.wmd.impl.CalculatorImp.*(int,int )))")
//想在目标方法正常执行完成之后执行
public static void logReturn() {
System.out.println("执行:xxx方法 返回:xxx");
}
@AfterReturning("execution(public int cn.com.wmd.impl.CalculatorImp.*(int,int ))")
//想在目标方法结束的时候执行
public static void logAfter() {
System.out.println("执行:xxx方法 完毕");
}
@AfterThrowing("execution(public int cn.com.wmd.impl.CalculatorImp.*(int,int ))")
//想在目标方法执行异常时执行
public static void lodException() {
System.out.println("方法:xxx执行异常!异常信息->xxx");
}
}
3.配置文件的写法:
<!--包扫描-->
<context:component-scan base-package="cn.com.wmd"></context:component-scan>
<!--开启基于注解aop功能-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
4.测试代码
@ContextConfiguration(locations = "classpath:ioc.xml")
@RunWith(SpringJUnit4ClassRunner.class)
public class Test {
@Autowired
private Calculator calculator;----------------->从ioc容器中拿到目标对象:注意(如果想要按照类型查找,必须是接口类型,不能用它的本类)
@org.junit.Test
public void test1() {
System.out.println(calculator);----------->输出:cn.com.wmd.impl.CalculatorImp@63021689
System.out.println(calculator.getClass());---->输出:class com.sun.proxy.$Proxy16:发现是代理对象
System.out.println(Arrays.asList(calculator.getClass().getInterfaces()));
--->此处输出[interface cn.com.wmd.inter.Calculator, interface org.springframework.aop.SpringProxy, interface org.springframework.aop.framework.Advised]
calculator.add(1, 2);
}
}
也可以通过id去获取代理对象:
@Autowired
@Qualifier("calculatorImp")
private Calculator calculator;
输出:
cn.com.wmd.impl.CalculatorImp@63021689
class com.sun.proxy.$Proxy16
[interface cn.com.wmd.inter.Calculator, interface org.springframework.aop.SpringProxy, interface org.springframework.aop.framework.Advised]
执行:xxx方法 参数列表为:xxx
执行:xxx方法 完毕
执行:xxx方法 返回:xxx
结论():
1.从ioc容器中拿到目标对象:注意(如果想要按照类型查找,必须是接口类型,不能用它的本类)
2.AOP底层就是动态代理,容器中保存的是它的代理对象;$Proxy16,当然不是本类的类型,孙然Calculator类有@Service注解,但本质创建的是代理对象
3.而且代理对象和目标对象唯一能产生联系的是(他们实现了共同的接口)
4.所以此处必须以接口获取
5.为啥用接口获取呢,接口并没有加载到容器中:在输出动态代理对象(从容器中获得)实现的接口:
5.各标签的执行时机()
try{
@Before
method.invoke(obj,args);--->执行目标方法
@AfterReturning
}catch(e){
@AfterThrowing
}finally{
@After
}
当目标类不实现接口时:
@Service
public class CalculatorImp/* implements Calculator */ {--------------->不实现接口,如何创建代理对象
public int add(int i, int j) {
return i + j;
}
public int sub(int i, int j) {
return i - j;
}
public int mul(int i, int j) {
return i * j;
}
public int div(int i, int j) {
return i / j;
}
}
测试代码:
@ContextConfiguration(locations = "classpath:ioc.xml")
@RunWith(SpringJUnit4ClassRunner.class)
public class Test {
@Autowired
CalculatorImp calculatorImp;------>当目标类没有实现接口时:用本类()
@org.junit.Test
public void test1() {
System.out.println(calculatorImp);--------------->输出:cn.com.wmd.impl.CalculatorImp@3e694b3f
System.out.println(calculatorImp.getClass());---->输出:class cn.com.wmd.impl.CalculatorImp$$EnhancerByCGLIB$$73ce2e96
System.out.println(Arrays.asList(calculatorImp.getClass().getInterfaces()));
calculatorImp.add(1, 2);
}
}
输出 :
cn.com.wmd.impl.CalculatorImp@3e694b3f
class cn.com.wmd.impl.CalculatorImp$$EnhancerByCGLIB$$73ce2e96
[interface org.springframework.aop.SpringProxy, interface org.springframework.aop.framework.Advised, interface net.sf.cglib.proxy.Factory]
执行:xxx方法 参数列表为:xxx
执行:xxx方法 完毕
执行:xxx方法 返回:xxx
结论:当目标类没有实现接口时,CGLIB会给目标类创建一个内部类EnhancerByCGLIB$$73ce2e96
结论:
当目标类实现了接口时,底层jdk为其创建代理
当目标类没有实现接口时,底层是CGLIB为其创建代理
有接口时,使用接口接收代理对象
没有接口时,使用本类接收对象
切入点表达式
1. *
1.1 匹配一个或多个字符:
execution(public int cn.com.wmd.impl.Calcula*.*(int,int ))
匹配以1.cn.com.wmd.impl.Calcula开头
2.参数列表为两个int
的任意类的任意方法
1.2匹配任意一个参数:第一个是int,第二个参数任意类型(匹配两个参数))
execution(public int cn.com.wmd.impl.Calcula*.*(int,* ))
匹配以:1以cn.com.wmd.impl.Calcula开头的任意类的任意方法
2.参数列表为两个:第一个为int 第二个为任意
1.3只能匹配一层目录:
execution(public int cn.com.wmd.*.Calcula*.*(..))
--->cn.com.wmd的.下一层目录任意.以Calcula开头的包.任意方法(任意类型任意长度)--->这里的*只能代表一层
2. ..
2.1匹配任意多个任意类型的参数
execution(public int cn.com.wmd.impl.Calcula*.*(..))
2.2匹配多层路径:
execution(public int cn.com.wmd..Calcula*.*(..))
表示cn.com.wmd任意多层目录下的.以Calcula开头的类的.所有方法(任意参数类型任意长度)
3.记住两种
3.1最精确:
execution(public int cn.com.wmd.impl.CalculatorImp.add(int,int )))
3.2最模糊:任意类的任意方法都会切:禁用
execution( * *(.. )))
或execution( * *.*(.. )))
4.4 && 表达式1&&表达式2 满足这两种表达式的方法才切入
execution(public int cn.com.wmd.impl.Calcula*.*(..))&&execution( * *(.. )))
4.5 || 表达式1||表达式2 满足表达式1或表达式2的方法都可以切入
execution(public int cn.com.wmd.impl.Calcula*.*(..))||execution( * *(.. )))
4.6! !表达式1 不满足表达式1的方法都切入
!execution(public int cn.com.wmd.impl.Calcula*.*(..))
如何在目标方法中获取参数列表和方法名称等信息
@Component
@Aspect
public class LogUtil {
@Before("execution(public * cn.com.wmd.imp.CalculatorImp.*(..))")
public void logBefore(JoinPoint joinPoint){----------------->添加JoinPoint对象,根据该对象可以获取到方法的相关信息
/**
* 我们只需要在通知方法的参数列表上写一个参数
* joinPoint joinPoint:封装了当前对象方法的详细信息
*/
//获取方法签名
Signature signature = joinPoint.getSignature();
//获取方法名
String methodName=signature.getName();
//获取参数列表
Object[] args = joinPoint.getArgs();
System.out.println("开始执行:"+methodName+" 参数列表:"+ Arrays.asList(args));
}
/**
*1.只需要为通知方法的参数列表写一个参数:JoinPoint joinPoint-->封装当前目标对象的详细信息
*2.告诉spring哪个参数来接受返回值:Object result
*/
@AfterReturning(value = "execution(public * cn.com.wmd.imp.CalculatorImp.*(..))",returning ="result" )---->通过returning 告诉容器返回值
public void logAfter(JoinPoint joinPoint,Object result){----->添加Object result参数,并且告诉spring容器哪个是返回!
//获取方法签名
Signature signature = joinPoint.getSignature();
//获取方法名
String methodName=signature.getName();
System.out.println("执行:"+methodName+" 返回:"+result);
}
@After("execution(public * cn.com.wmd.imp.CalculatorImp.*(..))")
public void logReturn(JoinPoint joinPoint){
//获取方法签名
Signature signature = joinPoint.getSignature();
//获取方法名
String methodName=signature.getName();
System.out.println("执行:"+methodName+" 方法完毕");
}
/**
*1.只需要为通知方法的参数列表写一个参数:JoinPoint joinPoint-->封装当前目标对象的详细信息
*2.告诉spring哪个参数来接受异常:Exception e
*/
@AfterThrowing(value = "execution(public * cn.com.wmd.imp.CalculatorImp.*(..))",throwing = "e")
public void logException(JoinPoint joinPoint,Exception e){
//获取方法签名
Signature signature = joinPoint.getSignature();
//获取方法名
String methodName=signature.getName();
System.out.println("执行:"+methodName+"异常!异常信息为:"+e);
}
}.
测试代码:
@ContextConfiguration(locations = "classpath:ioc.xml")
@RunWith(SpringJUnit4ClassRunner.class)
public class Test {
@Autowired
CalculatorImp calculatorImp;
@org.junit.Test
public void test(){
calculatorImp.div(1,0);
}
}
输出:
开始执行:div 参数列表:[1, 0]
执行:div 方法完毕
执行:div异常!异常信息为:java.lang.ArithmeticException: / by zero
结论:
spring对通知方法的要求不严格
唯一的要求就是方法的参数列表一定不能乱写
通知方法是spring利用反射调用的,没每次方法调用的确定这个方法的参数列表的值
(JoinPoint joinPoint,Exception e)
参数列表的每个参数,spring都得知道是什么?
JoinPoint joinPoint:认识
Exception e,不认识,必须告知spring容器
切入点表表达式的重用
1.原始的切面类:
@Component
@Aspect
public class LogUtil {
/**
* 5个通知注解
*
* @Before:在目标方法之前运行 前置通知
* @After:在目标方法之后运行 后置通知
* @AfterReturning:在目标方法正常返回之后执行 返回通知
* @AfterThrowing:在目标方法抛出异常后执行 异常通知
* @Around:环绕 环绕通知
*/
//想在目标方法执行之前执行:写入切入点表达式
//格式为:@Before("execution(访问权限符 返回值类型 方法签名)")
//若为目标类的所有方法可以用*:@Before("execution(public int cn.com.wmd.impl.CalculatorImp.*(int,int ))")
@Before("execution(public int cn.com.wmd..Calcula*.*(..))")
public static void logBefore() {
System.out.println("执行:xxx方法 参数列表为:xxx");
}
@After("execution( * *(.. )))")
//想在目标方法正常执行完成之后执行
public static void logReturn() {
System.out.println("执行:xxx方法 返回:xxx");
}
@AfterReturning("execution(public int cn.com.wmd.impl.CalculatorImp.*(int,int ))")
//想在目标方法结束的时候执行
public static void logAfter() {
System.out.println("执行:xxx方法 完毕");
}
@AfterThrowing("execution(public int cn.com.wmd.impl.CalculatorImp.*(int,int ))")
//想在目标方法执行异常时执行
public static void lodException() {
System.out.println("方法:xxx执行异常!异常信息->xxx");
}
}
2.如果切入点表达式有复用,应该怎么做
抽取可重用的切入点表达式:
2.1随便声明一个空的方法
2.2给方法加上@Pointcut注解
/*抽取切入点表达式*/
@Pointcut("execution(public int cn.com.wmd.impl.CalculatorImp.*(int,int ))")
public void qr() {
}
2.3在引用方法处:
@AfterThrowing("qr()")----->上述函数的名称
//想在目标方法执行异常时执行
public static void lodException() {
System.out.println("方法:xxx执行异常!异常信息->xxx");
}
环绕通知:
切面类代码:定义了被代理类方法执行前后要执行的代码
@Component
@Aspect
public class LogUtil {
/*抽取切入点表达式*/
@Pointcut("execution(public int cn.com.wmd.impl.CalculatorImp.*(int,int ))")
public void qr() {
}
/*
* @Around
* 环绕通知:spring中最强大的通知
* @Before/@After/@AfterReturning/@AfterThrowing的执行位置
* try{
* @Before
* method.invoke(obj,args);
* @AfterReturning
* }catch(e){
* @AfterThrowing
* }finally{
* @After
* }
*
*
* */
@Around("qr()")------>调用重用的切入点表达式
public Object myAround(ProceedingJoinPoint pjp) {
//获取请求参数
Object[] args = pjp.getArgs();
//获取执行的方法名
String mehtodNanme = pjp.getSignature().getName();
Object proceed = null;
try {
System.out.println("环绕的前置通知:" + mehtodNanme + "方法开始执行...");
//执行方法:相当于java底层的method.incoke(obj,args)
proceed = pjp.proceed(args);
System.out.println("环绕的返回通知:" + mehtodNanme + "方法执行结束,返回:" + proceed);
} catch (Throwable throwable) {
System.out.println("环绕异常通知!" + mehtodNanme + "执行异常:异常信息:" + throwable.getMessage());
throwable.printStackTrace();
} finally {
System.out.println("环绕后置通知:" + mehtodNanme + "执行结束!");
}
//必须将结果返回
return proceed;
}
}
3.测试代码:
@ContextConfiguration(locations = "classpath:ioc.xml")
@RunWith(SpringJUnit4ClassRunner.class)
public class Test {
@Autowired
CalculatorImp calculatorImp;
@org.junit.Test
public void test1() {
int result = calculatorImp.add(50, 0);
System.out.println(result);
}
}
4.输出:
环绕的前置通知:add方法开始执行...
执行加法:50+0
环绕的返回通知:add方法执行结束,返回:50
环绕后置通知:add执行结束!
50-------------------------------->可以正常输出计算结果:
1.第一种特殊情况:当环绕通知的方法返回为空
![](https://img2022.cnblogs.com/blog/1349485/202205/1349485-20220506215321904-1182415784.png)
2.第二种特殊情况:写死返回值
![](https://img2022.cnblogs.com/blog/1349485/202205/1349485-20220506215409247-2081637025.png)
3.不执行方法:不调用 pjp.proceed(args);方法,该方法相当于method.invoke(obj,args)
![](https://img2022.cnblogs.com/blog/1349485/202205/1349485-20220506215459296-1825020251.png)
环绕通知和普通通知的执行顺序
切面类的代码为:
@Component
@Aspect
public class LogUtil {
/**
* 抽取切入点表达式
*/
@Pointcut("execution(public * cn.com.wmd.imp.CalculatorImp.*(..))")
public void qr(){}
/**
* 前置通知
* @param joinPoint:获取执行方法的某些参数
*/
@Before("qr()")
public void logBefore(JoinPoint joinPoint){
/**
* 我们只需要在通知方法的参数列表上写一个参数
* joinPoint joinPoint:封装了当前对象方法的详细信息
*/
//获取方法签名
Signature signature = joinPoint.getSignature();
//获取方法名
String methodName=signature.getName();
//获取参数列表
Object[] args = joinPoint.getArgs();
System.out.println("[普通前置通知->@Before]开始执行:"+methodName+" 参数列表:"+ Arrays.asList(args));
}
/**
* 返回通知:方法正常执行完毕返回后执行
* @param joinPoint
* @param result
*/
@AfterReturning(value = "qr()",returning ="result" )
public void logAfter(JoinPoint joinPoint,Object result){
//获取方法签名
Signature signature = joinPoint.getSignature();
//获取方法名
String methodName=signature.getName();
System.out.println("[普通返回通知->@AfterReturning]执行:"+methodName+" 返回:"+result);
}
/**
* 后置通知:在目标方法执行结束后执行
* @param joinPoint
*/
@After("qr()")
public void logReturn(JoinPoint joinPoint){
//获取方法签名
Signature signature = joinPoint.getSignature();
//获取方法名
String methodName=signature.getName();
System.out.println("[普通后置通知->@After]执行:"+methodName+" 方法完毕");
}
/**
* 异常通知:在目标方法执行异常时执行
* @param joinPoint
* @param e
*/
@AfterThrowing(value = "qr()",throwing = "e")
public void logException(JoinPoint joinPoint,Exception e){
//获取方法签名
Signature signature = joinPoint.getSignature();
//获取方法名
String methodName=signature.getName();
System.out.println("[普通异常通知->@AfterThrowing]执行:"+methodName+"异常!异常信息为:"+e);
}
/**
* 定义环绕通知
* @param pjp:获取执行方法的某些餐宿:如运行参数列表,方法名称等
*/
@Around("qr()")
public Object myAround(ProceedingJoinPoint pjp){
//获取请求参数
Object[] args = pjp.getArgs();
//获取可以获取目标方法名的对象
Signature signature = pjp.getSignature();
//获取执行的目标方法
String name = signature.getName();
//执行目标方法并返回返回值(相当于method.invoke(obj,args))
Object result=null;
try {
System.out.println("【环绕的前置通知】:开始执行->"+name+"方法,入参是:"+Arrays.asList(args));
result = pjp.proceed(args);
System.out.println("【环绕的返回通知】:执行结束->"+name+"方法,入参数:"+Arrays.asList(args)+" 方法执行返回:"+result);
} catch (Throwable throwable) {
System.out.println("【环绕的异常通知】:执行异常->"+name+"方法,异常信息:"+throwable.getMessage());
throwable.printStackTrace();
}finally {
System.out.println("【环绕的后置通知:】"+name+"方法执行结束!");
}
return result;
}
}
测试代码为:
@ContextConfiguration(locations = "classpath:ioc.xml")
@RunWith(SpringJUnit4ClassRunner.class)
public class Test {
@Autowired
CalculatorImp calculatorImp;
@org.junit.Test
public void test(){
int result = calculatorImp.add(1, 0);
System.out.println("测试执行返回:"+result);
}
}
输出:
【环绕的前置通知】:开始执行->add方法,入参是:[1, 0]
[普通前置通知->@Before]开始执行:add 参数列表:[1, 0]
【环绕的返回通知】:执行结束->add方法,入参数:[1, 0] 方法执行返回:1
【环绕的后置通知:】add方法执行结束!
[普通后置通知->@After]执行:add 方法完毕
[普通返回通知->@AfterReturning]执行:add 返回:1
测试执行返回:1
发现其执行顺序为:
环绕前置---普通前置---目标方法执行---环绕正常返回/出现异常----环绕后置------普通后置----普通返回或者异常
结论:
环绕通知优先
环绕通知和普通通知的用法:
环绕通知相当于动态代理,本身含有控制方法执行的方法:pjp.proceed(args);,在其中可以做方法的更改之类的
而普通通知只是普通通知,无法控制目标方法的运行!
多个切面类同时执行的执行顺序
需求:有两个切面类(LogUtil和VaAspect)要在目标方法执行时切入
普通通知的执行顺序
try{
前置通知
@Before
//执行目标方法
method.invoke(obj,args)
返回通知
@afterreturning
}catch(){
异常通知
@AfterThrowing
}finally{
后置通知
@After
}
1.第一种情况(没有环绕通知时)
@Component
@Aspect
public class LogUtil {
/**
* 抽取切入点表达式
*/
@Pointcut("execution(public * cn.com.wmd.imp.CalculatorImp.*(..))")
public void qr(){}
@Before("qr()")
public void logBefore(JoinPoint joinPoint){
//获取方法签名
Signature signature = joinPoint.getSignature();
//获取方法名
String methodName=signature.getName();
//获取参数列表
Object[] args = joinPoint.getArgs();
System.out.println("[LogUtil-->普通前置通知->@Before]开始执行:"+methodName+" 参数列表:"+ Arrays.asList(args));
}
@AfterReturning(value = "qr()",returning ="result" )
public void logAfter(JoinPoint joinPoint,Object result){
//获取方法签名
Signature signature = joinPoint.getSignature();
//获取方法名
String methodName=signature.getName();
System.out.println("[LogUtil-->普通返回通知->@AfterReturning]执行:"+methodName+" 返回:"+result);
}
@After("qr()")
public void logReturn(JoinPoint joinPoint){
//获取方法签名
Signature signature = joinPoint.getSignature();
//获取方法名
String methodName=signature.getName();
System.out.println("[LogUtil-->普通后置通知->@After]执行:"+methodName+" 方法完毕");
}
@AfterThrowing(value = "qr()",throwing = "e")
public void logException(JoinPoint joinPoint,Exception e){
//获取方法签名
Signature signature = joinPoint.getSignature();
//获取方法名
String methodName=signature.getName();
System.out.println("[LogUtil-->普通异常通知->@AfterThrowing]执行:"+methodName+"异常!异常信息为:"+e);
}
}
另外一个切面类:
@Component
@Aspect
public class VaAspect {
@Before("cn.com.wmd.tools.LogUtil.qr()")
public void logBefore(JoinPoint joinPoint){
Signature signature = joinPoint.getSignature();
//获取方法名
String methodName=signature.getName();
//获取参数列表
Object[] args = joinPoint.getArgs();
System.out.println("[VaAspect-->普通前置通知->@Before]开始执行:"+methodName+" 参数列表:"+ Arrays.asList(args));
}
@After("cn.com.wmd.tools.LogUtil.qr()")
public void logReturn(JoinPoint joinPoint){
//获取方法签名
Signature signature = joinPoint.getSignature();
//获取方法名
String methodName=signature.getName();
System.out.println("[VaAspect-->普通后置通知->@After]执行:"+methodName+" 方法完毕");
}
@AfterReturning(value = "cn.com.wmd.tools.LogUtil.qr()",returning ="result" )
public void logAfter(JoinPoint joinPoint,Object result){
//获取方法签名
Signature signature = joinPoint.getSignature();
//获取方法名
String methodName=signature.getName();
System.out.println("[VaAspect-->普通返回通知->@AfterReturning]执行:"+methodName+" 返回:"+result);
}
@AfterThrowing(value = "cn.com.wmd.tools.LogUtil.qr()",throwing = "e")
public void logException(JoinPoint joinPoint,Exception e){
//获取方法签名
Signature signature = joinPoint.getSignature();
//获取方法名
String methodName=signature.getName();
System.out.println("[VaAspect-->普通异常通知->@AfterThrowing]执行:"+methodName+"异常!异常信息为:"+e);
}
}
执行输出:
[LogUtil-->普通前置通知->@Before]开始执行:add 参数列表:[1, 0]
[VaAspect-->普通前置通知->@Before]开始执行:add 参数列表:[1, 0]
执行目标方法!
[VaAspect-->普通后置通知->@After]执行:add 方法完毕
[VaAspect-->普通返回通知->@AfterReturning]执行:add 返回:1
[LogUtil-->普通后置通知->@After]执行:add 方法完毕
[LogUtil-->普通返回通知->@AfterReturning]执行:add 返回:1
测试执行返回:1
结论:发现先执行了LogUtil的前置...
如何控制哪个类的前置先执行呢(不加以控制是按照类名首字母来排序的)
@Component
@Aspect
@Order(2)-------------------->可以加上此标签,里面值越小,越先执行,值为int类型
public class LogUtil {
...
}
@Component
@Aspect
@Order(1)-------------------->可以加上此标签,里面值越小,越先执行,值为int类型
public class VaAspect {
}
此时输出:
[VaAspect-->普通前置通知->@Before]开始执行:add 参数列表:[1, 0]
[LogUtil-->普通前置通知->@Before]开始执行:add 参数列表:[1, 0]
执行目标方法!
[LogUtil-->普通返回通知->@AfterReturning]执行:add 返回:1
[LogUtil-->普通后置通知->@After]执行:add 方法完毕
[VaAspect-->普通返回通知->@AfterReturning]执行:add 返回:1
[VaAspect-->普通后置通知->@After]执行:add 方法完毕
测试执行返回:1
和上面的刚好相反!
如果有环绕通知,执行顺序应当如何呢
此处以没有加@Order标签为例,即先执行LogUtil
在LogUtil加入环绕通知
即:
@Component
@Aspect
public class LogUtil {
@Before("qr()")
public void logBefore(JoinPoint joinPoint){}
@AfterReturning(value = "qr()",returning ="result" )
public void logAfter(JoinPoint joinPoint,Object result){}
@After("qr()")
public void logReturn(JoinPoint joinPoint){}
@AfterThrowing(value = "qr()",throwing = "e")
public void logException(JoinPoint joinPoint,Exception e){}
加入环绕通知
@Around("qr()")
public Object myAround(ProceedingJoinPoint pjp){
//获取请求参数
Object[] args = pjp.getArgs();
//获取可以获取目标方法名的对象
Signature signature = pjp.getSignature();
//获取执行的目标方法
String name = signature.getName();
//执行目标方法并返回返回值(相当于method.invoke(obj,args))
Object result=null;
try {
System.out.println("LogUtil【环绕的前置通知】:开始执行->"+name+"方法,入参是:"+Arrays.asList(args));
result = pjp.proceed(args);
System.out.println("LogUtil【环绕的返回通知】:执行结束->"+name+"方法,入参数:"+Arrays.asList(args)+" 方法执行返回:"+result);
} catch (Throwable throwable) {
System.out.println("LogUtil【环绕的异常通知】:执行异常->"+name+"方法,异常信息:"+throwable.getMessage());
throw new RuntimeException(throwable);
}finally {
System.out.println("LogUtil【环绕的后置通知:】"+name+"方法执行结束!");
}
return result;
}
}
输出:
[LogUtil-->普通前置通知->@Before]开始执行:add 参数列表:[1, 0]
LogUtil【环绕的前置通知】:开始执行->add方法,入参是:[1, 0]
[VaAspect-->普通前置通知->@Before]开始执行:add 参数列表:[1, 0]
执行目标方法!
[VaAspect-->普通后置通知->@After]执行:add 方法完毕
[VaAspect-->普通返回通知->@AfterReturning]执行:add 返回:1
[LogUtil-->普通后置通知->@After]执行:add 方法完毕
LogUtil【环绕的返回通知】:执行结束->add方法,入参数:[1, 0] 方法执行返回:1
LogUtil【环绕的后置通知:】add方法执行结束!
[LogUtil-->普通返回通知->@AfterReturning]执行:add 返回:1
测试执行返回:1
AOP的使用场景
1.AOP做日志保存到数据库
2.AOP做权限验证
3.AOP做安全检查
4.AOP做事物控制
基于xml的AOP
<bean id="calculatorImp" class="cn.com.wmd.imp.CalculatorImp"></bean>
spring的pom文件
<bean id="logUtil" class="cn.com.wmd.tools.LogUtil"></bean>
<bean id="aspect" class="cn.com.wmd.tools.VaAspect"></bean>
<aop:config>
<aop:aspect ref="logUtil" order="1">
<aop:pointcut id="mypoint" expression="execution(public * cn.com.wmd.imp.CalculatorImp.*(..))"/>
<aop:before method="logBefore" pointcut="execution(public * cn.com.wmd.imp.CalculatorImp.*(..))"></aop:before>
<aop:after-returning method="logAfter" pointcut-ref="mypoint" returning="result"></aop:after-returning>
<aop:after-throwing method="logException" pointcut-ref="mypoint" throwing="e"></aop:after-throwing>
<aop:after method="logReturn" pointcut-ref="mypoint"></aop:after>
<aop:around method="myAround" pointcut-ref="mypoint"></aop:around>
</aop:aspect>
<aop:aspect ref="aspect" order="2">
<aop:before method="logBefore" pointcut-ref="mypoint"></aop:before>
<aop:after-returning method="logAfter" pointcut-ref="mypoint" returning="result"></aop:after-returning>
<aop:after-throwing method="logException" pointcut-ref="mypoint" throwing="e"></aop:after-throwing>
<aop:after method="logReturn" pointcut-ref="mypoint"></aop:after>
</aop:aspect>
</aop:config>