Liuqichun's Blog

Java 静态代理动态代理

代理的功能也就如这个模型一样,对销售这个流程多了些处理:原先供应商直接出售给客户的商品,由代理商宣传销售。

代理在 Java 中,一般是对方法做出一些修改,具体要看应用场景,如:

三种常见实现:自己写代理类(静态代理)、JDK Proxy(接口动态代理)、CGLIB(子类动态代理)。

静态代理

需要代理类实现被代理类的接口。

在这里插入图片描述

被代理类和接口

1public interface ISell {
2	void sell(double money);
3}
4
5class Seller implements ISell {
6	public void sell(double money) {
7		System.out.println("厂商得到:" + money);
8	}
9}

静态代理类实现

 1class StaticProxySeller implements ISell {
 2	private Seller superSeller;
 3
 4	public StaticProxySeller(Seller seller) {
 5		this.superSeller = seller;
 6	}
 7
 8	public void sell(double money) {
 9			double profit = money * 0.2;
10			System.out.println("代理销售利润:" + profit);
11			superSeller.sell(money - profit);
12	}
13}

优点是直观,编译期就能看到代理类。缺点是每多一个接口、每多一个方法,代理类都要手写一遍,业务一大就炸。


JDK 动态代理

使用 Proxy.newProxyInstance() 在运行时生成代理对象,对接口方法做增强。

三个参数:

在这里插入图片描述

匿名内部类实现

 1Seller superSeller = new Seller();
 2ISell proxySeller = (ISell) Proxy.newProxyInstance(
 3		superSeller.getClass().getClassLoader(),
 4		superSeller.getClass().getInterfaces(),
 5		new InvocationHandler() {
 6			public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
 7				if (method.getName().equals("sell")) {
 8					Double money = (Double) args[0];
 9					double profit = money * 0.2;
10					System.out.println("代理销售利润:" + profit);
11					return method.invoke(superSeller, money - profit);
12				}
13				return method.invoke(superSeller, args);
14			}
15		});
16proxySeller.sell(100);

invoke 的返回值要回传给调用方,不要把目标对象本身当返回值。

外部 Handler 实现

 1class Handler implements InvocationHandler {
 2	private Seller superSeller;
 3
 4	public Handler(Seller seller){
 5		this.superSeller = seller;
 6	}
 7
 8	/**
 9	 * @param proxy  生成的代理实例,不要在 invoke 里再调它的业务方法,容易递归
10	 * @param method 被调用的方法
11	 * @param args   方法参数
12	 */
13	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
14		if ("sell".equals(method.getName())) {
15			Double money = (Double) args[0];
16			double profit = money * 0.2;
17			System.out.println("代理销售利润:" + profit);
18			return method.invoke(superSeller, money - profit);
19		}
20		return method.invoke(superSeller, args);
21	}
22}

创建代理对象

1Seller seller = new Seller();
2Handler handler = new Handler(seller);
3ISell proxySeller = (ISell) Proxy.newProxyInstance(
4		seller.getClass().getClassLoader(),
5		seller.getClass().getInterfaces(),
6		handler);
7proxySeller.sell(100);

动态代理方法执行流程 在这里插入图片描述

JDK 动态代理的限制:目标类必须实现接口。生成的是接口的实现类,不是目标类的子类。java.lang.reflect.Proxy 在运行时写出字节码,默认会缓存同类接口的代理 Class。


CGLIB 动态代理

目标类没有接口,或者你想直接代理具体类时,用 CGLIB。

它在运行时生成目标类的子类,再覆盖父类方法做增强。底层靠 ASM 改字节码。因此:

Maven 依赖(Spring 项目一般已经间接引入):

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

没有接口的目标类:

1public class Seller {
2    public void sell(double money) {
3        System.out.println("厂商得到:" + money);
4    }
5}

用 Enhancer + MethodInterceptor:

 1import net.sf.cglib.proxy.Enhancer;
 2import net.sf.cglib.proxy.MethodInterceptor;
 3import net.sf.cglib.proxy.MethodProxy;
 4
 5import java.lang.reflect.Method;
 6
 7public class CglibSellInterceptor implements MethodInterceptor {
 8    @Override
 9    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
10        if ("sell".equals(method.getName())) {
11            Double money = (Double) args[0];
12            double profit = money * 0.2;
13            System.out.println("代理销售利润:" + profit);
14            args[0] = money - profit;
15        }
16        // 调父类方法,不要用 method.invoke(obj, args),会再次进 intercept,造成递归
17        return proxy.invokeSuper(obj, args);
18    }
19}
20
21Seller proxySeller = (Seller) Enhancer.create(Seller.class, new CglibSellInterceptor());
22proxySeller.sell(100);

Enhancer.create 等价于先 setSuperclass、再 setCallback、再 create。MethodProxy.invokeSuper 走的是生成子类里对 super.sell() 的调用。


三种代理怎么选

静态代理 JDK 动态代理 CGLIB
生成时机 编译期手写 运行时生成接口实现 运行时生成子类
目标类要求 通常也基于接口 必须有接口 具体类,非 final
性能 直接调用,无反射 调用走 InvocationHandler 多数场景比 JDK Proxy 快一些
适用 接口少、逻辑固定 MyBatis Mapper、Spring 对接口 Bean 的 AOP 没有接口的类、Spring 对类代理

Spring AOP、事务、日志切面,本质上都是这套「在调用前后插入逻辑」的代理。先搞清楚静态代理,再看 JDK / CGLIB,就不会觉得 AOP 是魔法。

<< Previous Post

|

Next Post >>

#Java