当前位置: 首页 > news >正文

第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 层:包括 EmployeeDaoUserDao,分别用于对员工表和用户表的数据增删改查。
    • Service 层:包括 EmployeeServiceUserService,分别提供了员工相关的业务逻辑和用户管理的业务逻辑。
      • EmployeeService:提供 entry 方法,模拟员工入职操作。
      • UserService:提供 createUser 方法(创建用户)和 generateRandomPassword 方法(生成随机密码)。

这些类的业务逻辑非常常规,但本节课我们将通过 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();  // 模拟创建用户的过程}
}

运行代码后,可以看到控制台输出显示了 UserServiceUserDao 中各个方法的执行情况。


2. 6. Spring AOP - 方法执行时间打印需求实现

课程目标

通过 AOP 实现对 ServiceDAO 层中任意方法的执行时间进行打印,并避免在每个方法中手动增加日志打印代码。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 配置,使得在调用 ServiceDAO 方法时,自动打印方法的执行时间。

配置 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 运行效果

  • 在运行程序时,任何 ServiceDAO 层方法执行前,控制台都会打印方法执行的时间、类名和方法名。
  • 示例输出:
-----
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,返回值可以是 voidObject,具体取决于通知类型。
    • 需包含 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 目标类和目标方法

  • 目标类和目标方法:指真正执行业务逻辑的类和方法,例如 ServiceDAO 层中的 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. 方法结构与切点表达式

一个完整的方法结构包含以下部分:

  • 修饰符(如 publicprivate 等)。
  • 返回值类型(如 voidString)。
  • 类的完整路径(如 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))" />
  • 匹配参数为 Stringint 类型的方法。

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. 环绕通知的关键点

  • ProceedingJoinPointProceedingJoinPointJoinPoint 的升级版,除了获取目标方法的信息,还可以控制目标方法的执行。
    • 关键方法: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 的实现方式有两种:
    1. JDK 动态代理:当目标类实现了接口时,使用 JDK 动态代理来实现。
    2. 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. 引言

  • 静态代理: 手动创建代理类,代理类持有目标对象的引用并在实现方法中扩展业务逻辑。

    • 要点:
      1. 代理类和委托类必须实现相同的接口。
      2. 代理类需要持有委托类的对象。
  • 问题: 手动创建大量代理类会导致工作量庞大。

10. 2. JDK动态代理概述

  • 引入: JDK 12后,使用反射机制自动生成代理类。
  • 动态代理: 代理类在运行时根据接口结构生成,而不需要手动书写。
  • 区别: 静态代理手动编写,动态代理则在内存中动态生成。

10. 3. 代码实现步骤

3.1 创建工程

  • 创建新的工程,设置 group IDartifact,工程名为 s05

3.2 编写代码

  • 复制之前的 UserService 接口和 UserServiceImpl 实现类。

3.3 创建 ProxyInvocationHandler

  • service 包中创建 ProxyInvocationHandler 类,实现 InvocationHandler 接口。
  • 实现 invoke 方法:
    • 参数:
      1. proxy: 代理类对象(由JDK动态代理生成)。
      2. method: 目标方法信息。
      3. 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 方法中:
    1. 创建目标对象 UserService userService = new UserServiceImpl()
    2. 实例化 ProxyInvocationHandler,传入目标对象。
    3. 使用 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 时,会经历三个主要步骤:
    1. 生成字节码文件: 在本地硬盘上创建一个 .class 文件,默认存放在 com.sun.proxy 包下。类名格式为 $Proxy0(以 $ 开头,加上数字)。
    2. 生成代理类: 代理类实现了原有的接口。
    3. 定义类: 通过 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方法,逻辑如下:
    1. 执行自定义前置处理代码。
    2. 调用父类的findById方法(使用super.findById())。
    3. 执行自定义后置处理代码。

12. 4. 示例演示

  • 通过之前写好的实例来展示CGlib的使用。
  • S03工程中,UserService类没有实现任何接口。
  • 断点调试程序,可以看到生成的类名包含$$EnhancerBySpringCGLIB,表明使用CGlib进行增强。

12. 5. 变更为接口实现

  • UserService类修改为实现一个接口IUserService
  • 修改引用为IUserService,保持方法调用部分不变。
  • 断点调试,生成的对象类名变为proxy,表示使用了JDK动态代理。

12. 6. AOP实现原理总结

  • 两种情况:
    1. 如果目标类实现了接口,Spring优先使用JDK动态代理生成代理类。
    2. 如果目标类没有实现接口,则使用CGlib通过继承对目标类进行扩展。
  • 了解这些细节在面试中回答Spring AOP实现原理时非常重要。

相关文章:

第21~22周Java主流框架入门-Spring 2.SpringAOP面向切面编程

