AOP
官方文档
AOP 讲解:spring-framework-5.3.8/docs/reference/html/core.html#aop
AOP API:spring-framework-5.3.8/docs/javadoc-api/index.html
动态代理
需求说明
- 有 Vehicle(交通工具接口,有一个 run 方法),下面有两个实现类 Car 和 Ship 。
- 当运行 Car 对象 的 run 方法和 Ship 对象的 run 方法时,输入如下内容, 注意观察前后 有统一的输出。
解决方案-传统方式
代码实现
package com.lzw.spring.proxy2;
/**
* @author LiAng
* 接口,该接口有run方法
*/
public interface Vehicle {
public void run();
}
//=====================================================
package com.lzw.spring.proxy2;
/**
* @author LiAng
*/
public class Car implements Vehicle {
@Override
public void run() {
System.out.println("交通工具开始运行了....");
System.out.println("小汽车在路上 running....");
System.out.println("交通工具停止运行了....");
}
}
//=====================================================
package com.lzw.spring.proxy2;
/**
* @author LiAng
*/
public class Ship implements Vehicle {
@Override
public void run() {
System.out.println("交通工具开始运行了....");
System.out.println("大轮船在水上 running....");
System.out.println("交通工具停止运行了....");
}
}
package com.lzw.spring.proxy2;
import org.junit.jupiter.api.Test;
/**
* @author LiAng
*/
public class TestVehicle {
@Test
public void run(){
Vehicle ship = new Ship();
ship.run();
}
}
问题
代码冗余,其实就是单个对象的调用,并没有很好的解决。
解决方案-动态代理方式!!!!!
代码实现
package com.lzw.spring.proxy2;
/**
* @author LiAng
*/
public class Car implements Vehicle {
@Override
public void run() {
//System.out.println("交通工具开始运行了....");
System.out.println("小汽车在路上 running....");
//System.out.println("交通工具停止运行了....");
}
}
package com.lzw.spring.proxy2;
/**
* @author LiAng
*/
public class Ship implements Vehicle {
@Override
public void run() {
//System.out.println("交通工具开始运行了....");
System.out.println("大轮船在水上 running....");
//System.out.println("交通工具停止运行了....");
}
}
package com.lzw.spring.proxy2;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* @author LiAng
* VehicleProxyProvider 该类可以返回一个代理对象
*/
public class VehicleProxyProvider {
//定义一个属性
//target_vehicle 表示真正要执行的对象,
//该对象的类实现了Vehicle接口
private Vehicle target_vehicle;
//构造器
public VehicleProxyProvider(Vehicle target_vehicle) {
this.target_vehicle = target_vehicle;
}
//编写一个方法,可以返回一个代理对象
//1.这个方法非常重要,理解有一定难度
public Vehicle getProxy(){
//得到类加载器
ClassLoader classLoader = target_vehicle.getClass().getClassLoader();
//得到要代理的对象/被执行对象 的接口信息,底层是通过接口来完成调用
Class<?>[] interfaces = target_vehicle.getClass().getInterfaces();
//创建InvocationHandler 对象
//因为 InvocationHandler 是接口,所以我们可以通过匿名对象的方式来创建该对象
/**
*
* public interface InvocationHandler {
* public Object invoke(Object proxy, Method method, Object[] args)
* throws Throwable;
* }
* invoke 方法是将来执行我们的target_vehicle的方法时,会调用到
*
*/
InvocationHandler invocationHandler = new InvocationHandler() {
/**
* invoke 方法是将来执行我们的target_vehicle的方法时,会调用到
* @param o 表示代理对象
* @param method 就是通过代理对象调用方法时,的哪个方法 代理对象.run()
* @param args : 表示调用 代理对象.run(xx) 传入的参数
* @return 表示 代理对象.run(xx) 执行后的结果.
* @throws Throwable
*/
@Override
public Object invoke(Object o, Method method, Object[] args) throws Throwable {
System.out.println("交通工具开始运行了....");
//method 是:public abstract void com.lzw.spring.proxy2.Vehicle.run()
//target_vehicle 是:Ship对象
//args 是null
//这里通过反射+动态绑定机制,就会执行到被代理对象的方法
//执行完毕就返回
Object result = method.invoke(target_vehicle, args);
System.out.println("交通工具停止运行了....");
return result;
}
};
/*
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
1. Proxy.newProxyInstance() 可以返回一个代理对象
2. ClassLoader loader: 类的加载器.
3. Class<?>[] interfaces 就是将来要代理的对象的接口信息
4. InvocationHandler h 调用处理器/对象 有一个非常重要的方法invoke
*/
Vehicle proxy = (Vehicle)Proxy.newProxyInstance(classLoader, interfaces, invocationHandler);
return proxy;
}
}
package com.lzw.spring.proxy2;
import org.junit.jupiter.api.Test;
/**
* @author LiAng
*/
public class TestVehicle {
@Test
public void run(){
Vehicle ship = new Ship();
ship.run();
}
@Test
public void proxyRun(){
Vehicle ship = new Ship();
//创建VehicleProxyProvider对象, 并且我们传入的要代理的对象
VehicleProxyProvider vehicleProxyProvider = new VehicleProxyProvider(ship);
//获取代理对象,该对象可以代理执行方法
//1. proxy 编译类型 Vehicle
//2. 运行类型 是代理类型 class com.sun.proxy.$Proxy9
Vehicle proxy = vehicleProxyProvider.getProxy();
System.out.println("proxy的编译类型是 Vehicle");
System.out.println("proxy的运行类型是 " + proxy.getClass());
//下面家解读怎么 执行到 代理对象的 public Object invoke(Object o, Method method, Object[] args)
//proxy的编译类型是 Vehicle, 运行类型是 class com.sun.proxy.$Proxy9
//所以当执行run方法时,会执行到 代理对象的invoke
//如何体现动态 [1. 被代理的对象 2. 方法]
proxy.run();
}
}

