Loading

JDK动态代理与Cglib动态代理对比

装饰模式与代理模式

如果开发者想对某个类的功能进行逻辑增强,可按需使用装饰模式或代理模式对该类进行功能增强;

装饰模式

装饰者模式能动态地给一个对象增加一些额外的职责,就新增功能来说,装饰者模式比生成子类更为灵活;

其中,Component是定义的一个对象接口,可以给这些对象动态地添加职责;ConcreteComponent是定义的一个具体对象,也可以给对象添加一些职责;BaseDecorator是装饰的抽象,实现了Component接口,由外部的类来扩展Component实例的功能,但对于Component实例来说,是无需知道BaseDecorator的存在;

装饰模式是一种用于替代继承的技术,无需通过继承增加子类就能扩展对象的新功能,它通过类与类之间的组合关系替代继承,对BaseDecorator中Component属性的setter方法来对对象进行包装,这样每个装饰对象的实现就和如何使用这个对象分离开,每个装饰对象只关心自己的功能,不需要关心如何被添加到对象链当中,这样可以简化原有地类,避免重复的装饰逻辑,更加灵活,同时避免类型体系的快速膨胀;

如当系统需要新功能的时候,是向旧的类中添加代码,这些新加的代码通常修饰了原有类的的核心职责或注意行为,它们在主类中加入了新的字段,新的方法和新的逻辑,从而增加了主类的复杂度,而这些新加入的东西仅仅是为了满足一些只在某种特定情况下才会执行的特殊行为的需要;而装饰模式把每个要装饰的功能放在单独的类中,并让这个类包装在它所要装饰的对象,因此,当需要执行特殊行为时,调用方可以根据需要有选择地,按顺序地使用装饰功能包装对象;

装饰者模式优点

相对于继承,装饰模式灵活性更强,扩展性更强;

  • 灵活性

装饰器模式将功能切分成一个个独立的装饰器,在运行期可以根据需要动态地添加功能,甚至对添加的新功能进行自由的组合;

  • 扩展性

当有新功能要添加的时候,只需要添加新的装饰器实现类,然后通过组合方式添加这个新装饰器,无需修改已有的代码,符合开闭原则;

 

