Java代理模式

一、代理模式

代理是一种结构设计模式,可让您为另一个对象提供替代品。 代理控制对原始对象的访问,允许您在请求到达原始对象之前或之后执行某些操作。

二、静态代理

(1)概念:

对目标对象的每个方法的增强都是手动完成。在编译时就已经将接口、被代理类、代理类等确定下来。在程序运行之前,代理类的.class文件就已经生成。

(2)静态代理简单实现
这里借用班长给学生代交班费的例子

1.创建接口并确定接口具体行为

1
2
3
4
public interface Person {
//上交班费
void giveMoney();
}

2.被代理对象实现接口,完成具体的业务逻辑

1
2
3
4
5
6
7
8
9
10
11
12
public class Student implements Person {
private String name;

public Student(String name) {
this.name = name;
}

@Override
public void giveMoney() {
System.out.println(name + "上交班费50元");
}
}

3.代理类实现接口对被代理类的执行进行控制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//学生代理类,也实现了Person接口,保存一个学生实体,这样既可以代理学生产生行为
public class StudentsProxy implements Person{
//被代理的学生
Student stu;

public StudentsProxy(Person stu) {
// 只代理学生对象
this.stu = (Student)stu;
}

//代理上交班费,调用被代理学生的上交班费行为
@Override
public void giveMoney() {
System.out.println("交之前做的事");
stu.giveMoney();
System.out.println("交之后做的事");
}
}

4.测试

1
2
3
4
5
6
7
8
9
10
11
12
13
public class StaticProxyTest {
public static void main(String[] args) {
//被代理的学生张三,他的班费上交有代理对象monitor(班长)完成
Person zhangsan = new Student("张三");

//生成代理对象,并将张三传给代理对象
Person monitor = new StudentsProxy(zhangsan);

//班长代理上交班费
monitor.giveMoney();
}
}

交之前做的事
张三上交班费50元
交之后做的事

三、动态代理

(1)概念:

代理类在程序运行时创建的代理方式被成为动态代理。

我们上面静态代理的例子中,代理类(studentProxy)是自己定义好的,在程序运行之前就已经编译完成。然而动态代理,代理类并不是在Java代码中定义的,而是在运行时根据我们在Java代码中的“指示”动态生成的。相比于静态代理, 动态代理的优势在于可以很方便的对代理类的函数进行统一的处理,而不用修改每个代理类中的方法。 比如说,想要在每个代理的方法前都加上一个处理方法:

1
2
3
4
5
public void giveMoney() {
//调用被代理方法前加入处理方法
beforeMethod();
stu.giveMoney();
}

这里只有一个giveMoney方法,就写一次beforeMethod方法,但是如果除了giveMonney还有很多其他的方法,那就需要写很多次beforeMethod方法,麻烦。所以建议使用动态代理实现。

(2)动态代理简单实现:

在java的java.lang.reflect包下提供了一个Proxy类和一个InvocationHandler接口,通过这个类和这个接口可以生成JDK动态代理类和动态代理对象。

1.创建接口并确定接口具体行为

1
2
3
4
public interface Person {
//上交班费
void giveMoney();
}

2.被代理对象实现接口,完成具体的业务逻辑

1
2
3
4
5
6
7
8
9
10
11
12
public class Student implements Person {
private String name;

public Student(String name) {
this.name = name;
}

@Override
public void giveMoney() {
System.out.println(name + "上交班费50元");
}
}

3.自定义 InvocationHandler 并重写invoke方法,在 invoke方法中我们会调用原生方法(被代理类的方法)并自定义一些处理逻辑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class StuInvocationHandler implements InvocationHandler {
//invocationHandler持有的被代理对象
private Object target;

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

/**
* proxy:代表动态代理对象
* method:代表正在执行的方法
* args:代表调用目标方法时传入的实参
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("代理执行" +method.getName() + "方法");
Object result = method.invoke(target, args);
return result;
}
}

4.测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class ProxyTest {
public static void main(String[] args) {

//创建一个实例对象,这个对象是被代理的对象
Person zhangsan = new Student("张三");

//创建一个与代理对象相关联的InvocationHandler
InvocationHandler stuHandler = new StuInvocationHandler(zhangsan);

//创建一个代理对象stuProxy来代理zhangsan,代理对象的每个执行方法都会替换执行Invocation中的invoke方法
Person stuProxy = (Person) Proxy.newProxyInstance(stu.getClass().getClassLoader(), stu.getClass().getInterfaces(), stuHandler);

//代理执行上交班费的方法
stuProxy.giveMoney();
}
}

代理执行giveMoney方法
张三上交班费50元

四、CGLIB 动态代理

(1)概念:

JDK 动态代理有一个最致命的问题是其只能代理实现了接口的类。为了解决这个问题,我们可以用 CGLIB 动态代理机制来避免。

(2)简单实现:

1.定义一个类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class NoInterfaceStudent {
private String name;

public NoInterfaceStudent() {
}

public NoInterfaceStudent(String name) {
this.name = name;
}



public void giveMoney() {
System.out.println(name + "上交班费50元");
}
}

2.自定义 MethodInterceptor并重写 intercept方法,intercept用于拦截增强被代理类的方法,和 JDK 动态代理中的 invoke方法类似;

1
2
3
4
5
6
7
8
//首先实现一个MethodInterceptor,方法调用会被转发到该类的intercept()方法。
public class MyMethodInterceptor implements MethodInterceptor {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("CGLIB代理执行" +method.getName() + "方法");
return methodProxy.invokeSuper(o, objects);
}
}

3.通过 Enhancer类的create()创建代理类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class CglibProxyFactory {

public static Object getProxy(Class<?> clazz) {
// 创建动态代理增强类
Enhancer enhancer = new Enhancer();
// 设置类加载器
enhancer.setClassLoader(clazz.getClassLoader());
// 设置被代理类
enhancer.setSuperclass(clazz);
// 设置方法拦截器
enhancer.setCallback(new MyMethodInterceptor());
// 创建代理类
return enhancer.create();
}
}

4.测试

1
2
3
4
5
6
public class CglibTest {
public static void main(String[] args) {
NoInterfaceStudent proxy =(NoInterfaceStudent) CglibProxyFactory.getProxy(NoInterfaceStudent.class);
proxy.giveMoney();
}
}

CGLIB代理执行giveMoney方法
上交班费50元

五、静态代理和动态代理的对比

1、灵活性:动态代理更加灵活,不需要必须实现接口,可以直接代理实现类,并且可以不需要针对每个目标类都创建一个代理类。另外,静态代理中,接口一旦新增加方法,目标对象和代理对象都要进行修改,这是非常麻烦的!

2、JVM层面:静态代理在编译时就将接口、实现类、代理类这些都变成了一个个实际的 class 文件。而动态代理是在运行时动态生成类字节码,并加载到 JVM 中的。

六、JDK 动态代理和 CGLIB 动态代理对比

1、JDK 动态代理只能代理实现了接口的类或者直接代理接口,而 CGLIB 可以代理未实现任何接口的类。 另外, CGLIB 动态代理是通过生成一个被代理类的子类来拦截被代理类的方法调用,因此不能代理声明为 final 类型的类和方法。

2、就二者的效率来说,大部分情况都是 JDK 动态代理更优秀。