动态代理深入
需求说明
有一个 SmartAnimal 接口,可以完成简单的加减法,要求在执行 getSum()和 getSub() 时,输出执行前,执行过程,执行后的日志输出,请思考如何实现。
getSum()如下,getSub()类似

传统方式实现
package com.lzw.spring.aop.proxy;
/**
* @author LiAng
*/
public interface SmartAnimal {
float getSum(float i, float j);
float getSub(float i, float j);
}
package com.lzw.spring.aop.proxy;
/**
* @author LiAng
*/
public class SmartDog implements SmartAnimal {
@Override
public float getSum(float i, float j) {
System.out.println("日志--方法名--getSum 方法开始--参数:" + i + "," + j);
float result = i + j;
System.out.println("方法内部打印:result=" + result);
System.out.println("日志--方法名--getSum 方法结束--结果:result=" + result);
return result;
}
@Override
public float getSub(float i, float j) {
System.out.println("日志--方法名--getSub 方法开始--参数:" + i + "," + j);
float result = i - j;
System.out.println("方法内部打印:result=" + result);
System.out.println("日志--方法名--getSub 方法结束--结果:result=" + result);
return result;
}
}
package com.lzw.spring.aop.proxy;
import org.junit.jupiter.api.Test;
/**
* @author LiAng
*/
public class AopTest {
@Test
public void smartDogTest(){
SmartDog smartDog = new SmartDog();
smartDog.getSum(10.0f,2.0f);
System.out.println("=================");
smartDog.getSub(10.0f,2.0f);
}
}
优点:实现简单。缺点:日志代码维护不方便,代码复用性差。
动态代理实现
package com.lzw.spring.aop.proxy;
/**
* @author LiAng
*/
public class SmartDog implements SmartAnimal {
@Override
public float getSum(float i, float j) {
//System.out.println("日志--方法名--getSum 方法开始--参数:" + i + "," + j);
float result = i + j;
System.out.println("方法内部打印:result=" + result);
//System.out.println("日志--方法名--getSum 方法结束--结果:result=" + result);
return result;
}
@Override
public float getSub(float i, float j) {
//System.out.println("日志--方法名--getSub 方法开始--参数:" + i + "," + j);
float result = i - j;
System.out.println("方法内部打印:result=" + result);
//System.out.println("日志--方法名--getSub 方法结束--结果:result=" + result);
return result;
}
}
package com.lzw.spring.aop.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.Arrays;
/**
* @author LiAng
*/
public class MyProxyProvider {
//定义我们要执行的目标对象,该对象需要实现 SmartAnimal
private SmartAnimal target_animal;
public MyProxyProvider(SmartAnimal target_animal) {
this.target_animal = target_animal;
}
//方法 可以返回代理对象,该代理对象可以执行目标对象
public SmartAnimal getProxy(){
//1.先得到类加载器/对象
ClassLoader classLoader = target_animal.getClass().getClassLoader();
//2.得到执行目标对象的接口信息
Class<?>[] interfaces = target_animal.getClass().getInterfaces();
//3.创建 InvocationHandler
InvocationHandler invocationHandler = new InvocationHandler() {
@Override
public Object invoke(Object o, Method method, Object[] args) throws Throwable {
Object result = null;
try {
System.out.println("方法执行前-日志--方法名--"+ method.getName() + " 方法开始--参数:" + Arrays.asList(args));
result = method.invoke(target_animal, args);
//从AOP看,也是一个横切关注点-返回通知
System.out.println("方法执行正常后-日志--方法名--"+ method.getName() + " 方法结束--结果:result=" + result);
return result;
} catch (Exception e) {
e.printStackTrace();
//如果反射执行方法时,出现异常,就会进入到catch{}
//从AOP看,也是一个横切关注点-异常通知
System.out.println("方法执行异常-日志--方法名--" + method.getName() + "-异常类型=" + e.getClass().getName());
}finally {//不管是否出现异常,最终都会执行到finally{}
//从AOP角度看,也是一个横切关注点-最终通知
System.out.println("日志--方法名:" + method.getName() + "--方法最终结束");
return result;
}
}
};
//创建代理对象
SmartAnimal proxy = (SmartAnimal)Proxy.newProxyInstance(classLoader, interfaces, invocationHandler);
return proxy;
}
}
package com.lzw.spring.aop.proxy;
import org.junit.jupiter.api.Test;
/**
* @author LiAng
*/
public class AopTest {
@Test
public void smartDogTestByProxy(){
SmartDog smartDog = new SmartDog();
MyProxyProvider myProxyProvider = new MyProxyProvider(smartDog);
SmartAnimal proxy = myProxyProvider.getProxy();
float sum = proxy.getSum(10, 2);
System.out.println("==========================================");
proxy.getSub(10,2);
}
}

问题再次出现
问题
在 MyProxyProvider.java 中,我们的输出语句功能比较弱,在实际开发中,我们希望是以一个方法的形式,嵌入到真正执行的目标方法前,怎么办?

