AOP之Java动态代理

1 Java动态代理的研究现状

        Java AOP实现分为静态代理和动态代理,静态代理是指在编译阶段将特定的装饰代码织入原始类中,其特点是效率较高,但是需要特定的编译器支持。而动态代理则是在运行阶段通过反射等方式动态的创建代理对象,将特定的切入代码织入动态创建的代理对象之中。

        Java动态代理目前有jdk动态代理和cglib动态代理两种主流方式。其中jdk动态代理要求被代理类实现对应接口,其实现原理是通过获取原始类的接口生成包装类对象,通过包装类去调用原始类,从而达到在调用方法时候自定义操作的目的;而cglib动态代理则是通过继承来实现的,其特点是不需要原始类进行接口和实现类分离,但是cglib动态代理因为采用继承方式,不支持对final关键字修饰的字段、方法进行代理。可以说这二者各有千秋。

        但是与现代开发语言如swift、go等相比,Java通过动态代理仍显“别扭”和古老。同目前主流的通过原生方法树进行拦截仍有一定的差距。

2 Jdk动态代理

        2.1 笔者总结jdk动态代理的实现大体可以分为以下几步:

        (1) 创建实现InvocationHandler接口的代理类;

        (2) 通过目标类初始化代理类(核心是通过Proxy.newProxyInstance方法通过目标类的接口构建代理类);

        (3) 通过代理类(代理类中需要被代理的方法必须放在接口中,并且代理类实现该接口)生成代理对象;

        (4) 使用的时候通过代理对象进行方法调用;

        (5) 实现自定义的invoke方法回调,达到对目标类方法调用的切入。

        2.2 具体实现如下:

        (1) 创建测试目标类Test1

public interface ITest1 {

    void jdkSayHello();

    void cglibSayHello();

}



import java.util.logging.Logger;

public class Test1 implements ITest1{

    Logger logger = Logger.getLogger(Test1.class.getName());
    
    @Override
    public void jdkSayHello(){
        logger.info("hello jdk dynamic invocation");
    }

    @Override
    public void cglibSayHello() {
        logger.info("hello cglib dynamic invocation");
    }

}

        (2)创建测试代理类

mport java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.logging.Logger;

public class JdkDynamicInvocationHandler implements InvocationHandler {

    final Logger logger = Logger.getLogger(JdkDynamicInvocationHandler.class.getName());

    private Object target;

    public void setTarget(Object target) {
        // 被代理的目标类
        this.target = target;
    }

    public JdkDynamicInvocationHandler(Object target){
        this.target = target;
    }
    
    public Object getProxy(){
        // 核心方法,通过Proxy.newProxyInstance动态的创建一个包装了目标类接口方法代理对象。
        return Proxy.newProxyInstance(this.getClass().getClassLoader(),target.getClass().getInterfaces(),this);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        logger.info("jdk proxy");
        Object result = method.invoke(target,args);
        return result;
    }

}

        (3)  测试


import com.test.ioc.JdkDynamicInvocationHandler;

public class Main {

    public static void main(String[] args){

        Test1 old = new Test1();
        JdkDynamicInvocationHandler jdkDynamicInvocationHandler = new JdkDynamicInvocationHandler(old);
        ITest1 jdkProxy = (ITest1) jdkDynamicInvocationHandler.getProxy();
        jdkProxy.jdkSayHello();

    }

}

        2.3 底层核心代码

        jdk动态代理的核心是通过以下Native方法动态的创建一个包装对象,通过调用该包装对象来替换调用原有对象实现AOP。

 private static native Object newInstance0(Constructor<?> c, Object[] args)
        throws InstantiationException,
               IllegalArgumentException,
               InvocationTargetException;

        

3 Cglib动态代理

        3.1 笔者总结Cglib动态代理大体分为以下几步:

        (1) 创建实现MethodInterceptor接口的代理类;

        (2) 通过目标类初始化代理类;

        (3) 通过代理类生成代理对象;

        (4) 使用的时候通过代理对象进行方法调用;

        (5) 实现自定义的intercept方法回调,达到对目标类方法调用的切入。

       

        3.2 具体实现如下:

        

 (1) 创建测试目标类Test1

public interface ITest1 {

    void jdkSayHello();

    void cglibSayHello();

}



import java.util.logging.Logger;

public class Test1 implements ITest1{

    Logger logger = Logger.getLogger(Test1.class.getName());
    
    @Override
    public void jdkSayHello(){
        logger.info("hello jdk dynamic invocation");
    }

    @Override
    public void cglibSayHello() {
        logger.info("hello cglib dynamic invocation");
    }

}

        (2)创建测试代理类

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.util.logging.Logger;

