代理模式

1.概念

为其他对象提供一种代理以控制对该对象的访问,代理就相当于一个中介。比如你去找房子,可以通过中介,你只要告诉中介需要什么样的房子,中介就会将对应的房子介绍给你。

2.常见的代理模式

远程代理:为不同地理位置的对象,提供一个局域网代表对象。
虚拟代理:根据需要将资源消耗量很大的对象进行延迟,真正需要时候才创建。
保护代理:主要用来对不同权限进行控制。
智能引用代理:提供对目标对象额外的服务。
这里主要介绍智能引用代理,在开发过程中,该代理使用到的最多的。它有两种实现方式,静态代理和动态代理,看一下关系图:

通过关系图我们可以发现,在不改变原有类的基础上,我们可以通过代理来增加一些功能,符合开闭原则,这是代理模式的优点。在我们实际开发中,由于时间紧迫我们一开始没有加如权限、日志等这些公共的功能,后期版本迭代时候想加入,这是我们就可以使用代理来完成,而没有必要修改原来的代码逻辑。

3.静态代理

代理对象是由我们手动创建的,代理对象和被代理对象在代理前关系已经确定,它们都实现相同的接口或继承相同的抽象类。静态代理实现很简单,首先需要定义一个接口,然后定义接口实现类和一个代理类实现接口。
第一步:定义一个接口

//操作用户信息接口
public interface UserService {

    public void add();
    public void delete();
}

第二步:定义接口实现类

//操作用户信息实现类
public class UserServiceImpl implements UserService {

    @Override
    public void add() {
        System.out.println("添加一个用户");
    }
    @Override
    public void delete() {
        System.out.println("删除一个用户");
    }
}

第三步:定义代理类

//代理类
public class MyProxy implements UserService {

    private UserService us;
    public MyProxy(UserService us) {
        this.us = us;
    }

    @Override
    public void add() {
        System.out.println("开启事务");
        us.add();
        System.out.println("开启关闭事务");
    }
    @Override
    public void delete() {
        System.out.println("开启事务");
        us.delete();
        System.out.println("开启关闭事务");
    }
}

测试类

//测试类
public class Test {

    public static void main(String[] args) {
        UserService us = new UserServiceImpl();
        MyProxy proxy = new MyProxy(us);
        proxy.add();
        proxy.delete();
    }
}

静态代理就实现了,通过以上简单代码我们发现一个代理类只可以服务一个接口,一个Proxy类实现了一个UserService接口,在实际开发中会有很多接口,如果使用静态代理就需要为这些接口一一创建代理类,而这些代理类除了调用方法不同外完成的功能是相同的,这就造成了代码的严重冗余,我们可不可以通过一个代理类来完成代理所有代理功能呢,动态代理就可以完成。

4.动态代理

动态的产生代理类,实现对不同类不同方法的代理,jdk动态代理的实现使用到了反射机制,有以下几个步骤:
1. 创建一个类实现java.lang.reflect.InvocationHandler接口,实现invoke()方法
2. 创建被代理类以及接口
3. 调用java.lang.reflect.Proxy类的静态方法,创建一个代理类
Proxy.newProxyInstance(ClassLoader l,Class[] interfaces,InvocationHandler h);
参数:
ClassLoader l:被代理类的类加载器
Class[] interfaces:被代理类实现的接口
InvocationHandler h:InvocationHandler接口的子类实例
4.通过以上创建的代理类调用对应方法
下面我们简单实现动态代理,使用以上静态代理例子中的UserService和UserServiceImpl,只修改代理类和测试类。
代理类(JDK动态代理的代理类):

public class JDKProxy implements InvocationHandler {

    public Object object;
    public JDKProxy(Object object){
        this.object = object;
    }

    /**
     * 参数说明:
     * proxy:被代理的对象
     * method:被代理对象的方法Method实例
     * args:方法的参数
     * 
     * 返回值:
     * Object:调用被代理类的方法的返回值
     */
    @Override
    public Object invoke(Object proxy, Method method,     Object[] args) throws Throwable {
        System.out.println("开启事务");
        //反射
        Object obj = method.invoke(us, args);
        System.out.println("关闭事务");
        return obj;
    }
}