土方法解决
package com.lzw.spring.aop.proxy3;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;
/**
* @author LiAng
*/
public class MyProxyProvider {
//定义我们要执行的目标对象,该对象需要实现 SmartAnimal
private SmartAnimal target_animal;
public MyProxyProvider(SmartAnimal target_animal) {
this.target_animal = target_animal;
}
//我们自己定义一个方法,在目标对象执行前执行
public void before(Object proxy, Method method, Object[] args){
System.out.println("before-方法执行前-日志--方法名--"+ method.getName() + " 方法开始--参数:" + Arrays.asList(args));
}
//我们自己定义一个方法,在目标对象执行后执行
public void after(Method method, Object result){
//从AOP看,也是一个横切关注点-返回通知
System.out.println("after-方法执行正常后-日志--方法名--"+ method.getName() + " 方法结束--结果:result=" + result);
}
//方法 可以返回代理对象,该代理对象可以执行目标对象
public SmartAnimal getProxy(){
//1.先得到类加载器/对象
ClassLoader classLoader = target_animal.getClass().getClassLoader();
//2.得到执行目标对象的接口信息
Class<?>[] interfaces = target_animal.getClass().getInterfaces();
//3.创建 InvocationHandler
InvocationHandler invocationHandler = new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result = null;
try {
before(proxy, method, args);
result = method.invoke(target_animal, args);
after(method,result);
return result;
} catch (Exception e) {
e.printStackTrace();
//如果反射执行方法时,出现异常,就会进入到catch{}
//从AOP看,也是一个横切关注点-异常通知
System.out.println("方法执行异常-日志--方法名--" + method.getName() + "-异常类型=" + e.getClass().getName());
}finally {//不管是否出现异常,最终都会执行到finally{}
//从AOP角度看,也是一个横切关注点-最终通知
System.out.println("日志--方法名:" + method.getName() + "--方法最终结束");
return result;
}
}
};
//创建代理对象
SmartAnimal proxy = (SmartAnimal)Proxy.newProxyInstance(classLoader, interfaces, invocationHandler);
return proxy;
}
}
问题:耦合度高
对土方法解耦-开发简易的AOP类
package com.lzw.spring.aop.proxy3;
import java.lang.reflect.Method;
import java.util.Arrays;
/**
* @author LiAng
* 我们自己编写一个极简的AOP类
*/
public class LzwAOP {
//我们自己定义一个方法,在目标对象执行前执行
public static void before(Object proxy, Method method, Object[] args){
System.out.println("LzwAOP-方法执行前-日志--方法名--"+ method.getName() + " 方法开始--参数:" + Arrays.asList(args));
}
//我们自己定义一个方法,在目标对象执行后执行
public static void after(Method method, Object result){
//从AOP看,也是一个横切关注点-返回通知
System.out.println("LzwAOP-方法执行正常后-日志--方法名--"+ method.getName() + " 方法结束--结果:result=" + result);
}
}
package com.lzw.spring.aop.proxy3;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;
/**
* @author LiAng
* @time 2022/6/9 16:29
*/
public class MyProxyProvider {
//定义我们要执行的目标对象,该对象需要实现 SmartAnimal
private SmartAnimal target_animal;
public MyProxyProvider(SmartAnimal target_animal) {
this.target_animal = target_animal;
}
//方法 可以返回代理对象,该代理对象可以执行目标对象
public SmartAnimal getProxy(){
//1.先得到类加载器/对象
ClassLoader classLoader = target_animal.getClass().getClassLoader();
//2.得到执行目标对象的接口信息
Class<?>[] interfaces = target_animal.getClass().getInterfaces();
//3.创建 InvocationHandler
InvocationHandler invocationHandler = new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result = null;
try {
LzwAOP.before(proxy, method, args);
result = method.invoke(target_animal, args);
LzwAOP.after(method,result);
return result;
} catch (Exception e) {
e.printStackTrace();
//如果反射执行方法时,出现异常,就会进入到catch{}
//从AOP看,也是一个横切关注点-异常通知
System.out.println("方法执行异常-日志--方法名--" + method.getName() + "-异常类型=" + e.getClass().getName());
}finally {//不管是否出现异常,最终都会执行到finally{}
//从AOP角度看,也是一个横切关注点-最终通知
System.out.println("日志--方法名:" + method.getName() + "--方法最终结束");
return result;
}
}
};
//创建代理对象
SmartAnimal proxy = (SmartAnimal)Proxy.newProxyInstance(classLoader, interfaces, invocationHandler);
return proxy;
}
}

问题:土方法:不够灵活,复用性差,还是一种硬编码(因为没有注解和反射支撑)
AOP的基本介绍
AOP 的全称(aspect oriented programming) ,面向切面编程。

AOP的快速入门
说明
需要引入核心的aspect包。
在切面类中声明通知方法
(1)前置通知:@Before
(2)返回通知:@AfterReturning
(3)异常通知:@AfterThrowing
(4)后置通知:@After
(5)环绕通知:@Around

代码实现
导入需要的包。

