【JavaEE】代理机制(工具思想)

1 篇文章 0 订阅

    在学习JavaSE的时候,我们学过一个非常厉害的技术,叫做反射机制。在我看来,一切框架都是基于反射技术的。而代理机制,则是又一个很厉害的技术。

先来介绍一下代理机制:
    主要作用是为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个对象不想或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。代理机制的思想是为了提供额外的处理或者不同的操作而在实际对象与调用者之间插入一个代理对象。这些额外的操作通常需要与实际对象进行通信。

代理机制分为两种:
JDK代理模式:
1、被代理的类,必须有接口;
2、产生的代理对象,其类型是接口类型的派生类类型;
3、代理对象只能调用接口方法。

CGLib代理模式:
1、被代理的类,不必须实现接口;
2、由于CGLib代理的原理是,创建一个被代理类的子类对象,因此,
如果被代理的类存在不能被继承的方法,则,这个代理类对象
当然就无法调用!即,被代理类中的final方法是不能被代理的;
当然,若被代理类本身是final类,则,不能被代理!
3、代理对象可以调用除了final修饰的其它所有方法。

下面,我来实现介绍以上两种代理技术。
先看JDK:

public class JDKProxy {
	
	public JDKProxy() {
	}
	
	@SuppressWarnings("unchecked")
	public static <T> T getProxy(T target) {
		Class<?> klass = target.getClass();
		ClassLoader classLoader = klass.getClassLoader();
		Class<?>[] interfaces = klass.getInterfaces();
		
		return (T) Proxy.newProxyInstance(classLoader, interfaces, 
				new InvocationHandler() {
					@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;
					}
				});
	}
}

上述有好多代码,说白了就是“巫师的咒语”,等以后我们再去剖析。对我们真正有用的是方法的执行和前置拦截以及后置拦截。因为上述的代码,其用法很简单,我们通过getProxy()方法,给它提供一个实现了接口类型的对象,方法的返回值是该接口类型的对象,然后我们就可以直接对象.方法来执行。最大的好处在于,我们可以对方法进行我们想要的拦截。这就是JDK代理机制。
验证JDK代理机制,我们需要另外给两个类:

public interface ISomeInterface {
	public String doSomething(String arg);
}

//	实现接口的方法
public class TargetClass implements ISomeInterface{

	public TargetClass() {
	}
	
	@Override
	public String doSomething(String arg) {
		System.out.println("正在执行doSomething()方法!");
		return "[" + arg + "]";
	}
	
	public String dealSomething(String arg) {
		return "{" + arg + "}";
	}
}

然后我们进行测试:

public static void main(String[] args) {
		TargetClass target = new TargetClass();
		
		ISomeInterface some = JDKProxy.getProxy(target);
		String str = some.doSomething("abcd");
		System.out.println(str);
	}

实验结果如下:
在这里插入图片描述
这就是JDK代理。下面看CGLiB代理:

public class CGLibProxy {

	public CGLibProxy() {
	}
	
	@SuppressWarnings("unchecked")
	public static <T> T getProxy(T target) {
		Class<?> klass = target.getClass();
		
		Enhancer enhancer = new Enhancer();
		enhancer.setSuperclass(klass);
		enhancer.setCallback(new MethodInterceptor() {
			@Override
			public Object intercept(Object object, Method method, Object[] args, 
					MethodProxy proxy) throws Throwable {
				System.out.println("前置拦截");
				Object result = null;
				result = method.invoke(target, args);
				System.out.println("后置拦截");
				
				return result;
			}
		});
		return (T) enhancer.create();
	}
	
}

与JDK代理类似,有好多“巫师的咒语”,我们以后再进行剖析。这个代理与JDK代理最大的区别是不需要接口类型,就可以产生代理。下面来进行测试。

	public static void main(String[] args) {
		TargetClass target = new TargetClass();
		
		TargetClass targetProxy = CGLibProxy.getProxy(target);
		String str = targetProxy.doSomething("XXX就是一个锤子!");
		System.out.println(str);
	}

结果如下:
在这里插入图片描述
上面的两种代理机制都可以产生代理对象,在客户端和目标对象之间起到中介的作用。而我实现的两种代理对象均是getBean()方法的返回值,对要代理的方法的“干涉”,则是通过内部类简单的实现了一下。可见,这种做法不符合工具思想,因为代码是定死的。
下面,我们把它做成我们的工具:
首先,我们需要一个IMethodInvoker接口,

public interface IMethodInvoker {
	//  前置拦截
	default boolean before(Object target,Method method,Object[] args) {
		return false;
	}
	//	按照自己的目的改变原来的方法
	<T> T methodInvoke(Object target,Method method,Object[] args);
	//	后置拦截
	default void after(Object target,Method method,Object[] args) {
		
	}
}

用户在使用这个工具时,必须要实现这个接口;否则这个工具将毫无意义。
接下来实现我们的MecProxy类:

public class MecProxy {
	public static final int JDKProxy = 0;
	public static final int CGLIBProxy = 1;
	//	默认实JDKProxy
	private static int DEFAULT_PROXY = JDKProxy;
	private IMethodInvoker methodInvoker;
	
	public MecProxy() {
	}

	public MecProxy(IMethodInvoker methodInvoker) {
		this.methodInvoker = methodInvoker;
	}
	
