设计模式—代理模式

代理模式是一个比较重要的设计模式在Spring框架中经常用到,对这个模式的学习有助于自己去理解Spring,舒服!?

代理模式概述


通过代理控制对象的访问,可以详细访问某个对象的方法,在这个方法调用处理,或调用后处理。即(AOP微实现) ,AOP核心技术面向切面编程。这里使用到编程中的一个思想(开闭原则):不要随意去修改别人已经写好的代码或者方法,如果需改修改,可以通过代理的方式来扩展该方法。

举个例子来说明代理的作用:假设我们想邀请一位明星,那么并不是直接连接明星,而是联系明星的经纪人,来达到同样的目的.明星就是一个目标对象,他只要负责活动中的节目,而其他琐碎的事情就交给他的代理人(经纪人)来解决.这就是代理思想在现实中的一个例子

设计模式—代理模式插图

代理模式应用场景

SpringAOP、事物原理、日志打印、权限控制、远程调用、安全代理 可以隐蔽真实角色。

代理的分类

  • 静态代理(静态定义代理类)
  • 动态代理(动态生成代理类)
    • Jdk自带动态代理
    • Cglib…

静态代理


什么是静态代理?

由程序员创建或工具生成代理类的源码,再编译代理类。所谓静态也就是在程序运行前就已经存在代理类的字节码文件,代理类和委托类的关系在运行前就确定了。需要生成一个代理对象

核心步骤:

  • 创建接口实现接口
  • 创建代理类处理目标对象实现功能扩展
  • 运行主类方法

✍?上代码:

/**
 * @author xianglei
 * 静态代理 模拟数据库存储
 */
public interface UserDao {
    void add();
}
/**
 * @author xianglei
 * Dao 实现类
 */
public class UserDaoImpl  implements UserDao{
    @Override
    public void add() {
        System.out.println("存入一条数据!");
    }
}
/**
 * @author xianglei
 * 创建代理类
 */
public class UserDaoProxy implements UserDao {
    private UserDao object;
    /**
     * 创建需要被代理的对象
     *
     */
    public UserDaoProxy(UserDao o){
      this.object=o;
    }

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

/**
 * @author xianglei
 */
public class Client {
    public static void main(String[] args) {
        UserDao userDao=new UserDaoImpl();
        UserDaoProxy userDaoProxy=new UserDaoProxy(userDao);
        userDaoProxy.add();
    }
}

?注意:
1.静态代理可以做到在不修改目标对象的功能前提下,对目标功能扩展.
2.缺点:因为代理对象需要与目标对象实现一样的接口,所以会有很多代理类,类太多.同时,一旦接口增加方法,目标对象与代理对象都要维护.

动态代理


  1. 代理对象,不需要实现接口
  2. 代理对象的生成,是利用JDK的API,动态的在内存中构建代理对象(需要我们指定创建代理对象/目标对象实现的接口的类型)
  3. 动态代理也叫做:JDK代理,接口代理

JDK动态代理

原理:是根据类加载器和接口创建代理类(此代理类是接口的实现类,所以必须使用接口 面向接口生成代理,位于java.lang.reflect包下)

核心步骤:

  • 创建接口实现接口方法
  • 创建代理类实现 InvocationHandler 重写 invoke 方法 返回目标对象
  • 拿到代理类对象,通过反射机制获取创建对象

✍?上代码:

/**
 * @author xianglei
 * 创建dao接口
 */
public interface UserDao {
     public void add();
}
/**
 * @author xianglei
 * 实现接口
 */
public class UserDaoImpl implements UserDao {
    @Override
    public void add() {
        System.out.println("存入数据");
    }
}
/**
 * @author xianglei
 * 使用JDK实现动态代理
 */
public class InvocationHandlerImpl implements InvocationHandler {
    private Object target;

    /**
     * 这其实业务实现类对象,用来调用具体的业务方法
     * 通过构造函数传入目标对象
     */

    public InvocationHandlerImpl(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Object result = null;
        System.out.println("开启事务");
        result = method.invoke(target, args);
        System.out.println("关闭事务");
        return result;
    }

