静态代理、动态代理、Spring 的 Aop 实现原理总结

不管现实多么惨不忍睹,都要持之以恒地相信,这只是黎明前短暂的黑暗而已。不要惶恐眼前的难关迈不过去,不要担心此刻的付出没有回报,别再花时间等待天降好运。真诚做人,努力做事!你想要的,岁月都会给你。静态代理、动态代理、Spring 的 Aop 实现原理总结,希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com,来源:原文

代理是一种设计模式,其解决问题的核心点,主要是在不改变原有类的代码基础上,对原有类的功能进行增强。本篇博客将紧紧围绕着这一核心点进行 Demo 的制作和实现方式的演示。在详细了解了代理的实现方式之后,自然就很容易理解 Spring 的 Aop 实现原理,因为 Spring 的 Aop 实现原理就是通过两种动态代理来实现的:基于 Jdk 的动态代理和基于 Cglib 的的动态代理。

由于篇幅有限,本篇博客不会介绍太多代理方面的理论知识,主要是通过代码 Demo 进行展示,注重实际运用和解决问题。在本篇博客的最后面,会提供源代码 Demo 的下载。

一、搭建工程

新建一个 maven 项目,导入两个 jar 包,我导入的都是最新版本的 jar 包,内容如下:

有关具体的 jar 包地址,可以在 https://mvnrepository.com 上进行查询。

<dependencies>
    <!--Spring的基础核心jar包-->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.3.17</version>
    </dependency>
    <!--第三方提供的Aop功能jar包-->
    <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjweaver</artifactId>
        <version>1.9.8</version>
        <!-- 注意:从 mvnrepository.com 复制的 jar 包坐标,包含了下面的 scope 标签,一定要删掉它。-->
        <!-- <scope>runtime</scope> -->
    </dependency>
</dependencies>

导入 jar 包时,需要特别注意:
从 mvnrepository.com 上搜索的 aspectjweaver 的 jar 包坐标,包含了 <scope>runtime</scope> 这一行内容,一定要删掉它,否则后续在 IDEA 中编写 Aop 功能类时,无法添加 Aop 相关的注解,导致编译不通过,无法运行代码。这个小问题耗费了我一些时间去排查和解决,真是太坑爹了,后续大家在 mvnrepository.com 上复制 jar 坐标时,建议特别注意这一点。当然有的 jar 包还是需要保留 scope 标签的,比如 junit 的 jar 包,其 scope 是 test,表示只会在测试时使用,最终不会被打包到项目 jar 包中。

然后打开右侧的 Maven 窗口,刷新一下,这样 Maven 会自动下载所需的 jar 包文件。

搭建好的项目工程整体目录比较简单,具体如下图所示:

image

项目工程结构简单介绍:

StaticProxy 包下存放的是静态代理的实现 demo

JdkProxy 包下存放的是基于 Jdk 的动态代理实现 demo

CglibProxy 包下存放的是基于 Cglib 的动态代理实现 demo

SpringAop 包下存放的是 Spring 基于纯注解的 Aop 实现 demo

本博客中的 demo 以增强员工的能力进行举例:假设一个员工最初只具有 java 语言的开发能力,我们通过静态代理demo、动态代理demo、SpringAop demo 为员工增加 C# 语言和 Go 语言的开发能力。当然这里所谓的开发能力展示,只是在控制台上打印出一句话而已。

二、静态代理

静态代理主要采用装饰设计模式,被增强的类需要实现接口,然后我们可以针对所实现的接口进行包装。所谓静态代理,就是代理包装生成新对象的代码实现,已经提前写好了,不是在程序运行期间通过反射机制动态生成新的代理对象。

静态代理的代码实现 demo 细节,如下如下所示:

package com.jobs.SpringAop.impl;

//首先要有一个接口
public interface EmployeeService {

    //获取员工的能力
    void getAbility();
}
package com.jobs.SpringAop.impl;

