代理是一种设计模式,其解决问题的核心点,主要是在不改变原有类的代码基础上,对原有类的功能进行增强。本篇博客将紧紧围绕着这一核心点进行 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 包文件。
搭建好的项目工程整体目录比较简单,具体如下图所示:
项目工程结构简单介绍:
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();
}
}
运行后,控制台打印结果如下所示:
三、基于 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();
}
}
运行后,控制台打印结果如下所示:
四、基于 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();
}
}
运行后,控制台打印结果如下所示:
五、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();
}
}
运行后,控制台打印结果如下所示:
到此为止,有关静态代理、动态代理、Spring 的 Aop 底层实现原理,通过具体 Demo 演示完毕。这里没有那么多理论知识,重点通过代码实现的方式进行演示,毕竟在实际开发中,代码实现和动手实现能力,最为贴合实际,大家可以自行上网查找理论相关的知识学习。
最后提供本 Demo 的源代码下载地址:https://files.cnblogs.com/files/blogs/699532/Aop_Implement.zip
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/191148.html