package com.lzw.spring.aop.aspectj;
/**
* @author LiAng
*/
public interface SmartAnimal {
float getSum(float i, float j);
float getSub(float i, float j);
}
package com.lzw.spring.aop.aspectj;
import com.lzw.spring.aop.proxy3.SmartAnimal;
import org.springframework.stereotype.Component;
/**
* @author LiAng
*/
@Component //使用@Component 当spring容器启动时,将SmartDog注入到容器中
public class SmartDog implements SmartAnimal {
@Override
public float getSum(float i, float j) {
float result = i + j;
System.out.println("方法内部打印:result=" + result);
return result;
}
@Override
public float getSub(float i, float j) {
float result = i - j;
System.out.println("方法内部打印:result=" + result);
return result;
}
}
下面的SmartAnimalAspect作用就是接管切面编程,此时原来的MyProxyProvider类就可以拿掉了。
package com.lzw.spring.aop.aspectj;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
import java.util.Arrays;
/**
* @author LiAng
* 切面类,类似于以前自己写的 MyProxyProvider,功能肯定强大很多
*/
@Aspect //表示是一个切面类[底层切面编程的支撑(动态代理+反射+动态绑定)]
@Component //会注入SmartAnimalAspect到容器
public class SmartAnimalAspect {
//希望将f1方法切入到 SmartDog-getSum()前执行-前置通知
/**
* 1. @Before 表示前置通知:即在我们的目标对象执行方法前执行
* 2. value = "execution(public float com.lzw.spring.aop.aspectj.SmartDog.getSum(float, float)
* 指定切入到哪个类的哪个方法 形式是: 访问修饰符 返回类型 全类名.方法名(形参列表)
* 3. showBeginLog方法可以理解成就是一个切入方法, 这个方法名是可以程序员指定 比如:showBeginLog
* 4. JoinPoint joinPoint 在底层执行时,由AspectJ切面框架,会给该切入方法传入 joinPoint对象
* , 通过该对象,程序员可以获取到 相关信息
* @param joinPoint
*/
@Before(value = "execution(public float com.lzw.spring.aop.aspectj.SmartDog.getSum(float, float))")
public static void f1(JoinPoint joinPoint){
//通过连接点对象joinPoint 可以获取方法签名
Signature signature = joinPoint.getSignature();
System.out.println("切面类f1()-方法执行前-日志--方法名--"+ signature.getName() + "-参数:" + Arrays.asList(joinPoint.getArgs()));
}
//返回通知;即把f2方法切入到目标对象正常执行完毕后的地方
@AfterReturning(value = "execution(public float com.lzw.spring.aop.aspectj.SmartDog.getSum(float, float))")
public static void f2(JoinPoint joinPoint){
Signature signature = joinPoint.getSignature();
System.out.println("切面类f2()-方法执行正常结束-日志--方法名--"+ signature.getName());
}
//异常通知;即把f3方法切入到目标对象方法执行异常的catch{}
@AfterThrowing(value = "execution(public float com.lzw.spring.aop.aspectj.SmartDog.getSum(float, float))")
public static void f3(JoinPoint joinPoint){
Signature signature = joinPoint.getSignature();
System.out.println("切面类f3()-方法执行异常-日志--方法名--"+ signature.getName());
}
//最终通知;即把f4方法切入到目标方法执行后(不管是否发生异常,都要执行 finally{})
@After(value = "execution(public float com.lzw.spring.aop.aspectj.SmartDog.getSum(float, float))")
public static void f4(JoinPoint joinPoint){
Signature signature = joinPoint.getSignature();
System.out.println("切面类f4()-方法最终执行完毕-日志--方法名--"+ signature.getName());
}
}
<?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/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/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
<context:component-scan base-package="com.lzw.spring.aop.aspectj"/>
<!--开启基于注解的AOP功能-->
<aop:aspectj-autoproxy/>
</beans>
package com.lzw.spring.aop.aspectj;
import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* @author LiAng
*/
public class AopAspectjTest {
@Test
public void smartDogTestByProxy(){
//得到spring容器
ApplicationContext ioc = new ClassPathXmlApplicationContext("beans08.xml");
//这里我们需要通过接口类型来获取到注入的SmartDog对象=>就是代理对象
SmartAnimal smartAnimal = ioc.getBean(SmartAnimal.class);
//SmartDog smartDog = ioc.getBean(SmartDog.class);
smartAnimal.getSum(10,2);
//System.out.println("smartAnimal运行类型:" + smartAnimal.getClass());//class com.sun.proxy.$Proxy17
}
}
细节说明
关于切面类方法命名可以自己规范一下,比如 showBeginLog(),showSuccessEndLog(),showExceptionLog(),showFinallyEndLog()。
切入表达式的更多配置,比如使用模糊配置。
@Before(value="execution(* com.lzw.aop.proxy.SmartDog.*(..))")
- 表示所有访问权限,所有包的下所有有类的所方法,都会被执行该前置通知方法
@Before(value="execution(* *.*(..))")
- 当 spring 容器开启了 基于注解的AOP功能,我们获取注入的对象,需要以接口的类型来获取,因为你注入的对象.getClass() 已经是代理类型了。
<!--开启基于注解的AOP功能-->
<aop:aspectj-autoproxy/>
- 当 spring 容器开启了基于注解的 AOP 功能,我们获取注入的对象,也可以通过 id 来获取,但是也要转成接口类型。
@Test
public void smartDogTestByProxy(){
//得到spring容器
ApplicationContext ioc = new ClassPathXmlApplicationContext("beans08.xml");
//这里我们需要通过接口类型来获取到注入的SmartDog对象=>就是代理对象
SmartAnimal smartAnimal = (SmartAnimal)ioc.getBean("smartDog");//使用的id获取
smartAnimal.getSum(10,2);
}
作业
- 有接口 UsbInterface (方法 work)
- 实现子类 Phone 和 Camera 实现 UsbInterface
- 请在 SmartAnimalAspect 切面类,写一个方法(可输出日志信息) 等作为前置通知,在 Phone 和 Camera 对象执行 work 方法前调用
- 其它返回通知,异常通知,后置通知,也可以加入
package com.lzw.spring.aop.aspectj;
/**
* @author LiAng
*/
public interface UsbInterface {
public void work();
}
//===========================================
package com.lzw.spring.aop.aspectj;
import org.springframework.stereotype.Component;
/**
* @author LiAng
*/
@Component //把Phone对象当做一个组件注入容器
public class Phone implements UsbInterface {
@Override
public void work() {
System.out.println("Phone work()");
}
}
//===========================================
package com.lzw.spring.aop.aspectj;
import org.springframework.stereotype.Component;
/**
* @author LiAng
*/
@Component
public class Camera implements UsbInterface {
@Override
public void work() {
System.out.println("Camera work()");
}
}
package com.lzw.spring.aop.aspectj;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
import java.util.Arrays;
/**
* @author LiAng
*/
@Aspect //表示是一个切面类[底层切面编程的支撑(动态代理+反射+动态绑定)]
@Component //会注入SmartAnimalAspect到容器
public class SmartAnimalAspect {
@Before(value = "execution(public void com.lzw.spring.aop.aspectj.UsbInterface.work())")
public static void hi(JoinPoint joinPoint){
//通过连接点对象joinPoint 可以获取方法签名
Signature signature = joinPoint.getSignature();
System.out.println("切面类前置通知--日志--方法名--"+ signature.getName());
}
}
beans.xml文件和上述beans08.xml文件相同
@Test
public void smartUsbTestByProxy(){
ApplicationContext ioc = new ClassPathXmlApplicationContext("beans08.xml");
UsbInterface phone = (UsbInterface) ioc.getBean("phone");
UsbInterface camera = (UsbInterface) ioc.getBean("camera");
phone.work();
System.out.println("================");
camera.work();
}
AOP-切入表达式
作用
通过表达式的方式定位一个或多个具体的连接点。
语法细节
execution([权限修饰符] [返回值类型] [简单类名/全类名] [方法名]([参数列表]))