    public static void main(String[] args) {

        UserDao userDao = new UserDaoImpl();
        InvocationHandlerImpl invocationHandler = new InvocationHandlerImpl(userDao);
        ClassLoader loader = userDao.getClass().getClassLoader();
        Class<?>[] interfaces = userDao.getClass().getInterfaces();
        try {
            /**
             * 一个小错误 反射拿接口的时候写错对象了 (┬_┬)
             */
            UserDao o = (UserDao) Proxy.newProxyInstance(loader, interfaces, invocationHandler);
            o.add();
        }catch (Exception e){
            System.out.println(e.getMessage());
        }


    }
}

?注意:

代理对象不需要实现接口,但是目标对象一定要实现接口,否则不能用动态代理。JDK动态代理只能对实现了接口的类生成代理,而不能针对类 。

CgLib动态代理


原理:利用asm开源包,对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。上面的静态代理和动态代理模式都是要求目标对象是实现一个接口的目标对象,但是有时候目标对象只是一个单独的对象,并没有实现任何的接口,这个时候就可以使用以目标对象子类的方式类实现代理。

Cglib代理,也叫作子类代理,它是在内存中构建一个子类对象从而实现对目标对象功能的扩展.

  • JDK的动态代理有一个限制,就是使用动态代理的对象必须实现一个或多个接口,如果想代理没有实现接口的类,就可以使用Cglib实现.
  • Cglib是一个强大的高性能的代码生成包,它可以在运行期扩展java类与实现java接口.它广泛的被许多AOP的框架使用,例如Spring AOP和synaop,为他们提供方法的interception(拦截)
  • Cglib包的底层是通过使用一个小而块的字节码处理框架ASM来转换字节码并生成新的类.不鼓励直接使用ASM,因为它要求你必须对JVM内部结构包括class文件的格式和指令集都很熟悉.

核心步骤:

  • 导入Cglib包
  • 创建接口(实现)或类
  • 创建代理类实现MethodInterceptor 重写intercept 返回目标对象
  • 创建代理类对象实现对目标对象的代理

✍?上代码:

/**
 * @author xianglei
 * 创建dao接口
 */
public interface UserDao {
     public void add();
}
/**
 * @author xianglei
 * 实现接口
 */
public class UserDaoImpl implements UserDao {
    @Override
    public void add() {
        System.out.println("存入数据");
    }
}
/**
*不是用接口场景
*/
public /*final*/ class People {
    public /*final*/ void sing(){
        System.out.println("人会唱歌");
    }
}
/**
 * @author xianglei
 * cglib 实现动态代理
 */
public class ClientCgLib implements MethodInterceptor {
    private Object targetObject;
    /**
     *  这里的目标类型为Object,则可以接受任意一种参数作为被代理类,实现了动态代理
     */

    public Object getInstance(Object target) {
        // 设置需要创建子类的类
        this.targetObject = target;
        //创建加强器,用来创建动态代理类
        Enhancer enhancer = new Enhancer();
        //为加强器指定要代理的业务类(即:为下面生成的代理类指定父类)
        enhancer.setSuperclass(target.getClass());
        //设置回调:对于代理类上所有方法的调用,都会调用CallBack,而Callback则需要实现intercept()方法进行拦
        enhancer.setCallback(this);
        return enhancer.create();
    }

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("开启事务");
        Object result = methodProxy.invoke(targetObject, objects);
        System.out.println("关闭事务");
        // 返回代理对象
        return result;
    }

    public static void main(String[] args) {
        ClientCgLib cglibProxy = new ClientCgLib();
        People people= (People)cglibProxy.getInstance(new People());
        people.sing();
       /* UserDao userDao = (UserDao) cglibProxy.getInstance(new UserDaoImpl());
        userDao.add();*/
    }

}

?注意:
使用cglib[Code Generation Library]实现动态代理,并不要求委托类必须实现接口,底层采用asm字节码生成框架生成代理类的字节码。在Spring的AOP编程中:如果加入容器的目标对象有实现接口,用JDK代理如果目标对象没有实现接口,用Cglib代理。


总结

Java动态代理是利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理。而cglib动态代理是利用asm开源包,对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。

  1. JDK动态代理是实现了被代理对象的接口,Cglib是继承了被代理对象。
  2. JDK和Cglib都是在运行期生成字节码,JDK是直接写Class字节码,Cglib使用ASM框架写Class字节码,Cglib代理实现更复杂,生成代理类比JDK效率低。
  3. JDK调用代理方法,是通过反射机制调用,Cglib是通过FastClass机制直接调用方法,Cglib执行效率更高。由于CGLib由于是采用动态创建子类的方法,对于final修饰的方法无法进行代理。

标签