JDK动态代理与CGLIB动态代理的实现方式

  代理模式  23种设计模式的一种,日常开发当中用的比较多,有必要深入研究一下,以便随时都能手写一个动态代理出来,

JDK代理分为三种,

第一种,静态代理,属于硬编码,使用局限基于接口定义,

第二 动态代理 基于接口定义,

第三种 CGLIB基于类的动态代理
第一种就不讨论了,主要针对第二种方式和第三种方式。

1 准备一个接口 userService

2 准备一个接口的实现 类 userServiceImpl

public interface UserService {

	public String getName(String name);
	public int getAge(int age);
	
}

public class UserServiceImpl implements UserService {

	/* JDK动态代理实现接口
	 * 
	 */
	 public UserServiceImpl() {
	    }
	 
	@Override
	public String getName(String name) {
		System.out.println("UserServiceImpl 实现方法getName: "+name);
		return name;
	}
	/* 
	 * 
	 */
	@Override
	public int getAge(int age) {
		System.out.println("UserServiceImpl 实现方法 :getAge: "+age);
		return age;
	}

}

 定义InvocationHandler: 代理类实现


import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 * @author lmc
 * @Email huyang@126.com
 * @date 2018年11月28日
 */
public class MyInvocationHandler implements InvocationHandler{
	
	/* JDK动态代理核心 的生成类
	 */
	
	//传入代理对象
	Object target;

	public MyInvocationHandler(Object tag) {
		this.target=tag;
	}
	
	/* InvocationHandler 接口的回调函数方法
	 */
	@Override
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		//回调返回的对象
				Object result =null;
				//判断在哪个方法执行之前做一些工作,比如日志,事务等
				if (method.getName().equals("getName")) {
					System.out.println("before 在调用原始接口方法之前做些准备工作 ");
					System.out.println("before+代理类的getName方法 打印日志: method.invoke(target,args)之前执行");
					
					 result =method.invoke(target, args);//target是被代理类的对象实例,后面是方法内的参数
					 
					System.out.println("after+代理类的getName方法 打印日志  在method.invoke(targe,agrs) 之后执行");
					System.out.println("after 代理执行原始接口方法之后,做一些清理工作,解放事务或者日志,清理的工作");
				}else{
					 result=method.invoke(target, args);
				}
		return result;
	}

	public static void main(String[] args){
		
		//创建被代理类实例对象
		UserServiceImpl userimpl = new UserServiceImpl();
		//代理类执行逻辑的实例对象
		MyInvocationHandler invocationHandler = new MyInvocationHandler(userimpl);
		//转换成调用 的调口对象
		UserService userProxy = (UserService) Proxy.newProxyInstance(userimpl.getClass().getClassLoader(), userimpl.getClass().getInterfaces(), invocationHandler);
		System.out.println("接口代理 UserService useProxy: "+userProxy.getName("limingcai"));
		System.out.println("接口代理 UserService useProxy:"+userProxy.getAge(18));
	}
	
}

测试结果如下

before 在调用原始接口方法之前做些准备工作 
before+代理类的getName方法 打印日志: method.invoke(target,args)之前执行
UserServiceImpl 实现方法getName: limingcai
after+代理类的getName方法 打印日志  在method.invoke(targe,agrs) 之后执行
after 代理执行原始接口方法之后,做一些清理工作,解放事务或者日志,清理的工作
接口代理 UserService useProxy: limingcai
UserServiceImpl 实现方法 :getAge: 18
接口代理 UserService useProxy:18

直接new InvocationHandler 接口的方式创建代理也比较方便

public static void main(String[] args){
		
	//这种直接new InvocationHandler的方式,可以随时创建代理,通用性不如实现接口,要通用型就创建类并实现InvocationHandler接口
		//创建被代理类实例对象
		UserServiceImpl userimpl = new UserServiceImpl();
			//代理类返回对象,必须实现此接口. 里面的代理实例对象必须是传进去的参数实例 userimpl
		InvocationHandler invocationHandler = new InvocationHandler() {
			@Override
			public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
				Object object = null;
				if (method.getName().equals("getName")) {
					System.out.println("调用之前执行 before getName ");
					object = method.invoke(userimpl, args);
					System.out.println("调用之后执行的方法after getName");
				}else{
					object = method.invoke(userimpl, args);
					System.out.println(" 直接调用代理方法 ");
				}
				return  object;
			}
		};
		//转换成调用 的调口对象
		UserService userProxy = (UserService) Proxy.newProxyInstance(userimpl.getClass().getClassLoader(),
				userimpl.getClass().getInterfaces(), invocationHandler);
		System.out.println("接口代理 UserService useProxy: "+userProxy.getName("limingcai"));
		System.out.println("接口代理 UserService useProxy:"+userProxy.getAge(18));
	}

