Spring的IOC容器和AOP切面中用到大量的动态代理,今天记录一下我自己学习的动态代理的过程,我没有看spring的源代码,但是估计底层应该也是这个样子。
java.lang.reflect.Proxy类,使用的用例API已经给出了
Proxy
提供用于创建动态代理类和实例的静态方法,它还是由这些方法创建的所有动态代理类的超类。
创建某一接口 Foo
的代理:
InvocationHandler handler = new MyInvocationHandler(...); Class proxyClass = Proxy.getProxyClass( Foo.class.getClassLoader(), new Class[] { Foo.class }); Foo f = (Foo) proxyClass. getConstructor(new Class[] { InvocationHandler.class }). newInstance(new Object[] { handler });或使用以下更简单的方法:
Foo f = (Foo) Proxy.newProxyInstance(Foo.class.getClassLoader(), new Class[] { Foo.class }, handler);
Proxy中的主要方法如下
java.lang.reflect.InvocationHandler接口中的
invoke(Object proxy, Method method, Object[] args)方法是在代理实例上处理方法调用并返回结果。在与方法关联的代理实例上调用方法时,将在调用处理程序上调用此方法。
参数说明:
proxy - 在其上调用方法的代理实例
method - 对应于在代理实例上调用的接口方法的 Method 实例。 Method 对象的声明类将是在其中声明方法的接口,该接口可以是代理类赖以继承方法的代理接口的超接口。
args - 包含传入代理实例上方法调用的参数值的对象数组,如果接口方法不使用参数,则为 null。基本类型的参数被包装在适当基本包装器类(如 java.lang.Integer 或 java.lang.Boolean)的实例中。
从代理实例的方法调用返回的值。如果接口方法的声明返回类型是基本类型,则此方法返回的值一定是相应基本包装对象类的实例;否则,它一定是可分配到声明返回类型的类型。如果此方法返回的值为 null 并且接口方法的返回类型是基本类型,则代理实例上的方法调用将抛出 NullPointerException。否则,如果此方法返回的值与上述接口方法的声明返回类型不兼容,则代理实例上的方法调用将抛出ClassCastException。
下面是我的代码的示例
需求:1.提供一个除法接口,一个接口的实现类;2.一个代理类,在除法进行的前后进行拦截,打印参数;3.参数的校验,如果参数合规则,则进行除法计算,如果不合规则,则不进行计算。返回0
一、接口及实现类
package com.dadi.aop;
/**
* 计算接口
*/
public interface CalculationService {
/**
* 加
*
* @param i
* @param j
* @return
*/
int add(int i, int j);
/**
* 减
*
* @param i
* @param j
* @return
*/
int sub(int i, int j);
/**
* 乘
*
* @param i
* @param j
* @return
*/
int mul(int i, int j);
/**
* 加
*
* @param i
* @param j
* @return
*/
int div(int i, int j);
}
我们在没有用到动态代理的时候,如果要验证参数和在方法执行的前后来打印日志的话,只能和业务放在一起,如下
public class CalculationServiceImpl implements CalculationService {
private int result = 0;
@Override
public int add(int i, int j) {
System.out.println("i : " + i + " , j : " + j);
System.out.println("CalculationServiceImpl--->add start");
result = i + j;
System.out.println("result : " + result);
System.out.println("CalculationServiceImpl--->add end");
return result;
}
@Override
public int sub(int i, int j) {
System.out.println("i : " + i + " , j : " + j);
System.out.println("CalculationServiceImpl--->sub start");
result = i - j;
System.out.println("result : " + result);
System.out.println("CalculationServiceImpl--->sub end");
return result;
}
@Override
public int mul(int i, int j) {
System.out.println("i : " + i + " , j : " + j);
System.out.println("CalculationServiceImpl--->mul start");
result = i * j;
System.out.println("result : " + result);
System.out.println("CalculationServiceImpl--->mul end");
return result;
}
@Override
public int div(int i, int j) {
System.out.println("i : " + i + " , j : " + j);
System.out.println("CalculationServiceImpl--->div start");
result = i / j;
System.out.println("result : " + result);
System.out.println("CalculationServiceImpl--->div end");
return result;
}
}
像上面这样的话,会使代码量非常的庞大,而且看起来非常的凌乱。
如何使我们只关系业务,而把数据的校验和日志放在日志只关心的类里面呢?所以我们在这里借助动态代理类来实现。
只关心核心业务的实现代码如下
package com.dadi.aop;
public class CalculationServiceImpl2 implements CalculationService {
@Override
public int add(int i, int j) {
return i + j;
}
@Override
public int sub(int i, int j) {
return i - j;
}
@Override
public int mul(int i, int j) {
return i * j;
}
@Override
public int div(int i, int j) { return i / j; }
}
二、动态代理计算的对象,并把被除数为0的值进行校验,如果为0则不计算。
package com.dadi.aop;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;
import java.util.Objects;
/**
* 算数计算效验和日志的记录,通过动态代理对象,对对象的方法的参数的参数进行校验和日志的输出
*/
public class CalculationServiceLoggingProxy {
private CalculationService target;
public CalculationServiceLoggingProxy(CalculationService target) {
this.target = target;
}
public CalculationService getLoggingProxy() {
// 当具体调用代理对象的方法时, 实际上就是调用 InvocationHandler 的 invoke 方法
InvocationHandler invocationHandler = new InvocationHandler() {
/**
* @param proxy 该方法被调用的代理实例
* @param method 代理实例上调用的接口方法对应的Method实例
* @param args 包含在代理实例上的方法调用中传递的参数值的对象数组,如果interface方法不带任何参数,则为null
* @return 从代理实例上的方法调用返回的值
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String methodName = method.getName();
// 方法开始执行,打印进来的参数
System.out.println("方法开始之前[before], 方法名:" + methodName + ",参数:" + Arrays.asList(args));
// 这个方法就是实现代理类的方法,target是代理的类,args是方法中的参数
Object result;
if (Objects.equals("div", methodName) && Objects.equals("0", String.valueOf(args[1]))) {
result = 0;
} else {
result = method.invoke(target, args);
}
// 方法运行结束,打印结束日志
System.out.println("方法结束之后[after], 方法名:" + methodName + ",结果:" + result);
return result;
}
};
// 通过类加载器来加载要动态代理的类
ClassLoader classLoader = target.getClass().getClassLoader();
// 指定代理对象的类型. 即代理对象中可以有哪些方法
Class[] interfaces = {CalculationService.class};
return (CalculationService) Proxy.newProxyInstance(classLoader, interfaces, invocationHandler);
}
}
三、测试用例
@Test
public void proxy(){
CalculationService cs = new CalculationServiceImpl();
CalculationService proxy = new CalculationServiceLoggingProxy(cs).getLoggingProxy();
int result = proxy.div(8,0);
String.format("结果: %s",result);
System.out.println("*************");
result = proxy.div(8,2);
String.format("结果: %s",result);
}