在AspectJ中,切入点表达式可以通过过 “&&”、“||”,“!“ 等操作符结合起来。

注意事项和细节
- 切入表达式也可以指向类的方法,这时切入表达式会对该类/对象生效。
- 切入表达式也可以指向接口的方法,这时切入表达式会对实现了接口的类/对象生效。
- 切入表达式也可以对没有实现接口的类,进行切入。
package com.lzw.spring.aop.aspectj;
import org.springframework.stereotype.Component;
/**
* @author LiAng
*/
@Component
public class Car {
public void run(){
System.out.println("Car run()");
}
}
package com.lzw.spring.aop.aspectj;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
import java.util.Arrays;
/**
* @author LiAng
*/
@Aspect //表示是一个切面类[底层切面编程的支撑(动态代理+反射+动态绑定)]
@Component //会注入SmartAnimalAspect到容器
public class SmartAnimalAspect {
//给Car配置一个前置通知
@Before(value = "execution(public void Car.run())")
public static void readyRun(JoinPoint joinPoint){
Signature signature = joinPoint.getSignature();
System.out.println("切面类readyRun--日志--方法名--"+ signature.getName());
}
}
@Test
public void test3(){
ApplicationContext ioc = new ClassPathXmlApplicationContext("beans08.xml");
Car car = ioc.getBean(Car.class);
//说明: car对象仍然是代理对象
System.out.println("car的运行类型=" + car.getClass());//class com.lzw.spring.aop.aspectj.Car$$EnhancerBySpringCGLIB$$11073d67
car.run();
}
- 动态代理 jdk 的 Proxy 与 Spring 的 CGlib 。https://www.cnblogs.com/threeAgePie/p/15832586.html。
(1)JDK动态代理是面向接口的,只能增强实现类中接口中存在的方法。CGlib是面向父类的,可以增强父类的所有方法。
(2)JDK得到的对象是JDK代理对象实例,而CGlib得到的对象是被代理对象的子类。
AOP-JoinPoint
通过 JoinPoint 可以获取到调用方法的签名。
public void beforeMethod(JoinPoint joinPoint){
joinPoint.getSignature().getName(); // 获取目标方法名
joinPoint.getSignature().getDeclaringType().getSimpleName(); // 获取目标方法所属 类的简单类名
joinPoint.getSignature().getDeclaringTypeName(); // 获取目标方法所属类的类名
joinPoint.getSignature().getModifiers(); // 获取目标方法声明类型数字(public、private、 protected)
Object[] args = joinPoint.getArgs(); // 获取传入目标方法的参数,返回一个数组
joinPoint.getTarget(); // 获取被代理的对象
joinPoint.getThis(); // 获取代理对象自己
}
AOP-返回通知获取结果

//1. 如果我们希望把目标方法执行的结果,返回给切入方法
//2. 可以在 @AfterReturning 增加属性 , 比如 returning = "res"
//3. 同时在切入方法增加 Object res
//4. 注意: returning = "res" 和 Object res 的 res名字一致
//@AfterReturning(value = "execution(public float com.lzw.spring.aop.aspectj.SmartDog.getSum(float, float))", returning = "res")
//使用切入点
@AfterReturning(value = "execution(public float com.lzw.spring.aop.aspectj.SmartDog.getSum(float, float))",
returning = "res")
public static void showSuccessEndLog(JoinPoint joinPoint, Object res){
System.out.println("获取到的结果是:" + res);
Signature signature = joinPoint.getSignature();
System.out.println("切面类f2()-方法执行正常结束-日志--方法名--"+ signature.getName());
}

AOP-异常通知中获取异常
@AfterThrowing(value = "execution(public float com.lzw.spring.aop.aspectj.SmartDog.getSum(float, float))",
throwing = "throwable")
public static void showExceptionLog(JoinPoint joinPoint, Throwable throwable){
System.out.println("获取到的异常信息:" + throwable);
Signature signature = joinPoint.getSignature();
System.out.println("切面类f3()-方法执行异常-日志--方法名--"+ signature.getName());
}

