0%

代理模式

代理模式

为其他对象提供一种代理以控制对这个对象的访问,就像中介一样

核心概念

  • 抽象角色 声明真实对象和代理对象的共同接口
  • 代理角色 代理角色内部含有真实对象的引用,从而操作真是对象
  • 真实角色

静态代理

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

静态代理使用的是组合模式,在代理类中包含有被代理类的对象

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
public class TestStaticProxy {
public static void main(String[] args) {
NikeClothFactory nikeClothFactory = new NikeClothFactory();
ProxyFactory proxyFactory = new ProxyFactory(nikeClothFactory);
proxyFactory.productCloth();
}
}

// 接口
interface ClothFactory{
void productCloth();
}

// 被代理类
class NikeClothFactory implements ClothFactory{

@Override
public void productCloth() {
System.out.println("Nike工厂生产了一件Nike");
}
}

// 代理类
class ProxyFactory implements ClothFactory{

private ClothFactory clothFactory;

public ProxyFactory(ClothFactory clothFactory){
this.clothFactory = clothFactory;
}
@Override
public void productCloth() {
System.out.println("代理类开始执行,准备调用被代理类");
clothFactory.productCloth();
}
}

静态代理虽然可以在不修改目标对象功能的前提下对目标功能进行扩展,但是一旦接口增加方法,目标对象和代理类都要同时修改,而且代理对象和被代理对象要实现一样的接口,导致有很多的代理类,不便于维护

动态代理

动态代理有很多种方式,如jdk代理,cglib,ASM等

jdk动态代理

jdk动态代理的底层是用的是java的反射,但是jdk代理的前提是目标类必须实现接口

Proxy

Proxy类是所有代理类的父类,用来生成代理类和代理对象,包含有两个重要方法

getProxyClass方法用来生成代理类的Class

1
2
public static Class<?> getProxyClass(ClassLoader loader,
Class<?>... interfaces)

newProxyInstance方法是用来生成代理类的对象

1
2
3
4
5
6
// - loader  当前目标对象使用的类加载器
//interfaces 目标对象实现的接口类型
// h 事件处理,执行目标对象的方法时,会触发事件处理器的方法,会把当前执行目标对象的方法作为参数传入
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)

使用步骤

  • 首先实现一个InvocationHandler接口,重写invoke方法,方法调用会被转发到该类的invoke()方法
  • 通过为Proxy类指向classLoader对象和一组interface来创建代理类
  • 使用代理对象调用方法
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
public class TestDynamicProxy {
public static void main(String[] args) {
RealSubject realSubject = new RealSubject();
MyInvationHandler myInvationHandler = new MyInvationHandler();
Object obj = myInvationHandler.bind(realSubject);
Subject sub = (Subject) obj;
sub.action();
}
}

// 接口
interface Subject{
void action();
}

// 真正执行的方法,被代理类
class RealSubject implements Subject{

@Override
public void action() {
System.out.println("被代理类开始执行");
}
}

class MyInvationHandler implements InvocationHandler{

// 实现了接口的被代理类的对象的声明
Object obj;
// 被代理类的实例
// 返回一个代理类的对象
public Object bind(Object obj){
this.obj = obj;
// ①使用被代理类的类加载器②被代理类的接口③代理类的实例
return Proxy.newProxyInstance(obj.getClass().getClassLoader(),obj.getClass().getInterfaces(),this);
}

/**
* 当通过代理类的对象被重写的方法调用时,都会转换为对invoke方法的调用
* @param proxy 正在返回的代理对象,一般情况下,在invoke方法中不使用该对象
* @param method 正在被调用的方法
* @param args 调用方法时,传入的参数
* @return
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("方法执行前");
Object result = method.invoke(obj,args);
System.out.println("方法执行后");
return result;
}
}

可以在单测方法或者main方法中添加System.getProperties().put(“sun.misc.ProxyGenerator.saveGeneratedFiles”,”true”);来看到代理类的class文件,文件会被生成到com/sun/proxy目录下

cglib代理

静态代理和jdk动态代理都要求目标对象一定要实现接口,但是有时候目标对象只是一个单独的对象,并没有实现任何接口,这个时候采用以目标对象子类的方式实现代理,该方法称为cglib代理

Cglib包的底层是通过使用一个字节码处理框架ASM来转换字节码并生成新的类,由于要生成子类,所以要被代理的类不可以被final修饰

需要引入cglib的包

1
2
3
4
5
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>

使用步骤

  • 首先实现MethodIntercepor,方法调用会被转发到intercept()方法
  • 使用CGLIB来获取代理对象
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
public class CglibProxy implements MethodInterceptor { 

public Object getProxy(Class clazz){
Enhancer enhancer = new Enhancer();
// 指定代理类的父类,其实CGLIB是为该实际类生成一个子类
enhancer.setSuperclass(clazz);
// 设置Callback对象
enhancer.setCallback(this);
// 通过字节码技术动态创建子类实例
return enhancer.create();
}
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("前置处理");
// 调用MethodProxy.invokeSuper方法将调用转发给原始对象
Object result = methodProxy.invokeSuper(o,objects);
System.out.println("后置处理");
return result;
}
}


public class Test {

public void print(){
System.out.println("方法执行");
}

public static void main(String[] args) {
CglibProxy proxy = new CglibProxy();
Test proxyImp = (Test) proxy.getProxy(Test.class);
proxyImp.print();

}
}

jdk动态代理和cglib的区别

java动态代理利用反射生成实现代理接口的匿名类,调用方法时调用InvokeHandler处理;cglib动态代理利用asm,将代理对象类的class文件加载进来,通过修改其字节码文件生成子类来处理

优缺点

优点

  • 业务类只需要关注业务逻辑本身
  • 降低了系统的耦合度

缺点

  • 由于调用方和被调用方之间增加了代理,会造成请求的处理速度变慢

欢迎关注我的其它发布渠道