	public MecProxy(int proxyChoice,IMethodInvoker methodInvoker) {
		DEFAULT_PROXY = proxyChoice;
		this.methodInvoker = methodInvoker;
	}
	//	这里给了两种获取代理对象的方法
	@SuppressWarnings("unchecked")
	public <T> T getProxy(Class<?> klass) {
		if (klass.isInterface()) {
			JDKProxy jdkProxy = new JDKProxy();
			jdkProxy.setMethodInvoker(methodInvoker);
			return  (T) jdkProxy.getProxy(klass);
		}
		Object object = null;
		try {
			object = klass.newInstance();
		} catch (InstantiationException e) {
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		}
		return getProxy(object);
	}
	
	@SuppressWarnings("unchecked")
	public <T> T getProxy(Object target) {
		if (DEFAULT_PROXY == JDKProxy) {
			JDKProxy jdkProxy = new JDKProxy();
			jdkProxy.setMethodInvoker(methodInvoker);
			return  (T) jdkProxy.getProxy(target);
		}
		if (DEFAULT_PROXY == CGLIBProxy) {
			CGLibProxy cgLibProxy = new CGLibProxy();
			cgLibProxy.setMethodInvoker(methodInvoker);
			return (T) cgLibProxy.getProxy(target);
		}
		return null;
	}
}

由于存在jdk和cglib两种代理,我们需要分别实现,但是实现手法和最开始给的稍有不同,因为这时的代码已经不再是定死的,而是执行用户实现的IMethodInvoker接口。

public class JDKProxy {
	private IMethodInvoker methodInvoker;
	
	public JDKProxy() {
	}
	
	public void setMethodInvoker(IMethodInvoker methodInvoker) {
		this.methodInvoker = methodInvoker;
	}

	@SuppressWarnings("unchecked")
	public <T> T getProxy(T target) {
		Class<?> klass = target.getClass();
		ClassLoader classLoader = klass.getClassLoader();
		Class<?>[] interfaces = klass.getInterfaces();
		
		return (T) Proxy.newProxyInstance(classLoader, interfaces, 
				new InvocationHandler() {
					@Override
					public Object invoke(Object proxy, Method method, Object[] args)
											 throws Throwable {
						
						Object result = null;
						if (methodInvoker == null) {
							System.err.println("JDKProxy类的IMethodInvoker未设置!");
							return null;
						} else {
							if (methodInvoker.before(target, method, args)) {
								result = method.invoke(target, args);
								methodInvoker.after(target, method, args);
							} else {
								result = methodInvoker.methodInvoke(target, method, args);								
							}
						}
						
						return result;
					}
				});
	}
	
}

public class CGLibProxy {
	private IMethodInvoker methodInvoker;

	public CGLibProxy() {
	}
	
	public void setMethodInvoker(IMethodInvoker methodInvoker) {
		this.methodInvoker = methodInvoker;
	}

	@SuppressWarnings("unchecked")
	public <T> T getProxy(Class<?> klass) {
		try {
			Object target = klass.newInstance();

			return (T) getProxy(target);
		} catch (InstantiationException e) {
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		}
		return null;
	}
	
	@SuppressWarnings("unchecked")
	public <T> T getProxy(T target) {
		Class<?> klass = target.getClass();
		
		Enhancer enhancer = new Enhancer();
		enhancer.setSuperclass(klass);
		enhancer.setCallback(new MethodInterceptor() {
			@Override
			public Object intercept(Object object, Method method
						, Object[] args, MethodProxy proxy) throws Throwable {
				Object result = null;
				if (methodInvoker == null) {
					result = method.invoke(target, args);
				} else {
					methodInvoker.methodInvoke(target, method, args);
				}

				return result;
			}
		});
		return (T) enhancer.create();
	}
	
}

上述对于IMethodInvoker接口的方法的执行顺序,需要画个图来解释:
在这里插入图片描述我们的before方法默认是返回false,也可以看出,代理机制如果不进行before方法的覆盖,那么会默认执行实现自己的方法。
到此,我们的代理机制工具已经写完。

感悟:
这个工具,最大的缺点就是单一,他可以进行拦截,但是它只能进行一次拦截,不能形成一个拦截器链。但是这也为我自主实现spring的aop打下了基础。
其实在开头,我已经讲了两种代理的不同之处。但是,作为工具,我个人更偏向于使用JDK代理。因为,在很多工程项目中,我们更多的是用接口实现对类的封装,比如,在网络传输中,我们用短链接,传输的是一个方法的信息,在服务器端,通过对方法的解析,得到这个方法然后进行处理,再将结果返回。因为传输是非常耗时的,为了缩短时间,我们不能把这个方法非常详细的信息进行传输,而是在服务器端,通过解析到的信息在beanFactory里面去匹配信息。
当时,我也请教过我的老师(铁血教主)这个问题,我问的是:咱们实现NetFrameWork的时候,用CGLiB代理其实也可以吧?而且我想了想,用户其实都没必要实现接口,直接写类就好,反正客户端的很多操作,也是由用我们工具的人编写的,他自己知道在哪调用这个方法就好。我们采用JDK代理,还得添加一个接口类型,岂不是更难?
他给我的答复是:CGLib在使用时确实相对“随意”。但是,这种随意在“工程控制”角度而言是有害的;而JDK正好补上了这个缺陷。
这是为“开发管理”制定的,有“长远的”好处。
他的这几个加引号的词,让我体会到了JDK代理的深邃,也让我明白了编程不是简简单单的做项目,更应该为长远考虑~

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值