代理模式


代理模式:为其他对象提供一种代理以控制对这个对象的访问。即通过代理对象访问目标对象,这样做的好处是:可以在目标对象实现的基础上,增强额外的功能操作,即扩展目标对象的功能;代理模式有不同的形式:静态代理、动态代理 (JDK代理、接口代理)和 Cglib代理 (可以在内存动态地创建对象,而不需要实现接口, 他是属于动态代理的范畴)。

静态代理原理及实践

静态代理模式:静态代理就是在程序运行前就已经存在代理类的字节码文件,代理类和原始类的关系在运行前就已经确定了,需要定义接口或者父类,被代理对象(即目标对象)与代理对象一起实现相同的接口或者是继承相同父类。

我们先定义一个用户操作接口IUserDao

package proxy.staticproxy;

public interface IUserDao {
    void save();
    void find();
}

编写它的实现类UserDao

package proxy.staticproxy;

public class UserDao implements IUserDao{
    @Override
    public void save() {
        System.out.println("模拟:保存用户!");
    }
    @Override
    public void find() {
        System.out.println("模拟:查询用户");
    }
}

编写代理类UserDaoProxy

package proxy.staticproxy;

public class UserDaoProxy implements IUserDao{
    // 代理对象,需要维护一个目标对象
    private IUserDao target = new UserDao();
    @Override
    public void save() {
        System.out.println("代理操作: 开启事务...");
        target.save();   // 执行目标对象的方法
        System.out.println("代理操作:提交事务...");
    }
    @Override
    public void find() {
        target.find();
    }
}

编写测试类TestProxy

package proxy.staticproxy;

public class TestProxy {
    public static void main(String[] args) {
        // 代理对象
        IUserDao proxy = new UserDaoProxy();
        proxy.save();
    }
}

测试结果

代理操作: 开启事务...
模拟:保存用户!
代理操作:提交事务...

总结

静态代理虽然保证了业务类只需关注逻辑本身,代理对象的一个接口只服务于一种类型的对象;但是因为代理对象需要与目标对象实现一样的接口,所以会有很多代理类,一旦接口增加方法,目标对象与代理对象都要维护,增加了代码的维护成本。那么要如何解决呢?答案是使用动态代理。

动态代理原理及实践

动态代理模式:动态代理类的源码是在程序运行期间通过JVM反射等机制动态生成,代理类和委托类的关系是运行时才确定的。

我们先定义一个用户操作接口IUserDao

package proxy.staticproxy;

public interface IUserDao {
    void save();
    void find();
}

编写它的实现类UserDao

package proxy.staticproxy;

public class UserDao implements IUserDao{
    @Override
    public void save() {
        System.out.println("模拟:保存用户!");
    }
    @Override
    public void find() {
        System.out.println("模拟:查询用户");
    }
}

编写代理工厂ProxyFactory,通过Java的反射机制给目标对象生成代理对象

public class ProxyFactory {
    // 接收一个目标对象
    private Object target;
    public ProxyFactory(Object target) {
        this.target = target;
    }
    // 返回对目标对象(target)代理后的对象(proxy)
    public Object getProxyInstance() {
        Object proxy = Proxy.newProxyInstance(
            target.getClass().getClassLoader(),  // 目标对象使用的类加载器
            target.getClass().getInterfaces(),   // 目标对象实现的所有接口
            new InvocationHandler() {           // 执行代理对象方法时候触发
                @Override
                public Object invoke(Object proxy, Method method, Object[] args)
                        throws Throwable {
 
                    // 获取当前执行的方法的方法名
                    String methodName = method.getName();
                    // 方法返回值
                    Object result = null;
                    if ("find".equals(methodName)) {
                        // 直接调用目标对象方法
                        result = method.invoke(target, args);
                    } else {
                        System.out.println("开启事务...");
                        // 执行目标对象方法
                        result = method.invoke(target, args);
                        System.out.println("提交事务...");
                    }
                    return result;
                }
            }
        );
        return proxy;
    }
}

编写测试类TestProxy

package proxy.dynamicProxy;

public class TestProxy {
    public static void main(String[] args) {
        // 创建目标对象
        IUserDao target = new UserDao();
        System.out.println("目标对象:" + target.getClass());
        // 代理对象
        IUserDao proxy = (IUserDao) new ProxyFactory(target).getProxyInstance();
        System.out.println("代理对象:" + proxy.getClass());
        // 执行代理对象的方法
        proxy.save();
    }
}

测试结构

目标对象:class proxy.dynamicProxy.UserDao
代理对象:class com.sun.proxy.$Proxy0
开启事务...
模拟:保存用户!
提交事务...

总结

在运行测试类中创建测试类对象代码中IUserDao proxy = (IUserDao)new ProxyFactory(target).getProxyInstance();其实是jdk动态帮我们生成了一个类去实现接口,所以使用jdk生成的动态代理的前提是目标类必须有实现的接口。但这里又引入一个问题,如果某个类没有实现接口,就不能使用jdk动态代理,所以Cglib代理就是解决这个问题的。

CGLib代理

CGLib代理:也叫作子类代理,以动态生成的子类继承目标的方式,在运行期动态的在内存中构建一个子类从而实现对目标对象功能扩展, 有些书也将CGLib代理归属到动态代理。

public class UserDao{}
//Cglib是以动态生成的子类继承目标的方式实现,程序执行时,隐藏了下面的过程
public class $Cglib_Proxy_class  extends UserDao{}

注意

1、Cglib使用的前提是目标类不能为final修饰。因为final修饰的类不能被继承;
2、目标对象的方法如果为final/static,那么就不会被拦截,即不会执行目标对象额外的业务方法。

你可能感兴趣的:(代理模式)