1.Spring AOP (Aspect-Oriented Programming) 1. 1. 什么是 Spring AOP&#xff1f; AOP&#xff08;面向切面编程&#xff09; 是 Spring 提供的一种可插拔的组件技术&#xff0c;允许我们在软件运行过程中添加额外的功能。场景&#xff1a;假设有两个模块&#xff0c;用户管…...

Flutter不常用组件----InteractiveViewer

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

【LeetCode HOT 100】详细题解之二分查找篇

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

管理篇(顶级思维模型(31个))(待做)

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

十一、数据库配置

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

day02 -- docker

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

ecmascript标准

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

在Linux命令行中一行执行多个命令

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

u盘快速格式化后怎么恢复文件:深入解析与全面指南

U盘&#xff0c;凭借其小巧便携、易于使用的特点&#xff0c;成为了我们日常生活中不可或缺的数据存储工具。然而&#xff0c;有时为了清除病毒、解决文件系统错误或准备存储新数据&#xff0c;我们可能需要对U盘进行快速格式化。但这一操作往往伴随着一个严峻的问题&#xff1…...

青少年编程能力等级测评CPA C++(二级)试卷(2)

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

aws 把vpc残留删除干净

最近忘了把vpc 删干净导致又被收了冤大头钱 在删除vpc 的收发现又eni在使用&#xff0c;但是忘了是哪个资源在占用 先用命令行把占用的资源找出来停掉 使用 AWS 命令行界面&#xff08;CLI&#xff09;来查看 VPC 的使用情况 列出子网&#xff1a; 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 创建一个表需要用到以下语法 注释已经写清楚各种语法的含义&#xff0c;记住缩进是你程序运行的关键&#xff0c;因为程序是看你的缩进来判断你的运行逻辑&#xff0c;像我这个就是缩进不合理导致的报错 那么今天分享就到这里&#xff0c;谢…...

【MySQL 数据库】之--基础知识

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

Flume面试整理-如何处理Flume中的数据丢失

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

文件处理新纪元:微信小程序的‘快递员’与‘整理师’

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

应付账款优化,自动化管理5要点

优化应付账款流程对企业现金流至关重要。通过自动化、规范采购订单、管理供应商、设计高效流程及保留数字记录&#xff0c;可显著提升效率与精确度。ZohoBooks在线财务记账软件助您简化应付账款处理&#xff0c;确保业务顺畅。 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、编辑一些基本信息&#xff0c;然后点击提交3、生成代码&#xff0c;压缩包里有前端和后端代码 三、配置后端模块1、新建模块2. 修改pom.xlm2.1 修改第一个pom.xml 2…...

【LaTeX和Word版】写论文时如何调整公式和文字的间距

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

快乐数--双指针

一&#xff1a;题目 题目链接&#xff1a;. - 力扣&#xff08;LeetCode&#xff09; 二&#xff1a;算法原理 三&#xff1a;代码编写 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数字模型中大洞修复的问题。 通过基于字典学习的方法解决了缺失区域推断问题&#xff0c;该方法利用从单个自相似结构和在线深度数据库中得出的几何先验。利用几何先验提供的线索&#xff0c;从洞的边界周围自适应地传播局部3D表面平滑性来恢复底层表面。在合成…...

数字货币交易所源码开发:场外(OTC)与币币交易所系统的构建指南

在区块链技术迅速发展的推动下&#xff0c;数字货币市场的需求大幅增加。数字货币交易所作为加密货币的主要交易场所&#xff0c;成为了开发者和企业关注的焦点。市场上有多种交易模式可供选择&#xff0c;最常见的是场外交易&#xff08;OTC&#xff09;和币币交易。本篇文章将…...

C++ 进阶:类相关特性的深入探讨

⭐在对C 中类的6个默认成员函数有了初步了解之后&#xff0c;现在我们进行对类相关特性的深入探讨&#xff01; &#x1f525;&#x1f525;&#x1f525;【C】类的默认成员函数&#xff1a;深入剖析与应用&#xff08;上&#xff09; 【C】类的默认成员函数&#xff1a;深入剖…...

C++ 多态、虚析构、模板类、常函数、虚继承、虚函数和纯虚函数相关知识和问题总结

1. C 中的多态 多态&#xff08;Polymorphism&#xff09;是面向对象编程中的一个重要特性&#xff0c;它允许使用相同的接口来表示不同的类型。由于派生类重写基类方法&#xff0c;然后用基类引用指向派生类对象&#xff0c;调用方法时候会进行动态绑定&#xff0c;这就是多态…...

计算机组成原理一句话

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

【Linux】僵尸进程和孤儿进程

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

Patchcore运行过程

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

一小时快速入门Android GPU Inspector

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