import org.springframework.stereotype.Service;

//然后员工实现上面的接口。
//下面这个 @Service 注解,是后续 Spring Aop 需要使用的,
//静态代理demo 和 动态代理的 demo 可以忽略下面的 @Service 注解
@Service("empService")
public class EmployeeServiceImpl implements EmployeeService{

    @Override
    public void getAbility() {
        System.out.println("拥有 Java 语言的开发能力");
    }
}

采用装饰设计模式,对接口进行包装:

package com.jobs.StaticProxy;

import com.jobs.SpringAop.impl.EmployeeService;

public class EmployeeAbility01 implements EmployeeService {

    private EmployeeService employeeService;

    public EmployeeAbility01(EmployeeService employeeService) {
        this.employeeService = employeeService;
    }

    @Override
    public void getAbility() {
        //原始方法调用
        employeeService.getAbility();
        //后置增强
        System.out.println("拥有 C# 语言的开发能力");
    }
}

如果你想继续进一步增强的话,可以继续进行包装:

package com.jobs.StaticProxy;

import com.jobs.SpringAop.impl.EmployeeService;

public class EmployeeAbility02 implements EmployeeService {

    private EmployeeService employeeService;

    public EmployeeAbility02(EmployeeService employeeService) {
        this.employeeService = employeeService;
    }

    @Override
    public void getAbility() {
        //原始方法调用
        employeeService.getAbility();
        //后置增强
        System.out.println("拥有 Go 语言的开发能力");
    }
}

下面列出通过上面的静态代理实现方式,在没有改变原有类(EmployeeService 接口和 EmployeeServiceImpl 类)的代码前提下,实现了对 EmployeeServiceImpl 员工能力实现类的功能增强,代码如下:

package com.jobs.StaticProxy;

import com.jobs.SpringAop.impl.EmployeeService;
import com.jobs.SpringAop.impl.EmployeeServiceImpl;

//通过【静态代理】增强原有功能的演示
public class ProxyApp {
    public static void main(String[] args) {

        System.out.println("员工原始能力:");
        EmployeeService es = new EmployeeServiceImpl();
        es.getAbility();

        System.out.println("==========================");

        System.out.println("员工使用 EmployeeAbility01 进行增强后:");
        //通过 EmployeeAbility01 对员工能力进行功能增强
        EmployeeAbility01 ea1 = new EmployeeAbility01(es);
        ea1.getAbility();

        System.out.println("==========================");

        System.out.println("员工使用 EmployeeAbility02 继续增强后:");
        //通过 EmployeeAbility02 在上面增强的基础上,再进行增强
        EmployeeAbility02 ea2 = new EmployeeAbility02(ea1);
        ea2.getAbility();
    }
}

运行后,控制台打印结果如下所示:

image

三、基于 Jdk 的动态代理

基于 Jdk 的动态代理是 Java 官网提供的动态代理实现方式,在我之前的博客中,已经发布过有关基于 Jdk 的动态代理文章,这里不做详细的解释。这种动态代理实现技术比较重要,其核心要求也是被增强的类,必须实现接口,这里还是以 EmployeeService 接口和 EmployeeServiceImpl 类进行代码细节展示,内容如下:

package com.jobs.JdkProxy;

import com.jobs.SpringAop.impl.EmployeeService;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

//编写一个类,其中包含一个方法,为实现具体接口的类的实例化对象,生成动态代理对象并返回
public class EmployeeAbilityJdkProxy {

    //创建 Jdk 动态代理
    public static EmployeeService createJdkProxy(EmployeeService employeeService) {

        //获取被代理对象的类加载器
        ClassLoader cl = employeeService.getClass().getClassLoader();
        //获取被代理对象实现的接口
        Class[] classes = employeeService.getClass().getInterfaces();
        //对原始方法执行进行拦截和增强
        InvocationHandler ih = new InvocationHandler() {
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

                //原始调用
                Object result = method.invoke(employeeService, args);
                if (method.getName().equals("getAbility")) {
                    //后置增强内容
                    System.out.println("拥有 C# 语言的开发能力");
                    System.out.println("拥有 Go 语言的开发能力");
                }

                return result;
            }
        };

