Java的代理模式

代理模式

在设计模式中有一种叫代理模式的设计模式,将原对象封装到一个代理对象中,通过代理对象访问原对象,这样可以在不修改原目标对象的前提下,提供额外的功能操作,扩展目标对象的功能(比如方法的前置或后置操作)。

静态代理

这种代理方式需要代理对象和目标对象实现一样的接口。在编译前完成对代理类的编写,将原对象封装至代理对象中,对原方法进行增强

优点:

  • 可以在不修改目标对象的前提下扩展目标对象的功能。

缺点:

  • 冗余。由于代理对象要实现与目标对象一致的接口,会产生过多的代理类。
  • 不易维护。一旦接口增加方法,目标对象与代理对象都要进行修改。
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
38
39
40
41
42
43
44
//IUserDao.java
public interface IUserDao {
public void insert();
}

//UserDao.java
public class UserDao implements IUserDao{

@Override
public void insert() {
System.out.println("插入新的用户");
}
}

//UserDaoProxy.java
public class UserDaoProxy implements IUserDao{

private IUserDao target;
public UserDaoProxy(IUserDao target) {
this.target = target;
}

//方法增强
@Override
public void insert() {
System.out.println("开启新的事务");
target.insert();
System.out.println("提交事务");
}
}

public class Main {
public static void main(String[] args){
IUserDao target = new UserDao();
UserDaoProxy proxy = new UserDaoProxy(target);
proxy.insert();
}
}

/**
* 开启新的事务
* 插入新的用户
* 提交事务
**/

动态代理

动态代理分为JDK动态代理以及CGLIB动态代理,动态代理是在内存中创建代理对象来实现对原对象的代理。

动态代理与静态代理的区别

  • 动态代理是在运行时动态生成的,所以在项目中是没有.java和.class文件,它们是在运行中生成字节码,由JVM执行的;静态代理是项目编译前已经编写好的Java类,所以编译后在项目中是由.java和.class文件的
  • 动态代理的代理对象不需要实现接口(注意是代理对象),而静态代理需要实现。

JDK动态代理

JDK动态代理的目标对象是需要实现原对象的接口的(这里说的是原对象,即代理对象通过getProxyInstance方法返回的对象,而前面说的是代理对象不需要实现接口,这里说的不矛盾),如果目标对象不识闲接口,则无法进行JDK动态代理。

主要方法:

1
2
3
4
5
6
7
8
9
//java.lang.reflect.Proxy#newProxyInstance
static Object newProxyInstance(ClassLoader loader, //指定当前目标对象使用类加载器
Class<?>[] interfaces, //目标对象实现的接口的类型
InvocationHandler h //事件处理器
)

//java.lang.reflect.InvocationHandler#invoke
Object invoke(Object proxy, Method method, Object[] args)
// 在代理实例上处理方法调用并返回结果。

举例

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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
//IUserDao.java
public interface IUserDao {
public void save();
}

//UserDao.java
public class UserDao implements IUserDao{
@Override
public void save() {
System.out.println("保存数据");
}
}

//UserProxyFactory.java
public class UserProxyFactory{

private Object target;// 维护一个目标对象

public ProxyFactory(Object target) {
this.target = target;
}

// 为目标对象生成代理对象
public Object getProxyInstance() {
return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(),
new InvocationHandler() {

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("开启事务");

// 执行目标对象方法
Object returnValue = method.invoke(target, args);

System.out.println("提交事务");
return null;
}
});
}
}

//Main.java
public class Main{
public static void main(String[] args){
IUserDao target = new UserDao();
IUserDao proxy = (IUserDao) new ProxyFactory(target).getProxyInstance();
proxy.save();
}
}

/**
*开启事务
*保存数据
*提交事务
*/

CGLIB代理

cglib(Code Generation Library) CGLIB是一款优秀的Java字节码生成框架,它可以生成并操纵Java字节码。因此,CGLIB的动态代理其实就是使用字节码技术为目标类创建子类,并且在子类中拦截父类方法的调用,并且顺势织入横切逻辑。

CGLib代理与JDK代理的区别:使用JDK代理需要目标类实现接口,而CGLib无需实现接口

使用cglib代理需要引入jar包(Spring中包含了CGLib),也可以通过maven引入依赖

1
2
3
4
5
6
7
8
<dependencies>
<!-- https://mvnrepository.com/artifact/cglib/cglib -->
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>2.2.2</version>
</dependency>
</dependencies>

举例

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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
//UserDao.java 注意此时无需定义接口
public class UserDao{
public void save() {
System.out.println("保存数据");
}
}

//ProxyFactory.java
public class ProxyFactory implements MethodInterceptor{

private Object target;//维护一个目标对象
public ProxyFactory(Object target) {
this.target = target;
}

//为目标对象生成代理对象
public Object getProxyInstance() {
//工具类
Enhancer en = new Enhancer();
//设置父类
en.setSuperclass(target.getClass());
//设置回调函数
en.setCallback(this);
//创建子类对象代理
return en.create();
}

@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("开启事务");
// 执行目标对象的方法
Object returnValue = method.invoke(target, args);
System.out.println("关闭事务");
return null;
}
}


public class Main{
public static void main(String[] args){
UserDao target = new UserDao();
UserDao proxy = (UserDao) new ProxyFactory(target).getProxyInstance();
proxy.save();
}
}

/**
*开启事务
*保存数据
*提交事务
*/

总结

  • 静态代理通过对原类的封装,对方法进行增强;静态代理的代理类需要实现原类的接口,会有侵入性,如果目标对象过多,则会产生很多代理类。
  • JDK动态代理与CGLib动态代理的区别是JDK需要实现接口而后者不用
  • JDK动态代理的代理类只需实现InvocationHandler接口
  • 动态代理生成的类为 lass com.sun.proxy.$Proxy4,cglib代理生成的类为class com.cglib.UserDao$$EnhancerByCGLIB$$552188b6。
  • 动态代理通过反射生成实例,反射的性能稍差
  • CGLib通过生成类字节码实现代理,比反射稍快,不存在性能问题,但cglib会继承目标对象,需要重写方法,所以目标对象不能为final类。
  • 动态代理是一种非常实用的技术,在Spring AOP以及MyBatis都使用了动态代理。

参考资料