测试类:

public class Test {

    public static void main(String[] args) {
        UserService us = new UserServiceImpl();
        //动态代理的入口
        UserService obj = (UserService)Proxy.newProxyInstance(
                     UserService.class.getClassLoader(),  
                     UserServiceImpl.class.getInterfaces(),
                     new JDKProxy(us));
        obj.add();
        obj.delete();
    }
}

这是JDK为我们提供的动态代理实现方式,在程序运行时动态的为我们创建代理类,需要被代理类实现接口,JDK动态代理是针对实现接口的类来生成代理的。假如类没有实现接口,就不能使用JDK了,可以使用CGLIB。
CGLIB动态代理:针对类实现代理的,对指定的目标类产生一个子类(使用继承),然后覆盖父类方法,因为使用继承,所以被final修饰的类是不能使用CGLIB动态代理的。使用CGLIB需要导入Jar包cglib-nodep-2.2.jar如果是Maven项目在pom文件添加以下依赖

<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib-nodep</artifactId>
    <version>2.2</version>
</dependency>

使用CGLIB例子

public class Car {

    public void addCar(){
        System.out.println("添加一辆车");
    }
}

实现CGLIB动态代理需要定义一个类实现MethodInterceptor

package proxy.cglib;

import java.lang.reflect.Method;

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

public class CGLIBProxy implements MethodInterceptor {

    private Enhancer e = new Enhancer();

    public Object getCGLIBProxy(Class<?> clazz){
        //设置为clazz类生成代理
        e.setSuperclass(clazz);
        e.setCallback(this);
        //生成代理类实例
        return e.create();
    }

    /**
     * 拦截所有目标类的方法
     * 参数:
     * obj 被代理类实例
     * m 被代理类方法
     * args 方法的参数
     * proxy 代理类对象
     */
    @Override
    public Object intercept(Object obj, Method m, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("开启事务");
        Object result = null;
        //调用父类方法
        result = proxy.invokeSuper(obj, args);
        System.out.println("关闭事务");
        return result;
    }
}

测试类

public class Test {

    public static void main(String[] args) {
        CGLIBProxy proxy = new CGLIBProxy();
        CarService cs = (CarService)proxy.getCGLIBProxy(CarService.class);
        cs.addCar();
    }
}

动态代理的作用可以理解为”横向抽取”,之所以称为”横向抽取”,是相对于继承而言的,在继承中可以把相同的逻辑抽取到父类中,继承父类即可,继承也称为”纵向抽取”。在程序中每业务逻辑都需要记录日志、添加权限校验以及开启关闭事务等,而这些操作都是相同的,却不能通过继承抽取。”横向抽取”就是将这些相同的操作抽取出来统一管理,这也是AOP的原理。

5.模拟JDK动态代理原理

JDK动态代理是通过Proxy的newProxyInstance方法生成代理对象的,下面自定义一个MyProxy类模拟Proxy实现动态创建代理对象,该类有一静态方法可以动态的为实现了某一/些接口(或本身就是接口)任意类的任一方法产生任意代理对象。
第一步:模拟JDK中Proxy.newProxyInstance方法

package proxy.customjdk;

import java.io.File;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;

import javax.tools.JavaCompiler;
import javax.tools.JavaCompiler.CompilationTask;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;

import org.apache.commons.io.FileUtils;

/**
 * JDK动态代理是通过Proxy的newProxyInstance方法生成代理对象的,下面将自定义一个MyProxy类模拟Proxy实现动态创建代理对象
 * 该类有一静态方法可以动态的为实现了某一/些接口(或本身就是接口)任意类的任一方法产生任意代理对象
 * 实现思路:首先需要声明一段源码,可以动态的生成代理类,然后使用JDK Compiler API编译该源码,生成一个新的类,
 * 然后将该类加载的内存中,产生一个新的对象,该对象既是我们需要的代理对象,最后返回改对象即可。
 */
public class MyProxy {

