JAVA的动态代理
代理模式
代理模式是常用的java设计模式,他的特征是代理类与委托类有同样的接口,代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类,以及事后处理消息等。代理类与委托类之间通常会存在关联关系,一个代理类的对象与一个委托类的对象关联,代理类的对象本身并不真正实现服务,而是通过调用委托类的对象的相关方法,来提供特定的服务。
按照代理的创建时期,代理类可以分为两种。
静态代理:由程序员创建或特定工具自动生成源代码,再对其编译。在程序运行前,代理类的.class文件就已经存在了。
动态代理:在程序运行时,运用反射机制动态创建而成。
静态代理
首先看以下静态代理:
package com.classdemo.test03;
import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.Properties;
/**
* 定义一个接口
*
*
*/
interface MyFactory {
public void product();
}
// 被代理类
class NikeFactory implements MyFactory {
@Override
public void product() {
System.out.println("Nile 生产");
}
}
class AnFactory implements MyFactory {
@Override
public void product() {
System.out.println("安踏生产");
}
}
/**
* 这是一个代理类(增强MyFactory实现类)
*
*
*/
class ProxyFactory implements MyFactory {
public MyFactory mf;
public ProxyFactory(MyFactory mf) {
this.mf = mf;
}
@Override
public void product() {
System.out.println("收代理费");
mf.product();
}
}
public class TestClient {
public static void main(String[] args) throws Exception {
MyFactory nf = new NikeFactory();
ProxyFactory pf = new ProxyFactory(nf);
pf.product();
MyFactory nf02 = new AnFactory();
pf = new ProxyFactory(nf02);
pf.product();
/*
* 输出:
* 收代理费
* Nile 生产
* 收代理费
* 安踏生产
*
*/
}
}
观察代码可以发现每一个代理类只能为一个接口服务,这样一来程序开发中必然会产生过多的代理,而且,所有的代理操作除了调用的方法不一样之外,其他的操作都一样,则此时肯定是重复代码。解决这一问题最好的做法是可以通过一个代理类完成全部的代理功能,那么此时就必须使用动态代理完成。
动态代理
再来看一下动态代理:
JDK动态代理
JDK动态代理中包含一个类和一个接口:
InvocationHandler接口:
public interface InvocationHandler {
public Object invoke(Object proxy,Method method,Object[] args) throws Throwable;
}
参数说明:
Object proxy:指被代理的对象。
Method method:要调用的方法
Object[] args:方法调用时所需要的参数
可以将InvocationHandler接口的子类想象成一个代理的最终操作类,替换掉ProxySubject。
Proxy类:
Proxy类是专门完成代理的操作类,可以通过此类为一个或多个接口动态地生成实现类,此类提供了如下的操作方法:
public static Object newProxyInstance(ClassLoader loader, Class
package com.lgh.spring.test;
import java.io.Serializable;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import com.lgh.spring.dao.UserDao;
import com.lgh.spring.dao.impl.UserDaoMysqlImpl;
interface Factory {
void build();
}
final class Nike implements Factory {
@Override
public void build() {
System.out.println("生产鞋子");
}
}
class MyProxy {
public Object getObject(Object obj) {
/*
* 第一个参数 目标对象的类加载器
* 第二个参数 目标对象实现的接口
* 第三个参数
* 定义一个拦截器。在调用目标方法时,
* JDK中的InvocationHandler接口方法拦截,
* 来实现你自己的代理逻辑
*/
return Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(),
new InvocationHandler() {
/*
* proxy 代理类
* method 方法
* args 方法中的参数
*
* */
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//System.out.println(proxy.getClass());
System.out.println("开始工作");
Object returnVlaue = method.invoke(obj, args);
return returnVlaue;
}
});
}
}
public class Test {
public Test() {
}
public static void main(String[] args) {
MyProxy mp = new MyProxy();
Factory f = (Factory) mp.getObject(new Nike());
f.build();
}
}
cglib动态代理
但是,JDK的动态代理依靠接口实现,如果有些类并没有实现接口,则不能使用JDK代理,这就要使用cglib动态代理了。
JDK的动态代理机制只能代理实现了接口的类,而不能实现接口的类就不能实现JDK的动态代理,cglib是针对类来实现代理的,他的原理是对指定的目标类生成一个子类,并覆盖其中方法实现增强,但因为采用的是继承,所以不能对final修饰的类进行代理。
CGLIB是一个强大的高性能的代码生成包。它被许多AOP的框架(例如Spring AOP)使用,为他们提供方法的interception(拦截)。Hibernate也使用CGLIB来代理单端single-ended(多对一和一对一)关联。EasyMock通过使用模仿(moke)对象来测试java代码的包。它们都通过使用CGLIB来为那些没有接口的类创建模仿(moke)对象。
CGLIB包的底层是通过使用一个小而快的字节码处理框架ASM,来转换字节码并生成新的类。不鼓励直接使用ASM,因为它要求你必须对JVM内部结构包括class文件的格式和指令集都很熟悉。
以下为模拟案例,在使用CGLIB时需引入cglib-2.2.2.jar包
我这里使用maven进行jar包管理
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.2.4</version>
</dependency>
定义一个ProductDao类,注意此处是个类,而不是接口
作为被代理的类
package com.cglib.demo02;
public class A {
public void save(){
System.out.println("保存商品");
}
public void del(){
System.out.println("删除商品");
}
}
CglibFactory.java
该类实现了创建子类的方法与代理的方法。getProxy(SuperClass.class)方法通过入参即父类的字节码,通过扩展父类的class来创建代理对象。intercept()方法拦截所有目标类方法的调用,obj表示目标类的实例,method为目标类方法的反射对象,args为方法的动态入参,proxy为代理类实例。proxy.invokeSuper(obj, args)通过代理类调用父类中的方法。
package com.cglib.demo02;
import java.lang.reflect.Method;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
public class CglibFactory<T> {
private T target;
public void setTarget(T target) {
this.target = target;
}
@SuppressWarnings("unchecked")
public T getProxy() {
// 增强器
Enhancer enhancer = new Enhancer();
// 设置需要创建子类的父类
enhancer.setSuperclass(target.getClass());
/*
* 定义一个拦截器。在调用目标方法时,CGLib会回调 MethodInterceptor接口方法拦截,来实现你自己的代理逻辑,
* 类似于JDK中的InvocationHandler接口。
*/
enhancer.setCallback(new MethodInterceptor() {
/*
* Object为由CGLib动态生成的代理类实例,
* Method为上文中实体类所调用的被代理的方法引用,
* Object[]为参数值列表,
* MethodProxy为生成的代理类对方法的代理引用。
* 返回:从代理实例的方法调用返回的值。
*
* 其中,proxy.invokeSuper(obj,arg):
*
* 调用代理类实例上的proxy方法的父类方法(即实体类ConcreteClassNoInterface中对应的方法)
*/
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("开启事务 ------------");
Object result = method.invoke(target, args);
//下面的代码效果与上面的相同
//Object result = proxy.invokeSuper(obj,args);
System.out.println("提交事务 ------------");
return result;
}
});
// 通过字节码技术动态创建子类实例
return (T) enhancer.create();
}
}
具体测试类:
CglibTest.java
package com.cglib.demo02;
public class CglibTest {
public static void main(String[] args) {
CglibFactory<A> cf = new CglibFactory<A>();
A a = new A();
cf.setTarget(a);
A proxy = cf.getProxy();
proxy.del();
}
}
运行结果为:
开启事务 ------------
删除商品
提交事务 ------------
CGLib创建的动态代理对象性能比JDK创建的动态代理对象的性能高不少,但是CGLib在创建代理对象时所花费的时间却比JDK多得多,所以对于单例的对象,因为无需频繁创建对象,用CGLib合适,反之,使用JDK方式要更为合适一些。同时,由于CGLib由于是采用动态创建子类的方法,对于final方法,无法进行代理。