两者的结果并没有什么区别,就是一个定义一个类实现了接口InvocationHandler  比较通用型的类规范

直接 new InvocationHandler  的方式是在临时处理代理的时候就用此方法,省去一些必要的代码,实现方式不一样,本质上是没有什么区别,只是在代码和通用性上不一样。

下面我们从源码角度分析一下这个过程的实现原理,先看InvocationHandler: 
InvocationHandler是一个接口,只有一个方法:

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;

在定义增强逻辑类时,需要实现该接口,并在invoke方法里实现增强逻辑和对真实对象方法的调用。对于invoke方法的三个参数,proxy表示代理对象,method表示真实对象的方法,args表示真实对象方法的参数,这里也可以看出对真实对象方法的调用是通过反射来实现的。

增强逻辑定义好了以后,我们需要一个代理对象来执行,先看如何产生这个代理对象。代理对象的产生是通过调用Proxy类的静态方法:newProxyInstance  源码

public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
        throws IllegalArgumentException

//该方法需要三个参数:ClassLoader确保返回的代理对象和真实对象由同一个类 加载器加载,interfaces用于定义代理类应该实现的方法, invocationHandler用于定义代理类的增强逻辑

 

//这一行代码很关键,这里是获取代理对象Class对象,Proxy类内部维护了代理

//对象的缓存,如果缓存里有则直接返回,如果没有,则生成它

Class<?> cl = getProxyClass0(loader, intfs);

//最后由构造器返回新的代理对象实例

return newInstance(cons, ih);

简单总结  Proxy类通过ProxyClassFactory生成了继承Proxy类并实现了真实对象接口的 $ProxyX代理对象的二进制字节码流,并加载该字节码返回代理对象Class对象,由该Class对象经反射得到构造器构造了代理对象的实例。 

使用JDK动态代理有一个很大的限制,就是它要求目标类必须实现了对应方法的接口,它只能为接口创建代理实例。我们在上文测试类中的Proxy的newProxyInstance方法中可以看到,该方法第二个参数便是目标类的接口。如果该类没有实现接口,这就要靠cglib动态代理了。

CGLib采用非常底层的字节码技术,可以为一个类创建一个子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,并顺势植入横切逻辑。

 

二、接下来我们进行cglib动态代理的演示。

首先我们需要导包,我用的包是cglib-nodep-2.1_3.jar。

我们首先创建一个代理创建器CglibProxy:

先定义类 UserCglibImpl

再定义代理类  UserCglibProxy

public class UserCglibImpl {

	public String getName(String name) {
		System.out.println("UserCglibImpl 实现方法getName: "+name);
		return name;
	}
	/* 
	 */
	public int getAge(int age) {
		System.out.println("UserCglibImpl 实现方法 :getAge: "+age);
		return age;
	}
}
public class UserCglibProxy implements MethodInterceptor {

	public Object getProxy(Object classz){
		Enhancer enhancer = new Enhancer();
		//设置需要创建的子类
		enhancer.setSuperclass(classz.getClass());
		//通过字节码技术动态创建子类实例
		enhancer.setCallback(this);
		return enhancer.create();
	}
	/* 实现代理接口的核心方法
	 */
	@Override
	public Object intercept(Object proxy, Method method, Object[] arg, MethodProxy methodProxy) throws Throwable {
		Object result = null;
		if (method.getName().equals("getName")) {
			System.out.println("调用代理方法getName 之前做些工作 befer 打印");
			//目标方法调用
			result = methodProxy.invokeSuper(proxy, arg);
			System.out.println("调用代理方法getName 之后做些工作 after 清理");
		}else{
			//目标方法调用
			result = methodProxy.invokeSuper(proxy, arg);
			System.out.println("直接调用被代理方法");
		}
		return result;
	}
	/**
	 * @param args
	 */
	public static void main(String[] args) {
		
		UserCglibProxy userCglibProxy = new UserCglibProxy();
		//传入被代理对象的实例new UserCglibImpl()
		UserCglibImpl userimpl = (UserCglibImpl) userCglibProxy.getProxy(new UserCglibImpl());
		System.out.println(userimpl.getName("lmc1888889999"));
		System.out.println(userimpl.getAge(188));
	}
}

