目录
[1.为什么需要动态代理? :](#1.为什么需要动态代理? :)
[3.动态代理的灵活性 :](#3.动态代理的灵活性 :)
[1.需求 :](#1.需求 :)
[2.实现 :](#2.实现 :)
[2.1 接口和实现类](#2.1 接口和实现类)
[2.2 提供代理对象的类](#2.2 提供代理对象的类)
[2.3 测试类](#2.3 测试类)
[3.引出AOP :](#3.引出AOP :)
一、前言
- 第四节内容,up打算和大家分享Spring 动态代理 相关的内容;动态代理本质上是Spring AOP的一个前置的引入内容,一个AOP开篇之作,但它却相当重要,且本身难度较大。
- 注意事项------①代码中的注释也很重要;②不要眼高手低,自己跟着过一遍才真正有收获;③点击文章的侧边栏目录或者文章开头的目录可以进行跳转。
- 良工不示人以朴,up所有文章都会适时补充完善。大家如果有问题都可以在评论区进行交流或者私信up。感谢阅读!
二、动态代理快速入门
1.为什么需要动态代理? :
在日常开发中,往往存在这样一种需求 ------同时存在多个对象,这些对象对应的类都实现了同一接口,并且这些对象会去调用这个接口中的某一个方法,即++多态++,但是我们要求这几个对象在调用方法前,和调用方法后都要做一些业务处理,eg : 权限校验、事务管理、日志管理、安全校验等。
如果我们将这些相同的业务处理,都下沉到每一个具体的类,就会造成代码冗余 ,并且没有办法进行对象的统一管理和调用;而动态代理的出现,尤对其症地解决了这个问题。
2.动态代理使用案例:
up先在dynamic_proxy包下创建Animal接口 ,以及Cat, Dog类,Cat类和Dog类都实现了Animal接口。如下图所示 :
Animal接口代码如下 :
java
package com.cyan.spring.dynamic_proxy;
/**
* @author : Cyan_RA9
* @version : 21.0
*/
public interface Animal {
public abstract void eat();
}
Cat类代码如下 :
java
package com.cyan.spring.dynamic_proxy;
/**
* @author : Cyan_RA9
* @version : 21.0
*/
public class Cat implements Animal{
@Override
public void eat() {
System.out.println("小猫爱吃鱼捏~");
}
}
Dog类代码如下 :
java
package com.cyan.spring.dynamic_proxy;
/**
* @author : Cyan_RA9
* @version : 21.0
*/
public class Dog implements Animal{
@Override
public void eat() {
System.out.println("小狗爱吃骨头捏~");
}
}
然后,我们创建一个AnimalProxyProvider类,见名知意,这个类可以提供一个Animal接口的代理对象,所以,该类中肯定会定义一个方法,用来返回Animal接口的代理实例,当然,这个方法稍微有点复杂,大家可以借助up的代码注释逐渐理解。
AnimalProxyProvider类代码如下 : (尤其注意匿名内部类中实现的invoke方法,其中的两条输出语句表示实现类对象相同的业务逻辑代码)
java
package com.cyan.spring.dynamic_proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* @author : Cyan_RA9
* @version : 21.0
*/
public class AnimalProxyProvider {
//利用接口类型对传入的对象做接收 (多态)
private Animal targetAnimal;
//通过带参构造传入一个Animal接口实现类的对象
public AnimalProxyProvider(Animal targetAnimal) {
this.targetAnimal = targetAnimal;
}
//编写一个方法,用于返回代理对象 (用到反射机制)
public Animal getAnimalProxy() {
/**
java.lang.reflect.Proxy类中的 newProxyInstance方法可以返回一个代理对象。
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException {...}
该方法需要传入三个实参 ---------
(1) ClassLoader loader : 类加载器
(2) Class<?>[] interfaces : 接口信息
(3) InvocationHandler h : 调用处理器,其本身也是一个接口,内部声明了抽象方法invoke。
*/
//(1)得到类加载器
ClassLoader classLoader = targetAnimal.getClass().getClassLoader();
//(2)得到被执行对象的接口信息(因为newProxyInstance方法底层是通过接口来调用的,即接口多态)
Class<?>[] interfaces = targetAnimal.getClass().getInterfaces();
//(3)得到处理器对象(通过匿名内部类实现,最终返回的是一个匿名内部类对象)
//!!![注意,处理器对象本身也是newProxyInstance方法的一个形参]
InvocationHandler invocationHandler = new InvocationHandler() {
/**
* @param proxy the proxy instance that the method was invoked on
*
* @param method the {@code Method} instance corresponding to
* the interface method invoked on the proxy instance. The declaring
* class of the {@code Method} object will be the interface that
* the method was declared in, which may be a superinterface of the
* proxy interface that the proxy class inherits the method through.
*
* @param args an array of objects containing the values of the
* arguments passed in the method invocation on the proxy instance,
* or {@code null} if interface method takes no arguments.
* Arguments of primitive types are wrapped in instances of the
* appropriate primitive wrapper class, such as
* {@code java.lang.Integer} or {@code java.lang.Boolean}.
*
* @return : the results of method instance invoked
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
/*
将相同的业务逻辑代码,放在处理器对象的invoke对象中,
避免了代码下沉到每个实现类所造成的代码冗余。
*/
System.out.println("要吃饭的嘛~");
//通过反射调用实现类中的方法
Object results = method.invoke(targetAnimal, args);
System.out.println("吃完了捏~");
return results;
}
};
Animal animalProxy = (Animal) Proxy.newProxyInstance(classLoader, interfaces, invocationHandler);
return animalProxy;
}
}
接着,up定义一个测试类,在测试类中定义一个单元测试方法,用以测试我们的动态代理是否生效。
TestAnimal类代码如下 :
java
package com.cyan.spring.dynamic_proxy;
import org.junit.jupiter.api.Test;
public class TestAnimal {
@Test
public void testGeyAnimalProxy() {
//构造接口多态
Animal animal = new Cat();
Animal animal2 = new Dog();
//传入需要被代理的对象
AnimalProxyProvider animalProxyProvider = new AnimalProxyProvider(animal);
AnimalProxyProvider animalProxyProvider2 = new AnimalProxyProvider(animal2);
//得到代理对象
/*
注意!
此处代理对象animalProxy的编译类型仍然是Animal类型,但运行类型却不再是Cat or Dog类型,
而是代理类型 --------- class com.sun.proxy.$Proxy9。
*/
Animal animalProxy = animalProxyProvider.getAnimalProxy();
Animal animalProxy2 = animalProxyProvider2.getAnimalProxy();
System.out.println("animalProxy's RuntimeType = " + animalProxy.getClass());
System.out.println("animalProxy2's RuntimeType = " + animalProxy2.getClass());
//通过代理对象调用实现类方法
animalProxy.eat();
System.out.println("==============================");
animalProxy2.eat();
}
}
运行结果 :
现在我们进行Debug断点调试,断点如下图所示 :
跳入eat()方法时会发现,IDEA直接跳到了 AnimalProxyProvider类的getAnimalProxy方法中------匿名内部类实现的invoke方法里面,如下图所示 :
再往下执行便是通过反射调用 对应的method,一直往下追,最终会跳到实现类的eat()方法中,如下图所示 :
3.动态代理的灵活性 :
动态代理的灵活性体现在哪里?
首先,被代理的对象是可变的。并且,代理对象所调用的方法也是可变的。
比方说,up在Animal接口中新定义了一个sleep方法,如下图所示 :
然后,up在Cat类中实现了sleep方法,如下图所示 :
接着,修改匿名内部类实现的invoke方法中的"业务逻辑"代码,如下图所示 :
最后,up在测试类中新定义一个单元测试方法,测试动态代理是否生效。
testSleep()方法代码如下 :
java
@Test
public void testSleep() {
//1.构造接口多态
Animal animal = new Cat();
//2.传入需要被代理的对象
AnimalProxyProvider animalProxyProvider = new AnimalProxyProvider(animal);
//3.获取代理对象
Animal animalProxy = animalProxyProvider.getAnimalProxy();
//4.通过代理对象调用实现类方法
String sleepMinutes = animalProxy.sleep(Long.valueOf(1314));
}
运行结果 :
三、深入动态代理
1.需求 :
定义Calculator接口,表示一个计算器,该接口中定义有可以完成简单加减乘除运算的方法,要求在每次执行运算方法前后,都打印出运算日志(运算法则和运算参数,以及运算结果)。
2.实现 :
2.1 接口和实现类
首先分析需求,既然要求在每次执行运算方法前后都打印出运算日志,显然我们会想到------仍是在匿名内部类实现的invoke方法中动手脚。
别的不说,先来定义Calculator接口和一个它的实现类 。
Calculator接口如下 : (声明了"加减乘除"四个抽象方法)
java
package com.cyan.spring.dynamic_proxy;
/**
* @author : Cyan_RA9
* @version : 21.0
*/
public interface Calculator {
public abstract double add(double n1, double n2);
public abstract double subtract(double n1, double n2);
public abstract double multiply(double n1, double n2);
public abstract double divide(double n1, double n2);
}
定义一个实现类Calculator_Demo1,代码如下 :
java
package com.cyan.spring.dynamic_proxy;
/**
* @author : Cyan_RA9
* @version : 21.0
*/
public class Calculator_Demo1 implements Calculator {
@Override
public double add(double n1, double n2) {
return n1 + n2;
}
@Override
public double subtract(double n1, double n2) {
return n1 - n2;
}
@Override
public double multiply(double n1, double n2) {
return n1 * n2;
}
@Override
public double divide(double n1, double n2) {
//分母不允许为0
if (n2 != 0) {
return n1 / n2;
}
return -1;
}
}
2.2 提供代理对象的类
定义一个CalculatorProxyProvider类,与上文 "动态代理使用案例" 中定义的"提供代理对象的类"类似,都需要定义一个方法用于返回代理对象。
CalculatorProxyProvider类代码如下 :
java
package com.cyan.spring.dynamic_proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.time.LocalDateTime;
public class CalculatorProxyProvider {
private Calculator calculator;
public CalculatorProxyProvider(Calculator calculator) {
this.calculator = calculator;
}
/*
底层仍然使用java.lang.reflect包下的Proxy类的newProxyInstance方法来获取代理对象。
*/
public Calculator getCalculatorProxy() {
//1.获取newProxyInstance方法的第一个参数------------类加载器
ClassLoader classLoader = calculator.getClass().getClassLoader();
//2.获取newProxyInstance方法的第二个参数------------接口信息
Class<?>[] interfaces = calculator.getClass().getInterfaces();
//3.获取newProxyInstance方法的第三个参数------------处理器对象
//仍然借助匿名内部类来实现,并通过构造接口多态的形式做接收。
InvocationHandler invocationHandler = new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//获取到当前传入的参数
double n1 = (double) args[0];
double n2 = (double) args[1];
//获取当前方法名
String name = method.getName();
//定义运算结果
double result = 0.0;
try {
//Δ在运算方法执行前打印出运算日志
System.out.println("运算日志------------运算法则 = " + name + ",传入两个参数分别是 " + n1 + " 和 " + n2);
//执行运算
result = (double) method.invoke(calculator, args);
System.out.println("result = " + result);
//Δ在运算方法执行后打印出运算日志
System.out.println("运算日志------------运算法则 = " + name + ",运算结果 = " + result);
//返回运算结果
return result;
} catch (Exception e) {
System.out.println("异常日志------------" + LocalDateTime.now() + ",方法" + method.getName() + "执行异常");
throw new RuntimeException(e);
} finally {
System.out.println("执行日志------------" + method.getName() + "方法执行结束。");
}
}
};
//4.调用newProxyInstance方法,得到代理对象
Calculator instance = (Calculator) Proxy.newProxyInstance(classLoader, interfaces, invocationHandler);
//5.返回获得的代理对象
return instance;
}
}
2.3 测试类
最后,仍然是在测试类中定义一个单元测试方法,up新定义了一个TestCalculator类,代码如下 :
java
package com.cyan.spring.dynamic_proxy;
import org.junit.jupiter.api.Test;
public class TestCalculator {
@Test
public void testArithmetic() {
//1.构造接口多态
Calculator calculator = new Calculator_Demo1();
//2.传入需要被代理的对象
CalculatorProxyProvider calculatorProxyProvider = new CalculatorProxyProvider(calculator);
//3.获取代理对象
Calculator calculatorProxy = calculatorProxyProvider.getCalculatorProxy();
//4.通过代理对象调用实现类方法
double addResult = calculatorProxy.add(200.333, 33);
System.out.println("-----------------------------");
double subtractResult = calculatorProxy.subtract(141, 5);
System.out.println("-----------------------------");
double multiplyResult = calculatorProxy.multiply(11.11, 2);
System.out.println("-----------------------------");
double divideResult = calculatorProxy.divide(3917.0, 500.00);
}
}
运行结果 :
注意------
(1) CalculatorProxyProvider类的这段代码,如下图所示 :
在AOP中,称为"++横切关注点++ ",也叫"前置通知"。
**(2)**而下面的这段代码,如下图所示 :
从AOP的角度来看,也称为一个"++横切关注点++ ",但也叫"后置通知"。
(3) 此外,异常处理------catch语句块中的这段代码,如下图所示 :
从AOP看,也称为一个"++横切关注点++ ",但又称为"异常通知"。
(4) 最后,finally代码块中的内容,如下图所示 :
从AOP看,也称为一个"++横切关注点++ ",但又称为"最终通知"。
3.引出AOP :
分析一下我们方才写得代码,如下图所示 :
可以看到,无论是"前置通知","后置通知",还是"异常通知","最终通知"。我们都只是草草地用了一条输出语句敷衍过去,这使得我们的代码不够牛逼,功能不够强大,且代码死板,不够灵活。
而作为一名OOP程序员,我们会容易联想到------假如此处的输出语句都替换成方法,用一个方法直接切入,那不就既满足灵活性,又可以实现强大的功能吗?
四、总结
- 🆗,以上就是Spring系列博文第四小节的全部内容了。
- 总结一下,我们应该明白动态代理究竟"动态"在哪里?------ 不止是被代理对象可变,且代理对象执行的方法也是可变的。我们还需要知道newProxyInstance方法的三个形参分别有什么作用,以及如何获取这三个形参(尤其是第三个形参------处理器对象的获取,用到了匿名内部类)。总之,这篇博文其实是为了给"Spring---AOP"的分享做一个铺垫,我们以一个问题开始,又以一个问题结尾,也符合这篇博客的定位。
- 下一节内容------Spring AOP---切入表达式和基于XML配置AOP,我们不见不散。感谢阅读!
System.out.println("END---------------------------------------");