举个栗子:
比如有一家美国大学,面向全世界招生,而我们国内的同学,需要去到某个大学。因为我们所处国内,并不知道这个大学的基本情况。那我们又想去了解,并且进入这个大学。这就衍生处理一个行业,
中介(代理)
。由代理招收学生到给到大学。也就是我们入学的事情交给了代理去完成。
特点:
代理模式:给某一个对象提供一个代理,并由代理对象来控制对真实对象的访问。代理模式是一种结构型设计模式。(逐字理解)
代理模式的结构比较简单,其核心是代理类,为了让客户端能够一致性地对待真实对象和代理对象,在代理模式中引入了抽象层。
可能这里还是看的云里雾里的,通过一个demo,来加深我们对于静态代理的理解
目录结构
// 等同于 Subject
public interface UserService {
// 定义的业务逻辑
void select();
// 定义的业务逻辑
void update();
}
// 等同于 RealSubject
public class UserServiceImpl implements UserService {
public void select() {
System.out.println("查询 selectById");
}
public void update() {
System.out.println("更新 update");
}
}
此时,我们的业务逻辑已经实现,但是我们的代理还未定义。我们都知道,代理简单来说,在不侵入原有业务代码的条件下,对其功能增强。
public class UserServiceProxy implements UserService {
private UserService target; // 被代理的对象
public UserServiceProxy(UserService target) {
this.target = target;
}
public void select() {
before(); // 增强操作
target.select(); // 这里才实际调用真实主题角色的方法
after(); // 增强操作
}
public void update() {
before(); // 增强操作
target.update(); // 这里才实际调用真实主题角色的方法
after(); // 增强操作
}
/**
* 在执行方法之前执行
*/
private void before() {
System.out.println(String.format("log start time [%s] ", new Date()));
}
/**
* 在执行方法之后执行
*/
private void after() {
System.out.println(String.format("log end time [%s] ", new Date()));
}
}
执行客户端测试:
public class Client {
public static void main(String[] args) {
// 创建业务处理类
UserService userService = new UserServiceImpl();
// 通过构造方法进行传入业务处理类到代理对象中 进行功能增强
UserServiceProxy proxy = new UserServiceProxy(userService);
// 代理执行目标方法
proxy.select();
}
}
执行结果:
可以看到通过静态代理,我们达到了功能增强的目的,而且没有侵入原代码,这是静态代理的一个优点。
虽然静态代理实现简单,且不侵入原代码,但是,当场景稍微复杂一些的时候,静态代理的缺点也会暴露出来。
如:当需要代理多个类的时候,由于代理对象要实现与目标对象一致的接口,有两种方式:
如何改进?
这就涉及到Java虚拟机的类加载机制了,推荐翻看《深入理解Java虚拟机》7.3节 类加载的过程。
Java虚拟机类加载过程主要分为五个阶段:加载、验证、准备、解析、初始化。其中加载阶段需要完成以下3件事情:
java.lang.Class
对象,作为方法区这个类的各种数据访问入口由于虚拟机规范对这3点要求并不具体,所以实际的实现是非常灵活的,关于第1点,获取类的二进制字节流(class字节码)就有很多途径:
*$Proxy
的代理类的二进制字节流所以,动态代理就是想办法,根据接口或目标对象,计算出代理类的字节码,然后再加载到JVM中使用。但是如何计算?如何生成?情况也许比想象的复杂得多,我们需要借助现有的方案。
Java的加载时反射系统
,它是一个用于在Java中编辑字节码的类库; 它使Java程序能够在运行时定义新类,并在JVM加载之前修改类文件。为了让生成的
代理类与目标对象(真实主题角色)保持一致性
,从现在开始将介绍以下两种最常见的方式:
注:使用ASM对使用者要求比较高,使用Javassist会比较麻烦。
JDK动态代理主要涉及两个类:
java.lang.reflect.Proxy
和java.lang.reflect.InvocationHandler
,我们仍然通过案例来学习编写一个调用逻辑处理器 LogHandler 类,提供日志增强功能,并实现 InvocationHandler 接口;在 LogHandler 中维护一个目标对象,这个对象是被代理的对象(真实主题角色);在invoke
方法中编写方法调用的逻辑处理。
public class LogHandler implements InvocationHandler {
private Object target;
//传入目标对象
public LogHandler(Object target) {
this.target = target;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
before();
Object result = method.invoke(target, args);// 调用 target 的 method 方法
after();
return result; // 返回方法的执行结果
}
// 调用invoke方法之前执行
private void before() {
System.out.println(String.format("log start time [%s] ", new Date()));
}
// 调用invoke方法之后执行
private void after() {
System.out.println(String.format("log end time [%s] ", new Date()));
}
}
客户端测试:
public class Client {
public static void main(String[] args) {
// 1. 创建被代理的对象,UserService接口的实现类
UserService userService = new UserServiceImpl();
// 2. 获取对应的 ClassLoader
ClassLoader classLoader = UserServiceImpl.class.getClassLoader();
// 3. 获取所有接口的Class,这里的UserServiceImpl只实现了一个接口UserService
Class<?>[] interfaces = UserServiceImpl.class.getInterfaces();
// 4. 创建一个将传给代理类的调用请求处理器,处理所有的代理对象上的方法调用
// 这里创建的是一个自定义的日志处理器,须传入实际的执行对象 userServiceImpl
InvocationHandler handler = new LogHandler(userService);
// 5. newProxyInstance 创建代理对象
// 参数1:需要传入一个类加载器 也就是需要代理的类
// 参数2:需要传入一个接口的Class 也就是代理的类需要实现的接口
// 参数3:需要传入一个调用处理类 也就是调用过程程中,对目标方法的增强
UserService proxy = (UserService) Proxy.newProxyInstance(classLoader, interfaces, handler);
// 通过代理类 调用目标方法
proxy.select();
}
}
执行日志:
JDK动态代理执行方法调用的过程简图如下:
public final
修饰,所以代理类只可被使用,不可以再被继承m + 数字
的格式命名super.h.invoke(this, m1, (Object[])null);
调用,其中的 super.h.invoke
实际上是在创建代理的时候传递给 Proxy.newProxyInstance
的 LogHandler 对象,它继承 InvocationHandler 类,负责实际的调用处理逻辑这里就不重复写文章了,引用大佬的文章。