动态代理
本节目标:
-
动态代理演示
-
动态代理原理
什么是动态代理?
使用一个代理将对象包装起来,然后用该代理对象取代原始对象,任何对原始对象的调用都得通过代理。代理对象决定是否以及何时将方法调用转到原始对象上。
Q1:做一个能加减乘除的计算器,用日志记录下计算前后的数据
没有设计思想,只求实现功能:
public class mathCalculatorImpl implements mathCalculator {
/**
* 日志在int result之前,后。
* @param i
* @param j
* @return
*/
@Override
public int add(int i, int j) {
System.out.println("日志记录:the method add begin with["+i+","+j+"]");
int result=i+j;
System.out.println("日志记录:the method add ends withd:"+result);
return result;
}
@Override
public int sub(int i, int j) {
System.out.println("日志记录:the method sub begin with["+i+","+j+"]");
int result=i-j;
System.out.println("日志记录:the method sub ends withd:"+result);
return result;
}
@Override
public int mul(int i, int j) {
System.out.println("日志记录:the method mul begin with["+i+","+j+"]");
int result=i*j;
System.out.println("日志记录:the method mul ends withd:"+result);
return result;
}
@Override
public int div(int i, int j) {
System.out.println("日志记录:the method div begin with["+i+","+j+"]");
int result=i/j;
System.out.println("日志记录:the method div ends withd:"+result);
return result;
}
}
我们发现这样的代码有几个问题:
将日志与业务逻辑代码耦合在一起,不好修改,会使得负责写日志模块和负责业务逻辑处理的人员工作量加大,将不相关的代码耦合在一起,是没有设计思想的。重复代码多,在日后的需求更改之时,得修改所有的模块。
Q2:如何解决这种问题呢?
用动态代理完成日志代理功能
JDK的动态代理
Proxy (reflex jar包下的):jdk的动态代理:是所有动态代理类的父类,专门用户生成代理类或者是代理对象
public static class<?> getProxyClass(ClassLoader loader,Class<?>..interfaces)
用于生成代理类的Class对象。
public static object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)
用于生成代理对象
2.InvocationHandler (接口):完成动态代理的整个过程。
public object invoke(Object proxy,Method method,Object[] args)throws Throwable;(动态代理想做什么事就写在动态代理就行了)
改进的计算器
package com.hxh.aop;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;
public class mathCalculatorProxy {
//动态代理: 目标对象 如何获取代理对象 代理要做什么
// mathCalculatorImpl是目标对象,通过父类型来声明它的一个变量,mathCalculatorProxy是要创建一个对象
//的,我们会给这个类提供一个方法用来获取代理对象。你想要调用方法就得有一个对象,有一个对象就
// 得new一个对象,就得执行一个构造器,再去创建对象。传入一个目标对象,告诉我要代理哪个对象,然后
// 赋值到target,相当于保存了目标对象。
private mathCalculator target;
public mathCalculatorProxy(mathCalculator target){
this.target=target;
}
//一个获取代理对象的方法
public Object getProxy(){
//定义代理对象
Object proxy;
/**
* loader: ClassLoader对象。类加载器对象,帮我们加载动态生成的代理类。
* interfaces:接口们,提供目标对象所有的接口。目的是让代理对象保证与目标对象都有接口中相同的方法。
* h: InvocationHandler类型的对象
*/
ClassLoader loader=target.getClass().getClassLoader();
Class[] interfaces = target.getClass().getInterfaces();
//new InvocationHandler()匿名内部类,执行newProxyInstance的时候不会执行invoke方法。
//proxy都还没生成,不能传入invoke方法里(参数)
proxy= Proxy.newProxyInstance(loader, interfaces, new InvocationHandler() {
/**invoke:代理对象调用代理方法,会回来调用invoke方法.
* proxy:代理对象,在invoke方法中一般不会使用
* method:正在被调用的方法对象
* args:正在被调用的方法的参数
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//完成动态代理的整个过程
//将方法的调用转回到目标对象上。
//获取方法的名字
String methodName=method.getName();
//记录日志,将日志的功能移到这里
System.out.println("LoggingProxy--> The method"+ methodName+"begin with:"+ Arrays.asList(args));
Object result=method.invoke(target,args);//目标对象执行目标方法,相当于执行 mathCalculatorImpl中的加减乘除
System.out.println("LoggingProxy--> The method"+methodName+"ends with:"+result);
//记录日志
//通过日志代理来记录日志,完全没必要写在业务逻辑处理(实现类mathCalculatorImpl)的代码里面了
return result;
}
});
return proxy;
}
}
Test类:
package com.hxh.aop;
public class Main {
public static void main(String[] args) {
//目标对象
mathCalculator target=new mathCalculatorImpl();
//获取代理对象
Object obj=new mathCalculatorProxy(target).getProxy();
//转回具体的对象
mathCalculator proxy= (mathCalculator) obj;
int result=proxy.add(1,1);
System.out.println("Main Result:"+result);
}
}
运行截图:
Q3: 1.代理对象能否转换成目标对象的类型? 2.代理对象调用代理方法,为什么会执行invocationHandler中的invoke方法
模拟底层动态代理类
获取动态代理类的名字
mathCalculator proxy= (mathCalculator) obj;
System.out.println(proxy.getClass().getName());
运行结果:
com.sun.proxy.$Proxy0
Proxy.class中
protected Proxy(InvocationHandler h){
Objects.requireNonNull(h);
this.h=h;
//将h赋值给了当前对象的h(protect InvocationHandler h;)
}
也就是说我把我创建好的InvocationHandler传到代理类里面,代理类又从它的构造器里面把h传到了父类里面,这样的话,我们父类里面的h其实就是自己创建的h
这里面的h就是我们自己创建的h,意味着会执行InvocationHandler里面的invoke方法,在invoke方法里面我们就能够完成整个动态代理的过程。其实并没有使用一开始add的功能。真正的加法是在如下代码中操作的
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String methodName=method.getName();
System.out.println("LoggingProxy--> The method"+ methodName+"begin with:"+ Arrays.asList(args));
Object result=method.invoke(target,args);//目标对象执行目标方法,相当于执行 mathCalculatorImpl中的加减乘除
System.out.println("LoggingProxy--> The method"+methodName+"ends with:"+result);
return result;
}
实现了目标对象的加减乘除移到了上述代码中,在代码的前后进行日志的记录
动态代理类是在运行的时候,动态生成的,动态生成的东西在内存里面,不会存在硬盘里,但是从内存中抓取动态代理太难了,不过有一个更简单的方法可以实现该功能
第二种动态代理的实现方法
public class mathCalculatorProxy2 {
private mathCalculator target;
public mathCalculatorProxy2(mathCalculator target) {
this.target = target;
}
public Object getProxy() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
//定义代理对象
Object proxy;
//第二种方法:public static class<?> getProxyClass(ClassLoader loader,Class<?>..interfaces)
//用于生成代理类的Class对象。
//先获取Proxy的class对象
//反射机制
ClassLoader loader=target.getClass().getClassLoader();
Class []interfaces=target.getClass().getInterfaces();
Class proxyClass=Proxy.getProxyClass(loader,interfaces);
//Class 创建对象? newInstance():依赖无参构造器(代理类没有) 通过Class对象获取到构造器对象,再通过
//构造器对象创建出具体的对象
Constructor con=proxyClass.getDeclaredConstructor(InvocationHandler.class);
proxy=con.newInstance(new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String methodName=method.getName();
System.out.println("LoggingProxy--> The method"+ methodName+"begin with:"+ Arrays.asList(args));
Object result=method.invoke(target,args);
System.out.println("LoggingProxy--> The method"+methodName+"ends with:"+result);
return result;
}
});
return proxy;
}
}
保存生成的动态代理类,将代码加入到Test类中即可
Properties properties=System.getProperties();
properties.put("sun.misc.ProxyGenerator.saveGeneratedFiles","true");