参考:[https://refactoringguru.cn/design-patterns/decorator]

 

代理模式

代理模式为其他对象提供一种代理以控制这个对象的访问;

主要用于以下场景:

  • 延迟初始化,如果程序中有一个偶尔使用的重量级服务对象一直保持该对象运行会消耗系统资源时,这时可使用代理模式,那么程序在启动时就无需创建该对象可将对象的初始化延迟到真正有需要的时候;
  • 安全代理,用于控制真实对象访问时的权限,一般用于对象应该有不同的访问权限的时候;
  • 远程代理,为一个对象在不同的地址空间提供局部代表,适用于服务对象位于远程服务器上的情形;在这种情形中,代理通过网络传递客户端请求,负责处理所有与网络相关的复杂细节;
  • 记录日志请求适用于当用户需要保存对于服务对象的请求历史记录时,代理可以在向服务传递请求前进行记录;
  • 缓存请求结果适用于需要缓存客户请求结果并对缓存生命周期进行管理时, 特别是当返回结果的体积非常大时,代理可对重复请求所需的相同结果进行缓存, 还可使用请求参数作为索引缓存的键值;
  • 智能引用,可在没有客户端使用某个重量级对象时立即销毁该对象;如计算真实对象的引用次数,这样当对象没有引用时,可以自动释放它;或当第一次引用一个持久对象时,将它装入内存;或在访问一个实际对象前,检查是否已经锁定它,以确保其他对象不能改变它;它们都是在访问一个对象时附加一些内务处理;

 

代理模式与装饰模式的区别

代理模式与装饰模式有类似的结构,这两个模式的构建都基于组合原则即一个对象应该将部分工作委派给另一个对象; 两者之间的不同之处在于通过第三方的代理对象来自行管理其服务对象的生命周期,也就是代理对象和被代理的对象不是同一种类型的; 的生成则总是由客户端进行控制,装饰的对象和被装饰的对象是同一种类型;

 

静态代理和动态代理

其中,从代理的生成方式来看,代理模式又分静态代理和动态代理;

静态代理

需要自行编写代理类,组合原有的目标对象,并实现原有目标对象实现的接口,以此来做到对原有对象的方法功能的增强;

当需要增强的类方法越来越多,那么需要的代理类也会相应的增多;

代理类需要实现的接口每更改一次,代理的实现类都需要修改;

参考:[https://refactoringguru.cn/design-patterns/proxy]

动态代理

动态代理只需要编写增强逻辑类,在运行时动态将增强逻辑类组合进原有的目标对象(这个过程对于调用方是无感知的),即可生成代理对象,完成对目标对象的方法功能增强,不需要代理类的存在;

与静态代理相比,使用了静态代理代码量会被固定下来,不会因业务扩大而扩大;

动态代理有JDK的动态代理Cglib的动态代理两种;

 

动态代理演示

下面通过模拟一个服务调用记录日志,演示JDK的动态代理和Cglib的动态代理

未使用动态代理前
查看代码
public class DemoService {
    public void invoke1() {
        System.out.println("invoke1执行前");
        System.out.println("invoke1");
        System.out.println("invoke1执行后");
    }

    public void invoke2() {
        System.out.println("invoke2执行前");
        System.out.println("invoke2");
        System.out.println("invoke2执行后");
    }
}

 

查看代码
public class Client {
    public static void main(String[] args) {
        DemoService service = new DemoService();
        service.invoke1();
        service.invoke2();
    }
}

 

执行结果如下:

 

JDK动态代理的使用

JDK的动态代理要求被代理的对象所属类必须实现一个以上的接口,代理对象的创建创建代理使用Proxy.newProxyInstance方法;

其中该方法有三个参数:

  • ClassLoader loader

被代理的对象所属的类加载器;

  • Class<?>[] interfaces

被代理的对象所属类实现的接口;

  • InvocationHandler h

代理的具体代码实现;

 

InvocationHandler是一个接口;

  • Object proxy

代理后的代理对象的引用;

  • Method method

代理对象执行的方法;

  • Object[] args

代理对象执行方法的参数列表;

具体的代理逻辑在InvocationHandler#invoke方法中编写;

JDK动态代理演示
查看代码
public interface IService {
    void invoke1();
    void invoke2();
}

 

查看代码
public class DemoService implements IService {
    @Override
    public void invoke1() {
        System.out.println("invoke1");
    }

    @Override
    public void invoke2() {
        System.out.println("invoke2");
    }
}

 

查看代码
public final class JdkHandler implements InvocationHandler {
    private final static IService SERVICE = new DemoService();

    // 获取代理对象
    public static IService getService() {
        InvocationHandler handler = new JdkHandler();

        return (IService) Proxy.newProxyInstance(SERVICE.getClass().getClassLoader(),
                SERVICE.getClass().getInterfaces(), handler);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Object rtn;
        if (Object.class.equals(method.getDeclaringClass())) {
            return method.invoke(this, args);
        } else {
            invokeBefore(method.getName());
            rtn = method.invoke(SERVICE, args);
            invokeAfter(method.getName());
        }

        return rtn;
    }

    private void invokeBefore(String methodName) {
        System.out.println(LocalDateTime.now() + " - " + methodName + " 执行前");
    }

    private void invokeAfter(String methodName) {
        System.out.println(LocalDateTime.now() + " - " + methodName + " 执行后");
    }
}

 

查看代码
public class Client {
    public static void main(String[] args) {
        IService service = JdkHandler.getService();
        System.out.println(service);
        service.invoke1();
        service.invoke2();
    }
}

 

执行结果如下:

 

Cglib动态代理的使用

使用Cglib,必须先引入Cglib的jar包;

<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.3.0</version>
</dependency>

 

使用Cglib有如下要求:

  • 被代理的类不能是final的,Cglib动态代理会创建子类继承原类,final类型的类无法继承;
  • 被代理的类必须有默认的构造方法,否则底层反射创建对象时拿不到构造方法参数;

Cglib动态代理的核心API

Enhancer#create(java.lang.Class, net.sf.cglib.proxy.Callback)

同JDK动态代理类似,Cglib动态代理只需要传入两个东西:

  • Class type

被代理的对象所属类的类型;

  • Callback callback

功能逻辑增强的代码实现;

一般情况下都是对类中的方法增强,在传入Callback时通常选择这个接口的子接口MethodInterceptor

 

MethodInterceptor#intercept方法中参数列表与InvocationHandler#invoke方法类似,但是多了MethodProxy,它是对参数列表中的Method做了一层封装,利用它可以直接执行被代理对象的方法,如下:

// 执行代理对象的方法
method.invoke(proxy, args);

// 执行原始对象(被代理对象)的方法
methodProxy.invokeSuper(proxy, args);

 

Cglib动态代理演示
查看代码
public class CglibHandler implements MethodInterceptor {
    private final static DemoService SERVICE = new DemoService();

    // 使用cglib创建代理对象
    public static DemoService getService() {
        CglibHandler handler = new CglibHandler();
        return (DemoService) Enhancer.create(DemoService.class, handler);
    }

    @Override
    public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        Object rtn;

        if (Object.class.equals(method.getDeclaringClass())) {
            return method.invoke(this, args);
        } else {
            invokeBefore(method.getName());
            rtn = method.invoke(SERVICE, args);
            invokeAfter(method.getName());
        }

        return rtn;
    }

    private void invokeBefore(String methodName) {
        System.out.println(LocalDateTime.now() + " - " + methodName + " 执行前");
    }

    private void invokeAfter(String methodName) {
        System.out.println(LocalDateTime.now() + " - " + methodName + " 执行后");
    }
}

 

查看代码
 public class Client {
    public static void main(String[] args) {
        DemoService service = CglibHandler.getService();
        System.out.println(service);
        service.invoke1();
        service.invoke2();
    }
}

 

执行结果如下:

 

上面两种动态代理都是在调用的方法前后增加了打印,这些方法的开始/结束都有相同的处理逻辑,这些逻辑可拿出来看作为一体的,这种是一种叫横切的思想,提取出来的逻辑组成的虚拟结构为横切面,如下:

 

简易版本的AOP实现

下面使用JDK的动态代理实现一个简单版本的AOP,该AOP同样也是用作于模拟一个服务调用记录日志;

首先需要考虑的是,如果有多个Client对象都依赖了这个Service服务(如多个Controller依赖同一个Service),这个Service是需要创建多次?

而且在Client中拿到的Service应该是被代理的的实例,这样在Client中调用才能做到无感知调用;

这时可使用一个BeanFactory管理Bean实例,而且在BeanFactory中应该是要创建被增强的代理对象,当需要有增强代理对象时,则使用代理对象替代原始对象,这样不论是代理还是原始的对象都会被BeanFactory管理;

 

查看代码
public class BeanFactory {
    private static final Map<String, Object> BEAN_MAP = new ConcurrentHashMap<>();
    private static final Properties PROPERTIES;

    // 使用静态代码块初始化properties,加载
    static {
        PROPERTIES = new Properties();
        try {
            PROPERTIES.load(BeanFactory.class.getClassLoader().getResourceAsStream("aop/factory.properties"));
        } catch (IOException e) {
            throw new ExceptionInInitializerError("BeanFactory initialize error, cause: " + e.getMessage());
        }
    }

    public static Object getBean(String beanName) {
        if (!BEAN_MAP.containsKey(beanName)) {
            try {
                // map中不存在,则通过反射创建该对象
                Class<?> beanClazz = Class.forName(PROPERTIES.getProperty(beanName));
                Object bean = beanClazz.newInstance();

                // 检查properties中是否有定义代理对象
                String proxyAdvisorClassName = PROPERTIES.getProperty(beanName + ".proxy.class");
                if (proxyAdvisorClassName != null && proxyAdvisorClassName.trim().length() > 0) {
                    // 有定义代理对象,则需要反射创建InvocationHandler的实现类
                    Class<?> proxyAdvisorClass = Class.forName(proxyAdvisorClassName);

                    // 从properties中找出当前bean需要增强的方法列表
                    String[] methods = PROPERTIES.getProperty(beanName + ".proxy.methods").split(",");
                    // 要求InvocationHandler的实现类必须声明两参数构造方法
                    // 其中第一个参数是被代理的目标对象,第二个参数是要增强的方法列表
                    InvocationHandler proxyHandler = (InvocationHandler) proxyAdvisorClass.getConstructors()[0]
                            .newInstance(bean, Arrays.asList(methods));
                    // 动态代理创建对象
                    Object proxy = Proxy.newProxyInstance(bean.getClass().getClassLoader(),
                            bean.getClass().getInterfaces(), proxyHandler);
                    // 经过该步骤后,放入map的对象就是已经被增强过的代理对象
                    bean = proxy;
                }

                BEAN_MAP.put(beanName, bean);
            } catch (ClassNotFoundException e) {
                throw new RuntimeException("BeanFactory have not [" + beanName + "] bean!", e);
            } catch (IllegalAccessException | InstantiationException | InvocationTargetException e) {
                throw new RuntimeException("[" + beanName + "] instantiation error!", e);
            }
        }

        return BEAN_MAP.get(beanName);
    }
}

 

在类路径resources目录下创建一个aop目录,之后在aop目录下创建一个factory.properties文件;

查看代码
# 原始对象
demoService=org.example.proxy.aop.DemoServiceImpl

#代理对象
demoService.proxy.class=org.example.proxy.aop.LogAdvisor

#需要增强的方法
demoService.proxy.methods=invoke1,invoke2

 

原始对象

查看代码
public interface DemoService {
    void invoke1();
    void invoke2();
}

 

查看代码
public class DemoServiceImpl implements DemoService {
    @Override
    public void invoke1() {
        System.out.println("invoke1");
    }

    @Override
    public void invoke2() {
        System.out.println("invoke2");
    }
}

 

由于使用JDK的动态代理演示,增强的代理对象需要通过实现InvocationHandler接口来实现增强的逻辑;而在执行代理的增强逻辑时,InvocationHandler实例需要拿到被代理的对象和需要增强的方法,因此通过构造器的形式将被代理对象(即原始对象)和需要增强的方法传入;

查看代码
public class LogAdvisor implements InvocationHandler {

    private Object target;
    private List<String> methods;

    public LogAdvisor(Object target, List<String> methods) {
        this.target = target;
        this.methods = methods;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        Object rtn;

        if (this.methods.contains(method.getName())) {
            invokeBefore(method.getName());
        }

        if (Object.class.equals(method.getDeclaringClass())) {
            return method.invoke(this, args);
        } else {
            rtn = method.invoke(target, args);
        }

        if (this.methods.contains(method.getName())) {
            invokeAfter(method.getName());
        }

        return rtn;
    }

    private void invokeBefore(String methodName) {
        System.out.println(LocalDateTime.now() + " - " + methodName + " 执行前");
    }

    private void invokeAfter(String methodName) {
        System.out.println(LocalDateTime.now() + " - " + methodName + " 执行后");
    }
}

 

Client调用

查看代码
public class Client {
    public static void main(String[] args) {
        DemoService demoService = (DemoService) BeanFactory.getBean("demoService");
        demoService.invoke1();
        demoService.invoke2();
    }
}

 

执行结果如下:

 

posted @ 2022-10-09 00:01  街头卖艺的肖邦  阅读(151)  评论(0)    收藏  举报