        //使用原始被代理对象创建新的代理对象
        EmployeeService service = (EmployeeService) Proxy.newProxyInstance(cl, classes, ih);
        return service;
    }
}

上面专门编写了一个类,里面包含了一个方法,主要实现的功能,就是为实现了具体接口的实例化对象,采用 Jdk 的动态代理技术,对其功能进行增强,并返回功能增强后的动态代理对象。下面编写代码,使用基于 Jdk 动态代理生成的代理对象,实现在没有改变原有类(EmployeeService 接口和 EmployeeServiceImpl 类)的代码前提下,实现了对 EmployeeServiceImpl 员工能力实现类的功能增强,代码如下:

package com.jobs.JdkProxy;

import com.jobs.SpringAop.impl.EmployeeService;
import com.jobs.SpringAop.impl.EmployeeServiceImpl;

public class ProxyApp {

    public static void main(String[] args) {

        System.out.println("员工原始能力:");
        EmployeeService es = new EmployeeServiceImpl();
        es.getAbility();

        System.out.println("==========================");

        System.out.println("员工使用【Jdk 动态代理】进行增强后:");
        EmployeeService esProxy = EmployeeAbilityJdkProxy.createJdkProxy(es);
        esProxy.getAbility();
    }
}

运行后,控制台打印结果如下所示:

image

四、基于 Cglib 的动态代理

基于 Cglib(Code Generation Library)的动态代理,不需要引入额外的 jar 包,因为引入 Spring 的核心基础 jar 包后,其已经包含了 Cglib 的 jar 包。基于 Cglib 的动态代理,不要求被增强类实现接口,其实现方案原理可以简单的理解为:以被增强类为父类,生成一个子类,并对父类方法进行重写,从而实现被增强类中相关方法的增强。这里还是以 EmployeeService 接口和 EmployeeServiceImpl 类进行代码细节展示,内容如下:

package com.jobs.CglibProxy;

import com.jobs.SpringAop.impl.EmployeeService;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

public class EmployeeAbilityCglibProxy {

    public static EmployeeService createCglibProxy(Class cls) {

        //创建Enhancer对象
        Enhancer enhancer = new Enhancer();
        //设置Enhancer对象的父类
        enhancer.setSuperclass(cls);
        //设置回调方法
        enhancer.setCallback(new MethodInterceptor() {
            public Object intercept(Object o, Method method, Object[] args,
                                    MethodProxy methodProxy) throws Throwable {

                //通过调用父类的方法实现对原始方法的调用
                Object result = methodProxy.invokeSuper(o, args);
                //后置增强内容,区别:JDKProxy仅对接口方法做增强,cglib对所有方法做增强,包括所有父类中的方法
                if (method.getName().equals("getAbility")) {
                    System.out.println("拥有 C# 语言的开发能力");
                    System.out.println("拥有 Go 语言的开发能力");
                }

                return result;
            }
        });
        //使用Enhancer对象创建对应的对象
        return (EmployeeService) enhancer.create();
    }
}

这里仍然专门编写了一个类,里面包含了一个方法,主要实现的功能,就是为具体的类,采用 Cglib 的动态代理技术,对其方法功能进行增强,并返回功能增强后的动态代理对象。下面编写代码,使用基于 Cglib 动态代理生成的代理对象,实现在没有改变原有类(EmployeeServiceImpl 类)的代码前提下,实现了对 EmployeeServiceImpl 员工能力实现类的功能增强,代码如下:

package com.jobs.CglibProxy;

import com.jobs.SpringAop.impl.EmployeeService;
import com.jobs.SpringAop.impl.EmployeeServiceImpl;

