第21~22周Java主流框架入门-Spring 2.SpringAOP面向切面编程
1.Spring AOP (Aspect-Oriented Programming)
1. 1. 什么是 Spring AOP?
- AOP(面向切面编程) 是 Spring 提供的一种可插拔的组件技术,允许我们在软件运行过程中添加额外的功能。
- 场景:假设有两个模块,用户管理模块A和员工管理模块B。现在需要在业务处理过程中添加权限过滤功能。
- 如果在两个模块中都添加权限判断代码,当权限需求变化时,需要再次修改代码,这样会增加开发和维护成本。
- 使用 Spring AOP,我们可以将权限判断的代码独立为一个切面,在代码执行前进行权限过滤,而不需要修改原业务逻辑。
1. 2. 面向切面编程 (AOP) 的核心概念
- 切面(Aspect):表示横切的功能模块,用于实现某些通用功能(如权限检查、日志记录等)。切面可以在方法执行前后插入。
- 权限切面:在执行业务逻辑之前判断用户权限。
- 日志切面:记录业务逻辑的执行时间、输入参数、输出结果等信息。
- 通过切面技术,日志和权限判断代码可以在不修改业务代码的情况下被“织入”程序。
- 如果业务需求发生变化,只需调整配置即可轻松移除切面,不影响核心业务逻辑。
1. 3. 切面与插件技术的类比
- 切面类似于我们在浏览器中安装的插件,可以为现有的业务模块增加额外的功能。
- 例如:安装翻译插件后,浏览器可以将英文网页自动翻译为中文。
- 一旦不需要这些功能,卸载插件即可,还原浏览器的原始状态。
- 切面也是如此,它为业务模块提供了额外的功能,但这些模块本身不会感知到切面的存在。
1. 4. 为什么叫“切面”?
- 正常的软件执行流程是从上到下按照代码顺序执行的,而切面则像一个横切面,在执行过程中横插进入业务流程中。
- 这些横切的功能模块就是所谓的“切面(Aspect)”,通过切面我们可以为现有的业务逻辑增加扩展功能。
1. 5. AOP 的最终目的
- 不修改源码 的情况下扩展程序行为。
- 通常将与业务无关的通用功能(如权限检查、日志记录)封装为切面类,通过配置来插入这些功能。
- 切面可以配置在目标方法的执行前、执行后,达到真正的“即插即用”。
2.Spring AOP - 实战配置项目
课程简介
本节课程将通过实际项目配置,带领大家一步一步理解 Spring AOP(面向切面编程)的功能。我们将基于 XML 配置的形式来实现 AOP,并通过演示了解 AOP 如何对现有系统进行功能扩展,而无需修改源代码。
2. 1. 项目结构介绍
- 本次演示基于 s01 工程,其中包含了两个主要部分:
- DAO 层:包括
EmployeeDao
和UserDao
,分别用于对员工表和用户表的数据增删改查。 - Service 层:包括
EmployeeService
和UserService
,分别提供了员工相关的业务逻辑和用户管理的业务逻辑。EmployeeService
:提供entry
方法,模拟员工入职操作。UserService
:提供createUser
方法(创建用户)和generateRandomPassword
方法(生成随机密码)。
- DAO 层:包括
这些类的业务逻辑非常常规,但本节课我们将通过 AOP 实现对方法执行时间的监控,解决手动添加代码带来的冗余和复杂度问题。
2. 2. 需求描述
我们希望在系统运行过程中,对所有 Service
层和 DAO
层的方法调用前打印执行时间,从而便于分析系统负载高峰时间。
问题:
- 如果直接在每个方法中手动添加
System.out.println()
代码,维护和删除这些代码将变得非常麻烦。 - AOP 可以在不修改原代码的情况下,灵活地添加或移除这些功能。
2. 3. 配置项目依赖
首先,我们需要在 pom.xml
文件中添加必要的依赖项:
<dependencies><!-- Spring context dependency --><dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>5.2.6.RELEASE</version></dependency><!-- AspectJ Weaver (AOP 底层依赖) --><dependency><groupId>org.aspectj</groupId><artifactId>aspectjweaver</artifactId><version>1.9.5</version></dependency>
</dependencies>
spring-context
是用来初始化 IOC 容器的基础依赖,而 aspectjweaver
则是 AOP 的底层依赖,负责切面功能的实现。
2. 4. 配置 applicationContext.xml
接下来,我们需要在 resources
目录下创建 applicationContext.xml
文件,这是 Spring IOC 的配置文件。
添加命名空间:
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:context="http://www.springframework.org/schema/context"xmlns:aop="http://www.springframework.org/schema/aop"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsdhttp://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"><!-- 配置 Beans --><bean id="userDao" class="com.example.dao.UserDao" /><bean id="employeeDao" class="com.example.dao.EmployeeDao" /><bean id="userService" class="com.example.service.UserService"><property name="userDao" ref="userDao" /></bean><bean id="employeeService" class="com.example.service.EmployeeService"><property name="employeeDao" ref="employeeDao" /></bean>
</beans>
引入 aop
命名空间:
该命名空间用于配置 AOP 所需的相关标签。它将帮助我们在不修改源代码的前提下,为现有方法添加执行时间打印功能。
2. 5. 初始化 IOC 容器并执行测试
接下来,我们在 aop
包下创建一个 Spring 应用的入口类:
public class SpringApplication {public static void main(String[] args) {ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");UserService userService = context.getBean("userService", UserService.class);userService.createUser(); // 模拟创建用户的过程}
}
运行代码后,可以看到控制台输出显示了 UserService
和 UserDao
中各个方法的执行情况。
2. 6. Spring AOP - 方法执行时间打印需求实现
课程目标
通过 AOP 实现对 Service
或 DAO
层中任意方法的执行时间进行打印,并避免在每个方法中手动增加日志打印代码。AOP 能够灵活地实现这些功能,且无需修改源代码。
1. 新增切面类 (Method Aspect)
在 AOP 配置中,我们需要创建一个切面类,用于扩展业务逻辑。在 aop
包下新增一个 aspect
包,创建切面类 MethodAspect
,用于打印方法的执行时间。
切面类 MethodAspect
public class MethodAspect {public void printExecutionTime(JoinPoint joinPoint) {// 获取当前时间并格式化SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");String now = sdf.format(new Date());// 获取目标类名和方法名String className = joinPoint.getTarget().getClass().getName();String methodName = joinPoint.getSignature().getName();// 打印执行时间信息System.out.println("-----");System.out.println("Time: " + now);System.out.println("Class: " + className);System.out.println("Method: " + methodName);System.out.println("-----");}
}
JoinPoint
参数用于获取目标类和目标方法的信息。printExecutionTime()
方法会在目标方法执行前打印当前时间、类名和方法名。
2. AOP 配置文件 applicationContext.xml
在 applicationContext.xml
文件中进行 AOP 配置,使得在调用 Service
或 DAO
方法时,自动打印方法的执行时间。
配置 AOP 切面
<bean id="methodAspect" class="com.example.aspect.MethodAspect" /><aop:config><!-- 定义切点,匹配 com.example 包下所有类的所有 public 方法 --><aop:pointcut id="serviceMethods" expression="execution(public * com.example..*(..))" /><!-- 定义切面 --><aop:aspect ref="methodAspect"><!-- 前置通知,在方法执行前打印执行时间 --><aop:before method="printExecutionTime" pointcut-ref="serviceMethods" /></aop:aspect>
</aop:config>
aop:pointcut
定义了切点,匹配com.example
包下的所有public
方法。aop:before
定义了前置通知,表示在目标方法执行之前调用printExecutionTime()
方法。
3. AOP 运行效果
- 在运行程序时,任何
Service
或DAO
层方法执行前,控制台都会打印方法执行的时间、类名和方法名。 - 示例输出:
-----
Time: 2024-10-16 10:35:12.123
Class: com.example.service.UserService
Method: createUser
-----
关闭功能
如果项目经理不再需要打印时间信息,只需注释掉 AOP 的配置部分即可。
3. Spring AOP - 关键概念与配置解析
3. 1. Spring AOP 和 AspectJ 的关系
- AspectJ:一种基于 Java 平台的面向切面编程(AOP)语言,提供完整的 AOP 编程体系。
- Spring AOP:Spring 提供的 AOP 实现,部分依赖 AspectJ。AspectJ 主要用于类和方法的匹配(通过
aspectjweaver
),而功能的增强由 Spring 本身通过代理模式实现。
3. 2. 关键概念
2.1 切面(Aspect)
- 切面:具体的可插拔组件功能类,通常用于实现通用功能。
- 切面类:一个标准的 Java 类,无需继承或实现其他类。可以包含多个切面方法,这些方法用于实现功能扩展。
- 切面方法:
- 例如:
printExecutionTime()
用于打印方法的执行时间。 - 方法必须为
public
,返回值可以是void
或Object
,具体取决于通知类型。 - 需包含
JoinPoint
参数,用于获取目标类和方法的信息。
- 例如:
2.2 连接点(JoinPoint)
- 连接点:获取目标类和目标方法的元数据对象。可通过
joinPoint.getTarget()
获取目标对象,通过joinPoint.getSignature().getName()
获取目标方法名。
2.3 切点(Pointcut)
- 切点:用于定义切面要作用的范围。通过
execution
表达式指定切面应在哪些类的哪些方法上生效。 - 切点表达式:在配置文件中使用
expression
属性指定作用范围。- 示例:
execution(public * com.example..*(..))
作用于com.example
包下所有类的所有public
方法。
- 示例:
2.4 通知(Advice)
- 通知(Advice):指定切面方法在何时执行。Spring AOP 支持五种通知类型:
- 前置通知(Before):在目标方法执行前执行。
- 后置通知(After):在目标方法执行后执行。
- 其他类型:包括返回后通知、异常通知、环绕通知。
2.5 目标类和目标方法
- 目标类和目标方法:指真正执行业务逻辑的类和方法,例如
Service
或DAO
层中的createUser()
或insert()
方法。
3. 3. AOP 配置步骤
通过 XML 配置 AOP 主要包含以下五个步骤:
3.1 引入 AspectJ 依赖
在 pom.xml
中引入 aspectjweaver
依赖:
<dependency><groupId>org.aspectj</groupId><artifactId>aspectjweaver</artifactId><version>1.9.5</version>
</dependency>
3.2 实现切面类和方法
切面类是一个标准的 Java 类,方法中需包含 JoinPoint
参数,用于获取目标类和方法的信息。
3.3 配置切面类
在 applicationContext.xml
中配置切面类:
<bean id="methodAspect" class="com.example.aspect.MethodAspect" />
3.4 定义切点
使用 pointcut
标签定义切点,指定切面的作用范围:
<aop:pointcut id="serviceMethods" expression="execution(public * com.example..*(..))" />
3.5 配置通知(Advice)
在目标方法执行前通过 before
标签调用切面方法:
<aop:before method="printExecutionTime" pointcut-ref="serviceMethods" />
4. Spring AOP - JoinPoint 连接点核心方法
4. 1. JoinPoint 连接点简介
- JoinPoint 连接点用于获取目标类和目标方法的相关信息,能够在切面方法中通过
JoinPoint
参数访问这些信息。 - JoinPoint 提供了三个核心方法,分别是:
getTarget()
:获取目标对象(由 IOC 容器管理的对象)。getSignature()
:获取目标方法的签名信息。getArgs()
:获取目标方法的实际参数。
4. 2. 核心方法介绍与演示
2.1 getTarget()
方法
- 作用:获取由 IOC 容器管理的目标对象。
- 演示:
- 在切面方法中调用
joinPoint.getTarget()
可以获取目标对象,再通过getClass().getName()
获取该目标对象所属的类名。
- 在切面方法中调用
2.2 getSignature()
方法
- 作用:获取目标方法的签名。
- 演示:
- 使用
joinPoint.getSignature().getName()
获取目标方法的名称。
- 使用
2.3 getArgs()
方法
- 作用:获取目标方法调用时传入的参数。
- 演示:
joinPoint.getArgs()
返回一个Object
数组,表示传入的参数。可以对该数组进行遍历,并打印每个参数的值。
5. Spring AOP - Pointcut 切点表达式详解
5. 1. Pointcut 切点的作用
- Pointcut 切点:用于告诉 AOP 哪些类的哪些方法应该应用切面逻辑。
- 切点表达式的作用:定义切面生效的范围。
5. 2. 方法结构与切点表达式
一个完整的方法结构包含以下部分:
- 修饰符(如
public
、private
等)。 - 返回值类型(如
void
、String
)。 - 类的完整路径(如
com.example.service.UserService
)。 - 方法名及参数(如
createUser()
)。
切点表达式的作用是匹配这些方法结构中的各个部分。它与方法结构一一对应。
5. 3. 切点表达式 execution
详解
execution
表达式用于指定切面生效的范围。其格式为:
execution([修饰符] [返回值] [类路径].[类名].[方法名]([参数]))
3.1 常见的通配符
*
:匹配任意返回值、类名、方法名等。..
:包通配符,匹配当前包及子包中的所有类或任意数量的参数。
5. 4. 实例讲解
4.1 匹配所有类的所有公共方法
<aop:pointcut expression="execution(public * com.example..*(..))" />
- 匹配
com.example
包及其子包下的所有类的public
方法。 *
表示任意返回值。..
表示任意包路径的匹配。*(..)
表示任意方法和任意参数。
4.2 匹配特定类名结尾的类
<aop:pointcut expression="execution(* com.example..*Service.*(..))" />
- 匹配
Service
结尾的类中的所有方法。
4.3 匹配返回 void
的方法
<aop:pointcut expression="execution(void com.example..*Service.*(..))" />
- 匹配返回类型为
void
的方法。
4.4 匹配返回 String
的方法
<aop:pointcut expression="execution(String com.example..*Service.*(..))" />
- 匹配返回类型为
String
的方法。
4.5 匹配以 create
开头的方法
<aop:pointcut expression="execution(* com.example..*Service.create*(..))" />
- 匹配方法名以
create
开头的方法。
4.6 匹配无参数的方法
<aop:pointcut expression="execution(* com.example..*Service.*())" />
- 匹配无参数的方法。
4.7 匹配有特定数量参数的方法
<aop:pointcut expression="execution(* com.example..*Service.*(String, int))" />
- 匹配参数为
String
和int
类型的方法。
6. Spring AOP - 五种通知类型
6. 1. 通知(Advice)的概念
- 通知 是指在什么时机去执行切面的方法。Spring AOP 提供了五种类型的通知,每种通知对应不同的执行时机。
6. 2. 五种通知类型详解
2.1 前置通知(Before Advice)
- 作用:在目标方法运行前执行切面方法。
- 示例:在用户创建方法前输出日志信息。
2.2 返回后通知(After Returning Advice)
- 作用:在目标方法返回结果后执行切面方法。
- 特点:可以获取目标方法的返回值。
- 示例:在用户创建成功后输出返回结果或状态。
2.3 异常通知(After Throwing Advice)
- 作用:在目标方法抛出异常后执行切面方法。
- 特点:可以获取并处理目标方法抛出的异常。
- 示例:捕获用户创建时的异常并输出相关信息。
2.4 后置通知(After Advice)
- 作用:在目标方法执行完毕后(无论是否成功)执行切面方法。
- 特点:类似于
finally
块,无论是否抛出异常,后置通知都会执行。 - 示例:在用户创建操作结束后,输出日志。
2.5 环绕通知(Around Advice)
- 作用:可以自定义通知的执行时机,并且决定目标方法是否执行。
- 特点:功能最强大,可以完全控制方法的执行流程。
- 示例:在用户创建方法前后执行额外的操作,并根据条件决定是否继续执行目标方法。
6. 3. After 类型通知的执行顺序
- After Returning 和 After Throwing 是互斥的:
- After Returning 在目标方法成功返回后执行。
- After Throwing 在目标方法抛出异常时执行。
- After Advice:无论成功与否,都会执行,类似于
try-catch-finally
结构中的finally
。
6. 4. 示例代码:后置通知
public void doAfter(JoinPoint joinPoint) {System.out.println("后置通知触发");
}
- 配置后置通知:
<aop:after method="doAfter" pointcut-ref="servicePointCut" />
6. 5. 返回后通知与异常通知的示例
5.1 返回后通知
public void doAfterReturning(JoinPoint joinPoint, Object retVal) {System.out.println("返回后通知,返回值: " + retVal);
}
- 配置返回后通知:
<aop:after-returning method="doAfterReturning" pointcut-ref="servicePointCut" returning="retVal" />
5.2 异常通知
public void doAfterThrowing(JoinPoint joinPoint, Throwable error) {System.out.println("异常通知,异常信息: " + error.getMessage());
}
- 配置异常通知:
<aop:after-throwing method="doAfterThrowing" pointcut-ref="servicePointCut" throwing="error" />
6. 6. 特殊通知:引介增强(Introduction Advice)
- 作用:可以为类动态添加新的属性或方法,类似于动态代理。
- 特点:与其他通知不同,它作用于类的增强,而非方法的增强。
- 场景:在运行时根据不同的环境动态改变类的行为。
6. 7. 结论
- 前四种通知类型各有用途,了解它们的执行时机和特点非常重要,特别是在调试和监控时。
- 环绕通知是最强大的通知类型,能够完全控制方法的执行流程。
- 引介增强是高级应用,允许在运行时为类动态添加行为,使用场景较为特殊。
7. Spring AOP - 环绕通知案例
7. 1. 场景介绍
- 在实际工作中,随着用户量和数据量的增长,系统可能会变慢。为了定位具体是哪个方法执行缓慢,我们可以利用环绕通知来记录每个方法的执行时间,并将超过预定时间阈值的方法记录下来,便于后续优化。
- 环绕通知 是 Spring AOP 中最强大的通知类型,可以完整控制目标方法的执行周期。
7. 2. 环绕通知的使用方法
- 环绕通知 可以在目标方法执行前、执行后获取时间,计算出方法的执行时长。
- 通过
ProceedingJoinPoint
参数,可以控制目标方法是否执行。
示例代码:环绕通知
public Object checkExecutionTime(ProceedingJoinPoint pjp) throws Throwable {// 记录开始时间long startTime = new Date().getTime();// 执行目标方法Object retVal = pjp.proceed();// 记录结束时间long endTime = new Date().getTime();long executionTime = endTime - startTime;// 如果执行时间超过1秒,输出日志if (executionTime >= 1000) {System.out.println("方法执行时间过长:" + executionTime + " 毫秒");}// 返回目标方法的执行结果return retVal;
}
7. 3. 环绕通知的关键点
- ProceedingJoinPoint:
ProceedingJoinPoint
是JoinPoint
的升级版,除了获取目标方法的信息,还可以控制目标方法的执行。- 关键方法:
proceed()
,用于执行目标方法并返回结果。
- 关键方法:
- 执行时间的记录:在方法执行前记录开始时间,执行后记录结束时间,然后计算执行时长。
- 异常处理:环绕通知可以捕获并处理目标方法抛出的异常。
7. 4. 配置环绕通知
在 applicationContext.xml
中配置环绕通知:
<bean id="methodChecker" class="com.example.aspect.MethodChecker" /><aop:config><aop:pointcut id="servicePointCut" expression="execution(* com.example..*Service.*(..))" /><aop:aspect ref="methodChecker"><aop:around method="checkExecutionTime" pointcut-ref="servicePointCut" /></aop:aspect>
</aop:config>
7. 5. 环绕通知与其他通知的比较
- 环绕通知 可以完成其他四种通知的所有工作:
- 方法执行前相当于 前置通知。
- 方法执行后相当于 后置通知。
- 返回值可以通过 返回后通知 处理。
- 异常处理则对应 异常通知。
- 因此,环绕通知是最为灵活和强大的通知类型。
7. 6. 总结
- 环绕通知可以控制目标方法的完整生命周期,并通过
ProceedingJoinPoint
来决定是否执行目标方法。 - 使用环绕通知,我们可以轻松捕捉方法的执行时间、处理返回值以及异常。
- 了解环绕通知的工作原理后,你可以灵活运用它来解决复杂的系统性能问题。
8. Spring AOP - 基于注解的配置
8. 1. 基于注解的 AOP 简介
- 之前我们通过 XML 配置 Spring AOP,虽然功能强大,但配置较为繁琐。
- Spring 提供了基于注解的方式,简化了 AOP 的配置,将配置信息从 XML 移动到源代码中。
8. 2. 配置步骤
2.1 引入依赖
在 pom.xml
中引入 Spring 和 AspectJ 相关依赖:
<dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>5.2.6.RELEASE</version>
</dependency>
<dependency><groupId>org.aspectj</groupId><artifactId>aspectjweaver</artifactId><version>1.9.5</version>
</dependency>
2.2 配置 applicationContext.xml
在 applicationContext.xml
中启用注解扫描和 AOP 注解模式:
<context:component-scan base-package="com.example" />
<aop:aspectj-autoproxy />
component-scan
:扫描指定包下的组件。aspectj-autoproxy
:启用 Spring AOP 注解模式。
8. 3. 在源代码中使用注解
3.1 Service 和 DAO 注解
在 Service 和 DAO 类上使用 @Service
和 @Repository
注解,将它们注册到 Spring IOC 容器中:
@Service
public class UserService {@Resourceprivate UserDao userDao;// Service 方法
}
@Repository
public class UserDao {// DAO 方法
}
3.2 切面类配置
创建切面类 MethodChecker
,并使用注解定义环绕通知:
@Component
@Aspect
public class MethodChecker {@Around("execution(* com.example..*Service.*(..))")public Object checkExecutionTime(ProceedingJoinPoint pjp) throws Throwable {// 记录开始时间long startTime = new Date().getTime();// 执行目标方法Object retVal = pjp.proceed();// 记录结束时间long endTime = new Date().getTime();long executionTime = endTime - startTime;// 如果方法执行时间超过1秒,记录日志if (executionTime >= 1000) {System.out.println("方法执行时间过长:" + executionTime + " 毫秒");}// 返回目标方法的执行结果return retVal;}
}
@Component
:将MethodChecker
注册为 Spring IOC 容器的组件。@Aspect
:表明该类是一个切面类。@Around
:定义环绕通知,并通过execution
表达式指定切点。
8. 4. 其他通知注解
除了 @Around
,还有其他几种通知注解:
@Before
:前置通知,在目标方法执行前调用。@After
:后置通知,在目标方法执行后调用。@AfterReturning
:返回后通知,在目标方法成功返回后调用。@AfterThrowing
:异常通知,在目标方法抛出异常时调用。
8. 5. 总结
- 基于注解的 Spring AOP 配置大大简化了开发过程,只需在代码中添加注解即可完成配置。
@Aspect
和@Around
等注解可以帮助我们灵活地实现切面逻辑,而无需繁琐的 XML 配置。- 通过组件扫描和 AOP 自动代理,Spring 可以轻松管理切面和服务类的生命周期。
9. Spring AOP - 代理模式与实现原理
9. 1. Spring AOP 的底层实现原理
- Spring AOP 的底层核心是基于设计模式中的 代理模式,用于实现功能的动态扩展。
- Spring AOP 的实现方式有两种:
- JDK 动态代理:当目标类实现了接口时,使用 JDK 动态代理来实现。
- CGLIB:当目标类没有实现接口时,使用 CGLIB 进行代理。
9. 2. 代理模式的基本概念
- 代理模式 是通过代理对象对原对象进行功能扩展的设计模式。
- 代理对象:代表真正执行业务逻辑的对象,并在其基础上添加额外的功能。
- 生活中的例子:租房时,中介作为代理人帮助客户(租客)和房东进行交易,完成租房过程。
9. 3. 静态代理的实现
3.1 接口定义
创建一个 UserService
接口,定义用户服务的基础功能:
public interface UserService {void createUser();
}
3.2 委托类实现
创建 UserServiceImpl
类,作为接口的具体实现类,负责用户创建的实际业务逻辑:
public class UserServiceImpl implements UserService {@Overridepublic void createUser() {System.out.println("执行创建用户业务逻辑");}
}
3.3 代理类实现
通过创建代理类 UserServiceProxy
来扩展 UserService
接口的功能:
public class UserServiceProxy implements UserService {private UserService userService;// 构造方法接受委托类的对象public UserServiceProxy(UserService userService) {this.userService = userService;}@Overridepublic void createUser() {// 扩展功能:打印方法执行前的时间System.out.println("执行前时间: " + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));// 调用委托类的实际业务逻辑userService.createUser();// 扩展功能:可以在这里添加更多逻辑}
}
9. 4. 动态代理的嵌套
通过创建多个代理类,可以实现代理嵌套:
public class UserServiceProxy1 implements UserService {private UserService userService;public UserServiceProxy1(UserService userService) {this.userService = userService;}@Overridepublic void createUser() {// 调用委托类的方法userService.createUser();// 扩展功能:在方法执行后进行额外处理System.out.println("方法后扩展功能");}
}
- 可以使用多个代理类进行嵌套代理,通过一层层扩展功能。
9. 5. 示例代码:静态代理的调用
在 Application
类中调用代理类,实现方法的功能扩展:
public class Application {public static void main(String[] args) {// 实例化委托类UserService userService = new UserServiceImpl();// 使用代理类对委托类进行扩展UserService proxy = new UserServiceProxy(new UserServiceProxy1(userService));proxy.createUser();}
}
9. 6. 静态代理的缺点
- 每增加一个业务类,都需要手动创建相应的代理类,导致代码冗余和复杂度增加。
- 如果系统中有大量的业务类,则需要编写大量的代理类,这样会使系统变得笨重。
10. JDK动态代理课程笔记
10. 1. 引言
-
静态代理: 手动创建代理类,代理类持有目标对象的引用并在实现方法中扩展业务逻辑。
- 要点:
- 代理类和委托类必须实现相同的接口。
- 代理类需要持有委托类的对象。
- 要点:
-
问题: 手动创建大量代理类会导致工作量庞大。
10. 2. JDK动态代理概述
- 引入: JDK 12后,使用反射机制自动生成代理类。
- 动态代理: 代理类在运行时根据接口结构生成,而不需要手动书写。
- 区别: 静态代理手动编写,动态代理则在内存中动态生成。
10. 3. 代码实现步骤
3.1 创建工程
- 创建新的工程,设置
group ID
和artifact
,工程名为s05
。
3.2 编写代码
- 复制之前的
UserService
接口和UserServiceImpl
实现类。
3.3 创建 ProxyInvocationHandler
- 在
service
包中创建ProxyInvocationHandler
类,实现InvocationHandler
接口。 - 实现
invoke
方法:- 参数:
proxy
: 代理类对象(由JDK动态代理生成)。method
: 目标方法信息。args
: 目标方法的实际参数。
- 实现逻辑:
- 输出当前时间。
- 调用目标方法并返回结果。
- 参数:
public class ProxyInvocationHandler implements InvocationHandler {private Object target;public ProxyInvocationHandler(Object target) {this.target = target;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("当前时间: " + System.currentTimeMillis());return method.invoke(target, args);}
}
3.4 使用 ProxyInvocationHandler
- 在
main
方法中:- 创建目标对象
UserService userService = new UserServiceImpl()
。 - 实例化
ProxyInvocationHandler
,传入目标对象。 - 使用
Proxy.newProxyInstance
创建代理类。
- 创建目标对象
public static void main(String[] args) {UserService userService = new UserServiceImpl();ProxyInvocationHandler handler = new ProxyInvocationHandler(userService);UserService proxy = (UserService) Proxy.newProxyInstance(userService.getClass().getClassLoader(),userService.getClass().getInterfaces(),handler);proxy.createUser();
}
10. 4. 注意事项
- 接口要求: JDK动态代理必须实现接口,如果类不实现接口,则无法使用此机制。
- Spring解决方案: 对于不实现接口的类,Spring提供CGlib库实现类的增强。
11. JDK动态代理原理与实现细节课程笔记
11. 1. 引言
- 本课程通过源代码的回顾,深入探讨JDK动态代理的底层原理和实现细节。
11. 2. 重要概念
- Proxy.newProxyInstance: JDK 反射包中的一个方法,用于根据接口生成对应的代理类。
- 由于JDK底层实现过程不可见,通过图示化理解其执行过程将更加有助于掌握。
# 11. 3. 执行过程
3.1 创建代理类
- 执行
Proxy.newProxyInstance
时,会经历三个主要步骤:- 生成字节码文件: 在本地硬盘上创建一个
.class
文件,默认存放在com.sun.proxy
包下。类名格式为$Proxy0
(以$
开头,加上数字)。 - 生成代理类: 代理类实现了原有的接口。
- 定义类: 通过
defineClass
方法将字节码文件加载到JVM的方法区中。
- 生成字节码文件: 在本地硬盘上创建一个
3.2 伪代码示例
public class $Proxy0 implements UserService {private UserService target;public $Proxy0(UserService target) {this.target = target;}public void createUser() {System.out.println("执行前置逻辑");target.createUser();}
}
- 该伪代码展示了生成的代理类结构,持有目标对象的引用。
11. 4. JVM内存操作
- 代理对象被创建并保存到JVM的堆内存中,持有被代理类的引用。
- 当调用
userServiceProxy.createUser()
时,实际上会执行生成的代理类中的逻辑。
12. Spring AOP与CGlib课程笔记
12. 1. 引言
- 上一节课使用代码演示了如何通过JDK动态代理实现Spring AOP功能。
- JDK动态代理要求目标类必须实现接口,但在实际应用中,许多类并不实现接口,这时需要其他解决方案。
12. 2. CGlib介绍
- CGlib: Code Generation Library的缩写,是一种运行时字节码增强技术。
- Spring AOP使用CGlib来扩展没有实现接口的类。
- 当一个类没有实现接口时,AOP会在运行时生成目标类的继承字节码,从而进行行为扩展。
12. 3. CGlib原理
- 假设有一个
Service
类,包含一个findById
方法,但该类没有实现接口。 - 由于无法使用JDK动态代理,Spring会自动使用CGlib通过继承来对类进行扩展。
- 生成的子类类名格式为:
OriginalClassName$$EnhancerByCGLIB
。
3.1 继承与方法重写
- 子类会重写父类中的
findById
方法,逻辑如下:- 执行自定义前置处理代码。
- 调用父类的
findById
方法(使用super.findById()
)。 - 执行自定义后置处理代码。
12. 4. 示例演示
- 通过之前写好的实例来展示CGlib的使用。
- 在
S03
工程中,UserService
类没有实现任何接口。 - 断点调试程序,可以看到生成的类名包含
$$EnhancerBySpringCGLIB
,表明使用CGlib进行增强。
12. 5. 变更为接口实现
- 将
UserService
类修改为实现一个接口IUserService
。 - 修改引用为
IUserService
,保持方法调用部分不变。 - 断点调试,生成的对象类名变为
proxy
,表示使用了JDK动态代理。
12. 6. AOP实现原理总结
- 两种情况:
- 如果目标类实现了接口,Spring优先使用JDK动态代理生成代理类。
- 如果目标类没有实现接口,则使用CGlib通过继承对目标类进行扩展。
- 了解这些细节在面试中回答Spring AOP实现原理时非常重要。
相关文章:

第21~22周Java主流框架入门-Spring 2.SpringAOP面向切面编程
1.Spring AOP (Aspect-Oriented Programming) 1. 1. 什么是 Spring AOP? AOP(面向切面编程) 是 Spring 提供的一种可插拔的组件技术,允许我们在软件运行过程中添加额外的功能。场景:假设有两个模块,用户管…...

Flutter不常用组件----InteractiveViewer
在现代移动应用开发中,用户互动性是提升体验的关键。Flutter 提供了多种组件来帮助开发者实现丰富的交互功能,其中一个强大的组件便是 InteractiveViewer。它允许用户通过手势对内容进行缩放、平移和旋转,适用于需要用户查看大图、地图或者其…...

【LeetCode HOT 100】详细题解之二分查找篇
【LeetCode HOT 100】详细题解之二分查找篇 35 搜索插入位置思路代码(左闭右闭)代码(左闭右开) 74 搜索二维矩阵思路代码(左闭右闭) 34 在排序数组中查找元素的第一个和最后一个位置思路代码 33 搜索旋转排序数组思路代码 153 寻找旋转排序数组中的最小值思路代码 4 寻找两个正…...

管理篇(顶级思维模型(31个))(待做)
目录 一、成长进阶模型 二、优势探索模型 三、优势层次模型 四、人生定位模型 五、看懂人性模型 六、如何抉择模型 七、本质思考模型 八、心流模型 九、精力管理模型 十、高效沟通模型 十一、100%传递模型 十二、高效倾听模型 十三、高效表达模型 十四、精准提问模…...

十一、数据库配置
一、Navicat配置 这个软件需要破解 密码是:123456; 新建连接》新建数据库 创建一个表 保存出现名字设置 双击打开 把id设置为自动递增 这里就相当于每一次向数据库添加一个语句,会自动增长id一次 二、数据库的增删改查 1、Vs 建一个控…...

day02 -- docker
1.docker的介绍 Docker 是一个开源的应用容器引擎,基于 Go语言 并遵从 Apache2.0 协议开源。Docker 可以让开发者打包他们的应用以及依赖包到一个轻量级、可移植的容器中,然后发布到任何流行的 Linux 机器上,也可以实现虚拟化。容器是完全使…...

ecmascript标准
1. 简介 1.1. 概述 ECMAScript(简称ES)是JavaScript编程语言的一个标准化版本。它是为网络开发设计的一种轻量级的脚本语言,主要用于在网页上实现交互性和动态效果。ECMAScript是该语言的标准名称,而JavaScript是其最知名和广泛使用的实现。 1.2. 特点 跨平台性 :ECMAS…...

在Linux命令行中一行执行多个命令
原文地址链接: https://kashima19960.github.io/2024/10/22/Linux/在Linux命令行中一行执行多个命令/,一般有最新的修改都是在我的个人博客里面,所以在当前平台的更新会比较慢,请见谅😃 前言 在shell中一个一个命令行,…...

u盘快速格式化后怎么恢复文件:深入解析与全面指南
U盘,凭借其小巧便携、易于使用的特点,成为了我们日常生活中不可或缺的数据存储工具。然而,有时为了清除病毒、解决文件系统错误或准备存储新数据,我们可能需要对U盘进行快速格式化。但这一操作往往伴随着一个严峻的问题࿱…...

青少年编程能力等级测评CPA C++(二级)试卷(2)
青少年编程能力等级测评CPA C(二级)试卷(2) 一、单项选择题(共20题,每题3.5分,共70分) CP2_2_1.下列C程序段中,对二维数组arr的定义不正确是( &…...

aws 把vpc残留删除干净
最近忘了把vpc 删干净导致又被收了冤大头钱 在删除vpc 的收发现又eni在使用,但是忘了是哪个资源在占用 先用命令行把占用的资源找出来停掉 使用 AWS 命令行界面(CLI)来查看 VPC 的使用情况 列出子网: aws ec2 describe-subnets …...

平衡二叉树最全代码
#include<stdio.h> #include<stdlib.h>typedef struct Node {int val;int height;struct Node *left;struct Node *right; }Node;//创建新结点 Node* newNode(int val) {Node *node (Node*)malloc(sizeof(Node));node->val val;node->height 1;node->l…...

数据库表的创建
运用的环境是pychram python3.11.4 创建一个表需要用到以下语法 注释已经写清楚各种语法的含义,记住缩进是你程序运行的关键,因为程序是看你的缩进来判断你的运行逻辑,像我这个就是缩进不合理导致的报错 那么今天分享就到这里,谢…...

【MySQL 数据库】之--基础知识
1. MySQL 数据库基础概念 数据库: 逻辑上存储和管理数据的集合。MySQL 是一个常用的关系型数据库管理系统。 2. 创建数据库 要创建一个新的数据库,可以使用 CREATE DATABASE 语句。 语法: CREATE DATABASE 数据库名; 示例: CREATE DATABASE my_database; 注意事…...

Flume面试整理-如何处理Flume中的数据丢失
在Apache Flume中,数据丢失是一个可能出现的严重问题,特别是在处理大规模数据时。数据丢失通常会发生在数据从Source(源)到Channel(通道),或从Channel到Sink(汇)传输的过程中。如果不处理得当,Flume的崩溃或网络故障可能会导致丢失的数据无法恢复。以下是几种常见的F…...

文件处理新纪元:微信小程序的‘快递员’与‘整理师’
嗨,我是中二青年阿佑,今天阿佑将带领大家如何通过巧妙的文件处理功能,让用户体验从‘杂乱无章’到‘井井有条’的转变! 文章目录 微信小程序的文件处理文件上传:小程序的“快递服务”文件下载:小程序的“超…...

应付账款优化,自动化管理5要点
优化应付账款流程对企业现金流至关重要。通过自动化、规范采购订单、管理供应商、设计高效流程及保留数字记录,可显著提升效率与精确度。ZohoBooks在线财务记账软件助您简化应付账款处理,确保业务顺畅。 1、自动化您的应付账款流程 通过自动化你的应付账…...

Win安装Redis
目录 1、下载 2、解压文件并修改名称 3、前台简单启动 4、将redis设置成服务后台启动 5、命令启停redis 6、配置文件设置 1、下载 【下载地址】 2、解压文件并修改名称 3、前台简单启动 redis-server.exe redis.windows.conf 4、将redis设置成服务后台启动 redis-server -…...

手把手带你安装U9【win10+sql+U9】,同样适用U9C的安装
一、Win10操作系统设置 1、Windows 10内置账号administrator启用 a、登录到Windows 10系统以后,鼠标右键点击桌面左下角“win图标”,在弹出画面选择“命令提示符(管理员)”或”windows power shell(管理员)”,如下图: b、在”命令提示符(管理员)”或”windows power sh…...

若依前后端框架学习——新建模块(图文详解)
若依框架—新建模块 一、项目地址1、后端启动2、前端启动 二、生成代码1、添加菜单2、创建表结构3、生成代码2、编辑一些基本信息,然后点击提交3、生成代码,压缩包里有前端和后端代码 三、配置后端模块1、新建模块2. 修改pom.xlm2.1 修改第一个pom.xml 2…...

【LaTeX和Word版】写论文时如何调整公式和文字的间距
在撰写论文时,公式和文字段落的间距可能会显得不一致,特别是插入的公式占用单独一行时。这种情况下,可以通过以下两种方法来调整公式和文字段落的间距,使论文排版看起来更加整齐和一致。 1. 使用 LaTeX 调整段落间距 (1) 调整行…...

快乐数--双指针
一:题目 题目链接:. - 力扣(LeetCode) 二:算法原理 三:代码编写 int Sum(int n){int sum 0;while(n){sum pow(n%10,2);n / 10;}return sum;}bool isHappy(int n) {int slow n,fast Sum(n);while(slow …...

论文阅读-三维结构几何修复(导-4)
摘要 解决了3D数字模型中大洞修复的问题。 通过基于字典学习的方法解决了缺失区域推断问题,该方法利用从单个自相似结构和在线深度数据库中得出的几何先验。利用几何先验提供的线索,从洞的边界周围自适应地传播局部3D表面平滑性来恢复底层表面。在合成…...

数字货币交易所源码开发:场外(OTC)与币币交易所系统的构建指南
在区块链技术迅速发展的推动下,数字货币市场的需求大幅增加。数字货币交易所作为加密货币的主要交易场所,成为了开发者和企业关注的焦点。市场上有多种交易模式可供选择,最常见的是场外交易(OTC)和币币交易。本篇文章将…...

C++ 进阶:类相关特性的深入探讨
⭐在对C 中类的6个默认成员函数有了初步了解之后,现在我们进行对类相关特性的深入探讨! 🔥🔥🔥【C】类的默认成员函数:深入剖析与应用(上) 【C】类的默认成员函数:深入剖…...

C++ 多态、虚析构、模板类、常函数、虚继承、虚函数和纯虚函数相关知识和问题总结
1. C 中的多态 多态(Polymorphism)是面向对象编程中的一个重要特性,它允许使用相同的接口来表示不同的类型。由于派生类重写基类方法,然后用基类引用指向派生类对象,调用方法时候会进行动态绑定,这就是多态…...

计算机组成原理一句话
文章目录 计算机系统概述存储系统指令系统 计算机系统概述 指令和数据以同等地位存储在存储器中,形式上没有差别,但计算机应能区分他们。通过指令周期的不同阶段。 完整的计算机系统包括,1)软件系统:程序、文档和数据&…...

【Linux】僵尸进程和孤儿进程
一、僵尸进程 何为僵尸进程? 在 Unix/Linux 系统中,正常情况下,子进程是通过父进程创建的,且两者的运行是相互独立的,父进程永远无法预测子进程到底什么时候结束。当一个进程调用 exit 命令结束自己的生命时ÿ…...

Patchcore运行过程
论文github地址:https://github.com/amazon-science/patchcore-inspection 平台:autodl云服务器 1.将下载的代码上传到autodl-tmp/PatchCore里面解压,将数据集上传path_to_mvtec_folder/mvtec里,目录结构如图 2.安装依赖 cd au…...

一小时快速入门Android GPU Inspector
本文介绍如何使用 Android GPU Inspector (AGI) 对Android 应用进行系统性能分析和帧性能分析 。面向熟悉Android图形的开发者。 待分析应用需要的前置条件 (1) 将应用设置为可调试状态 <application [...] android:debuggable"true">(2)…...