一、动态代理
AOP 即 Aspect Oriented Programming面向切面编程,它是基于面向对象编程之上的新的编程思想,是指将某段代码动态的切入到指定方法的指定位置并运行。
新建一个maven项目spring-bean-aop,导入依赖
<properties> <spring-version>5.3.13</spring-version> </properties> <dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-beans</artifactId> <version>${spring-version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>${spring-version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>${spring-version}</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency> </dependencies> 复制代码
在util包定义一个计算器接口类,定义四个方法加减乘除,增加一个实现类,实现加减乘除四个方法
public interface Calculator { int add(int x, int y); int sub(int x, int y); int mul(int x, int y); int div(int x, int y); } 复制代码
在util.impl包中增加实现类
@Component public class AppleCalculator implements Calculator { @Override public int add(int x, int y) { int result = x + y; return result; } @Override public int sub(int x, int y) { int result = x - y; return result; } @Override public int mul(int x, int y) { int result = x * y; return result; } @Override public int div(int x, int y) { int result = x / y; return result; } } 复制代码
增加application.xml,将组件注入容器中
<?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:util="http://www.springframework.org/schema/util" xmlns:p="http://www.springframework.org/schema/p" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.3.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd"> <context:component-scan base-package="com.citi"> </context:component-scan> </beans> 复制代码
生成Spring Test测试类AppleCalculatorTest
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations = "classpath:application.xml") public class AppleCalculatorTest { @Autowired private AppleCalculator appleCalculator; @Test public void add() { } @Test public void sub() { } @Test public void mul() { } @Test public void div() { } } 复制代码
给每个方法运行前后增加日志输出,如何在不改变代码的前提下完成?
动态代理方式解决
首先创建一个代理CalculatorProxy,包含一个方法,可以返回代理对象,给代理对象加一些方法,通过代理对象执行目标方法
创建一个代理,静态方法getProxy用户获取Calculator的代理对象,
public class CalculatorProxy { public static Calculator getProxy(Calculator calculator){ // 方法执行器 InvocationHandler invocationHandler = (Object proxy, Method method, Object[] args) -> { System.out.println("动态代理执行目标方法:" + method.getName()); // 反射执行目标方法 Object result = method.invoke(calculator, args); return result; }; // 类接口 Class<?>[] interfaces = calculator.getClass().getInterfaces(); // 类加载器 ClassLoader classLoader = calculator.getClass().getClassLoader(); Object proxyInstance = Proxy.newProxyInstance(classLoader, interfaces, invocationHandler); return (Calculator) proxyInstance; } } 复制代码
该方法需要传入被代理的对象,通过newProxyInstance()创建一个代理,再强转为目标类返回,其中参数invocationHandler为目标方法执行器,通过invoke方法执行目标方法并使用Object接收保存后返回目标方法执行后的返回值, intefaces为被代理类的类接口列表,classLoader为被代理类的类加载器
在测试类中通过getProxy方法获取Calculator的代理对象,使用代理对象执行方法
@Test public void add() { Calculator calculator = CalculatorProxy.getProxy(appleCalculator); calculator.add(2,1); } 复制代码
根据控制台打印可以看出,代理类成功执行了add方法
那么要实现在方法执行前后增加日志记录,就可以修改代理类CalculatorProxy的getProxy方法,在调用invoke方法前后增加日志输出,包括对方法执行异常时的处理
public class CalculatorProxy { public static Calculator getProxy(Calculator calculator){ // 方法执行器 InvocationHandler invocationHandler = (Object proxy, Method method, Object[] args) -> { Object result = null; try { // 反射执行目标方法 // System.out.println("动态代理执行目标方法:" + method.getName()); System.out.println(method.getName() + "方法开始执行,参数为:" + Arrays.toString(args)); result = method.invoke(calculator, args); System.out.println(method.getName() + "方法执行结束,结果为:" + result); } catch (Exception e){ System.out.println(method.getName() + "方法执行异常,异常信息:" + e.getCause()); } finally { System.out.println(method.getName() + "方法最终执行结束"); return result; } }; // 类接口 Class<?>[] interfaces = calculator.getClass().getInterfaces(); // 类加载器 ClassLoader classLoader = calculator.getClass().getClassLoader(); Object proxyInstance = Proxy.newProxyInstance(classLoader, interfaces, invocationHandler); return (Calculator) proxyInstance; } } 复制代码
再次执行测试类
定义异常情况
@Test public void div() { Calculator calculator = CalculatorProxy.getProxy(appleCalculator); calculator.div(2,0); } 复制代码
执行异常情况的测试方法div,输出了异常的原因
如果目标对象没有实现任何接口,是无法创建代理对象的,而Spring AOP可以解决这个问题,Spring AOP 底层就是动态代理