AOP-环绕通知【了解】
环绕通知可以完成其它四个通知要做的事情。
package com.lzw.spring.aop.aspectj;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
import java.util.Arrays;
import java.util.List;
/**
* @author LiAng
* 切面类,类似于以前自己写的 MyProxyProvider,功能肯定强大很多
*/
@Aspect //表示是一个切面类[底层切面编程的支撑(动态代理+反射+动态绑定)]
@Component //会注入SmartAnimalAspect2到容器
public class SmartAnimalAspect2 {
//演示环绕通知的使用-了解
//1. @Around: 表示这是一个环绕通知[完成其它四个通知的功能]
//2. value = "execution(public float com.lzw.spring.aop.aspectj.SmartDog.getSum(float, float)) 切入点表达式
//3. doAround 表示要切入的方法 - 调用结构 try-catch-finally
@Around(value = "execution(public float com.lzw.spring.aop.aspectj.SmartDog.getSum(float, float))")
public Object doAround(ProceedingJoinPoint joinPoint) {
Object result = null;
String methodName = joinPoint.getSignature().getName();
try {
//1.相当于前置通知完成的事情
Object[] args = joinPoint.getArgs();
List<Object> argList = Arrays.asList(args);
System.out.println("AOP环绕通知[-前置通知]" + methodName + "方法开始了--参数有:" + argList);
//在环绕通知中一定要调用joinPoint.proceed()来执行目标方法
result = joinPoint.proceed();
//2.相当于返回通知完成的事情
System.out.println("AOP环绕通知[-返回通知]" + methodName + "方法结束了--结果是:" + result);
} catch (Throwable throwable) {
//3.相当于异常通知完成的事情
System.out.println("AOP环绕通知[-异常通知]" + methodName + "方法抛异常了--异常对象:" + throwable);
} finally {
//4.相当于最终通知完成的事情
System.out.println("AOP环绕通知[-后置通知]" + methodName + "方法最终结束了...");
}
return result;
}
}
@Test
public void testDoAround() {
//得到spring容器
ApplicationContext ioc = new ClassPathXmlApplicationContext("beans08.xml");
SmartAnimal smartAnimal = ioc.getBean(SmartAnimal.class);
smartAnimal.getSum(10, 2);
}

AOP-切入点表达式重用
为了统一管理切入点表达式,可以使用切入点表达式重用技术。

package com.lzw.spring.aop.aspectj;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
import java.util.Arrays;
/**
* @author LiAng
* 使用切入点表达式重用
*/
@Aspect //表示是一个切面类[底层切面编程的支撑(动态代理+反射+动态绑定)]
@Component //会注入SmartAnimalAspect到容器
public class SmartAnimalAspect {
//定义一个切入点,在后面使用时可以直接引用,提高了复用性
@Pointcut(value = "execution(public float com.lzw.spring.aop.aspectj.SmartDog.getSum(float, float))")
public void mePointCut(){
}
//使用切入点
@Before(value = "mePointCut()")
public static void showBeginLog(JoinPoint joinPoint){
//通过连接点对象joinPoint 可以获取方法签名
Signature signature = joinPoint.getSignature();
System.out.println("切面类f1()[使用myPointCut()]-方法执行前-日志--方法名--"+ signature.getName() + "-参数:" + Arrays.asList(joinPoint.getArgs()));
}
//使用切入点
@AfterReturning(value = "mePointCut()", returning = "res")
public static void showSuccessEndLog(JoinPoint joinPoint, Object res){
System.out.println("获取到的结果是:" + res);
Signature signature = joinPoint.getSignature();
System.out.println("切面类f2()-方法执行正常结束-日志--方法名--"+ signature.getName());
}
//使用切入点
@AfterThrowing(value = "mePointCut()", throwing = "throwable")
public static void showExceptionLog(JoinPoint joinPoint, Throwable throwable){
System.out.println("获取到的异常信息:" + throwable);
Signature signature = joinPoint.getSignature();
System.out.println("切面类f3()-方法执行异常-日志--方法名--"+ signature.getName());
}
//使用切入点
@After(value = "mePointCut()")
public static void showFinallyEndLog(JoinPoint joinPoint){
Signature signature = joinPoint.getSignature();
System.out.println("切面类f4()-方法最终执行完毕-日志--方法名--"+ signature.getName());
}
}
AOP-切面优先级问题
如果同一个方法,有多个切面在同一个切入点切入,那么执行的优先级如何控制。
基本语法
@order(value=n) 来控制 n 值越小,优先级越高。
package com.lzw.spring.aop.aspectj;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import java.util.Arrays;
/**
* @author LiAng
*/
@Order(value = 2)//表示该切面类执行的顺序,value的值越小,优先级越高
@Aspect
@Component
public class SmartAnimalAspect {
//定义一个切入点,在后面使用时可以直接引用,提高了复用性
@Pointcut(value = "execution(public float com.lzw.spring.aop.aspectj.SmartDog.getSum(float, float))")
public void mePointCut(){
}
@Before(value = "mePointCut()")
public static void showBeginLog(JoinPoint joinPoint){
//通过连接点对象joinPoint 可以获取方法签名
Signature signature = joinPoint.getSignature();
System.out.println("SmartAnimalAspect-切面类f1()[使用myPointCut()]-方法执行前-日志--方法名--"+ signature.getName() + "-参数:" + Arrays.asList(joinPoint.getArgs()));
}
@AfterReturning(value = "mePointCut()", returning = "res")
public static void showSuccessEndLog(JoinPoint joinPoint, Object res){
System.out.println("获取到的结果是:" + res);
Signature signature = joinPoint.getSignature();
System.out.println("SmartAnimalAspect-切面类f2()-方法执行正常结束-日志--方法名--"+ signature.getName());
}
@AfterThrowing(value = "mePointCut()", throwing = "throwable")
public static void showExceptionLog(JoinPoint joinPoint, Throwable throwable){
System.out.println("获取到的异常信息:" + throwable);
Signature signature = joinPoint.getSignature();
System.out.println("SmartAnimalAspect-切面类f3()-方法执行异常-日志--方法名--"+ signature.getName());
}
//使用切入点
@After(value = "mePointCut()")
public static void showFinallyEndLog(JoinPoint joinPoint){
Signature signature = joinPoint.getSignature();
System.out.println("SmartAnimalAspect-切面类f4()-方法最终执行完毕-日志--方法名--"+ signature.getName());
}
}
package com.lzw.spring.aop.aspectj;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import java.util.Arrays;
/**
* @author LiAng
*/
@Order(value = 1)
@Aspect
@Component
public class SmartAnimalAspect3 {
@Before(value = "execution(public float SmartDog.getSum(float, float))")
public static void showBeginLog(JoinPoint joinPoint){
//通过连接点对象joinPoint 可以获取方法签名
Signature signature = joinPoint.getSignature();
System.out.println("SmartAnimalAspect3-切面类f1()-方法执行前-日志--方法名--"+ signature.getName() + "-参数:" + Arrays.asList(joinPoint.getArgs()));
}
@AfterReturning(value = "execution(public float com.lzw.spring.aop.aspectj.SmartDog.getSum(float, float))",
returning = "res")
public static void showSuccessEndLog(JoinPoint joinPoint, Object res){
System.out.println("获取到的结果是:" + res);
Signature signature = joinPoint.getSignature();
System.out.println("SmartAnimalAspect3-切面类f2()-方法执行正常结束-日志--方法名--"+ signature.getName());
}
@AfterThrowing(value = "execution(public float com.lzw.spring.aop.aspectj.SmartDog.getSum(float, float))",
throwing = "throwable")
public static void showExceptionLog(JoinPoint joinPoint, Throwable throwable){
System.out.println("获取到的异常信息:" + throwable);
Signature signature = joinPoint.getSignature();
System.out.println("SmartAnimalAspect3-切面类f3()-方法执行异常-日志--方法名--"+ signature.getName());
}
@After(value = "execution(public float com.lzw.spring.aop.aspectj.SmartDog.getSum(float, float))")
public static void showFinallyEndLog(JoinPoint joinPoint){
Signature signature = joinPoint.getSignature();
System.out.println("SmartAnimalAspect3-切面类f4()-方法最终执行完毕-日志--方法名--"+ signature.getName());
}
}
@Test
public void smartDogTestByProxy(){
ApplicationContext ioc = new ClassPathXmlApplicationContext("beans08.xml");
SmartAnimal smartAnimal = ioc.getBean(SmartAnimal.class);
smartAnimal.getSum(10,2);
}

