从零开始写javaweb框架笔记18-使框架具备AOP特性-代理技术简介

时间:2021-04-27 13:13:12

       代理,或者称为Proxy。意思就是你不需要去做,别人代替你去处理。它在程序开发中起到了非常重要的作用,比如传说中的AOP,就是针对代理的一种应用。此外,在设计模式中,还有一个代理模式。在公司里要上外网,要在浏览器设置一个HTTP代理,可见代理无处不在。

      一:简单的代理

     学习由浅到深,先看一个简单的HelloWorld,首先写一个接口:

    

package org.smart4j.framework.test;

/**
* Created by jack on 2017/6/27.
*/
public interface Hello {
void sayHello(String name);
}
     上面是一个接口,下面是实现类:

package org.smart4j.framework.test;

/**
* Created by liangboqun on 2017/6/27.
*/
public class HelloImp implements Hello{
public void sayHello(String name) {
System.out.println("Hello ! "+name);
}
}

       如果要在println方法前面和后面分别需要处理一些逻辑,该怎么做了?把这些逻辑写死在方法里面吗?这样代码肯定不优雅,这时候我们就可以使用代理模式,写一个HelloProxy类,让它去调用HelloImp的sayHello方法,在调用的前后分别进行逻辑处理,代码如下:

     

package org.smart4j.framework.test;

/**
* Created by liangboqun on 2017/6/27.
*/
public class HelloProxy implements Hello{
private Hello hello;
public void sayHello(String name) {
before();
hello.sayHello(name);
after();
}

public HelloProxy() {
this.hello = new HelloImp();
}
private void before(){
System.out.println("Before");
}
private void after(){
System.out.println("After");
}
}

    用HelloProxy类实现了Hello接口,并且在构造方法中new出一个HelloImp类的实例,这样一来,我们就可以在HelloProxy的sayHello方法去调用HelloImp的sayHello方法了。更重要的是,我们还可以在调用的前后分别加上before与after方法,在这两个方法里去实现那些前后逻辑。

      下面是一个测试类,代码如下:

package org.smart4j.framework.test;

/**
* Created by liangboqun on 2017/6/27.
*/
public class MainTest1 {
public static void main(String [] args){
Hello helloProxy = new HelloProxy();
helloProxy.sayHello("JACK");
}
}

    运行测试程序,输出如下:

从零开始写javaweb框架笔记18-使框架具备AOP特性-代理技术简介

    看是不是很简单,其实这只是入门,并且是静态代理,真正的代理远远没这么简单。


    二:JDK动态代理

   下面是使用JDK提供的动态代理方案,代码如下:

package org.smart4j.framework.test;

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

/**
* Created by jack on 2017/6/27.
* 使用JDK实现动态代理
*/
public class DynamicProxy implements InvocationHandler{
private Object target;

public DynamicProxy(Object target) {
//保存真实的对象
this.target = target;
}

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
before();
//通过反射调用函数
Object result = method.invoke(target,args);
after();
//返回函数调用的返回结果
return result;
}

private void before(){
System.out.println("Before");
}
private void after(){
System.out.println("After");
}
}

      在DynamicProxy类中,定义了一个Object类型的target变量,它就是被代理的目标对象,通过构造函数来初始化。 DynamicProxy实现了InvocationHandler接口,那么必须实现该接口的invoke方法,参数是JRE给我们传递的,直接通过反射区invoke method,在调用前后,分别处理before与after,最后将result返回。

     下面是测试代码:

package org.smart4j.framework.test;

import java.lang.reflect.Proxy;

/**
* Created by jack on 2017/6/27.
*/
public class MainTest1 {
public static void main(String[] args) {
/* Hello helloProxy = new HelloProxy();
helloProxy.sayHello("JACK");*/
//测试JDK实现的动态代理
Hello hello = new HelloImp();
DynamicProxy dynamicProxy = new DynamicProxy(hello);
Hello helloProxy = (Hello) Proxy.newProxyInstance(hello.getClass().getClassLoader(), hello.getClass().getInterfaces(), dynamicProxy);
helloProxy.sayHello("jack");
}
}

    上面代码的意思就是用这个通用的DynamicProxy类去包装HelloImp实例,然后再调用JDK给我们提供的Proxy类的工厂方法new ProxyInstance去动态地创建一个Hello接口的代理类,最后调用这个代理类的sayHello方法。

    运行结果如下:

从零开始写javaweb框架笔记18-使框架具备AOP特性-代理技术简介


    注意看测试代码,Proxy.newProxyInstance方法的参数是不是醉啦

    参数1:ClassLoader

    参数2:该实现类的所有接口

    参数3:动态代理对象

    调用完了还要来一个强制类型转换一下。

   这一块需要想办法封装一下,避免再次出现到处是Proxy.newProxyInstance方法的情况。于是将这个DynamicProxy重构一下,代码如下:

package org.smart4j.framework.test;

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

