java学习之静态代理、JDK原生和CGLIB动态代理

一、何为代理(Proxy)

   代理,即:你不用去做,别人代替你去处理。

  代理是基本的设计模式之一,它是你为了提供额外的或不同的操作,而插入的用来代替“实际”对象的对象。

   如:在公司里要上外网,要在浏览器里设置一个 HTTP 代理;

         王宝强事业忙没空照顾妻子,被宋JJ代替照顾了(O(∩_∩)O哈哈~)。

        Spring的AOP底层原理就是通过JDK原生动态代理实现的。

 

二、静态代理

先看一个简单的例子:

public interface Star {
	void sing(String songName);
	void goShow(String city);
}

 

@Component
public class DengChao implements Star{

	@Override
	public void sing(String songName) {
		System.out.println("请邓超唱一首:"+songName);
	}

	@Override
	public void goShow(String city) {
		System.out.println("让邓超去"+city+"参加节目");
	}

}

DengChao实现了Star这个接口,假如我需要在实现类中两个方法的输出语句(System.out...)前后加入一些内容,比如:日志、校验等信息时,如果直接把这些逻辑写死在这两个方法里面,感觉不太好,不够灵活,所以为了不侵入原本的业务逻辑,使用静态代理,代码如下:

@Component
public class StaticProxy implements Star{
	/**
	 * 委托类
	 */
	private DengChao dengChao;
	
	public StaticProxy(DengChao dengChao) {
		super();
		this.dengChao = dengChao;
	}

	@Override
	public void sing(String songName) {
		System.out.println("前置信息");
		dengChao.sing(songName);
		System.out.println("后置信息");
	}

	@Override
	public void goShow(String city) {
		System.out.println("前置信息");
		dengChao.goShow(city);
		System.out.println("后置信息");
	}
}

测试一下:

class TestStaticProxy {
	@Test
	void test() {
		DengChao dengChao = new DengChao();
		Star star = new StaticProxy(dengChao);
		star.sing("我是超级英雄");
		star.goShow("深圳");
	}

}

结果为:

前置信息
请邓超唱一首:我是超级英雄
后置信息
前置信息
让邓超去深圳参加节目
后置信息

虽然实现了相关的功能,但是感觉还是不太好,因为

1、代理类要跟被代理类一样实现相同的接口,多个接口的实现类要被代理的话就需要写多个代理类,那么就会出现大量重复的代码,作为一个有思想的程序员是绝对不允许的。

2、如果接口增加一个方法时,实现类要修改,代理类也要修改,增加了代码维护的复杂度

更好的方式就是动态代理。

 

三、动态代理

动态代理:应用程序发布后,通过动态创建代理对象;动态代理可以动态地创建代理并动态地处理对所代理方法的调用。

其中动态代理又可分为:

1、JDK原生动态代理

JDK动态代理只能针对实现了接口的类生成代理。

只需要一个代理类,而不是针对每个类编写代理类

新增一个动态代理类实现实现了 InvocationHandler 接口,那么必须实现该接口的 invoke 方法。

通过调用静态方法Proxy.newProxyInstance()可以创建动态代理,这个方法需要

  1. 一个类加载器(通过可以从已经被加载的对象中获取其类加载器)
  2. 你希望代理实现的接口列表(不是类或抽象类)
  3. InvocationHandler接口的一个实现

动态代理可以将所有调用重定向到调用处理器,因此通常会向调用处理器的构造器传递给一个“实际”对象的引用,从而使得调用处理器在执行其中介任务时,可以将请求转发。

在invoke()内部,在代理上调用方法时需要格外小心,因为对接口的调用将被重定向为对代理的调用。

Method.invoke()将请求转发给被代理对象,并传入必需的参数。

代码如下:

public class DynamicProxyHander implements InvocationHandler{
	
	private Object target;//用于接收具体实现类的实例对象
	/**
	 * 使用带参数的构造器来传递具体实现类的对象
	 * @param object
	 */
	public  DynamicProxyHander(Object target) {
		this.target = target;
	}
	
	@SuppressWarnings("unchecked")
	public <T> T getProxy() {
		/**
		 * this:当前对象自己
		 */
		System.out.println("this is "+this);
		System.out.println("Interfaces is" +Arrays.asList(target.getClass().getInterfaces()));
		return (T) Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
	}
	
	/**
	 * proxy:代理对象
	 * method:原对象被调用的方法
	 * args:方法的参数
	 */
	@Override
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		System.out.println("前置通知");
		Object result = method.invoke(target, args);
		System.out.println("后置通知");
		return result;
	}

}

测试一下:

@Test
	void test() {
		DynamicProxyHander dp = new DynamicProxyHander(new DengChao());
		Star starProxy = dp.getProxy();
		starProxy.sing("超级英雄");
		starProxy.goShow("南昌");
		
	}

结果如下:

this is com.cb.springstudy.dynamicproxy.DynamicProxyHander@3f49dace
/**两个接口是因为我让DengChao这个 类实现了Star和Actor两个接口**/
Interfaces is[interface com.cb.springstudy.dynamicproxy.Star, interface com.cb.springstudy.dynamicproxy.Actor]
前置通知
请邓超唱一首:超级英雄
后置通知
前置通知
让邓超去南昌参加节目
后置通知

JDK动态代理有个局限是只能针对实现了接口的类生成代理,如果没有实现接口的类想生成代理呢,那么就能用

CGLIB动态代理

2、CGLIB动态代理

CGLIB(CODE GENERLIZE LIBRARY)代理是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的所有方法,所以该类或方法不能声明称final的。

增加一个CGLibProxy类实现MethodInterceptor接口,要实现intercept这个方法

public class CGLibProxy implements MethodInterceptor{

private static CGLibProxy instance = new CGLibProxy();
    
    private CGLibProxy() {
    }

    public static CGLibProxy getInstance() {
        return instance;
    }
    
    public <T> T getProxy(Class<T> cls) {
        return (T) Enhancer.create(cls, this);
    }

    @Override
    public Object intercept(Object arg0, Method arg1, Object[] arg2, MethodProxy arg3) throws Throwable {
        System.out.println("前置日志");
        Object result = arg3.invokeSuper(arg0, arg2);
        System.out.println("后置日志");
        return result;
    }
}

代理没有实现接口的类,如:

@Component
public class DengChao{

	public void sing(String songName) {
		System.out.println("请邓超唱一首:"+songName);
	}

	public void goShow(String city) {
		System.out.println("让邓超去"+city+"参加节目");
	}

}

测试一下:

@Test
    void test() {
        DengChao dengChao = CGLibProxy.getInstance().getProxy(DengChao.class);
        dengChao.sing("中华英雄");
    }

结果:

前置日志
请邓超唱一首:中华英雄
后置日志

 

 

 

 

 

 

 

 

 

 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值