    public static Object newProxyInstance(Class<?> interfaces,MyInvocationHandler h) throws Exception{
        //Windows下的换行符号
        String wrap = "\r\n";
        String method = "";
        for(Method m:interfaces.getMethods()){
            method +="  @Override"+ wrap +
                     "  public void "+m.getName()+"() {"+ wrap +
                     "  try{" + wrap +
                     //获取接口方法的反射对象
                     "  Method md = " +interfaces.getName() +".class.getMethod(\""+m.getName()+"\");" +wrap +
                     //调用自定义InvocationHandler的invoke()方法
                     "  h.invoke(this,md);"+ wrap +
                     "  }catch(Exception e){e.printStackTrace();}" + wrap +
                     "  }"+ wrap ;
        }
        //声明一段源码
        String source = 
            "package proxy.customjdk;"+ wrap +

            "import java.lang.reflect.Method;" +wrap +
            "import proxy.customjdk.MyInvocationHandler;"+ wrap +
            //模拟JDK生成的代理类名称$Proxy0
            "public class $Proxy0 implements " + interfaces.getName() + "{"+ wrap +
            "   private MyInvocationHandler h;"+ wrap +
            "   public $Proxy0(MyInvocationHandler h) {"+ wrap +
            "       this.h = h;"+ wrap +
            "   }"+ wrap +
            method+
            "}";

        //编译以上源码,需要生成一个Java文件,对该文件进行编译
        //定义文件路径,当前包下
        String fileName = System.getProperty("user.dir") +"/src/main/java/proxy/customjdk/$Proxy0.java";
        File file = new File(fileName);
        //写文件,在类目录下生成一个$Proxy0.java文件,需要导如commons-io-2.4.jar包
        FileUtils.writeStringToFile(file, source);

        //编译,以下编译方法可查询JavaAPI,在这里不详细说明
        //获取当前使用的编译器实例
        JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
        //获取一个标准文件管理器实例
        StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null);
        //获取文件
        Iterable jfile = fileManager.getJavaFileObjects(fileName);
        //编译任务
        CompilationTask task = compiler.getTask(null, fileManager, null, null, null, jfile);
        //编译,生成$Proxy0.class文件
        task.call();
        fileManager.close();

        //加载到内存
        ClassLoader loader = ClassLoader.getSystemClassLoader();
        //使用指定的二进制名称加载类
        Class c = loader.loadClass("proxy.customjdk.$Proxy0");
        //获取构造方法反射对象
        Constructor constructor = c.getConstructor(MyInvocationHandler.class);
        //创建类的实例并返回
        return constructor.newInstance(h);
    }
}

第二步:模拟InvocationHandler

package proxy.customjdk;

import java.lang.reflect.Method;

//定义自己的InvocationHandler
public interface MyInvocationHandler {

    /**
     * 模拟调用无参方法,没有传递参数
     * @param o 代理对象
     * @param m 被代理方法
     */
    public void invoke(Object o,Method m);
}

第三步:实现InvocationHandler

package proxy.customjdk;

import java.lang.reflect.Method;

public class UserHandler implements MyInvocationHandler {

    //被代理对象
    private Object obj;

    public UserHandler(Object obj) {
        this.obj = obj;
    }

    @Override
    public void invoke(Object o, Method m) {
        try {
            System.out.println("开启事务");
            m.invoke(obj);
            System.out.println("关闭事务");
        } catch (Exception e) {
            e.printStackTrace();
        } 
    }
}

第四步:测试

package proxy.customjdk;
//这里使用的UserService和UserServiceImpl是以上静态代理的例子
public class Test {

    public static void main(String[] args) throws Exception {
        UserHandler handler = new UserHandler(new UserServiceImpl());
        UserService u = (UserService)MyProxy.newProxyInstance(UserService.class,handler);
        u.add();
        u.delete();
    }
}

执行之后类目录结构,$Proxy0.java和$Proxy0.class既是生成的的代理文件
类目录接口
打印结果

开启事务
添加一个用户
关闭事务
开启事务
删除一个用户
关闭事务
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值