Java代理模式

代理是一种设计模式,提供了对目标对象的另外一种访问方式。代理模式,是对修改封闭,对扩展开放的体现。

在JAVA中有三种代理的设计模式:

  • 静态代理
  • JDK动态代理
  • 字节码生成代理

  • Subject:抽象主题角色。可以是接口,也可以是抽象类。
  • RealSubject:真实主题角色。业务逻辑的具体执行者。
  • Proxy:代理主题角色。内部含有RealSubject的引用,负责对真实角色的调用,并在真实主题角色处理前后做预处理和善后工作。

静态代理

静态代理在使用时,需要定义接口或者父类,被代理对象与代理对象一起实现相同的接口或者是继承相同父类.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// IUserDao.java
public interface IUserDao {
void save();
}
//UserDao.java
public class UserDao implements IUserDao {
public void save() {
System.out.println("----已经保存数据!----");
}
}
//UserDaoProxy.java
public class UserDaoProxy implements IUserDao {
private IUserDao target;
public UserDaoProxy(IUserDao userDao) {
this.target = userDao;
}
public void save() {
System.out.println("开始事务...");
target.save();//执行目标对象的方法
System.out.println("提交事务...");
}
}
//App.java
public class App {
public static void main(String[] args) {
IUserDao userDao = new UserDao();
UserDaoProxy proxy = new UserDaoProxy(userDao);
proxy.save();
}
}

优点:

可以做到在不修改目标对象的功能前提下,对目标功能扩展.

缺点:

因为代理对象需要与目标对象实现一样的接口,所以会有很多代理类,类太多.同时,一旦接口增加方法,目标对象与代理对象都要维护.

JDK动态代理

动态代理的代理对象不需要实现接口。代理对象的生成,是利用JDK的API,动态的在内存中构建代理对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
//UserInvocationHandler.java
public class UserInvocationHandler implements InvocationHandler {
private IUserDao target;
public UserInvocationHandler(IUserDao target) {
this.target = target;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("开始事务2");
//执行目标对象方法
Object returnValue = method.invoke(target, args);
System.out.println("提交事务2");
return returnValue;
}
}
//App.java
public class App {
public static void main(String[] args) {
IUserDao userDao = new UserDao();
UserInvocationHandler handler = new UserInvocationHandler(userDao);
IUserDao proxy = (IUserDao) Proxy.newProxyInstance(userDao.getClass().getClassLoader(), userDao.getClass().getInterfaces(), handler);
proxy.save();
}
}

当我们通过这行代码实现动态代理的时候,都发生了什么

1
IUserDao proxy = (IUserDao) Proxy.newProxyInstance(userDao.getClass().getClassLoader(), userDao.getClass().getInterfaces(), handler);

Proxy类的newProxyInstance方法,主要业务逻辑如下:

1
2
3
4
5
6
//生成代理类class,并加载到jvm中
Class<?> cl = getProxyClass0(loader, interfaces);
//获取代理类参数为InvocationHandler的构造函数
final Constructor<?> cons = cl.getConstructor(constructorParams);
//生成代理类,并返回
return newInstance(cons, ih);
  • 根据传入的参数interfaces动态生成一个类,它实现interfaces中的接口,该例中即 IUserDao 接口的implDemands方法。假设动态生成的类为$Proxy0。
  • 通过传入的classloder,将刚生成的$Proxy0类加载到jvm中。
  • 利用中介类,调用$Proxy0的$Proxy0(InvocationHandler)构造函数,创建$Proxy0类的实例,其InvocationHandler属性,为我们创建的中介类。

代理对象不需要实现接口,但是目标对象一定要实现接口,否则不能用动态代理。

CGLIB代理

静态代理和动态代理模式都是要求目标对象是实现一个接口的目标对象,但是有时候目标对象只是一个单独的对象,并没有实现任何的接口,这个时候就可以使用以目标对象子类的方式类实现代理,这种方法就叫做:Cglib代理。

CGLIB库是基于ASM的上层应用。对于代理没有实现接口的类,CGLIB非常实用。本质上来说,对于需要被代理的类,它只是动态生成一个子类以覆盖非final的方法,同时绑定钩子回调自定义的拦截器。值得说的是,它比JDK动态代理还要快。

在实现内部,CGLIB库使用了ASM这一个轻量但高性能的字节码操作框架来转化字节码,产生新类。ASM使用了一个类似于SAX分析器的机制来达到高性能。

  • net.sf.cglib.core:底层字节码操作类;大部分与ASM相关。
  • net.sf.cglib.transform:编译期、运行期的class文件转换类。
  • net.sf.cglib.proxy:代理创建类、方法拦截类。
  • net.sf.cglib.reflect:更快的反射类、C#风格的代理类。
  • net.sf.cglib.util:集合排序工具类
  • net.sf.cglib.beans:JavaBean相关的工具类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
//SampleClass.java
public class SampleClass {
public void save() {
System.out.println("----已经保存数据!----");
}
}
//App.java
public class App {
public static void main(String[] args) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(SampleClass.class);
enhancer.setCallback(new MethodInterceptor() {
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("开始事务...");
Object result = proxy.invokeSuper(obj, args); //执行目标对象的方法
System.out.println("提交事务...");
return result;
}
});
SampleClass proxy = (SampleClass) enhancer.create();
proxy.save();
}
}

CGLIB是一个强大的高性能的代码生成库。作为JDK动态代理的互补,它对于那些没有实现接口的类提供了代理方案。在底层,它使用ASM字节码操纵框架。本质上来说,CGLIB通过产生子类覆盖非final方法来进行代理。它比使用Java反射的JDK动态代理方法更快。CGLIB不能代理一个final类或者final方法。通常来说,你可以使用JDK动态代理方法来创建代理,对于没有接口的情况或者性能因素,CGLIB是一个很好的选择。