另外一种方式是直接 new  MethodInterceptor接口方式调用

public class CGLIBproxy {

	public String getName(String name){
		System.out.println("类初始 name: "+name);
		return name;
	}
	public int getAge(int age){
	    System.out.println("类初始 age:"+age);
	 return age;
	}
	public Object getProxy(Object classz){
		// 定义增强器
        Enhancer enhancer = new Enhancer();
        // 定义要代理的对象
        enhancer.setSuperclass(classz.getClass());
        // 定义回调函数
        enhancer.setCallback(new MethodInterceptor() {
        	 //这里要理解intercept方法的几个参数代表的意思
            //obj指的是代理类对象
            //Method指的是 目标类中被拦截的方法
            //args指的是 调用拦截方法所需的参数
            //MethodProxy指的是用来调用目标类被拦截方法的方法,这个方法比反射更快
			@Override
			public Object intercept(Object obj, Method method, Object[] arg, MethodProxy methrodProxy) throws Throwable {
				Object result = null;
				System.out.println("invoke 调用之前打印日志");
				 //这里只能用invokeSuper
				result = methrodProxy.invokeSuper(obj, arg);
				System.out.println("invoke 代理调用之后打印日志");
				return result;
			}
		});
		return enhancer.create();
	}
	/**
	 * @param args
	 */
	public static void main(String[] args) {
		//getProxy 这个方法完成可以改写其它方式
		CGLIBproxy cglibproxy = new CGLIBproxy();
		 // 生成代理对象
		CGLIBproxy cglib = (CGLIBproxy) cglibproxy.getProxy(cglibproxy); //(cglibproxy) enhancer.create();
		// 在代理对象上调用方法
		System.out.println("CGLIBproxy 实现类调用:  "+cglib.getName("lmc168168168"));
		//实现类的方式去调用代理
		//UserCglibImpl userproxy = (UserCglibImpl) cglibproxy.getProxy(new UserCglibImpl());
	   //System.out.println("UserCglibImpl 实现类调用:  "+userproxy.getName("lmc168168168"));
	}
}

两种接口实现方式,没什么本质区别,都需要传入被代理的对象实例

CGLib动态代理能代理类和接口,但是不能代理final类,也是有一定局限性。

JDK动态代理和CGLib动态代理都是运行时增强,通过将横切代码植入代理类的方式增强。与此不同的是AspectJ,它能够在通过特殊的编译器在编译时期将横切代码植入增强,这样的增强处理在运行时候更有优势,因为JDK动态代理和CGLib动态代理每次运行都需要增强。

源码分析

代理类源码概览

从上面的示例代码中,我们知道通过cglib生成代理类只需要一个目标类和一个回调函数(增强逻辑),下面我们从在代理对象上调用getName()方法出发,一步一步分析cglib动态代理的实现原理。

 

跟JDK动态代理InvocationHandler一样,先执行前置增强逻辑,然后将目标类的真实逻辑。注意此处目标类的真实逻辑执行cglib的实现方式与JDK实现方式不同:JDK使用的是反射技术,而cglib则使用了FastClass构建方法索引+继承的方式访问目标类的方法。

methrodProxy.invokeSuper(obj, arg); 为什么代码中不能调用invoke,而是只能调用invokeSuper。invoke代码:

因为会陷入无限递归,直至栈溢出

public Object invoke(Object obj, Object[] args) throws Throwable {
        try {
            init();
            FastClassInfo fci = fastClassInfo;
            //因为在回调函数中obj传入的代理对象,这里实际上是在代理对象上调用
            //getName方法,将陷入无限递归,直至栈溢出
            return fci.f1.invoke(fci.i1, obj, args);
        } catch (InvocationTargetException e) {
            throw e.getTargetException();
        } catch (IllegalArgumentException e) {
            if (fastClassInfo.i1 < 0)
                throw new IllegalArgumentException("Protected method: " + sig1);
            throw e;
        }
    }

 

源文参考  https://blog.csdn.net/john_lw/article/details/79539070

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值