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

Spring框架(二)

目录

一、AOP入门案例

二、AOP的相关概念

2.1 AOP的概念

2.2 AOP的优势

2.3 AOP的底层原理

三、Spring的AOP技术

3.1 AOP相关术语

3.2 AOP配置文件方式的入门

3.3 切入点表达式

3.4 AOP通知类型

四、Spring的AOP技术-注解方式

4.1 AOP注解方式的入门

4.2 纯注解方式


一、AOP入门案例

1. 创建mavenJava项目,引入开发坐标

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>com.qcby</groupId><artifactId>springAOP01</artifactId><version>1.0-SNAPSHOT</version><dependencies><!--spring核心依赖--><dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>5.0.2.RELEASE</version></dependency><!--日志相关--><dependency><groupId>commons-logging</groupId><artifactId>commons-logging</artifactId><version>1.2</version></dependency><dependency><groupId>log4j</groupId><artifactId>log4j</artifactId><version>1.2.12</version></dependency><!--有单元测试的环境,Spring5 版本,Junit4.12 版本--><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.12</version><scope>test</scope></dependency><!--连接池 阿里巴巴 第三方--><dependency><groupId>com.alibaba</groupId><artifactId>druid</artifactId><version>1.1.10</version></dependency><!--mysql 驱动包--><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.25</version></dependency><!-- Spring 整合 Junit 测试的 jar 包 --><dependency><groupId>org.springframework</groupId><artifactId>spring-test</artifactId><version>5.0.2.RELEASE</version><scope>test</scope></dependency></dependencies></project>

2. 创建TxUtils 类,进行事务管理

