说明:本章节是在Spring的AOP的基础上,进行岔查漏补缺的
一、主线:下面的两种方式
1. 基于JDK的动态代理(重点)
* 必须是面向接口的,只有实现了具体接口的类才能生成代理对象
2. 基于CGLIB动态代理
* 对于没有实现了接口的类,也可以产生代理,产生这个类的子类的方式
二、回顾动态代理的思想
思想:运行期间动态的生成字节码文件(在内存中--本地文件系统不存在此字节码文件),由指定的类加载器加载,并生成代理对象
代理对象的特点:实现了被代理对象(真实对象的)接口(拥有真实对象的全部信息),对真实对象进行部分功能的增强!
思考:实现被代理对象接口的原因?
原因:代理对象仅仅是对真实对象部分功能的增强(指定方法上),除此之外与真实对象并没有什么区别,提供了一组接口给它,那么这个代理对象就宣称实现了该接口(多态),这样我就能调用这组接口中的方法了,保持二者的一致性!
说明:一旦对象被代理,执行的时候总会去判断方法是否增强(性能较差),所以除非特别需要(框架底层的实现),一般不要使用
代理对象的实质:实现被代理对象全部接口的子类
三、突破Proxy和InvocationHandler
3.1 Proxy
Proxy:提供用于创建动态代理类和实例的静态方法,它还是由这些方法创建的所有动态代理类的超类。
核心方法:
public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)
分析:newProxyInstance的三个参数
参数1:获取类加载器(自定义的类所用的类加载器是一样的,避免双亲委派检查(不需要一层层询问了,节省时间)
参数2:获取当前被代理对象实现的所有接口,代理对象也要实现此接口,才能保证一致性,获取全部的方法
参数3:代理对象必须要实现的接口,执行此接口的invoke方法(代理对象调用方法时的委派给此接口的方法处理)
此方法源代码的简单理解:
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,InvocationHandler h)
throws IllegalArgumentException
{
Objects.requireNonNull(h);
//(1)复制真实对象的接口
final Class<?>[] intfs = interfaces.clone();
final SecurityManager sm = System.getSecurityManager();
if (sm != null) {
checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
}
//(2)查找代理类字节码文件(已存在)或者动态生成代理类字节码文件(不存在)--核心
Class<?> cl = getProxyClass0(loader, intfs);//核心(回头重点关注下)
//(3)使用指定的调用处理程序调用其构造函数
try {
if (sm != null) {
checkNewProxyPermission(Reflection.getCallerClass(), cl);
}
//1:获取构造函数对象
final Constructor<?> cons = cl.getConstructor(constructorParams);
final InvocationHandler ih = h;
//2:修饰符检查(擦除类的访问权限)--getModifiers()--获取修饰符
if (!Modifier.isPublic(cl.getModifiers())) {
//AccessController:https://blog.csdn.net/heipacker/article/details/9295031
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
//取消构造方法的访问权限(私有),便于创建对象
cons.setAccessible(true);
return null;
}
});
}
//3:创建代理对象时,对构造函数传参(赋值)
return cons.newInstance(new Object[]{h});//h表示真实对象和真实对象调用的方法以及方法携带的参数
} catch (IllegalAccessException|InstantiationException e) {
throw new InternalError(e.toString(), e);
} catch (InvocationTargetException e) {
Throwable t = e.getCause();
if (t instanceof RuntimeException) {
throw (RuntimeException) t;
} else {
throw new InternalError(t.toString(), t);
}
} catch (NoSuchMethodException e) {
throw new InternalError(e.toString(), e);
}
}
思考:如何在内存中生成class(字节码)文件、以及加载资源(内存中class)的方式
3.2 InvocationHandler
接口说明:每一个动态代理类都必须要实现InvocationHandler这个接口,并且每个代理类的实例都关联到了一个handler,当我们通过代理对象调用一个方法的时候(类似观察者模式),这个方法的调用就会被转发为由InvocationHandler这个接口的 invoke 方法来进行调用。我们来看看InvocationHandler这个接口的唯一一个方法 invoke 方法
public Object invoke(Object proxy, Method method, Object[] args)throws Throwable;
参数说明:代理对象方法一执行,就会转发给invoke方法,就会执行一次(横向增强)
(1)参数1 (代理对象还是真实对象?)就是那个生成的代理对象
(2)参数2 代理对象所调用的方法对象(被代理对象的方法,根据需求进行扩展)
(3)参数3 代理对象所调用的方法对象中的参数对象数组(被代理对象方法对象的参数)
生成代理对象的工具类
package org.westos.demo2;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class ProxyUtils {
/**
* @param dao 参 数:被代理的对象(演员)--参数是目标对象
* @return BookDao 返回值:代理的对象(经纪人)
*/
public static BookDao getProxy(BookDao dao){
// 思考:怎么来生成这个代理对象(核心)?
// 答案:只有实现了"具体接口的类"才能生成"代理对象",问题来了
BookDao obj =(BookDao) Proxy.newProxyInstance(dao.getClass().getClassLoader(), dao.getClass().getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//判断:是不是要增强的方法(位置--前置)
if(method.getName().equals("save")){
System.out.println("记录日志");
}
if(method.getName().equals("update")){
System.out.println("开启事务");
}
//反射:让目标对象的方法正常执行--返回值
Object invoke = method.invoke(dao, args);
//位置:后置
if(method.getName().equals("update")){
System.out.println("提交事务");
}
return invoke;//返回值决定放行不放行(null不放行)
}
});
return obj;
}
}
解释一下为什么我们这里可以将其转化为BookDao类型的对象?原因:就是在newProxyInstance这个方法的第二个参数上,我们给这个代理对象提供了一组什么接口,那么我这个代理对象就会实现了这组接口,这个时候我们当然可以将这个代理对象强制类型转化为这组接口中的任意一个,因为这里的接口是BookDao类型,所以就可以将其转化为BookDao类型了。
BookDao
package org.westos.demo2;
public interface BookDao {
void save();
void update();
}
BookDaoImpl
package org.westos.demo2;
public class BookDaoImpl implements BookDao {
@Override
public void save() {
System.out.println("保存的方法");
}
@Override
public void update() {
System.out.println("修改的方法");
}
}
测试类
package org.westos.demo2;
import org.junit.Test;
public class MyTest {
@Test
public void test1(){
BookDao dao = new BookDaoImpl();
dao.save();
dao.update();
System.out.println("==============================");
BookDao proxy = ProxyUtils.getProxy(dao);
proxy.save();
proxy.update();
System.out.println(proxy.getClass.getName())
}}
补充:记住通过 Proxy.newProxyInstance 创建的代理对象是在jvm运行时动态生成的一个对象,它并不是我们的InvocationHandler类型,也不是我们定义的那组接口的类型,而是在运行是动态生成的一个对象,并且命名方式都是这样的形式,以$开头,proxy为中,最后一个数字表示对象的标号。
测试:invoke方法打印method和proxy;当我通过代理对象来调用方法的时候,起实际就是委托由其关联到的 handler 对象的invoke方法中来调用,并不是自己来真实调用,而是通过代理的方式来调用的
思考:如果没有实现了接口的类,如何生成代理对象呢?
博客:点击打开链接(比较完善)