总所周知,在Java中,非abstract
的class
可以被实例化;而interface
不能被实例化
所有interface
类型的变量总是通过某个实例向上转型并赋值给接口类型变量的:
CharSequence cs = new StringBuilder();
那么,有没有可能不编写实现类,直接在运行期创建某个interface的实例呢?
这是可能的,因为Java标准库提供了一种动态代理(Dynamic Proxy)的机制:可以在运行期动态创建某个interface的实例。
代理模式
本文将介绍的Java动态代理与设计模式中的代理模式有关,什么是代理模式呢?
代理模式:给某一个对象提供一个代理,并由代理对象来控制对真实对象的访问。代理模式是一种结构型设计模式。
代理模式角色分为 3 种:
Subject(抽象主题角色):定义代理类和真实主题的公共对外方法,也是代理类代理真实主题的方法;
RealSubject(真实主题角色):真正实现业务逻辑的类;
Proxy(代理主题角色):用来代理和封装真实主题;
代理模式的结构比较简单,其核心是代理类,为了让客户端能够一致性地对待真实对象和代理对象,在代理模式中引入了抽象层。
如果根据字节码的创建时机来分类,可以分为静态代理和动态代理:
- 所谓静态也就是在程序运行前就已经存在代理类的字节码文件,代理类和真实主题角色的关系在运行前就确定了。
- 而动态代理的源码是在程序运行期间由JVM根据反射等机制动态的生成,所以在运行前并不存在代理类的字节码文件
静态代理
例子1
定义接口:
public interface Hello {
void morning(String name);
}
编写实现类:
public class HelloWorld implements Hello {
public void morning(String name) {
System.out.println("Good morning, " + name);
}
}
创建实例,转型为接口并调用:
Hello hello = new HelloWorld();
hello.morning("Bob");
例子2
编写一个接口 UserService ,以及该接口的一个实现类 UserServiceImpl
public interface UserService {
public void select();
public void update();
}
public class UserServiceImpl implements UserService {
public void select() {
System.out.println("查询 selectById");
}
public void update() {
System.out.println("更新 update");
}
}
我们将通过静态代理对 UserServiceImpl 进行功能增强,在调用select和update之前记录一些日志。写一个代理类 UserServiceProxy,代理类需要实现 UserService
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 Client1 {
public static void main(String[] args) {
UserService userServiceImpl = new UserServiceImpl();
UserService proxy = new UserServiceProxy(userServiceImpl);
proxy.select();
proxy.update();
}
}
输出
log start time [Thu Dec 20 14:13:25 CST 2018]
查询 selectById
log end time [Thu Dec 20 14:13:25 CST 2018]
log start time [Thu Dec 20 14:13:25 CST 2018]
更新 update
log end time [Thu Dec 20 14:13:25 CST 2018]
通过静态代理,我们达到了功能增强的目的,而且没有侵入原代码,这是静态代理的一个优点。
静态代理的缺点:
虽然静态代理实现简单,且不侵入原代码,但是,当场景稍微复杂一些的时候,静态代理的缺点也会暴露出来。
- 1、 当需要代理多个类的时候,由于代理对象要实现与目标对象一致的接口,有两种方式:
(1)只维护一个代理类,由这个代理类实现多个接口,但是这样就导致代理类过于庞大
(2)新建多个代理类,每个目标对象对应一个代理类,但是这样会产生过多的代理类 - 2、 当接口需要增加、删除、修改方法的时候,目标对象与代理类都要同时修改,不易维护。
实现动态代理的思考方向
为了让生成的代理类与目标对象(真实主题角色)保持一致性,从现在开始将介绍以下两种最常见的方式:
- 通过实现接口的方式 -> JDK动态代理
- 通过继承类的方式 -> CGLIB动态代理
基于JDK的动态代理
动态代理在Java中有着广泛的应用,比如Spring AOP、Hibernate数据查询、测试框架的后端mock、RPC远程调用、Java注解对象获取、日志、用户鉴权、全局性异常处理、性能监控,甚至事务处理等。
JDK动态代理主要涉及两个类:java.lang.reflect.Proxy
和 java.lang.reflect.InvocationHandler
例子1
我们仍然先定义了接口Hello,但是我们并不去编写实现类,而是直接通过JDK
提供的一个Proxy.newProxyInstance()
创建了一个Hello接口对象。这种没有实现类但是在运行期动态创建了一个接口对象的方式,我们称为动态代码。JDK提供的动态创建接口对象的方式,就叫动态代理。
定义接口:
interface Hello {
void morning(String name);
}
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class Main {
public static void main(String[] args) {
InvocationHandler handler = new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println(method);
if (method.getName().equals("morning")) {
System.out.println("Good morning, " + args[0]);
}
return null;
}
};
Hello hello = (Hello) Proxy.newProxyInstance(
Hello.class.getClassLoader(), // 传入ClassLoader
new Class[] { Hello.class }, // 传入要实现的接口
handler); // 传入处理调用方法的InvocationHandler
hello.morning("Bob");
}
}
在运行期动态创建一个interface实例的方法如下:
- 1、定义一个
InvocationHandler
实例,它负责实现接口的方法调用; - 2、通过
Proxy.newProxyInstance()
创建interface
实例,它需要3个参数:
(1)使用的ClassLoader
,通常就是接口类的ClassLoader;
(2)需要实现的接口数组,至少需要传入一个接口进去;
(3)用来处理接口方法调用的InvocationHandler
实例。 - 3、将返回的
Object
强制转型为接口。
动态代理实际上是JVM在运行期动态创建class字节码并加载的过程,把上面的动态代理改写为静态实现类大概长这样:
public class HelloDynamicProxy implements Hello {
InvocationHandler handler;
public HelloDynamicProxy(InvocationHandler handler) {
this.handler = handler;
}
public void morning(String name) {
handler.invoke(
this,
Hello.class.getMethod("morning", String.class),
new Object[] { name });
}
}
其实就是JVM帮我们自动编写了一个上述类(不需要源码,可以直接生成字节码),并不存在可以直接实例化接口的黑魔法。
Java标准库提供了动态代理功能,允许在运行期动态创建一个接口的实例;
动态代理是通过Proxy
创建代理对象,然后将**接口方法“代理”**给InvocationHandler
完成的。
例子2
我们仍然通过案例来学习编写一个调用逻辑处理器 LogHandler 类,提供日志增强功能,并实现 InvocationHandler 接口;在 LogHandler 中维护一个目标对象,这个对象是被代理的对象(真实主题角色);在 invoke 方法中编写方法调用的逻辑处理。
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.Date;
public class LogHandler implements InvocationHandler {
Object target; // 被代理的对象,实际的方法执行者
public LogHandler(Object target) {
this.target = target;
}
@Override
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()));
}
}
编写客户端,获取动态生成的代理类的对象须借助 Proxy 类的 newProxyInstance 方法,具体步骤可见代码和注释
import proxy.UserService;
import proxy.UserServiceImpl;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
public class Client2 {
public static void main(String[] args) throws IllegalAccessException, InstantiationException {
// 设置变量可以保存动态代理类,默认名称以 $Proxy0 格式命名
// System.getProperties().setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
// 1. 创建被代理的对象,UserService接口的实现类
UserServiceImpl userServiceImpl = new UserServiceImpl();
// 2. 获取对应的 ClassLoader
ClassLoader classLoader = userServiceImpl.getClass().getClassLoader();
// 3. 获取所有接口的Class,这里的UserServiceImpl只实现了一个接口UserService,
Class[] interfaces = userServiceImpl.getClass().getInterfaces();
// 4. 创建一个将传给代理类的调用请求处理器,处理所有的代理对象上的方法调用
// 这里创建的是一个自定义的日志处理器,须传入实际的执行对象 userServiceImpl
InvocationHandler logHandler = new LogHandler(userServiceImpl);
/*
5.根据上面提供的信息,创建代理对象 在这个过程中,
a.JDK会通过根据传入的参数信息动态地在内存中创建和.class 文件等同的字节码
b.然后根据相应的字节码转换成对应的class,
c.然后调用newInstance()创建代理实例
*/
UserService proxy = (UserService) Proxy.newProxyInstance(classLoader, interfaces, logHandler);
// 调用代理的方法
proxy.select();
proxy.update();
// 保存JDK动态代理生成的代理类,类名保存为 UserServiceProxy
// ProxyUtils.generateClassFile(userServiceImpl.getClass(), "UserServiceProxy");
}
}
运行结果
log start time [Thu Dec 20 16:55:19 CST 2018]
查询 selectById
log end time [Thu Dec 20 16:55:19 CST 2018]
log start time [Thu Dec 20 16:55:19 CST 2018]
更新 update
log end time [Thu Dec 20 16:55:19 CST 2018]
InvocationHandler 和 Proxy 的主要方法介绍如下:
java.lang.reflect.InvocationHandler 接口
Object invoke(Object proxy, Method method, Object[] args)
定义了代理对象调用方法时希望执行的动作,用于集中处理在动态代理类对象上的方法调用
也就是说,我们创建的每一个代理实例都要有一个关联的InvocationHandler,并且在调用代理实例的方法时,会被转到InvocationHandler的invoke方法上。
该invoke方法的作用是:处理代理实例上的方法调用并返回结果。其有三个参数,分别为:
- proxy:是调用该方法的代理实例。
- method:是在代理实例上调用的接口方法对应的Method实例。
- args:一个Object数组,是在代理实例上的方法调用中传递的参数值。如果接口方法为无参,则该值为null。
其返回值为:调用代理实例上的方法的返回值。
java.lang.reflect.Proxy 类
Proxy类提供了创建动态代理类及其实例的静态方法,该类也是动态代理类的超类。
代理类的名称以 “$Proxy” 开头,后面跟着一个数字序号。
1、static InvocationHandler getInvocationHandler(Object proxy)
用于获取指定代理对象所关联的调用处理器
2、static Class<?> getProxyClass(ClassLoader loader, Class<?>... interfaces)
返回指定接口的代理类
3、static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
构造实现指定接口的代理类的一个新实例,所有方法会调用给定处理器对象的 invoke 方法
该方法有三个参数:
- loader:为类加载器。
- interfaces:为接口的Class对象数组。
- h:指定的调用处理器。
返回值为指定接口的代理类的实例。
4、static boolean isProxyClass(Class<?> cl)
返回 cl 是否为一个代理类
代理类的调用过程
UserServiceProxy 继承了 Proxy 类,并且实现了被代理的所有接口,以及equals、hashCode、toString等方法
由于 UserServiceProxy 继承了 Proxy 类,所以每个代理类都会关联一个 InvocationHandler 方法调用处理器
类和所有方法都被 public final 修饰,所以代理类只可被使用,不可以再被继承
每个方法都有一个 Method 对象来描述,Method 对象在static静态代码块中创建,以 m + 数字 的格式命名
调用方法的时候通过 super.h.invoke(this, m1, (Object[])null); 调用,其中的 super.h.invoke 实际上是在创建代理的时候传递给 Proxy.newProxyInstance 的 LogHandler 对象,它继承 InvocationHandler 类,负责实际的调用处理逻辑
而 LogHandler 的 invoke 方法接收到 method、args 等参数后,进行一些处理,然后通过反射让被代理的对象 target 执行方法
例子3
/**
* 学生接口,能跑,能吃,能写作文。
*/
interface Student {
void eat();
void run();
void write();
}
/**
* 小韭菜,能跑,能吃,能写作文。
*/
class OrdinaryStudents implements Student {
@Override
public void eat() {
System.out.println("我在吃饭!");
}
@Override
public void run() {
System.out.println("我在跑步!");
}
@Override
public void write() {
System.out.println("我在写作文!");
}
}
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
public class DynamicProxy {
public static void main(String[] args) {
// 小韭菜学生类
Student ordinaryStudents = new OrdinaryStudents();
ordinaryStudents.eat();
ordinaryStudents.write();
// 现在有一位特殊的学生,他是区长的儿子,我们自然要对他额外照顾,要给他加一下功能。
// 一种思路是定义一个类:区长的儿子类,他继承自学生类,但世上儿子千千万,有区长的儿子,也有市长的儿子,更有省长的儿子,不能把他们挨个定义出来,
// 现在就可以使用动态代理机制,动态的给区长的儿子加上功能,以后碰到市长、省长的儿子也同样处理。
// InvocationHandler作用就是,当代理对象的原本方法被调用的时候,会重定向到一个方法,
// 这个方法就是InvocationHandler里面定义的内容,同时会替代原本方法的结果返回。
// InvocationHandler接收三个参数:proxy,代理后的实例对象。 method,对象被调用方法。args,调用时的参数。
InvocationHandler handler = (proxy, method, handlerArgs) -> {
// 从定义eat方法。
if ("eat".equals(method.getName())) {
System.out.println("我可以吃香喝辣!");
return null;
}
// 从定义write方法。
if ("write".equals(method.getName())) {
System.out.println("我的作文题目是《我的区长父亲》。");
// 调用普通学生类的write方法,流程还是要走的,还是要交一篇作文上去,不能太明目张胆。
method.invoke(ordinaryStudents, handlerArgs);
System.out.println("我的作文拿了区作文竞赛一等奖!so easy!");
return null;
}
return null;
};
// 对这个实例对象代理生成一个代理对象。
// 被代理后生成的对象,是通过People接口的字节码增强方式创建的类而构造出来的。它是一个临时构造的实现类的对象。
// loder和interfaces基本就是决定了这个类到底是个怎么样的类。而h是InvocationHandler,决定了这个代理类到底是多了什么功能.
// 通过这些接口和类加载器,拿到这个代理类class。然后通过反射的技术复制拿到代理类的构造函数,
// 最后通过这个构造函数new个一对象出来,同时用InvocationHandler绑定这个对象。
// 最终实现可以在运行的时候才切入改变类的方法,而不需要预先定义它。
Student sonOfDistrict = (Student) Proxy.newProxyInstance(ordinaryStudents.getClass().getClassLoader(), ordinaryStudents.getClass().getInterfaces(), handler);
sonOfDistrict.eat();
sonOfDistrict.write();
}
}
CGLIB动态代理
maven引入CGLIB包,然后编写一个UserDao类,它没有接口,只有两个方法,select() 和 update()
public class UserDao {
public void select() {
System.out.println("UserDao 查询 selectById");
}
public void update() {
System.out.println("UserDao 更新 update");
}
}
编写一个 LogInterceptor ,继承了 MethodInterceptor,用于方法的拦截回调
import java.lang.reflect.Method;
import java.util.Date;
public class LogInterceptor implements MethodInterceptor {
/**
* @param object 表示要进行增强的对象
* @param method 表示拦截的方法
* @param objects 数组表示参数列表,基本数据类型需要传入其包装类型,如int-->Integer、long-Long、double-->Double
* @param methodProxy 表示对方法的代理,invokeSuper方法表示对被代理对象方法的调用
* @return 执行结果
* @throws Throwable
*/
@Override
public Object intercept(Object object, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
before();
Object result = methodProxy.invokeSuper(object, objects); // 注意这里是调用 invokeSuper 而不是 invoke,否则死循环,methodProxy.invokesuper执行的是原始类的方法,method.invoke执行的是子类的方法
after();
return result;
}
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()));
}
}
测试
import net.sf.cglib.proxy.Enhancer;
public class CglibTest {
public static void main(String[] args) {
LogInterceptor daoProxy = new LogInterceptor();
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(Dao.class); // 设置超类,cglib是通过继承来实现的
enhancer.setCallback(daoProxy);
Dao dao = (Dao)enhancer.create(); // 创建代理类
dao.update();
dao.select();
}
}
运行结果
log start time [Fri Dec 21 00:06:40 CST 2018]
UserDao 查询 selectById
log end time [Fri Dec 21 00:06:40 CST 2018]
log start time [Fri Dec 21 00:06:40 CST 2018]
UserDao 更新 update
log end time [Fri Dec 21 00:06:40 CST 2018]
还可以进一步多个 MethodInterceptor 进行过滤筛选
public class LogInterceptor2 implements MethodInterceptor {
@Override
public Object intercept(Object object, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
before();
Object result = methodProxy.invokeSuper(object, objects);
after();
return result;
}
private void before() {
System.out.println(String.format("log2 start time [%s] ", new Date()));
}
private void after() {
System.out.println(String.format("log2 end time [%s] ", new Date()));
}
}
// 回调过滤器: 在CGLib回调时可以设置对不同方法执行不同的回调逻辑,或者根本不执行回调。
public class DaoFilter implements CallbackFilter {
@Override
public int accept(Method method) {
if ("select".equals(method.getName())) {
return 0; // Callback 列表第1个拦截器
}
return 1; // Callback 列表第2个拦截器,return 2 则为第3个,以此类推
}
}
再次测试
public class CglibTest2 {
public static void main(String[] args) {
LogInterceptor logInterceptor = new LogInterceptor();
LogInterceptor2 logInterceptor2 = new LogInterceptor2();
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(UserDao.class); // 设置超类,cglib是通过继承来实现的
enhancer.setCallbacks(new Callback[]{logInterceptor, logInterceptor2, NoOp.INSTANCE}); // 设置多个拦截器,NoOp.INSTANCE是一个空拦截器,不做任何处理
enhancer.setCallbackFilter(new DaoFilter());
UserDao proxy = (UserDao) enhancer.create(); // 创建代理类
proxy.select();
proxy.update();
}
}
运行结果
log start time [Fri Dec 21 00:22:39 CST 2018]
UserDao 查询 selectById
log end time [Fri Dec 21 00:22:39 CST 2018]
log2 start time [Fri Dec 21 00:22:39 CST 2018]
UserDao 更新 update
log2 end time [Fri Dec 21 00:22:39 CST 2018]
CGLIB 创建动态代理类的模式是:
- 1、查找目标类上的所有非final 的public类型的方法定义;
- 2、将这些方法的定义转换成字节码;
- 3、将组成的字节码转换成相应的代理的class对象;
- 4、实现 MethodInterceptor接口,用来处理对代理类上所有方法的请求
JDK动态代理与CGLIB动态代理对比
JDK动态代理:基于Java反射机制实现,必须要实现了接口的业务类才能用这种办法生成代理对象。
cglib动态代理:基于ASM机制实现,通过生成业务类的子类作为代理类。
JDK Proxy 的优势:
- 最小化依赖关系,减少依赖意味着简化开发和维护,JDK 本身的支持,可能比 cglib 更加可靠。
- 平滑进行 JDK版本升级,而字节码类库通常需要进行更新以保证在新版 Java 上能够使用。
- 代码实现简单。
基于类似 cglib 框架的优势:
- 无需实现接口,达到代理类无侵入 。
- 只操作我们关心的类,而不必为其他相关类增加工作量。
- 高性能。