/**
* Created by jack on 2017/6/27.
* 使用JDK实现动态代理
*/
public class DynamicProxy implements InvocationHandler{
private Object target;

public DynamicProxy(Object target) {
//保存真实的对象
this.target = target;
}

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
before();
//通过反射调用函数
Object result = method.invoke(target,args);
after();
//返回函数调用的返回结果
return result;
}

private void before(){
System.out.println("Before");
}
private void after(){
System.out.println("After");
}

public <T> T getProxy(){
return (T) Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),this);
}
}

     在DynamicProxy里添加了一个getProxy方法,无需传入任何参数,将刚才所说的那一块代码放在这个方法中,并且该方法返回一个泛型类型,就不会强制转换类型了。修改测试代码,如下:

package org.smart4j.framework.test;

import java.lang.reflect.Proxy;

/**
* Created by jack on 2017/6/27.
*/
public class MainTest1 {
public static void main(String[] args) {
/* Hello helloProxy = new HelloProxy();
helloProxy.sayHello("JACK");*/
//测试JDK实现的动态代理
/* Hello hello = new HelloImp();
DynamicProxy dynamicProxy = new DynamicProxy(hello);
Hello helloProxy = (Hello) Proxy.newProxyInstance(hello.getClass().getClassLoader(), hello.getClass().getInterfaces(), dynamicProxy);
helloProxy.sayHello("jack");*/
DynamicProxy dynamicProxy = new DynamicProxy(new HelloImp());
Hello helloProxy = dynamicProxy.getProxy();
helloProxy.sayHello("jack");
}
}

     重构之后,使用简单的几行代码就实现了动态代理了。


     三:CGlib动态代理

     用了这个DynamicProxy以后,还是非常好的,好的地方是接口变了,这个动态代理类不用动。而静态代理就不一样了,接口变了,代理类也要动。但动态代理并不是万灵丹,它也有搞不定的时候,比如要代理一个没有任何接口的类,它就没有用武之地了。这个时候就只能使用CGlib这个类库。在spring和hibernate中都有使用CGlib实现动态代理。它是在一个运行期间动态生成字节码的工具,也就是动态生成代理类了,下面使用CGlib实现动态代理,写一个CGLibProxy:

       

package org.smart4j.framework.test;


import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

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

/**
* Created by jack on 2017/6/27.
* 使用CGlib实现动态代理
*/
public class CGLibProxy implements MethodInterceptor{

public Object intercept(Object object, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
before();
Object result = methodProxy.invokeSuper(object,args);
after();
return result;
}

private void before(){
System.out.println("Before");
}
private void after(){
System.out.println("After");
}

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

        注意:需要引入cglib类库,代码如下:

       

<!--添加cglib类库,实现动态代理-->
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>2.2</version>
</dependency>

     需要实现CGLib给我们提供的MethodInterceptor实现类,并填充intercept方法。方法中最后一个MethodProxy类型的参数proxy需要注意。CGLlib给我们提供的是方法级别的代理,也可以理解为对方法的拦截。这个供暖对我们程序员而言,如同雪中送炭。我们直接调用proxy的invokeSuper方法,将代理的对象obj以及方法参数args传入其中即可。

       与DynamicProxy类似,在CGlibProxy中也添加了一个getProxy方法,便于我们可以快速地获取自动生成的代理对象。下面是测试代码:

package org.smart4j.framework.test;

/**
* Created by jack on 2017/6/27.
* CGlib动态代理测试
*
*/
public class MainTest2 {
public static void main(String[] args) {
CGLibProxy cgLibProxy = new CGLibProxy();
Hello helloProxy = cgLibProxy.getProxy(HelloImp.class);
helloProxy.sayHello("jack");
}
}

      通过2行代码就可以返回代理对象,与JDK动态代理不同的是,这里不需要任何的接口信息,对谁都可以生成动态代理对象。

      用2行代码返回代理对象还是有一些多余,不行总是取new这个CGLibProxy对象,最后new一次,以后随时拿随时用,于是我们可以采用单例模式,代码如下:

package org.smart4j.framework.test;


import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

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

/**
* Created by jack on 2017/6/27.
* 使用CGlib实现动态代理
*/
public class CGLibProxy implements MethodInterceptor{

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

public Object intercept(Object object, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
before();
Object result = methodProxy.invokeSuper(object,args);
after();
return result;
}

private void before(){
System.out.println("Before");
}
private void after(){
System.out.println("After");
}

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

public static CGLibProxy getInstance(){
return instance;
}
}

     测试代码如下:

     

package org.smart4j.framework.test;

/**
* Created by jack on 2017/6/27.
* CGlib动态代理测试
*
*/
public class MainTest2 {
public static void main(String[] args) {
Hello helloProxy = CGLibProxy.getInstance().getProxy(HelloImp.class);
helloProxy.sayHello("jack");
}
}

     只需一行代码就可以获取代理对象。测试结果如下:

 从零开始写javaweb框架笔记18-使框架具备AOP特性-代理技术简介