Spring 框架从入门到精通(第二篇)—— 依赖注入(DI)与 AOP 面向切面编程
在第一篇博客中,我们掌握了 Spring IOC 容器的核心思想与 Bean 管理方法,解决了 “对象创建控制权” 的问题。但企业级开发中,仅创建对象不够 —— 还需处理 “对象间的依赖关系”(如 Service 依赖 Dao)和 “通用逻辑的重复编码”(如日志、权限)。
本篇将聚焦 Spring 的另外两大核心能力:依赖注入(DI) 与面向切面编程(AOP),结合《Spring 框架学习笔记.pdf》中的实战案例,带大家从 “会创建对象” 升级到 “会管理依赖、解耦通用逻辑”。
目录
一、依赖注入(DI):让 Spring 自动 “缝合” 对象依赖
1.1 为什么需要 DI?传统依赖管理的痛点
1.2 DI 的 3 种核心实现方式
方式 1:构造注入 —— 通过构造方法传递依赖
实战步骤(基于 XML 配置):
关键特性:
方式 2:设值注入 —— 通过 set 方法传递依赖
实战步骤(基于注解配置):
关键特性:
方式 3:自动注入 —— 让 Spring “智能匹配” 依赖
自动注入的 2 种核心策略:
自动注入的优缺点:
1.3 DI 注入不同数据类型的技巧
1. 注入基本类型与 String
2. 注入 List 集合
3. 注入 Map 集合
4. 注入 Properties 配置
二、AOP 基础:从代理模式到 AOP 核心概念
2.1 代理模式:AOP 的 “底层实现原理”
1. 静态代理:手动编写代理类
实现步骤:
静态代理的痛点:
2. 动态代理:Spring AOP 的 “核心实现”
(1)JDK 动态代理:基于接口的代理
实现步骤:
(2)CGLIB 动态代理:基于父类的代理
实现步骤:
两种动态代理的对比:
2.2 AOP 核心概念:理解 “切面” 与 “通知”
1. 切面(Aspect):“通用逻辑” 的载体
2. 连接点(Join Point):“可能被增强” 的位置
3. 通知(Advice):“何时执行” 的增强逻辑
4. 切点(Pointcut):“在哪里执行” 的筛选条件
5. 织入(Weaving):“将切面应用到目标对象” 的过程
概念关系讲解:
三、Spring AOP 实战:XML 与注解配置切面
3.1 基于 XML 配置 AOP
实战场景:为UserService的所有方法添加 “权限检查”(前置通知)和 “日志记录”(后置通知)。
步骤 1:编写切面类(通用逻辑)
步骤 2:XML 配置 AOP(核心)
步骤 3:测试 XML 配置 AOP
3.2 基于注解配置 AOP(企业主流)
步骤 1:编写注解式切面类
步骤 2:XML 开启注解 AOP 支持
步骤 3:测试注解配置 AOP
3.3 切点表达式(execution)详解
Execution表达式语法:
常用表达式示例:
四、总结与下一步预告
一、依赖注入(DI):让 Spring 自动 “缝合” 对象依赖
DI(Dependency Injection,依赖注入)是 IOC 思想的具体实现 —— 当一个对象(如UserService
)依赖另一个对象(如UserDao
)时,Spring 容器会自动将依赖对象 “注入” 到目标对象中,无需程序员手动new
或set
。文档中明确提到:“DI 的作用是给对象中的全局属性赋值,解除类与类之间的高耦合性”。
1.1 为什么需要 DI?传统依赖管理的痛点
先看一个传统开发中 “Service 依赖 Dao” 的案例:
// 传统方式:Service直接new Dao,耦合度极高
public class UserServiceImpl implements UserService {// 硬编码依赖:DaoImpl修改时,此处必须同步修改private UserDao userDao = new UserDaoImpl();@Overridepublic User findUser(int id) {return userDao.findById(id);}
}
这种写法有两个致命问题:
- 耦合紧:Service 与 Dao 的实现类强绑定,若后续将
UserDaoImpl
替换为UserDaoMyBatisImpl
,需修改 Service 代码; - 测试难:无法替换 Dao 为 Mock 对象(如模拟数据库查询结果),单元测试只能依赖真实数据库。
而通过 DI,Spring 会帮我们 “注入” 依赖对象,Service 只需声明 “需要什么”,无需关心 “依赖从哪来”:
// DI方式:依赖由容器注入,无硬编码
@Service
public class UserServiceImpl implements UserService {// 只声明依赖接口,不关心实现类@Autowiredprivate UserDao userDao;@Overridepublic User findUser(int id) {return userDao.findById(id);}
}
这种写法彻底解耦了 Service 与 Dao 的实现,修改 Dao 实现类时,只需调整 Spring 配置,无需改动业务代码。
1.2 DI 的 3 种核心实现方式
Spring 提供三种依赖注入方式,分别对应不同场景。文档中详细拆解了每种方式的配置步骤,我们结合案例逐一讲解。
方式 1:构造注入 —— 通过构造方法传递依赖
构造注入是 “强制性依赖” 的最佳选择(如 Service 必须依赖 Dao 才能工作),Spring 通过调用目标对象的有参构造,将依赖对象传入。
实战步骤(基于 XML 配置):
- 定义 Service 与 Dao 接口及实现类:
// Dao接口
public interface UserDao {User findById(int id);
}// Dao实现类
public class UserDaoImpl implements UserDao {@Overridepublic User findById(int id) {// 模拟数据库查询return new User(id, "张三", 20);}
}// Service接口
public interface UserService {User findUser(int id);
}// Service实现类(含依赖Dao的有参构造)
public class UserServiceImpl implements UserService {private UserDao userDao;// 有参构造:接收Dao依赖public UserServiceImpl(UserDao userDao) {this.userDao = userDao;}@Overridepublic User findUser(int id) {return userDao.findById(id);}
}
- XML 配置 Bean 与构造注入:
通过<constructor-arg>
标签指定构造方法的参数,ref
属性引用依赖的 Bean(Dao):
<!-- 1. 配置Dao Bean -->
<bean id="userDao" class="com.jr.dao.impl.UserDaoImpl"/><!-- 2. 配置Service Bean,通过构造注入Dao -->
<bean id="userService" class="com.jr.service.impl.UserServiceImpl"><!-- ref:引用已配置的userDao Bean --><constructor-arg ref="userDao"/>
</bean>
- 测试构造注入:
@Test
public void testConstructorInjection() {ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");UserService service = context.getBean("userService", UserService.class);User user = service.findUser(1);System.out.println(user); // 输出:User{id=1, name='张三', age=20}
}
关键特性:
- 强制性:若未注入依赖(如忘记配置
<constructor-arg>
),Spring 启动时会报错,避免运行时 NullPointerException; - 顺序无关:可通过
name
或type
属性指定参数,无需严格匹配构造方法参数顺序(如<constructor-arg name="userDao" ref="userDao"/>
)。
方式 2:设值注入 —— 通过 set 方法传递依赖
设值注入是 “可选性依赖” 的常用方式(如 Service 可选择是否依赖日志组件),Spring 通过调用目标对象的set
方法注入依赖,文档中称其 “执行属性的 set 方法,结合<property>
标签实现”。
实战步骤(基于注解配置):
- 修改 Service 实现类,添加 set 方法:
@Service // 注解声明Bean
public class UserServiceImpl implements UserService {private UserDao userDao;// set方法:供Spring注入依赖public void setUserDao(UserDao userDao) {this.userDao = userDao;}@Overridepublic User findUser(int id) {return userDao.findById(id);}
}// Dao实现类添加注解
@Repository
public class UserDaoImpl implements UserDao {// ... 实现不变
}
- XML 开启注解扫描,或用 @Autowired 自动注入:
方式一:XML 配置设值注入(通过<property>
标签):
<!-- 开启注解扫描,扫描@Service、@Repository -->
<context:component-scan base-package="com.jr"/><!-- 或手动配置Bean,通过<property>设值注入 -->
<bean id="userService" class="com.jr.service.impl.UserServiceImpl"><!-- name:对应set方法名(setUserDao→userDao),ref:引用Dao Bean --><property name="userDao" ref="userDao"/>
</bean>
方式二:用@Autowired
注解自动注入(无需 XML 配置<property>
):
@Service
public class UserServiceImpl implements UserService {// @Autowired:自动按类型匹配注入UserDao@Autowiredprivate UserDao userDao;// 无需手动写set方法(Spring可通过反射注入)@Overridepublic User findUser(int id) {return userDao.findById(id);}
}
- 测试设值注入:
@Test
public void testSetterInjection() {ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");UserService service = context.getBean(UserService.class); // 按类型获取BeanUser user = service.findUser(2);System.out.println(user); // 输出:User{id=2, name='李四', age=25}
}
关键特性:
- 灵活性:依赖可选,若未注入依赖,属性值为
null
(需在代码中处理空值); - 扩展性:可后续通过
set
方法修改依赖(如切换 Dao 实现类)。
方式 3:自动注入 —— 让 Spring “智能匹配” 依赖
当 Bean 数量较多时,手动配置<constructor-arg>
或<property>
会很繁琐。Spring 提供 “自动注入” 功能,可按 “名称” 或 “类型” 自动匹配依赖,文档中提到 “自动注入分全局配置和局部配置,支持 5 个可选值”。
自动注入的 2 种核心策略:
- 按名称注入(byName):
Spring 会寻找 “Bean id 与目标属性名相同” 的依赖并注入。例如:Service 的userDao
属性,会自动匹配id="userDao"
的 Bean。
配置示例(XML 局部自动注入):
<!-- autowire="byName":按属性名匹配依赖 -->
<bean id="userService" class="com.jr.service.impl.UserServiceImpl" autowire="byName"/>
<bean id="userDao" class="com.jr.dao.impl.UserDaoImpl"/> <!-- id与属性名一致 -->
- 按类型注入(byType):
Spring 会寻找 “类型与目标属性类型相同” 的依赖并注入,若存在多个同类型 Bean,会报错。
配置示例(XML 全局自动注入):
在<beans>
根标签配置default-autowire="byType"
,所有 Bean 默认按类型自动注入:
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsd"default-autowire="byType"> <!-- 全局按类型自动注入 --><bean id="userDao" class="com.jr.dao.impl.UserDaoImpl"/><bean id="userService" class="com.jr.service.impl.UserServiceImpl"/> <!-- 自动注入UserDao --></beans>
自动注入的优缺点:
- 优点:减少配置代码,简化开发;
- 缺点:依赖匹配逻辑隐式,排查依赖问题时难度增加(建议复杂项目慎用全局自动注入,优先用
@Autowired
注解显式注入)。
1.3 DI 注入不同数据类型的技巧
除了注入 “对象类型” 依赖(如 Dao、Service),实际开发中还需注入 “基本类型”(如 String、int)、“集合类型”(如 List、Map)等。文档中详细列出了不同数据类型的注入方式,我们整理成实战案例。
1. 注入基本类型与 String
通过<property>
或<constructor-arg>
的value
属性注入(区别于ref
引用对象):
<bean id="user" class="com.jr.pojo.User"><property name="id" value="1"/> <!-- int类型 --><property name="name" value="张三"/> <!-- String类型 --><property name="age" value="20"/> <!-- int类型 -->
</bean>
2. 注入 List 集合
通过<list>
标签包裹<value>
或<ref>
,注入多个元素:
<bean id="userService" class="com.jr.service.impl.UserServiceImpl"><property name="userList"><list><ref bean="user1"/> <!-- 注入对象 --><ref bean="user2"/><value>普通字符串</value> <!-- 注入基本类型 --></list></property>
</bean><!-- 配置两个User Bean -->
<bean id="user1" class="com.jr.pojo.User"><property name="name" value="张三"/>
</bean>
<bean id="user2" class="com.jr.pojo.User"><property name="name" value="李四"/>
</bean>
3. 注入 Map 集合
通过<map>
标签包裹<entry>
,key
和value
可分别指定类型:
<bean id="userService" class="com.jr.service.impl.UserServiceImpl"><property name="userMap"><map><entry key="1" value-ref="user1"/> <!-- key=String,value=对象 --><entry key="2" value-ref="user2"/><entry key="3" value="普通值"/> <!-- key=String,value=String --></map></property>
</bean>
4. 注入 Properties 配置
通过<props>
标签注入键值对(常用于加载配置文件参数):
<bean id="config" class="com.jr.config.DBConfig"><property name="dbProps"><props><prop key="driver">com.mysql.cj.jdbc.Driver</prop><prop key="url">jdbc:mysql://localhost:3306/test</prop></props></property>
</bean>
二、AOP 基础:从代理模式到 AOP 核心概念
解决了 “依赖管理” 问题后,我们面临另一个痛点:通用逻辑的重复编码(如日志记录、权限检查、事务控制)。例如,每个 Service 方法都需要 “执行前检查权限,执行后记录日志”,传统写法会导致代码冗余。Spring AOP(面向切面编程)正是为解决这一问题而生,文档中定义 AOP“是 OOP 的有益补充,通过横向切割提取横切逻辑”。
2.1 代理模式:AOP 的 “底层实现原理”
AOP 的核心思想源于 “代理模式”—— 通过一个 “代理对象” 包裹 “目标对象”,在不修改目标对象代码的前提下,为其添加额外功能(如日志、权限)。文档中将代理模式分为 “静态代理” 和 “动态代理”,我们先从静态代理理解其本质。
1. 静态代理:手动编写代理类
场景:为UserService
的findUser
方法添加 “权限检查” 和 “日志记录” 功能。
实现步骤:
定义目标接口与目标对象:
// 目标接口
public interface UserService {User findUser(int id);
}// 目标对象(业务逻辑)
public class UserServiceImpl implements UserService {@Overridepublic User findUser(int id) {// 核心业务逻辑:查询用户System.out.println("执行查询用户业务:id=" + id);return new User(id, "张三", 20);}
}
编写静态代理类:
代理类实现与目标对象相同的接口,在调用目标方法前后添加通用逻辑:
// 静态代理类:包裹目标对象,添加通用逻辑
public class UserServiceProxy implements UserService {// 持有目标对象引用private UserService target;public UserServiceProxy(UserService target) {this.target = target;}@Overridepublic User findUser(int id) {// 1. 前置增强:权限检查System.out.println("权限检查:允许查询用户");// 2. 调用目标对象核心业务User user = target.findUser(id);// 3. 后置增强:日志记录System.out.println("日志记录:查询用户id=" + id + "成功");return user;}
}
测试静态代理:
@Test
public void testStaticProxy() {// 目标对象UserService target = new UserServiceImpl();// 代理对象UserService proxy = new UserServiceProxy(target);// 调用代理对象方法(间接调用目标对象)proxy.findUser(1);
}
静态代理的痛点:
- 代码冗余:一个目标接口需对应一个代理类,若有 10 个 Service,需写 10 个代理类;
- 维护困难:若通用逻辑(如权限检查)需要修改,需改动所有代理类。
2. 动态代理:Spring AOP 的 “核心实现”
为解决静态代理的痛点,Spring AOP 采用 “动态代理”—— 在程序运行时,通过反射或字节码技术动态生成代理对象,无需手动编写代理类。文档中提到 Spring AOP 支持两种动态代理:JDK 动态代理(基于接口)和 CGLIB 动态代理(基于父类)。
(1)JDK 动态代理:基于接口的代理
JDK 动态代理是 Spring AOP 的默认实现,要求目标对象必须实现接口。
实现步骤:
编写动态代理处理器(InvocationHandler):
实现InvocationHandler
接口,在invoke
方法中定义 “增强逻辑” 和 “目标方法调用”:
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;// 动态代理处理器:通用增强逻辑
public class JdkProxyHandler implements InvocationHandler {// 目标对象private Object target;// 生成代理对象的方法public Object createProxy(Object target) {this.target = target;// Proxy.newProxyInstance:动态生成代理对象return Proxy.newProxyInstance(target.getClass().getClassLoader(), // 类加载器target.getClass().getInterfaces(), // 目标对象实现的接口this // 代理处理器);}// 核心方法:代理对象调用任何方法时,都会触发此方法@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {// 1. 前置增强:权限检查System.out.println("JDK动态代理:权限检查");// 2. 调用目标对象的核心方法Object result = method.invoke(target, args);// 3. 后置增强:日志记录System.out.println("JDK动态代理:日志记录");return result;}
}
测试 JDK 动态代理:
@Test
public void testJdkDynamicProxy() {// 1. 目标对象(必须实现接口)UserService target = new UserServiceImpl();// 2. 创建动态代理处理器JdkProxyHandler handler = new JdkProxyHandler();// 3. 动态生成代理对象UserService proxy = (UserService) handler.createProxy(target);// 4. 调用代理对象方法proxy.findUser(1);
}
(2)CGLIB 动态代理:基于父类的代理
若目标对象未实现接口(如普通 POJO 类),JDK 动态代理无法使用,此时 Spring 会自动切换为 CGLIB 动态代理 —— 通过生成目标对象的 “子类” 作为代理对象。
实现步骤:
-
添加 CGLIB 依赖(Maven):
Spring 核心依赖已包含 CGLIB,无需额外导入,文档中列出了spring-core
等依赖。 -
编写 CGLIB 代理处理器(MethodInterceptor):
实现MethodInterceptor
接口,在intercept
方法中定义增强逻辑:
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;// CGLIB代理处理器
public class CglibProxyHandler implements MethodInterceptor {// 目标对象private Object target;// 生成代理对象(子类)public Object createProxy(Object target) {this.target = target;Enhancer enhancer = new Enhancer();// 设置父类(目标对象)enhancer.setSuperclass(target.getClass());// 设置回调处理器enhancer.setCallback(this);// 生成并返回代理对象return enhancer.create();}// 核心方法:代理对象调用方法时触发@Overridepublic Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {// 1. 前置增强:权限检查System.out.println("CGLIB动态代理:权限检查");// 2. 调用目标对象方法(通过方法代理)Object result = methodProxy.invokeSuper(o, args);// 3. 后置增强:日志记录System.out.println("CGLIB动态代理:日志记录");return result;}
}
测试 CGLIB 动态代理:
@Test
public void testCglibDynamicProxy() {// 目标对象(无需实现接口)UserServiceImpl target = new UserServiceImpl();// 创建CGLIB代理处理器CglibProxyHandler handler = new CglibProxyHandler();// 动态生成代理对象(子类)UserServiceImpl proxy = (UserServiceImpl) handler.createProxy(target);// 调用代理对象方法proxy.findUser(1);
}
两种动态代理的对比:
代理类型 | 依赖条件 | 优点 | 缺点 |
---|---|---|---|
JDK 动态代理 | 目标对象必须实现接口 | 无需额外依赖,JDK 原生支持 | 不支持无接口的类 |
CGLIB 动态代理 | 无(可代理普通类) | 支持所有类,性能略高 | 生成子类,无法代理 final 类 /method |
2.2 AOP 核心概念:理解 “切面” 与 “通知”
通过代理模式,我们实现了 “通用逻辑与业务逻辑的分离”,但还需一套 “标准化术语” 描述 AOP 的组成部分。文档中定义了 AOP 的 5 个核心要素,我们结合 “日志增强” 场景逐一解释:
1. 切面(Aspect):“通用逻辑” 的载体
切面是 “横切逻辑” 的封装类(如日志切面、权限切面),包含 “何时执行”(通知)和 “在哪里执行”(切点)的定义。例如,LogAspect
类就是一个切面,封装了日志记录的逻辑。
2. 连接点(Join Point):“可能被增强” 的位置
连接点是程序运行过程中 “可以插入切面” 的点,通常是方法的执行(如UserService.findUser
、OrderService.createOrder
)。Spring AOP 仅支持 “方法级别的连接点”。
3. 通知(Advice):“何时执行” 的增强逻辑
通知定义了 “切面在连接点的哪个时机执行”,Spring AOP 支持 5 种通知类型,文档中明确了每种通知的执行时机:
- 前置通知(Before):目标方法执行前执行(如权限检查);
- 后置通知(After):目标方法执行后执行(无论是否异常,如释放资源);
- 返回通知(AfterReturning):目标方法正常返回后执行(如日志记录);
- 异常通知(AfterThrowing):目标方法抛出异常后执行(如异常报警);
- 环绕通知(Around):包裹目标方法,可自定义执行时机(最灵活,如事务控制)。
4. 切点(Pointcut):“在哪里执行” 的筛选条件
切点是 “符合条件的连接点”—— 通过表达式筛选出需要增强的方法(如 “所有 ServiceImpl 类的 add * 方法”)。例如,execution(* com.jr.service.impl.*.add*(..))
就是一个切点表达式,匹配所有 Service 实现类的 “add 开头” 方法。
5. 织入(Weaving):“将切面应用到目标对象” 的过程
织入是 Spring 将切面动态 “缝合” 到目标对象的过程,分为 3 个时机:
- 编译期织入:编译时将切面代码植入目标类(需特殊编译器,如 AspectJ);
- 类加载期织入:类加载时通过字节码技术植入切面(如 Spring 的 LoadTimeWeaver);
- 运行期织入:程序运行时通过动态代理生成代理对象(Spring AOP 默认方式)。
概念关系讲解:
以 “给 UserService 的 findUser 方法添加日志” 为例:
- 切面(Aspect):
LogAspect
(封装日志逻辑); - 连接点(Join Point):
UserService.findUser
、UserService.deleteUser
等方法; - 切点(Pointcut):
execution(* com.jr.service.UserService.findUser(..))
(筛选出 findUser 方法); - 通知(Advice):
@AfterReturning
(findUser 正常返回后记录日志); - 织入(Weaving):Spring 运行时生成 UserService 的代理对象,植入日志逻辑。
三、Spring AOP 实战:XML 与注解配置切面
理解了 AOP 的核心概念后,我们通过实战掌握 Spring AOP 的两种配置方式:XML 配置(传统方式)和注解配置(企业主流),文档中对两种方式都有详细案例。
3.1 基于 XML 配置 AOP
实战场景:为UserService
的所有方法添加 “权限检查”(前置通知)和 “日志记录”(后置通知)。
步骤 1:编写切面类(通用逻辑)
// 切面类:封装权限检查和日志记录逻辑
public class AuthLogAspect {// 前置通知:权限检查public void checkAuth() {System.out.println("AOP前置通知:权限检查通过");}// 后置通知:日志记录public void recordLog() {System.out.println("AOP后置通知:记录方法执行日志");}
}
步骤 2:XML 配置 AOP(核心)
需引入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:aop="http://www.springframework.org/schema/aop" <!-- 引入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-4.3.xsd"><!-- 1. 配置目标对象(UserService) --><bean id="userService" class="com.jr.service.impl.UserServiceImpl"/><!-- 2. 配置切面类(AuthLogAspect) --><bean id="authLogAspect" class="com.jr.aspect.AuthLogAspect"/><!-- 3. AOP配置:关联切面、切点、通知 --><aop:config><!-- 3.1 配置切面:ref引用切面Bean --><aop:aspect ref="authLogAspect"><!-- 3.2 配置切点:定义需要增强的方法 --><aop:pointcut id="userServicePointcut" expression="execution(* com.jr.service.impl.UserServiceImpl.*(..))"/><!-- 3.3 配置通知:关联切点和增强方法 --><aop:before method="checkAuth" pointcut-ref="userServicePointcut"/> <!-- 前置通知 --><aop:after method="recordLog" pointcut-ref="userServicePointcut"/> <!-- 后置通知 --></aop:aspect></aop:config>
</beans>
步骤 3:测试 XML 配置 AOP
@Test
public void testAopByXml() {ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext-aop.xml");UserService service = context.getBean("userService", UserService.class);service.findUser(1); // 调用目标方法
}
3.2 基于注解配置 AOP(企业主流)
注解配置比 XML 更简洁,通过@Aspect
、@Pointcut
、@Before
等注解即可完成 AOP 配置,文档中称其 “自动为 @AspectJ 切面的 Bean 创建代理”。
步骤 1:编写注解式切面类
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;// @Component:声明为Spring Bean
// @Aspect:声明为切面类
@Component
@Aspect
public class AuthLogAspect {// 1. 配置切点:@Pointcut定义切点表达式,方法体为空@Pointcut("execution(* com.jr.service.impl.UserServiceImpl.*(..))")public void userServicePointcut() {} // 切点标识方法// 2. 前置通知:@Before关联切点@Before("userServicePointcut()")public void checkAuth() {System.out.println("AOP注解前置通知:权限检查通过");}// 3. 后置通知:@After关联切点@After("userServicePointcut()")public void recordLog() {System.out.println("AOP注解后置通知:记录方法执行日志");}
}
步骤 2:XML 开启注解 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:aop="http://www.springframework.org/schema/aop"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/aophttp://www.springframework.org/schema/aop/spring-aop-4.3.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context-4.3.xsd"><!-- 1. 开启注解扫描,扫描@Component、@Service等 --><context:component-scan base-package="com.jr"/><!-- 2. 开启注解AOP支持:自动代理@Aspect切面 --><aop:aspectj-autoproxy/><!-- 3. 配置目标对象(或通过@Service注解扫描) --><bean id="userService" class="com.jr.service.impl.UserServiceImpl"/>
</beans>
步骤 3:测试注解配置 AOP
@Test
public void testAopByAnnotation() {ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext-aop-anno.xml");UserService service = context.getBean(UserService.class);service.findUser(1);
}
3.3 切点表达式(execution)详解
切点表达式是 AOP 的 “核心筛选规则”,用于定义 “哪些方法需要被增强”。文档中详细讲解了execution
表达式的语法,其完整结构为:
execution( [访问修饰符] 返回值类型 包名.类名.方法名(参数类型) [异常类型] )
其中,除 “返回值类型”“包名。类名。方法名”“参数类型” 外,其他部分均可省略。
Execution表达式语法:
execution((访问修饰符(可省略) 返回值类型 包.包.类.方法名(参数) )异常类型?)
除了返回类型模式,方法名模式和参数模式外,其他项都是可选的。
通配符 | 含义 | 示例 |
---|---|---|
* | 匹配任意字符(1 个) | *Service :匹配 XxxService 类 |
.. | 匹配任意字符(0 个或多个,支持子包) | com.jr..* :匹配 com.jr 子包 |
+ | 匹配指定类及其子类 | UserService+ :匹配接口及实现 |
常用表达式示例:
匹配所有 public 方法:
execution(public * *(..))
public
:访问修饰符;*
:返回值类型(任意);*(..)
:任意方法名,任意参数。
匹配 UserService 接口的所有方法:
execution(* com.jr.service.UserService.*(..))
com.jr.service.UserService.*
:UserService 接口的所有方法。
匹配 ServiceImpl 包下所有类的 add 开头方法:
execution(* com.jr.service.impl.*.add*(..))
com.jr.service.impl.*
:ServiceImpl 包下的所有类;add*
:add 开头的方法;(..)
:任意参数(0 个或多个)。
匹配 com.jr 包及其子包下所有类的方法:
execution(* com.jr..*.*(..))
com.jr..*
:com.jr 包及其子包下的所有类。
四、总结与下一步预告
本篇博客我们聚焦 Spring 的 “依赖管理” 与 “通用逻辑解耦”,核心知识点总结如下:
- 依赖注入(DI):掌握了构造注入(强制依赖)、设值注入(可选依赖)、自动注入(智能匹配),以及不同数据类型(基本类型、集合、Properties)的注入技巧,解决了 “对象间高耦合” 问题;
- AOP 基础:理解了代理模式(静态 / 动态)是 AOP 的底层原理,掌握了 AOP 的 5 个核心概念(切面、连接点、通知、切点、织入);
- AOP 实战:通过 XML 和注解两种方式配置 AOP,学会了
execution
切点表达式的编写,实现了 “通用逻辑与业务逻辑的分离”。
第三篇预告:我们将进入 Spring 的 “企业级实战” 阶段,重点讲解:
- Spring 整合 MyBatis:如何通过 Spring 管理 MyBatis 的 SqlSessionFactory、Mapper 接口,实现数据持久化;
- 声明式事务管理:基于 XML 和注解配置事务,解决 “多 SQL 操作的原子性” 问题;
- Spring 常用注解总结:梳理
@Component
、@Autowired
、@Transactional
等核心注解的使用场景与注意事项。
跟着系列博客一步步深入,你会发现 Spring 的两大核心(IOC/DI、AOP)是所有高级特性的基础 —— 整合 MyBatis 依赖 IOC 管理 Bean,事务管理依赖 AOP 织入增强逻辑。掌握这些内容,你就能独立开发 Spring 企业级应用,应对面试中的核心考点。