代理

预备知识

  • 反射:可以在运行期间分析某个类,每个类在JVM方法区都有一个Class对象,用来描述这个类的信息,比如这个类有什么属性,什么方法,修饰符等,通过Class对象可以创建对应类的实例,比如通过newInstance();

代理

  • 为某个对象提供一个代理,以控制对这个对象的访问,好比明星的经纪人是明星的代理,和明星商量事之前可能需要经过他的经纪人。
  • 代理可以将一些非业务逻辑代码交由代理对象进行处理,比如记录日志、记录某个操作耗时。

静态代理

  • 通过事先写好的代理类的源代码,在编译阶段生成相应的字节码文件,即在程序运行之前就确定了代理类。
  • 创建一个类的代理对象可以通过创建该类的子类,然后重写要代理的方法。
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

// 客户端
public class Client() {
// 被代理类
static class LoginService {
public void login(){
do();
}
}
// 代理类
static class LoginServiceProxy extends LoginService {
@Override
public void login() {
log.info("开始登录");
super.login();
log.info("登录结束");
}
}
// 测试
public static void main(String[] args) {
LoginService loginService = new LoginServiceProxy();
// 实现上调用的是代理对象的login();
loginService.login();
}
}
  • 如果被代理类实现了接口,还可以通过创建一个实现了被代理类实现的接口的代理类对象。
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
// 客户端
public class Client() {

interface ILoginService {
void login();
}

// 被代理对象
static class LoginService implements ILoginService {
@Override
public void login() {
do();
}
}

// 代理对象类,提前写好
static class LoginServiceProxy implements ILoginService {
// 需要有被代理对象的引用
LoginService loginService;

Proxy(LoginService loginService) {
this.loginService = loginService;
}

@Override
public void login() {
log.info("开始登录");
loginService.do();
log.info("登录结束");
}
}

public static void main(String[] args) {
ILoginService loginService = new LoginServiceProxy();
// 实现上调用的是代理对象的login();
loginService.login();
}
}
  • 优点:将非业务代码抽取出来,减少耦合性。
  • 缺点:如果被代理的方法数量较多的话,工作量会较大。

动态代理

  • 在编译阶段不会生成字节码文件,在运行期间通过反射创建代理对象。
  • 在Java里有JDK动态代理或者CGLIB动态代理两种实现。

JDK动态代理

  • 运行期间,Proxy会生成目标类的代理类的字节码,通过类加载器加载进虚拟机,最终通过反射来创建代理类的实例。代理类默认继承了Proxy,并且实现了目标类实现的接口。
  • 要求
    • 被代理对象所属的类需要实现接口
    • 创建一个处理器InvocationHandler用于集中处理方法调用
  • 被代理对象和代理对象是兄弟关系
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
public interface ITarget {
void sayHello();
}

// 被代理对象,需要实现接口
public class Target implements ITarget {
@Override
public void sayHello() {
System.out.println("hello world");
}
}

// 处理器
public class Handler implements InvocationHandler {
// 被代理对象
Object target;

Handler(Object target) {
this.target = target;
}

@Override
public Object invoke(Object Proxy, Method method, Object[] args)
throws Throwable {
System.out.println("before");
Object result = method.invoke(target,args);
System.out.println("end");
return result;
}
}

public class Client {
public static void main(String[] args) {
// 创建处理器,并传入被代理对象
Handler handler = new Handler(new Target());
// Proxy通过反射创建代理对象
// 代理对象是实现了目标对象的接口的一个对象,所以强制转换成目标对象(Target),就会报错,应该转换成它们共同的接口(ITarget)
ITarget proxyInstance = (ITarget) Proxy.newProxyInstance(Target.class.getClassLoader(),
Target.class.getInterfaces(),handler);
// 实际调用的代理对象的sayHello();
proxyInstance.sayHello();
}
}

CLGLIB动态代理

  • 在运行期间通过ASM解析目标类的字节码,进行修改(为了生成被代理对象的子对象),形成新的字节数组,重新通过类加载器ClassLoader加载到虚拟机中,通过反射创建代理对象,此代理对象是被代理对象的子对象。
  • 要求
    • 被代理对象不能用final修饰,因为需要创建被代理对象的子对象作为代理对象
  • 被代理对象和代理对象之间是父子关系
1
2
3
4
5
6
7
8
9
10
// 创建一个增强器,用来在运行时生成类
Enhancer ch = new Enhancer();
// 设置要继承的目标类
eh.setSuperclass(Target.class);
// 设置拦截器,通过回调实现,类似JDK动态代理里的InvocationHandler处理器
eh.setCallback(new XXXInterceptor());
// 生成新的代理类
Target target = (Target)eh.create();
// 调用代理类的方法
target.function();

参考