package com.qcby.utils;
import com.alibaba.druid.pool.DruidDataSource;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;/*** 事务的工具类*/
public class TxUtils {private static DruidDataSource ds = null;// 使用ThreadLocal存储当前线程中的Connection对象private static ThreadLocal<Connection> threadLocal = new ThreadLocal<Connection>();// 在静态代码块中创建数据库连接池static {try {// 通过代码创建C3P0数据库连接池ds = new DruidDataSource();ds.setDriverClassName("com.mysql.jdbc.Driver");ds.setUrl("jdbc:mysql:///spring_db");ds.setUsername("root");ds.setPassword("12345");} catch (Exception e) {throw new ExceptionInInitializerError(e);}}/*** @Method: getConnection* @Description: 从数据源中获取数据库连接* @Anthor:* @return Connection* @throws SQLException*/public static Connection getConnection() throws SQLException {// 从当前线程中获取ConnectionConnection conn = threadLocal.get();if (conn == null) {// 从数据源中获取数据库连接conn = getDataSource().getConnection();// 将conn绑定到当前线程threadLocal.set(conn);}return conn;}/*** @Method: startTransaction* @Description: 开启事务* @Anthor:**/public static void startTransaction() {try {Connection conn = threadLocal.get();if (conn == null) {conn = getConnection();// 把 conn绑定到当前线程上threadLocal.set(conn);}// 开启事务conn.setAutoCommit(false);} catch (Exception e) {throw new RuntimeException(e);}}/*** @Method: rollback* @Description:回滚事务* @Anthor:*/public static void rollback() {try {// 从当前线程中获取ConnectionConnection conn = threadLocal.get();if (conn != null) {// 回滚事务conn.rollback();}} catch (Exception e) {throw new RuntimeException(e);}}/*** @Method: commit* @Description:提交事务* @Anthor:*/public static void commit() {try {// 从当前线程中获取ConnectionConnection conn = threadLocal.get();if (conn != null) {// 提交事务conn.commit();}} catch (Exception e) {throw new RuntimeException(e);}}/*** @Method: close* @Description:关闭数据库连接(注意,并不是真的关闭,而是把连接还给数据库连接池)* @Anthor:**/public static void close() {try {// 从当前线程中获取ConnectionConnection conn = threadLocal.get();if (conn != null) {conn.close();// 解除当前线程上绑定connthreadLocal.remove();}} catch (Exception e) {throw new RuntimeException(e);}}/*** @Method: getDataSource* @Description: 获取数据源* @Anthor:* @return DataSource*/public static DataSource getDataSource() {// 从数据源中获取数据库连接return ds;}}

3. 编写持久层、业务层相关代码

package com.qcby.dao;import com.qcby.model.Account;import java.sql.SQLException;/*** AOP 概念引入需要的持久层接口* 模拟转账业务*/
public interface AccountDao {public void save(Account account) throws SQLException;
}package com.qcby.dao;import com.qcby.model.Account;
import com.qcby.utils.TxUtils;
import org.springframework.stereotype.Repository;import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;/*** AOP 概念引入需要的持久层接口* 模拟转账业务*/
@Repository("accountDao")
public class AccountDaoImpl implements AccountDao{public void save(Account account) throws SQLException {
//        // 把数据存储到数据库中
//        // 先获取到连接
//        Connection conn = null;
//        try {
//            conn = TxUtils.getConnection();
//            // 编写 sql 语句
//            String sql = "insert into account values (null,?,?)";
//            // 预编译 SQL 语句
//            PreparedStatement stmt = conn.prepareStatement(sql);
//            // 设置值
//            stmt.setString(1,account.getName());
//            stmt.setDouble(2,account.getMoney());
//            // 执行操作
//            stmt.executeUpdate();
//            // 关闭资源 ,conn 不能关闭
//            stmt.close();
//        } catch (SQLException e) {
//            e.printStackTrace();
//        }finally {
//            //TxUtils.close();
//        }Connection conn = TxUtils.getConnection();// 编写 sql 语句String sql = "insert into account values (null,?,?)";// 预编译 SQL 语句PreparedStatement stmt = conn.prepareStatement(sql);// 设置值stmt.setString(1,account.getName());stmt.setDouble(2,account.getMoney());// 执行操作stmt.executeUpdate();// 关闭资源 ,conn 不能关闭stmt.close();}
}
package com.qcby.service;import com.qcby.model.Account;import java.sql.SQLException;/*** AOP 概念引入需要的业务层接口* 模拟转账业务*/
public interface AccountService {//转账逻辑方法 account1扣款,account2增款public void saveAll(Account account1,Account account2) throws SQLException;
}package com.qcby.service.Impl;import com.qcby.dao.AccountDao;
import com.qcby.model.Account;
import com.qcby.service.AccountService;
import com.qcby.utils.TxUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Repository;
import org.springframework.stereotype.Service;import java.sql.SQLException;/*** AOP 概念引入需要的业务层实现类* 模拟转账业务*/
@Service
public class AccountServiceImpl implements AccountService{@Autowired@Qualifier("accountDao")private AccountDao accountDao;public void saveAll(Account account1, Account account2) throws SQLException {
//        try {
//            //开启事务
//            TxUtils.startTransaction();
//            //转账业务 扣款
//            accountDao.save(account1);
//            //模拟异常
//            //int i=10/0;
//            //收款
//            accountDao.save(account2);
//            TxUtils.commit();
//            System.out.println("转账成功!");
//        }catch (Exception e){
//            System.out.println("转账失败!!!");
//            TxUtils.rollback();
//            e.printStackTrace();
//        }finally {
//            TxUtils.close();
//        }//转账业务 扣款accountDao.save(account1);//模拟异常//int i=10/0;//收款accountDao.save(account2);}
}

4. 编写Spring核心配置文件

<?xml version="1.0" encoding="UTF-8"?>
<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"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context.xsd"><!-- 开启注解扫描 --><context:component-scan base-package="com.qcby"/><bean id="accountService" class="com.qcby.service.Impl.AccountServiceImpl"/></beans>

5. 生成代理对象