public class ProxyApp {
    public static void main(String[] args) {
        System.out.println("员工原始能力:");
        EmployeeService es = new EmployeeServiceImpl();
        es.getAbility();

        System.out.println("==========================");

        System.out.println("员工使用【Cglib 动态代理】进行增强后:");
        EmployeeService esProxy =
               EmployeeAbilityCglibProxy.createCglibProxy(EmployeeServiceImpl.class);
        esProxy.getAbility();
    }
}

运行后,控制台打印结果如下所示:

image

五、Spring 的 Aop 的实现原理

Spring 的 Aop 切面增强技术,本质上就是基于上面两种动态代理技术:基于 Jdk 的动态代理和基于 Cglib 的的动态代理。

默认情况下,Spring 采用基于 Jdk 的动态代理代理技术,如果被增强类没有实现接口,将采用基于 Cglib 的动态代理技术。

如果被增强类实现了接口,也想采用基于 Cglib 的动态代理技术,可以在 Spring 的配置类上,增加注解进行配置,具体细节为:在 Spring 的配置类上面增加注解 @EnableAspectJAutoProxy 表示启用 Aop 功能,该注解有一个参数 proxyTargetClass 默认值是 false,表示采用基于 Jdk 的动态代理技术实现 Aop,如果将该参数值设置为 true,则表示基于 Cglib 的动态代理技术实现 Aop,内容如下:

package com.jobs.SpringAop;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

@Configuration
@ComponentScan("com.jobs.SpringAop.impl")

//默认情况下 proxyTargetClass 是 false,表示使用 Jdk 动态代理实现 Aop
//如果 proxyTargetClass 是 true,表示使用 Cglib 动态代理实现 Aop
//@EnableAspectJAutoProxy(proxyTargetClass = true)

@EnableAspectJAutoProxy
public class SpringConfig {
}

下面列出 Aop 增强方法类的实现细节,这里采用 Aop 环绕通知,对被增强类的方法实现前置增强和后置增强,内容如下:

package com.jobs.SpringAop.impl;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

@Component
@Aspect
public class EmployeeAop {

    //切入点表达式设置
    @Pointcut("execution(* com.jobs.SpringAop.impl.*Service.*(..))")
    public void pt() {
    }

    @Around("pt()")
    public Object runtimeAround(ProceedingJoinPoint pjp) {

        try {
            System.out.println("Aop 前置增强:拥有 C# 语言的开发能力");
            pjp.proceed(pjp.getArgs());
            System.out.println("Aop 后置增强:拥有 Go 语言的开发能力");
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }

        return null;
    }
}

下面列出 Spring 的 Aop 具体执行代码:

package com.jobs.SpringAop;

import com.jobs.SpringAop.impl.EmployeeService;
import com.jobs.SpringAop.impl.EmployeeServiceImpl;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class AopApp {
    public static void main(String[] args) {

        System.out.println("员工原始能力:");
        EmployeeService es = new EmployeeServiceImpl();
        es.getAbility();

        System.out.println("==========================");

        System.out.println("员工使用 Spring 的 Aop 技术进行增强后:");
        //使用 Spring Aop 进行功能增强
        ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
        EmployeeService empService = (EmployeeService) ctx.getBean("empService");
        empService.getAbility();
    }
}

运行后,控制台打印结果如下所示:

image

到此为止,有关静态代理、动态代理、Spring 的 Aop 底层实现原理,通过具体 Demo 演示完毕。这里没有那么多理论知识,重点通过代码实现的方式进行演示,毕竟在实际开发中,代码实现和动手实现能力,最为贴合实际,大家可以自行上网查找理论相关的知识学习。

最后提供本 Demo 的源代码下载地址:https://files.cnblogs.com/files/blogs/699532/Aop_Implement.zip

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/191148.html

(0)
小半的头像小半

相关推荐

极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!