后执行的,会被先执行的包裹起来。
注意事项和细节说明
- 不能理解成:优先级高的每个消息通知都先执行,这个和方法调用机制(和 Filter 过滤器 链式调用类似)。
- 执行顺序

AOP-基于 XML 配置 AOP
package com.lzw.spring.aop.xml;
/**
* @author LiAng
*/
public interface SmartAnimal {
float getSum(float i, float j);
float getSub(float i, float j);
}
package com.lzw.spring.aop.xml;
/**
* @author LiAng
*/
public class SmartDog implements SmartAnimal {
@Override
public float getSum(float i, float j) {
float result = i + j;
System.out.println("方法内部打印:result=" + result);
return result;
}
@Override
public float getSub(float i, float j) {
float result = i - j;
System.out.println("方法内部打印:result=" + result);
return result;
}
}
package com.lzw.spring.aop.xml;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import java.util.Arrays;
/**
* @author LiAng
* 这是我们开发的一个切面类,不用注解,而是使用XML配置
*/
public class SmartAnimalAspect {
public static void showBeginLog(JoinPoint joinPoint){
//通过连接点对象joinPoint 可以获取方法签名
Signature signature = joinPoint.getSignature();
System.out.println("SmartAnimalAspect-XML配置-切面类f1()-方法执行前-日志--方法名--"+ signature.getName() + "-参数:" + Arrays.asList(joinPoint.getArgs()));
}
public static void showSuccessEndLog(JoinPoint joinPoint, Object res){
System.out.println("获取到的结果是:" + res);
Signature signature = joinPoint.getSignature();
System.out.println("SmartAnimalAspect-切面类f2()-方法执行正常结束-日志--方法名--"+ signature.getName());
}
public static void showExceptionLog(JoinPoint joinPoint, Throwable throwable){
System.out.println("获取到的异常信息:" + throwable);
Signature signature = joinPoint.getSignature();
System.out.println("SmartAnimalAspect-切面类f3()-方法执行异常-日志--方法名--"+ signature.getName());
}
public static void showFinallyEndLog(JoinPoint joinPoint){
Signature signature = joinPoint.getSignature();
System.out.println("SmartAnimalAspect-切面类f4()-方法最终执行完毕-日志--方法名--"+ signature.getName());
}
}
<?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/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/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
<!--使用XML配置,完成AOP编程-->
<!--配置一个切面类-bean-->
<bean class="com.lzw.spring.aop.xml.SmartAnimalAspect" id="smartAnimalAspect"/>
<!--配置一个SmartDog对象-bean-->
<bean class="com.lzw.spring.aop.xml.SmartDog" id="smartDog"/>
<!--配置切面类, 细节一定要引入 xmlns:aop-->
<aop:config>
<!--配置切入点-->
<aop:pointcut id="myPointCut" expression="execution(public float com.lzw.spring.aop.xml.SmartDog.getSum(float, float))"/>
<!--配置切面的前置,返回, 异常, 最终通知-->
<aop:aspect ref="smartAnimalAspect" order="10">
<!--配置前置通知-->
<aop:before method="showBeginLog" pointcut-ref="myPointCut"/>
<!--返回通知-->
<aop:after-returning method="showSuccessEndLog" pointcut-ref="myPointCut" returning="res"/>
<!--异常通知-->
<aop:after-throwing method="showExceptionLog" pointcut-ref="myPointCut" throwing="throwable"/>
<!--最终通知-->
<aop:after method="showFinallyEndLog" pointcut-ref="myPointCut"/>
<!--配置环绕通知-->
<!--<aop:around method=""/>-->
</aop:aspect>
</aop:config>
</beans>
package com.lzw.spring.aop.xml;
import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* @author LiAng
*/
public class AopAspectjXMLTest {
@Test
public void testAspectByXML(){
ApplicationContext ioc = new ClassPathXmlApplicationContext("beans09.xml");
SmartAnimal smartAnimal = ioc.getBean(SmartAnimal.class);
smartAnimal.getSum(10,2);
}
}
作业
请编写一个 Cal 接口
方法 cal1(int n) 计算 1+2..+n
方法 cal2(int n) 计算 1 * 2 * ... * n
实现类 MyCal implements Cal
请分别使用注解方式 / XML 配置方式 完成 AOP 编程
(1) 在执行 cal1 前打印开始执行的时间,在 执行完后打印时间
(2) 在执行 cal2 前打印开始执行的时间,在 执行完后打印时间

