第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…...

手游刚开服就被攻击怎么办?如何防御DDoS?
开服初期是手游最脆弱的阶段,极易成为DDoS攻击的目标。一旦遭遇攻击,可能导致服务器瘫痪、玩家流失,甚至造成巨大经济损失。本文为开发者提供一套简洁有效的应急与防御方案,帮助快速应对并构建长期防护体系。 一、遭遇攻击的紧急应…...

以下是对华为 HarmonyOS NETX 5属性动画(ArkTS)文档的结构化整理,通过层级标题、表格和代码块提升可读性:
一、属性动画概述NETX 作用:实现组件通用属性的渐变过渡效果,提升用户体验。支持属性:width、height、backgroundColor、opacity、scale、rotate、translate等。注意事项: 布局类属性(如宽高)变化时&#…...

理解 MCP 工作流:使用 Ollama 和 LangChain 构建本地 MCP 客户端
🌟 什么是 MCP? 模型控制协议 (MCP) 是一种创新的协议,旨在无缝连接 AI 模型与应用程序。 MCP 是一个开源协议,它标准化了我们的 LLM 应用程序连接所需工具和数据源并与之协作的方式。 可以把它想象成你的 AI 模型 和想要使用它…...

智能在线客服平台:数字化时代企业连接用户的 AI 中枢
随着互联网技术的飞速发展,消费者期望能够随时随地与企业进行交流。在线客服平台作为连接企业与客户的重要桥梁,不仅优化了客户体验,还提升了企业的服务效率和市场竞争力。本文将探讨在线客服平台的重要性、技术进展、实际应用,并…...

什么是库存周转?如何用进销存系统提高库存周转率?
你可能听说过这样一句话: “利润不是赚出来的,是管出来的。” 尤其是在制造业、批发零售、电商这类“货堆成山”的行业,很多企业看着销售不错,账上却没钱、利润也不见了,一翻库存才发现: 一堆卖不动的旧货…...

ServerTrust 并非唯一
NSURLAuthenticationMethodServerTrust 只是 authenticationMethod 的冰山一角 要理解 NSURLAuthenticationMethodServerTrust, 首先要明白它只是 authenticationMethod 的选项之一, 并非唯一 1 先厘清概念 点说明authenticationMethodURLAuthenticationChallenge.protectionS…...
Robots.txt 文件
什么是robots.txt? robots.txt 是一个位于网站根目录下的文本文件(如:https://example.com/robots.txt),它用于指导网络爬虫(如搜索引擎的蜘蛛程序)如何抓取该网站的内容。这个文件遵循 Robots…...
LLM基础1_语言模型如何处理文本
基于GitHub项目:https://github.com/datawhalechina/llms-from-scratch-cn 工具介绍 tiktoken:OpenAI开发的专业"分词器" torch:Facebook开发的强力计算引擎,相当于超级计算器 理解词嵌入:给词语画"…...
DeepSeek 技术赋能无人农场协同作业:用 AI 重构农田管理 “神经网”
目录 一、引言二、DeepSeek 技术大揭秘2.1 核心架构解析2.2 关键技术剖析 三、智能农业无人农场协同作业现状3.1 发展现状概述3.2 协同作业模式介绍 四、DeepSeek 的 “农场奇妙游”4.1 数据处理与分析4.2 作物生长监测与预测4.3 病虫害防治4.4 农机协同作业调度 五、实际案例大…...

SAP学习笔记 - 开发26 - 前端Fiori开发 OData V2 和 V4 的差异 (Deepseek整理)
上一章用到了V2 的概念,其实 Fiori当中还有 V4,咱们这一章来总结一下 V2 和 V4。 SAP学习笔记 - 开发25 - 前端Fiori开发 Remote OData Service(使用远端Odata服务),代理中间件(ui5-middleware-simpleproxy)-CSDN博客…...