public class CglibDynamicInvocationHandler<T>  implements MethodInterceptor {

    final Logger logger = Logger.getLogger(CglibDynamicInvocationHandler.class.getName());

    private final T obj;

    public CglibDynamicInvocationHandler(T obj) {
        this.obj = obj;
    }
    public T getProxy(){
        // 1. 创建Enhancer类对象
        Enhancer enhancer = new Enhancer();
        // 2. 设置父类的字节码对象。
        Class clazz = obj.getClass();
        enhancer.setSuperclass(clazz);
        // 3. 设置回调函数
        enhancer.setCallback(this);
        // 4. 创建代理对象
        return (T) enhancer.create();
    }

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        logger.info("cglib proxy");
        method.invoke(obj,objects);
        return o;
    }

}

         

        (3)  测试


import com.test.ioc.JdkDynamicInvocationHandler;

public class Main {

    public static void main(String[] args){

        Test1 old = new Test1();
        JdkDynamicInvocationHandler jdkDynamicInvocationHandler = new JdkDynamicInvocationHandler(old);
        ITest1 jdkProxy = (ITest1) jdkDynamicInvocationHandler.getProxy();
        jdkProxy.jdkSayHello();

    }

}

4 Spring AOP的实现原理。

        通过以上分析,相信读者已经了解了Java AOP的实现机制动态代理的原理,以及不同的实现方式。但是细心的读者会发现,以上的AOP实现方式仍然显得复杂,那么有没有更优雅的实现方式呢,当然是有的,这就是Spring 的 IOC机制。通过注解或者xml配置将对象交给Spring IOC的context容器管理,再通过@Autowired或者@Resources获取对象实例的时候实质上拿到的并不是对象本身的实例,而是Spring通过动态代理增加以后的实例对象。在调用方法的时候Spring自然就可以就行任意切入,因此在Spring框架下要实现某个Spring管理的Bean的AOP编程才会变得那么优雅和简单。

        所有由Spring IOC Context管理的对象被称为Bean,而Spring在动态代理的具体实现上是当Bean有实现接口时,Spring就会用JDK的动态代理,当Bean没有实现接口时,Spring则会使用CGlib。所有我们平常的开发中并不会察觉到“异常”。

        Spring本身维护着多个不同类型的切入链,用户只要把自己的切点实现放入切入链条中并且指定注解或者包名的匹配方式,即可实现对方法的AOP。

       4.1 Spring AOP的核心概念

  • 切面(Aspect):被抽取的公共模块,可能会横切多个对象。在Spring AOP中,切面可以使用通用类(基于模式的风格) 或者在普通类中以 @AspectJ 注解来实现。

  • 连接点(Join point):指方法,在Spring AOP中,一个连接点 总是 代表一个方法的执行。

  • 通知(Advice):在切面的某个特定的连接点(Join point)上执行的动作。通知有各种类型,其中包括“around”、“before”和“after”等通知。许多AOP框架,包括Spring,都是以拦截器做通知模型, 并维护一个以连接点为中心的拦截器链。

  • 切入点(Pointcut):切入点是指 我们要对哪些Join point进行拦截的定义。通过切入点表达式,指定拦截的方法,比如指定拦截add*、search*

  • 引入(Introduction):(也被称为内部类型声明(inter-type declaration))。声明额外的方法或者某个类型的字段。Spring允许引入新的接口(以及一个对应的实现)到任何被代理的对象。例如,你可以使用一个引入来使bean实现 IsModified接口,以便简化缓存机制。

  • 目标对象(Target Object):被一个或者多个切面(aspect)所通知(advise)的对象。也有人把它叫做被通知(adviced) 对象。既然Spring AOP是通过运行时代理实现的,这个对象永远是一个 被代理(proxied) 对象。

  • 织入(Weaving):指把增强应用到目标对象来创建新的代理对象的过程。Spring是在运行时完成织入。

        4.2 下面举一个WebMvc切入链实现的简单的例子

@Configuration
public class InterceptorConfig extends WebMvcConfigurationSupport {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new CustomAspect()).addPathPatterns("/**");
        super.addInterceptors(registry);
    }

}

@Component
public class CustomAspectextends HandlerInterceptorAdapter {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse res, Object handler) throws Exception {
            doSomeson();
        return true;// 是否继续向下调用,返回false则会中断方法调用
    }

}

5 总结

        综上所述,Java的AOP实现核心是静态代理和动态代理,动态代理相对静态代理而言应用更为灵活,因此应用更为广泛,以Spring为代表的Java 应用开发框架大量使用着动态代理。而动态代理的实现本质无非是包装和继承两种方式。只要理解的AOP和动态代理的核心,读者自己实现一个Spring容器也并非难事。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值