        代理对象——因为每进行一次转账业务,都需要在业务层(service层)进行开启事务、提交事务、出现异常回滚事务以及关闭资源的操作,所以我们使用代理对象进行优化处理,在代理对象中进行上述的相关操作(4种操作)以及对目标方法进行增强。有了代理对象后,在业务层的实现类中,我们就可以只进行转账的操作,事物的相关处理交给代理对象去处理。

package com.qcby.jdkUtils;import com.qcby.service.AccountService;
import com.qcby.utils.TxUtils;import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;/*** 传入目标对象,生成该对象的代理对象,返回。* 对目标对象的方法进行增强*/
public class JdkProxy {/*** 获取代理对象,返回,增强目标对象的方法* @param accountService* @return*/public static Object getProxy(final AccountService accountService){/*** 使用 Jdk 的动态代理生成代理对象*/Object proxy = Proxy.newProxyInstance(JdkProxy.class.getClassLoader(),accountService.getClass().getInterfaces(), new InvocationHandler(){/*** 调用代理对象的方法,invoke 方法就会去执行* @param proxy* @param method* @param args* @return* @throws Throwable*/public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {Object result = null;try {// 开启事务TxUtils.startTransaction();// 对象目标对象的方法进行增强result = method.invoke(accountService,args);// 提交事务TxUtils.commit();} catch (Exception e) {e.printStackTrace();TxUtils.rollback();} finally {// 关闭资源TxUtils.close();}return result;}});return proxy;}
}

6. 编写测试类

package com.qcby.springAOPTest;import com.qcby.jdkUtils.JdkProxy;
import com.qcby.model.Account;
import com.qcby.service.AccountService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;import java.sql.SQLException;@RunWith(value = SpringJUnit4ClassRunner.class) // 运行单元测试
@ContextConfiguration(value = "classpath:applicationContext.xml")
public class Demo1 {@Autowiredprivate AccountService accountService;@Testpublic void run() throws SQLException {
//        ApplicationContext ac = new
//                ClassPathXmlApplicationContext("applicationContext.xml");
//        // 获取 service 对象
//        AccountService accountService = (AccountService) ac.getBean("accountService");
//        Account account1 = new Account();
//        account1.setName("张三");
//        account1.setMoney(1000d);
//        Account account2 = new Account();
//        account2.setName("李四");
//        account2.setMoney(1000d);
//        accountService.saveAll(account1,account2);Account account1 = new Account();account1.setName("张三");account1.setMoney(1000d);Account account2 = new Account();account2.setName("李四");account2.setMoney(1000d);//代理//生成代理对象Object proxyobj=JdkProxy.getProxy(accountService);//强转AccountService proxy= (AccountService) proxyobj;//调用代理对象方法proxy.saveAll(account1,account2);}
}

        在上述代码中实现了新增账户的操作,若在创建账户的过程中发生异常则事务会执行回滚操作,若正常执行则事务进行提交,数据库中会正确插入两个账户的信息。

二、AOP的相关概念

2.1 AOP的概念

        AOP(Aspect Oriented Programming,面向切面编程)是一种编程范式,旨在通过预编译方式或运行期动态代理实现程序功能的统一维护。AOP最早由AOP联盟提出,Spring框架将其引入并遵循AOP联盟的规范。

2.2 AOP的优势

  • 减少重复代码:通过切面将横切关注点(如日志、事务管理)从业务逻辑中分离出来。
  • 提高开发效率:开发者可以专注于业务逻辑,而不必重复编写相同的代码。
  • 便于维护:横切关注点的修改只需在切面中进行,降低了代码的耦合度。

2.3 AOP的底层原理

  • JDK动态代理
    • 为目标对象创建代理类的字节码文件;
    • 使用ClassLoader将字节码文件加载到JVM;
    • 创建代理类实例对象,执行对象的目标方法。
  • CGLIB代理:通过继承目标类生成代理类。

三、Spring的AOP技术

3.1 AOP相关术语

Joinpoint(连接点):被拦截到的点,Spring中通常指方法(spring 只支持方法类型的连接点)。——在上述案例中相当于save方法

Pointcut(切入点):定义对哪些Joinpoint进行拦截。——也可以理解为save方法

Advice(通知/增强):拦截到Joinpoint后要做的事情就是通知,包括前置通知、后置通知、异常通知、最终通知、环绕通知(切面要完成的功能)。——相当于开启事务、提交事务、出现异常回滚事务以及关闭资源

Target(目标对象):代理的目标对象。——accountService

Weaving(织入):将增强应用到目标对象来创建新的代理对象的过程。

Proxy(代理):被AOP织入增强后产生的代理类。

Aspect(切面):切入点和通知的结合。

3.2 AOP配置文件方式的入门

1. 创建mavenJava项目,引入相关依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>com.qcby</groupId><artifactId>springAOP02</artifactId><version>1.0-SNAPSHOT</version><dependencies><dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>5.0.2.RELEASE</version></dependency><dependency><groupId>commons-logging</groupId><artifactId>commons-logging</artifactId><version>1.2</version></dependency><dependency><groupId>log4j</groupId><artifactId>log4j</artifactId><version>1.2.12</version></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-test</artifactId><version>5.0.2.RELEASE</version></dependency><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.12</version></dependency><!-- AOP 联盟 --><dependency><groupId>aopalliance</groupId><artifactId>aopalliance</artifactId><version>1.0</version></dependency><!-- Spring Aspects --><dependency><groupId>org.springframework</groupId><artifactId>spring-aspects</artifactId><version>5.0.2.RELEASE</version></dependency><!-- aspectj --><dependency><groupId>org.aspectj</groupId><artifactId>aspectjweaver</artifactId><version>1.8.3</version></dependency></dependencies></project>

2. Spring配置文件,引入AOP的schema约束

<?xml version="1.0" encoding="UTF-8"?>
<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/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/aophttp://www.springframework.org/schema/aop/spring-aop.xsd"></beans>

3. 创建包结构,编写具体的接口和实现类

package com.qcby.service;public interface UserService {public void save();
}package com.qcby.service.impl;import com.qcby.service.UserService;public class UserServiceImpl implements UserService {public void save() {System.out.println("业务层:保存用户...");}
}

将目标类配置到 Spring

<!--管理 bean 对象-->
<bean id="userService" class="com.qcby.service.impl.UserServiceImpl"></bean>

4. 定义切面类并进行配置

package com.qcby.aspect;/*** 自定义切面类 = 切入点(表达式) + 通知(增强的代码)*/
public class MyXmlAspect {/*** 通知*/public void log(){// 发送手机短信// 发送邮件/记录日志/事务管理System.out.println("增强的方法执行了...");}}

在配置文件中定义切面类和AOP配置:

<!--配置切面类,把该类交给 IOC 容器管理--><bean id="myXmlAspect" class="com.qcby.aspect.MyXmlAspect"></bean><!--配置 AOP 的增强--><aop:config><!--配置切面 = 切入点 + 通知组成--><aop:aspect ref="myXmlAspect"><!--AOP 的通知类型前置通知:目标方法执行前,进行增强。<aop:before method="log" pointcut="execution(*com.qcby.*.*ServiceImpl.save*(..))" />最终通知:目标方法执行成功或者失败,进行增强。<aop:after method="log" pointcut="execution(*com.qcby.*.*ServiceImpl.save*(..))" />后置通知:目标方法执行成功后,进行增强。<aop:after-returning method="log" pointcut="execution(* com.qcby.*.*ServiceImpl.save*(..))" />异常通知:目标方法执行失败后,进行增强。<aop:after-throwing method="log" pointcut="execution(* com.qcby.*.*ServiceImpl.save*(..))" />环绕通知:目标方法执行前后,都可以进行增强。目标对象的方法需要手动执行。--><!--前置通知:UserServiceImpl 的 save 方法执行前,会增强--><aop:before method="log"pointcut="execution(public void com.qcby.service.impl.UserServiceImpl.save())" /></aop:aspect></aop:config>

5. 编写测试类

package com.qcby.springAOP02Test;import com.qcby.service.UserService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class Demo {@Autowiredprivate UserService userService;/*** 测试*/@Testpublic void run1(){userService.save();}}

3.3 切入点表达式

execution([修饰符] 返回值类型 包名.类名.方法名(参数))
  • 修饰符​​(可选):如 publicprivate,通常可省略。

  • ​返回值类型​​(必填):int String 通用的写法,可用 * 通配任意返回值(如 * 表示任意返回值)。

  • ​包名.类名​​(必填):

    • 包名从最外层开始写,不可省略 com 等顶级包。

    • 可用 * 替代部分包名或类名(如 com.qcby.* 匹配 qcby 下所有类)。

    • 用 .. 表示当前包及其子包(如 com.qcby..* 匹配 qcby 及其子包下所有类)。

  • ​方法名​​(必填):可用 * 匹配任意方法名,或用 *前缀后缀* 匹配部分名称(如 save* 匹配 savesaveUser)。

  • ​参数列表​​(必填):

    • () 表示无参数。

    • (..) 表示任意数量和类型的参数。

    • (*) 表示单个任意参数。

    • (..,String) 表示最后一个参数为 String,前面可有多个任意参数。

比较通用的表达式:execution(public * com.qcby.*.*ServiceImpl.*(..))

3.4 AOP通知类型

通知类型xml方式注解说明
前置通知

aop:before

@Before目标方法执行前执行(进行增强)
最终通知

aop:after

@After目标方法执行后(无论是否成功),进行增强
后置通知

aop:after-returning

@AfterReturning目标方法成功执行后执行
异常通知

aop:after-throwing

@AfterThrowing目标方法抛出异常时/执行失败后执行
环绕通知无直接xml支持@Around

目标方法执行前后执行,目标对象的方法需要手动执行(手动调用方法)

四、SpringAOP技术-注解方式

4.1 AOP注解方式的入门

1. 创建maven工程,引入相关依赖

2. 编写切面类

给切面类添加注解 @Aspect,编写增强的方法,使用通知类型注解声明

package com.qcby.demo3;import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;/*** 切面类*/
@Component // 把该类交给 IOC 去管理
@Aspect // 声明是切面类 == <aop:aspect ref="myXmlAspect">
public class MyAnnoAspect {/*** 通知的方法*/@Before(value = "execution(public void com.qcby.service.impl.UserServiceImpl.save())")public void log(){System.out.println("增强了...");}
}

 3. 在Spring配置文件开启自动代理(启用注解扫描和AOP自动代理

<?xml version="1.0" encoding="UTF-8"?>
<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/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/aophttp://www.springframework.org/schema/aop/spring-aop.xsd"><!--管理 bean 对象--><bean id="userService" class="com.qcby.service.impl.UserServiceImpl"></bean><!-- 开启自动代理 --><aop:aspectj-autoproxy/></beans>

4. 编写测试类

package com.qcby.springAOP02Test;import com.qcby.service.UserService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class Demo3 {@Autowiredprivate UserService userService;/*** 测试*/@Testpublic void run1(){userService.save();}}

4.2 纯注解方式

1. 纯注解配置类

package com.qcby.demo4;import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;@Configuration // 配置类
@ComponentScan(value = "com.qcby.demo4") // 扫描包
@EnableAspectJAutoProxy // 开启自动代理 == <aop:aspectj-autoproxy/>
public class SpringConfig {}

2. 定义切面类

package com.qcby.demo4;import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;@Component  // 将切面类纳入IOC容器管理
@Aspect     // 声明为切面类
public class MyAnnoAspect {// 前置通知:目标方法执行前触发@Before("execution(* com.qcby.demo4.OrderServiceImpl.save(..))")public void log() {System.out.println("【AOP前置通知】增强了目标方法...");}
}

3. 编写具体的接口和实现类

package com.qcby.demo4;public interface OrderService {public void save();
}package com.qcby.demo4;import org.springframework.stereotype.Service;@Service  // 标记为Service层组件
public class OrderServiceImpl implements OrderService{public void save() {System.out.println("业务逻辑:保存订单成功!");}
}

4. 编写测试类实现

package com.qcby.springAOP02Test;import com.qcby.demo4.OrderService;
import com.qcby.demo4.SpringConfig;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;@RunWith(SpringJUnit4ClassRunner.class)  // 使用Spring测试运行器
@ContextConfiguration(classes = SpringConfig.class) // 加载纯注解配置类
public class demo4 {@Autowiredprivate OrderService orderService;@Testpublic void run1() {orderService.save();}
}

关键点说明

  1. 配置类

    • @Configuration 声明为配置类,替代XML文件。

    • @ComponentScan 指定包扫描路径,确保切面类和目标类被IOC容器管理。

    • @EnableAspectJAutoProxy 启用AOP自动代理,无需XML配置。

  2. 切面类

    • @Aspect 声明切面,@Component 确保切面类被扫描到。

    • 使用 @Before 定义前置通知,通过表达式 execution(...) 指定切入点。

  3. 测试类

    • @RunWith(SpringJUnit4ClassRunner.class) 集成Spring测试环境。

    • @ContextConfiguration(classes = SpringConfig.class) 加载纯注解配置。

    • 注入 OrderService 并调用方法,验证切面逻辑是否触发。

http://www.xdnf.cn/news/5741.html

相关文章:

  • 2025年渗透测试面试题总结-渗透测试红队面试八(题目+回答)
  • 使用 Kyverno 验证 Kubernetes 容器镜像:实用指南
  • AUTOSAR图解==>AUTOSAR_TR_AIMeasurementCalibrationDiagnostics
  • 软考 系统架构设计师系列知识点之杂项集萃(57)
  • IIS URL静态化 伪静态组件ISAPI_Rewrite安装配置 伪静态不生效解决办法 避坑版
  • 音视频学习:使用NDK编译FFmpeg动态库
  • 【002】renPy android端启动流程分析
  • 主播美颜API常见问题解析:兼容性、性能与SDK效果调优
  • 【MCP】其他MCP服务((GitHub)
  • 001大模型-认识大模型以及大模型应用场景
  • docker gaussdb常用命令
  • 从MCU到SoC的开发思维转变
  • 【Python】杂乱-转义字符
  • 安装 NVIDIA 驱动 570.133.20 的自动化脚本
  • 鲁滨逊归结原理详解:期末考点+解题指南
  • 【AI论文】HunyuanCustom:一种多模态驱动的定制视频生成架构
  • 自适应蒙特卡洛定位-AMCL
  • HomeBank:免费且强大的个人财务管理软件
  • 【MySQL】牛客网sql语句简单例题,sql入门
  • 使用Daemonset部署日志收集守护进程
  • snoop操作怎么维护一致性?
  • 射频ADRV9026驱动
  • 供应链学习
  • 电脑端实用软件合集:土拨鼠+Rufus+实时网速监控工具
  • IDEA+git将分支合并到主分支、IDEA合并分支
  • 力扣2094题解
  • langchain4j中使用milvus向量数据库做RAG增加索引
  • 激光雷达点云畸变消除:MCU vs CPU 方案详解
  • AI Agent(11):垂直行业应用
  • 如何避免Java中的ConcurrentModificationException