代理模式(Proxy Pattern),就是使用一个代理类来代理另外一个类的功能。在Java相关的技术框架中使用非常广泛。搞清楚代理模式,是读懂Java相关技术框架源码的基础。
下面来看一下代理模式实现的几种方式:
- 静态代理
先创建一个代理接口:
public interface IUserService {
void say(String words);
}
再创建一个接口的实现类
public class UserService implements IUserService {
@Override
public void say(String words) {
System.out.println("hello ,"+words);
}
}
这就是Java开发中常用的一种方式。然后再创建这个接口的静态代理类
public class UserServiceStaticProxy implements IUserService {
/**
* 接收保存目标对象
*/
private IUserService target;
public UserServiceStaticProxy(IUserService target) {
this.target = target;
}
@Override
public void say(String words) {
System.out.println("添加代理之前逻辑");
try {
target.say(words);
} catch (Exception e) {
e.printStackTrace();
System.out.println("添加处理发生异常之后的逻辑");
}
System.out.println("添加代理之后的逻辑");
}
}
- 优点:可以做到在不修改目标对象的功能前提下,对目标功能扩展.
- 缺点:因为代理对象需要与目标对象实现一样的接口,所以会有很多代理类,类太多.同时,一旦接口增加方法,目标对象与代理对象都要维护
因此,静态代理模式是不推荐使用的。
- 动态代理
实际上动态代理是使用最为广泛的。而且也有几种不同的实现。如下:
- JDK动态代理
在Java源码的java.lang.reflect包中提供了一个Proxy的工具类。用于生成一个类的动态代理。
使用Java动态代理需要满足以下条件:
① 代理的目标对象必须要实现一个接口,这也是Java动态代理的局限性所在
② 代理的目标对象是public的非抽象的类,如果不是public,则代理必须定义在目标对象所在包中
Java动态代理的实现方式如下:
public class JavaDynamicProxy {
/**
* 维护一个目标对象
*/
private Object target;
public JavaDynamicProxy(Object target){
this.target=target;
}
/**
* 给目标对象生成代理对象
* @return
*/
public Object getProxyInstance(){
return Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
(proxy, method, args) -> {
System.out.println("开始事务2");
//执行目标对象方法
Object returnValue = method.invoke(target, args);
System.out.println("提交事务2");
return returnValue;
}
);
}
}
测试:
// 目标对象
IUserService target = new UserService();
// 原始的类型
System.out.println(target.getClass());
// 给目标对象,创建代理对象
IUserService proxy = (IUserService) new JavaDynamicProxy(target).getProxyInstance();
// class $Proxy0 内存中动态生成的代理对象
System.out.println(proxy.getClass());
// 执行方法 【代理对象】
proxy.say("dongdong");
输出结果:
class com.dong.designs.proxy.UserService
class com.sun.proxy.$Proxy3
开始事务2
hello ,dongdong
提交事务2
Process finished with exit code 0
下面再来看一下Java动态代理在Mybatis框架中的应用。 最典型的就是MapperProxy 。这是所有Mapper接口的代理实现。也是mybatis核心设计逻辑的体现。
构建InvocationHandler实例,它是Proxy生成代理对象的必须参数,包装了代理目标。
public class MapperProxy<T> implements InvocationHandler, Serializable {
private static final long serialVersionUID = -6424540398559729838L;
private final SqlSession sqlSession;
private final Class<T> mapperInterface;
private final Map<Method, MapperMethod> methodCache;
public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) {
this.sqlSession = sqlSession;
this.mapperInterface = mapperInterface;
this.methodCache = methodCache;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
} else if (isDefaultMethod(method)) {
return invokeDefaultMethod(proxy, method, args);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
final MapperMethod mapperMethod = cachedMapperMethod(method);
return mapperMethod.execute(sqlSession, args);
}
private MapperMethod cachedMapperMethod(Method method) {
MapperMethod mapperMethod = methodCache.get(method);
if (mapperMethod == null) {
mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
methodCache.put(method, mapperMethod);
}
return mapperMethod;
}
@UsesJava7
private Object invokeDefaultMethod(Object proxy, Method method, Object[] args)
throws Throwable {
final Constructor<MethodHandles.Lookup> constructor = MethodHandles.Lookup.class
.getDeclaredConstructor(Class.class, int.class);
if (!constructor.isAccessible()) {
constructor.setAccessible(true);
}
final Class<?> declaringClass = method.getDeclaringClass();
return constructor
.newInstance(declaringClass,
MethodHandles.Lookup.PRIVATE | MethodHandles.Lookup.PROTECTED
| MethodHandles.Lookup.PACKAGE | MethodHandles.Lookup.PUBLIC)
.unreflectSpecial(method, declaringClass).bindTo(proxy).invokeWithArguments(args);
}
/**
* Backport of java.lang.reflect.Method#isDefault()
*/
private boolean isDefaultMethod(Method method) {
return (method.getModifiers()
& (Modifier.ABSTRACT | Modifier.PUBLIC | Modifier.STATIC)) == Modifier.PUBLIC
&& method.getDeclaringClass().isInterface();
}
}
在MapperProxyFactory中生成代理对象实例:
public class MapperProxyFactory<T> {
//代理的接口
private final Class<T> mapperInterface;
private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<Method, MapperMethod>();
public MapperProxyFactory(Class<T> mapperInterface) {
this.mapperInterface = mapperInterface;
}
public Class<T> getMapperInterface() {
return mapperInterface;
}
public Map<Method, MapperMethod> getMethodCache() {
return methodCache;
}
@SuppressWarnings("unchecked")
protected T newInstance(MapperProxy<T> mapperProxy) {
//使用Proxy.newProxyInstance()API来生成代理
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
}
- Cglib动态代理
在spring aop中,可以指定生成代理的当时,Java动态代理和Cglib 。对于没有实现接口的对象。默认使用Cglib来生成代理。下面来看一下Cglib工具包是如何创建一个代理对象的。
public class CglibProxy implements MethodInterceptor {
//维护目标对象
private Object target;
public CglibProxy(Object target) {
this.target = target;
}
//给目标对象创建一个代理对象
public Object getProxyInstance(){
//1.工具类
Enhancer en = new Enhancer();
//2.设置父类
en.setSuperclass(target.getClass());
//3.设置回调函数
en.setCallback(this);
//4.创建子类(代理对象)
return en.create();
}
@Override
public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
System.out.println("开始事务...");
//执行目标对象的方法
Object returnValue = method.invoke(target, args);
System.out.println("提交事务...");
return returnValue;
}
}
Cglib是一个强大的高性能的代码生成包,它可以在运行期扩展java类与实现java接口.它广泛的被许多AOP的框架使用。Cglib代理,也叫作子类代理,它是在内存中构建一个子类对象从而实现对目标对象功能的扩展。
Cglib包的底层是通过使用一个小而块的字节码处理框架ASM来转换字节码并生成新的类.不鼓励直接使用ASM,因为它要求你必须对JVM内部结构包括class文件的格式和指令集都很熟悉。
- javassist实现动态代理
Javassist是一个开源的分析、编辑和创建Java字节码的类库。javassist是jboss的一个子项目,其主要的优点,在于简单,而且快速。直接使用java编码的形式,而不需要了解虚拟机指令,就能动态改变类的结构,或者动态生成类。
先来看一下基本使用:
ClassPool pool = ClassPool.getDefault();
//构建一个接口 类的全限定名
CtClass sClass = pool.makeInterface("com.dong.designs.proxy.Animal") ;
sClass.addMethod(CtNewMethod.make("void sayHello(String words);",sClass));
sClass.toClass();
//构建一个猫作为子类
String className = "com.dong.designs.proxy."+"cat$javassit";
CtClass ctClass = pool.makeClass(className);
//生成两个私有属性 name 和 age
CtField field1 = new CtField(CtClass.charType,"name",ctClass);
field1.setModifiers(Modifier.PRIVATE);
CtField field2 = new CtField(CtClass.intType,"age",ctClass);
field2.setModifiers(Modifier.PRIVATE);
ctClass.addField(field1);
ctClass.addField(field2);
//添加无参构造函数
ctClass.addConstructor(CtNewConstructor.defaultConstructor(ctClass));
//生成getter和setter方法
ctClass.addMethod(CtNewMethod.getter("getName",field1));
ctClass.addMethod(CtNewMethod.getter("getAge",field2));
ctClass.addMethod(CtNewMethod.setter("setName",field2));
ctClass.addMethod(CtNewMethod.getter("setAge",field2));
//创建一个 sayHello的方法
ctClass.addMethod(CtNewMethod.make("public void sayHello(String words){ System.out.println(words);}",ctClass));
//设置Cat类实现接口Animal
ctClass.addInterface(sClass);
//生成class
Class<?> aClass = ctClass.toClass();
//实例化一个Cat类
Object o = aClass.newInstance();
System.out.println(o.getClass());
//ctClass.defrost()方法后,可以继续动态修改这个类的类容。
ctClass.defrost();
//再给cat添加一个方法
ctClass.addMethod(CtNewMethod.make("public void testDynamicChangeClass(){}",ctClass));
//将cat的class文件写入到指定文件夹
ctClass.writeFile("D:\\mypro\\letcode\\src");
执行完之后,会在对应目录生成一个 class文件。反编译之后的类容如下:
package com.dong.designs.proxy;
public class cat$javassit implements Animal {
private char name;
private int age;
public cat$javassit() {
}
public char getName() {
return this.name;
}
public int getAge() {
return this.age;
}
public void setName(int var1) {
this.age = var1;
}
public int setAge() {
return this.age;
}
public void sayHello(String var1) {
System.out.println(var1);
}
public void testDynamicChangeClass() {
}
}
再来看一下它如何能实现动态代理功能
① 先手动新增一个类,如下:
public class CatService {
public void sayHello(String word){
System.out.println("the cat is saying : hello "+word);
}
}
② 使用Javassist动态在目标类方法执行之前添加自己的执行逻辑
ClassPool classPool = ClassPool.getDefault();
CtClass ctClass = classPool.get("com.dong.designs.proxy.CatService");
CtMethod m = ctClass.getDeclaredMethod("sayHello");
m.insertBefore("{ System.out.println(\"BB测试javassist动态修改class类容\"); }");
m.insertAfter("{ System.out.println(\"AA测试javassist动态修改class类容\"); }");
Class<?> c = ctClass.toClass();
CatService h = (CatService)c.newInstance();
h.sayHello("hello world");
输出结果:
BB测试javassist动态修改class类容
the cat is saying : hello hello world
AA测试javassist动态修改class类容
可以看到,成功的在目标方法执行前后加上了自己的逻辑。实现了动态代理的效果。
在Dubbo框架中默认使用 javassist来实现类的动态代理。动态的生产类的代理对象。dubbo的实现比较复杂,有兴趣可以去研究一下dubbo源码中JavassistProxyFactory的详细实现。