注解方式
package com.lzw.spring.aop.homework;
/**
* @author LiAng
*/
public interface Cal {
public void cal1(int n);
public void cal2(int n);
}
package com.lzw.spring.aop.homework;
import org.springframework.stereotype.Component;
/**
* @author LiAng
*/
@Component
public class MyCal implements Cal {
@Override
public void cal1(int n) {
int res = 0;
for (int i = 1; i <= n ; i++) {
res += i;
}
System.out.println("cal1 res = " + res);
}
@Override
public void cal2(int n) {
int res = 1;
for (int i = 1; i <= n ; i++) {
res *= i;
}
System.out.println("cal2 res = " + res);
}
}
package com.lzw.spring.aop.homework;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
/**
* @author LiAng
*/
@Aspect
@Component
public class MyCalAOP {
@Pointcut(value = "execution(public void MyCal.*(..))")
public void myPointCut(){
}
@Before(value = "myPointCut()")
public void startCal(){
System.out.println("开始执行计算 " + System.currentTimeMillis());
}
@AfterReturning(value = "myPointCut()")
public void endCal(){
System.out.println("结束执行计算 " + System.currentTimeMillis());
}
}
<?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/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/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
<context:component-scan base-package="com.lzw.spring.aop.homework"/>
<!--开启基于注解的AOP功能-->
<aop:aspectj-autoproxy/>
</beans>
package com.lzw.spring.aop.homework;
import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* @author LiAng
*/
public class TestMyCalAOP {
@Test
public void test(){
ApplicationContext ioc = new ClassPathXmlApplicationContext("beans10.xml");
Cal bean = ioc.getBean(Cal.class);
bean.cal1(10);
System.out.println("=============================");
bean.cal2(10);
}
}
XML配置方式
package com.lzw.spring.aop.homework.xml;
/**
* @author LiAng
*/
public interface Cal {
public void cal1(int n);
public void cal2(int n);
}
package com.lzw.spring.aop.homework.xml;
/**
* @author LiAng
*/
public class MyCal implements Cal {
@Override
public void cal1(int n) {
int res = 0;
for (int i = 1; i <= n ; i++) {
res += i;
}
System.out.println("cal1 res = " + res);
}
@Override
public void cal2(int n) {
int res = 1;
for (int i = 1; i <= n ; i++) {
res *= i;
}
System.out.println("cal2 res = " + res);
}
}
package com.lzw.spring.aop.homework.xml;
/**
* @author LiAng
*/
public class MyCalAOP {
public void startCal(){
System.out.println("基于xml配置开始执行计算 " + System.currentTimeMillis());
}
public void endCal(){
System.out.println("基于xml配置结束执行计算 " + System.currentTimeMillis());
}
}
<?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: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/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
<!--配置MyCalAOP-bean-->
<bean class="com.lzw.spring.aop.homework.xml.MyCalAOP" id="myCalAOP"/>
<!--配置MyCal-bean-->
<bean class="com.lzw.spring.aop.homework.xml.MyCal" id="myCal"/>
<!--配置切面类-->
<aop:config>
<aop:pointcut id="myPointCut" expression="execution(public void com.lzw.spring.aop.homework.xml.MyCal.*(int))"/>
<aop:aspect ref="myCalAOP" order="1">
<aop:before method="startCal" pointcut-ref="myPointCut"/>
<aop:after-returning method="endCal" pointcut-ref="myPointCut"/>
</aop:aspect>
</aop:config>
</beans>
package com.lzw.spring.aop.homework.xml;
import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* @author LiAng
*/
public class TestMyCalAOP {
@Test
public void test(){
ApplicationContext ioc = new ClassPathXmlApplicationContext("beans11.xml");
Cal bean = ioc.getBean(Cal.class);
bean.cal1(10);
System.out.println("=============================");
bean.cal2(10);
}
}