上文中, 我们学习到了 Spring系列三:基于注解配置bean
接下来我们学习, AOP切面编程
💗AOP-官方文档
🍝AOP 讲解
AOP 讲解: spring-framework-5.3.8\docs\reference\html/core.html
🍝AOP APIs
AOP APIs: spring-framework-5.3.8\docs\reference\html/core.html
spring-framework-5.3.8/docs/javadoc-api/index.html
💗动态代理
🍝初探动态代理
需求说明
1.由Vehicle (交通工具接口, 有一个run方法), 下面有两个类 Car 和 Ship
2.当运行Car对象的 run 方法 和 ship对象的 run 方法时, 输入如下内容
交通工具开始运行了…
轮船在海上航行…
交通工具停止运行了…
交通工具开始运行了…
小汽车在路上跑…
交通工具停止运行了…
解决方案一: 传统方案
1.新建com.zzw.spring.aop.proxy.Vehicle
接口
//接口, 该接口有run方法
public interface Vehicle {
void run();
}
2.新建com.zzw.spring.aop.proxy.Car
public class Car implements Vehicle {
@Override
public void run() {
System.out.println("交通工具开始运行了....");
System.out.println("小汽车在路上 running....");
System.out.println("交通工具停止运行了....");
}
}
3.新建com.zzw.spring.aop.proxy.Ship
public class Ship implements Vehicle {
@Override
public void run() {
System.out.println("交通工具开始运行了....");
System.out.println("大轮船在路上 running....");
System.out.println("交通工具停止运行了....");
}
}
4.新建com.zzw.spring.aop.proxy.TestVehicle
public class TestVehicle {
@Test
public void run() {
//OOP基础=>java基础
Vehicle vehicle = new Ship();
//动态绑定
vehicle.run();
}
}
来思考一下, 这个解决方案好吗? ====> 代码冗余, 其实就是单个对象的调用, 并没有很好的解决.
解决方案二: 动态代理方式
动态代理解决思路: 在调用方法时, 使用反射机制, 根据方法去决定调用哪个对象方法
1.新建com.zzw.spring.aop.proxy.VehicleProxyProvider
public class VehicleProxyProvider {
//定义一个属性
//target_vehicle 表示真正要执行的对象
//该对象实现了Vehicle接口
private Vehicle target_vehicle;
//构造器
public VehicleProxyProvider(Vehicle target_vehicle) {
this.target_vehicle = target_vehicle;
}
//编写一个方法, 可以返回一个代理对象
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() {
/*
class VehicleProxyProvider$01 implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("交通工具开始运行了....");
//这里是我们的反射基础 => OOP
Object result = method.invoke(target_vehicle, args);
System.out.println("交通工具停止运行了....");
return result;
}
}
InvocationHandler invocationHandler = new VehicleProxyProvider$01();
*/
/**
* invoke 方法是将来执行我们的target_vehicle的方法时, 会调用到
*
* @param proxy 表示代理对象
* @param method 就是通过代理对象调用方法时, 的那个方法 代理对象.run()
* @param args 表示调用 代理对象.run(xx) 传入的参数
* @return 表示 代理对象.run(xx) 执行后的结果.
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("交通工具开始运行了....");
//这里是我们的反射基础 => OOP
//method 是 public abstract void com.zzw.spring.aop.proxy.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;
}
}
2.修改com.zzw.spring.aop.proxy.Vehicle
public interface Vehicle {
void run();
public String fly(int height);
}
3.修改com.zzw.spring.aop.proxy.Ship
public class Ship implements Vehicle {
@Override
public void run() {
//System.out.println("交通工具开始运行了....");
System.out.println("大轮船在路上 running....");
//System.out.println("交通工具停止运行了....");
}
@Override
public String fly(int height) {
return "轮船可以飞行 高度=" + height + "米";
}
}
4.com.zzw.spring.aop.proxy.TestVehicle
public class TestVehicle {
@Test
public void proxyRun() {
//创建Ship对象
Ship ship = new Ship();
//创建VehicleProxyProvider对象, 并且我们要传入代理的对象
VehicleProxyProvider vehicleProxyProvider
= new VehicleProxyProvider(ship);
//获取代理对象, 该对象可以代理执行方法
//解读
//1.proxy 编译类型Vehicle,
//2.运行类型 是代理类型, 即 class com.sun.proxy.$Proxy8
Vehicle proxy = vehicleProxyProvider.getProxy();
System.out.println("proxy的编译类型是 Vehicle");
System.out.println("proxy的运行类型是" + proxy.getClass());
//下面解读/debug怎么执行到 代理对象的 public Object invoke(Object proxy, Method method, Object[] args)
//梳理完毕, proxy的编译类型是Vehicle, 运行类型是Proxy class com.sun.proxy.$Proxy8
//所以当执行run方法时, 会执行到 代理对象的invoke
//如果体现动态 [1.被代理的对象 2.方法]
//proxy.run();
String result = proxy.fly(10000);
System.out.println("result=" + result);
System.out.println("ok");
}
5.debug
🍝动态代理深入
需求说明
1.有一个SmartAnimal
接口, 可以完成简单的加减法, 要求在执行 getSum()
和 getSub()
时, 输出执行前, 执行过程, 执行后的日志结果. 输出内容如下:
日志-方法名-getSum-参数 1.5 4.5
方法内部打印result = 6.0
日志-方法名-getSum-结果result= 6.0
=================================
日志-方法名-getSub-参数 1.4 3.3
方法内部打印result = -1.9
日志-方法名-getSub-结果result= -1.9
解决方案一: 传统方案
1.新建com.zzw.spring.aop.proxy2.SmartAnimalAble
接口
public interface SmartAnimalAble {
//求和
float getSum(float i, float j);
//求差
float getSub(float i, float j);
}
2.com.zzw.spring.aop.proxy2.SmartCat
public class SmartCat implements SmartAnimalAble {
@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= " + (i + j));
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= " + (i - j));
return result;
}
}
3.com.zzw.spring.aop.proxy2.AopTest
public class AopTest {
@Test
public void run() {
SmartAnimalAble smartAnimalAble = new SmartCat();
smartAnimalAble.getSum(1.5f, 4.5f);
System.out.println("=================================");
smartAnimalAble.getSub(1.4f, 3.3f);
}
}
解决方案二: 动态代理方式
考虑代理对象调用方法(底层是反射调用)时, 可能出现的异常
- [横切关注点]
1.新建com.zzw.spring.aop.proxy2.MyProxyProvider
//可以返回一个动态代理对象, 可以执行SmartCat对象的方法
public class MyProxyProvider {
//这是一个属性, 是我们要执行的目标对象
//该对象实现了SmartAnimalAble接口
private SmartAnimalAble target_obj;
//构造器
MyProxyProvider(SmartAnimalAble target_obj) {
this.target_obj = target_obj;
}
//编写一个方法, 可以返回一个代理对象
//该代理对象可以执行目标方法
public SmartAnimalAble getProxy() {
//1.得到类加载器
ClassLoader classLoader =
target_obj.getClass().getClassLoader();
//2.得到要执行的目标对象的接口信息
Class<?>[] interfaces = target_obj.getClass().getInterfaces();
//3.创建InvocationHandler 对象
InvocationHandler invocationHandler = new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String name = method.getName();//方法名
Object result = null;
try {
System.out.println("方法执行前-日志-方法名-" + name + "-参数 "
+ Arrays.asList(args));//这里从aop的角度看,就是一个横切关注点-前置通知
//使用反射调用方法
result = method.invoke(target_obj, args);
System.out.println("方法执行正常结束-日志-方法名-" + name + "-结果result= "
+ result);//这里从aop的角度看, 也是一个横切关注点-返回通知
return result;
} catch (Exception e) {
e.printStackTrace();
//如果反射执行方法时, 出现异常, 就会进入到catch{}
System.out.println("方法执行异常-日志-方法名-" + name + "-异常类型="
+ e.getClass().getName());//这里从aop的角度看, 又是一个横切关注点-异常通知
} finally {//不管你是否出现了异常, 最终都会执行到 finally {}
//这里从aop的角度看, 还是一个横切关注点-最终通知
System.out.println("方法最终结束-日志-方法名-" + name);
}
return result;
}
};
//创建代理对象
SmartAnimalAble proxy =
(SmartAnimalAble) Proxy.newProxyInstance(classLoader, interfaces, invocationHandler);
return proxy;
}
}
2.修改com.zzw.spring.aop.proxy2.SmartCat
public class SmartCat implements SmartAnimalAble {
@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= " + (i + j));
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= " + (i - j));
return result;
}
}
3.com.zzw.spring.aop.proxy2.AopTest
public class AopTest {
@Test
public void smartCatTestProxy() {
//创建SmartCat对象
SmartAnimalAble smartAnimalAble = new SmartCat();
MyProxyProvider myProxyProvider
= new MyProxyProvider(smartAnimalAble);
//获取代理对象, 该对象可以代理执行方法
SmartAnimalAble proxy = myProxyProvider.getProxy();
System.out.println("proxy的编译类型是 SmartAnimalAble");
System.out.println("proxy的运行类型是 " + proxy.getClass());
//proxy的编译类型是SmartAnimalAble, 运行类型是 Class com.sun.proxy.$Proxy8
//所以当执行getSum方法时, 会执行到 代理对象的invoke
proxy.getSum(1.2f, 2.4f);
System.out.println("=================================");
proxy.getSub(1.3f, 4.5f);
System.out.println("ok");
}
}
🍝AOP问题提出
在MyProxyProvider.java
中, 我们的输出语句功能比较弱, 在实际开发中, 我们希望是以一个方法的形式, 嵌入到真正执行的目标方法前.
如图分析
📗使用土方法解决
需求分析
使用土方法解决前面的问题, 后面使用Spring的AOP组件完成
1.先建一个包, 把相关文件拷贝过来, 进行修改完成. ----这里只是模拟, 并没有真的新建包
//我们的一个方法, 在目标对象执行前执行
public void before(Method method, Object[] args) {
System.out.println("before方法执行前-日志-方法名-" + method.getName() + "-参数 "
+ Arrays.asList(args));//这里从aop的角度看,就是一个横切关注点-前置通知
}
//我们的一个方法, 在目标对象执行后执行
public void after(Method method, Object result) {
System.out.println("after方法执行正常结束-日志-方法名-" + method.getName() + "-结果result= "
+ result);//这里从aop的角度看, 也是一个横切关注点-返回通知
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String name = method.getName();//方法名
Object result = null;
before(method, args);
//使用反射调用方法
result = method.invoke(target_obj, args);
after(method, result);
return result;
}
2.该方法问题分析: 耦合度高
📗 对土方法解耦-开发最简单的AOP类
1.新建com.zzw.spring.aop.proxy2.ZzwAOP
public class ZzwAOP {
//我们的一个方法, 在目标对象执行前执行
public static void before(Method method, Object[] args) {
System.out.println("ZzwHsp-方法执行前-日志-方法名-" + method.getName() + "-参数 "
+ Arrays.asList(args));//这里从aop的角度看,就是一个横切关注点-前置通知
}
//我们的一个方法, 在目标对象执行后执行
public static void after(Method method, Object result) {
System.out.println("ZzwHsp-方法执行正常结束-日志-方法名-" + method.getName() + "-结果result= "
+ result);//这里从aop的角度看, 也是一个横切关注点-返回通知
}
}
2.修改com.zzw.spring.aop.proxy2.MyProxyProvider
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String name = method.getName();//方法名
Object result = null;
try {
//before(method, args);
ZzwAOP.before(method, args);
//使用反射调用方法
result = method.invoke(target_obj, args);
//after(method, result);
ZzwAOP.after(method, result);
return result;
} catch (Exception e) {
}
}
📗 土方法缺点
土方法 不够灵活;
土方法 复用性差;
土方法 是一种硬编码 (因为没有注解和反射支撑)
Spring AOP 闪亮登场 - 底层是ASPECTJ