《Spring Framework 核心原理与实践指南》
文章目录
- 1.Spring 相关概念
- 1.1 Spring Framework 系统架构图
- 1.2 核心概念
- 2. IOC内容
- 2.1 bean的基础配置
- (1) 非注解写法和注意事项
- <1> 知识点速览
- <2> 准备工作
- <3> 获取IOC容器并调用方法 (id 和 class的使用)
- <4> 配置别名(name的使用)
- <5>测试单例/非单例(scope的使用)
- (2) 注解写法和注意事项
- <1> 知识点速览:
- <2> 改进方案:
- 2.2 bean实例化
- (1) 非注解写法和注意事项
- <1> 知识点速览
- <2> 准备工作
- <3> 构造方法实例化
- <4> 静态工厂实例化
- <5> 实例工厂与FactoryBean
- (2) 实例化bean的三种方式
- 2.3 bean的生命周期
- (1) 非注解写法和注意事项
- <1> 知识点速览
- <2> 准备工作
- <3> 生命周期设置
- a. 正常实现
- b. 基于接口实现
- (2) 注解写法和注意事项
- <1> 知识点速览
- <2> 改进方案
- 2.4 核心容器
- (1) 知识点速览
- (2) 容器的创建方式
- (3) Bean的三种获取方式
- (4) BeanFactory的使用
- 3. DI相关内容
- (1) 非注解写法和注意事项
- <1> 知识点速览
- <2> 准备工作
- <3> setter注入
- <4> 构造器注入
- <5> 自动装配
- <6> 集合注入
- (2) 注解写法和注意事项
- <1> 知识点速览
- <2> 改进方案
- 4.AOP相关内容
- (1) AOP的核心概念
- (2) 基于注解实现步骤
- <1> 知识点一览
- <2> 准备工作
- <3> 实现步骤
- (3) AOP工作流程
- (4) AOP配置管理
- <1> 切入点表达式
- <2> AOP通知类型
- a.知识点总结
- b. 实现
- <3> AOP通知获取参数
- (1) 获取参数(⭐)
- (2) 获取返回值
- (3) 获取异常(了解)
- (5) AOP事务管理
- <1> 知识点总览
- <2> 概念
- <3> 快速使用
- 5. 结语
1.Spring 相关概念
Spring 是一个开源的 Java 企业级应用开发框架,它的核心目标是简化企业级应用开发的复杂性,提供一站式的解决方案。
1.1 Spring Framework 系统架构图

在Spring这部分内容中主要包含IOC/DI和AOP,在后面也会基于这几个部分进行讲解。
1.2 核心概念
概念:
IOC (Inversion of Control) 控制反转:对象的创建控制权由程序转移到外部,这种思想称为控制反转。
即控制权从应用程序代码转移到框架容器,由容器负责管理对象生命周期和依赖关系。
Bean:在Spring中提供了IOC容器,而用于充当外部,负责对象的创建、初始化等工作,被创建或被管理的对象在IOC容器中的统称为Bean。
DI(Dependency Injection)依赖注入:在容器中建立bean与bean之间的依赖关系的整个过程,称为依赖注入
2. IOC内容
IOC主要是控制反转思想,在这里分为bean基础配置,bean实例化,bean的生命周期讲解,分别采用非注解,注解的写法。
同时讲解核心容器,也就是ApplicationContext。
2.1 bean的基础配置
(1) 非注解写法和注意事项

<1> 知识点速览
id:bean的id,使用容器可以通过id值获取对应的bean,在一个容器中id值唯一。
class:bean的类型,即配置的bean的全路径类名
name:bean的别名配置,别名可以有多个,使用逗号、分号、空格分隔
scope:负责控制bean的作用范围,有单例和非单例
<2> 准备工作
前置工作:创建Maven项目,添加Spring对应jar包和所需类
使用思路:添加Spring配置文件,完成配置,进行使用
Spring配置文件示例(在resources下建立)
resources是 Java 项目(尤其是 Maven/Gradle 等构建工具管理的项目)中专门用于存放非 Java 源代码的资源文件的标准目录
<?xml version="1.0" encoding="UTF-8"?>
<beans>
<!--文档前面内容省略--><bean id="bookDao" class="com.Coolipa.Dao.Impl.BookDaoImpl" scope="singleton" name="bookDao2 bookDao"/><bean id="bookService" class="com.Coolipa.Service.Impl.BookServiceImpl" scope="prototype"/>
</beans>
<3> 获取IOC容器并调用方法 (id 和 class的使用)
启动类通过配置文件中的xml接口完成IOC容器创建,并通过id和class调用内部的方法
配置文件关键点:
<bean id="bookDao" class="com.Coolipa.Dao.Impl.BookDaoImpl" />
启动类:
public class App {public static void main(String[] args) {ApplicationContext app = new ClassPathXmlApplicationContext("ApplicationContext.xml");BookDao bookDao= (BookDao) app.getBean("bookDao");bookDao.save();}
}
<4> 配置别名(name的使用)
id可能会由于命名习惯而产生分歧,可以利用别名去解决这个问题
配置文件关键点:
<bean id="bookDao" class="com.Coolipa.Dao.Impl.BookDaoImpl" name="bookDao2"/>
启动类:
public class App {public static void main(String[] args) {ApplicationContext app = new ClassPathXmlApplicationContext("ApplicationContext.xml");BookDao bookDao= (BookDao) app.getBean("bookDao2");bookDao.save();}
}
拓充:
可以利用bean依赖注入的ref属性指定bean,必须在容器中存在,不存在将会报错NoSuchBeanDefinitionException
<bean id ="bookService" name="service service2" class="com.Coolipa.Dao.Impl.BookDaoImpl"><property name="bookDao" ref="dao"/>
</bean>

<5>测试单例/非单例(scope的使用)
在默认情况下,Spring创建的Bean对象都是单例了,所以需要手动更改
配置文件关键点:
<bean id="bookDao" class="com.Coolipa.Dao.Impl.BookDaoImpl"
scope="singleton"/>
其中单例为singleton,非单例为prototype
拓充:
为什么bean默认为单例?
单例指在IOC容器中只会有该类的一个对象,bean对象只有一个就避免了对象的频繁创建与销毁,达到了bean对象的复用,性能高
bean在容器中是单例的,会不会产生线程安全问题?
如果对象有成员变量可以用来存储数据,由于线程共用一个bean对象,会存在线程安全问题。
如果对象没有成员变量进行数据存储,则方法中的局部变量在方法调用完毕后会被销毁,不会存在线程安全问题
(2) 注解写法和注意事项
<1> 知识点速览:
知识点1:@Component等
| 名称 | @Component/@Controller/@Service/@Repository |
|---|---|
| 类型 | 类注解 |
| 位置 | 类定义上方 |
| 作用 | 设置该类为spring管理的bean |
| 属性 | value(默认):定义bean的id |
知识点2:@Configuration
| 名称 | @Configuration |
|---|---|
| 类型 | 类注解 |
| 位置 | 类定义上方 |
| 作用 | 设置该类为spring配置类 |
| 属性 | value(默认):定义bean的id |
知识点3:@ComponentScan
| 名称 | @ComponentScan |
|---|---|
| 类型 | 类注解 |
| 位置 | 类定义上方 |
| 作用 | 设置spring配置类扫描路径,用于加载使用注解格式定义的bean |
| 属性 | value(默认):扫描路径,此路径可以逐层向下扫描 |
知识点4:@Scope
| 名称 | @Scope |
|---|---|
| 类型 | 类注解 |
| 位置 | 类定义上方 |
| 作用 | 设置该类创建对象的作用范围 可用于设置创建出的bean是否为单例对象 |
| 属性 | value(默认):定义bean作用范围, 默认值singleton(单例),可选值prototype(非单例) |
<2> 改进方案:
创建java的配置类,用@configuration注解替换配置类,用@ComponentScan替换指定class文件
配置类:
@Configuration
@Scope("singleton")
@ComponentScan("com.Coolipa")
public class SpringConfig {
}
被替换部分:
<bean id="bookDao" class="com.Coolipa.Dao.Impl.BookDaoImpl" scope="singleton" name="bookDao2"/><bean id="bookService" class="com.Coolipa.Service.Impl.BookServiceImpl" scope="prototype"/>
启动类:
public class App {public static void main(String[] args) {ApplicationContext app = new AnnotationConfigApplicationContext(SpringConfig.class); //名称和字节码都可以BookDao bookDao= (BookDao) app.getBean("bookDao");bookDao.save();BookService bookService =app.getBean(BookService.class);bookService.save();}
}
被替换部分对比:
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
ClassPathXmlApplicationContext是加载XML配置文件AnnotationConfigApplicationContext是加载配置类
2.2 bean实例化
这里主要为实例化bean的方式
(1) 非注解写法和注意事项
<1> 知识点速览
bean是由构造方法创建的
Spring的IOC实例化对象的三种方式分别为构造方法,静态方法,实例方法,主要掌握构造方法和实例方法中的FactoryBean。
需要注意的一点是,构造方法在类中默认会提供,但是如果重写了构造方法,默认的就会消失,在使用的过程中需要注意,如果需要重写构造方法,最好把默认的构造方法也重写下。
<2> 准备工作
前置工作:准备创建的类,配置类,启动类
<3> 构造方法实例化
在类中添加无参构造函数即可
public class BookDaoImpl implements BookDao {public BookDaoImpl() {System.out.println("book dao constructor is running ....");}public void save() {System.out.println("book dao save ...");}}
Spring容器在创建对象时用的就是构造函数(无参构造),同时Spring底层用的是反射(可以访问私有构造方法)
<4> 静态工厂实例化
配置类添加内容
class:工厂类全名
factory-method:具体工厂类中创建对象的方法名
<bean id="orderDao" class="com.itheima.factory.OrderDaoFactory" factory-method="getOrderDao"/>
编写运行类运行
public class AppForInstanceOrder {public static void main(String[] args) {ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");OrderDao orderDao = (OrderDao) ctx.getBean("orderDao");orderDao.save();}
}
在工厂静态方法,可以作其他的业务操作
<5> 实例工厂与FactoryBean
实例工厂:
配置类内容
<bean id="userFactory" class="com.itheima.factory.UserDaoFactory"/>
<bean id="userDao" factory-method="getUserDao" factory-bean="userFactory"/>
factory-bean:工厂的实例对象
factory-method:工厂对象中的具体创建对象的方法名
编写运行类,在类中通过工厂获取对象
public class AppForInstanceUser {public static void main(String[] args) {ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");UserDao userDao = (UserDao) ctx.getBean("userDao");userDao.save();}
}
FactoryBean:
创建类,实现FactoryBean接口,重写方法
public class UserDaoFactoryBean implements FactoryBean<UserDao> {//代替原始实例工厂中创建对象的方法public UserDao getObject() throws Exception {return new UserDaoImpl();}//返回所创建类的Class对象public Class<?> getObjectType() {return UserDao.class;}
}
FactoryBean接口有三个方法:
getObject():返回由FactoryBean创建的对象实例getObjectType():返回创建对象的类型isSingleton():默认返回true,表示创建的bean是单例的。可以通过重写此方法返回false来创建原型(prototype)bean
验证方法:可以通过在Spring容器中多次获取同一个FactoryBean创建的bean,观察是否为同一个实例来验证单例行为。
T getObject() throws Exception;Class<?> getObjectType();default boolean isSingleton() {return true;
}
(2) 实例化bean的三种方式
2.3 bean的生命周期
这里主要知识点为bean生命周期
(1) 非注解写法和注意事项
<1> 知识点速览
关于Spring中对bean生命周期提供两种方式
-
在配置文件中的bean标签,可以添加
init-method和destroy-method属性 -
类实现
InitializingBean与DisposableBean接口,这种方式了解下即可。
关闭容器分为close()和registerShutdownHook()两个方法
<2> 准备工作
前置工作:准备创建的类,配置类,启动类
<3> 生命周期设置
a. 正常实现
添加初始化和销毁方法
public class BookDaoImpl implements BookDao {public void save() {System.out.println("book dao save ...");}//表示bean初始化对应的操作public void init(){System.out.println("init...");}//表示bean销毁前对应的操作public void destory(){System.out.println("destory...");}
}
在配置文件添加配置,如下:
<bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl" init-method="init" destroy-method="destory"/>
由于在普通Java应用中,JVM退出时不会自动调用bean的销毁方法,可以通过以下两种方式触发:
-
显式关闭:使用
ClassPathXmlApplicationContext的close()方法ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml"); // 使用bean... ctx.close(); // 显式关闭容器,触发销毁方法 -
注册关闭钩子:使用
registerShutdownHook()方法,在JVM关闭时自动调用销毁方法ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml"); ctx.registerShutdownHook(); // 注册JVM关闭钩子
区别:
close():需要手动调用,立即触发销毁registerShutdownHook():自动在JVM关闭时触发,更适合生产环境
b. 基于接口实现
在实现类添加两个接口InitializingBean, DisposableBean并实现接口中的两个方法afterPropertiesSet和destroy
public class BookServiceImpl implements BookService, InitializingBean, DisposableBean {private BookDao bookDao;public void setBookDao(BookDao bookDao) {this.bookDao = bookDao;}public void save() {System.out.println("book service save ...");bookDao.save(); }public void destroy() throws Exception {System.out.println("service destroy");}public void afterPropertiesSet() throws Exception {System.out.println("service init");}
}
(2) 注解写法和注意事项
<1> 知识点速览
知识点1:@PostConstruct
| 名称 | @PostConstruct |
|---|---|
| 类型 | 方法注解 |
| 位置 | 方法上 |
| 作用 | 设置该方法为初始化方法 |
| 属性 | 无 |
知识点2:@PreDestroy
| 名称 | @PreDestroy |
|---|---|
| 类型 | 方法注解 |
| 位置 | 方法上 |
| 作用 | 设置该方法为销毁方法 |
<2> 改进方案
使用@PostConstruct、@PreDestroy定义bean生命周期
@Repository
//@Scope设置bean的作用范围
@Scope("singleton")
public class BookDaoImpl implements BookDao {public void save() {System.out.println("book dao save ...");}//@PostConstruct设置bean的初始化方法@PostConstructpublic void init() {System.out.println("init ...");}//@PreDestroy设置bean的销毁方法@PreDestroypublic void destroy() {System.out.println("destroy ...");}
}
2.4 核心容器
(1) 知识点速览
核心容器可以简单理解为ApplicationContext
-
容器创建的两种方式
- ClassPathXmlApplicationContext[掌握]
- FileSystemXmlApplicationContext[知道即可]
-
获取Bean的三种方式
- getBean(“名称”):需要类型转换
- getBean(“名称”,类型.class):多了一个参数
- getBean(类型.class):容器中不能有多个该类的bean对象
上述三种方式,各有各的优缺点,用哪个都可以。
-
容器类层次结构
- 只需要知晓容器的最上级的父接口为 BeanFactory即可
-
BeanFactory
- 使用BeanFactory创建的容器是延迟加载
- 使用ApplicationContext创建的容器是立即加载(可通过配置实现延迟加载)
- 具体BeanFactory如何创建只需要了解即可。
bean常用属性

依赖注入常用属性

(2) 容器的创建方式
分为类路径下的XML配置文件和文件系统下的XML配置文件
类路径下的XML配置文件
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
文件系统下的XML配置文件
ApplicationContext ctx = new FileSystemXmlApplicationContext("D:\\workspace\\spring\\spring_10_container\\src\\main\\resources\\applicationContext.xml");
(3) Bean的三种获取方式
方式一:
BookDao bookDao = (BookDao) ctx.getBean("bookDao");
缺点:需要强转
方式二:
BookDao bookDao = ctx.getBean("bookDao",BookDao.class);
缺点:需要多加一个参数
方式三:
BookDao bookDao = ctx.getBean(BookDao.class);
缺点:IOC容器中对应的bean对象只能有一个
(4) BeanFactory的使用
public class AppForBeanFactory {public static void main(String[] args) {Resource resources = new ClassPathResource("applicationContext.xml");BeanFactory bf = new XmlBeanFactory(resources);BookDao bookDao = bf.getBean(BookDao.class);bookDao.save();}
}
-
BeanFactory是延迟加载,只有在获取bean对象的时候才会去创建
-
ApplicationContext是立即加载,容器加载的时候就会创建bean对象
-
ApplicationContext要想成为延迟加载,只需要按照如下方式进行配置
3. DI相关内容
主要围绕引用类型和简单类型的注入方式进行讲解
(1) 非注解写法和注意事项
<1> 知识点速览
<2> 准备工作
<3> setter注入
通过set方法进行注入
引用类型
在配置类中使用propety和ref注入引用类型对象
<bean id="bookService" class="com.itheima.service.impl.BookServiceImpl"><!--property标签:设置注入属性--><!--name属性:设置注入的属性名,实际是set方法对应的名称--><!--ref属性:设置注入引用类型bean的id或name--><property name="bookDao" ref="bookDao"/></bean>
在bean中定义引用数据属性并提供set方法即可
public class BookServiceImpl implements BookService{private BookDao bookDao;//setter注入需要提供要注入对象的set方法public void setBookDao(BookDao bookDao) {this.bookDao = bookDao;}
}
简单类型
在配置类中使用propety和value属性注入简单类型数据
<bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl"><!--property标签:设置注入属性--><!--name属性:设置注入的属性名,实际是set方法对应的名称--><!--value属性:设置注入简单类型数据值--><property name="connectionNum" value="100"/><property name="databaseName" value="mysql"/></bean>
<4> 构造器注入
通过将set方法改为构造方法传参,进行注入
引用注入
配置类使用constructor-arg标签ref属性注入引用类型对象
<bean id="bookService" class="com.itheima.service.impl.BookServiceImpl"> <!-- 根据构造方法参数名称注入--><constructor-arg name="userDao" ref="userDao"/><constructor-arg name="bookDao" ref="bookDao"/></bean>
这里的ref指向的是参数,如果改成BookServiceImpl(BookDao bookDao1, UserDao userDao1),则配置中name指向userDao1和bookDao1
实现类中定义引用类型属性,并提供构造方法
public class BookServiceImpl implements BookService{private BookDao bookDao;private UserDao userDao;public BookServiceImpl(BookDao bookDao, UserDao userDao) {this.bookDao = bookDao;this.userDao = userDao;}public void save() {System.out.println("book service save ...");bookDao.save();userDao.save();}
}
简单类型
在配置类中使用constructor-arg标签和value属性注入简单类型数据
<bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl"><constructor-arg name="databaseName" value="mysql"/><constructor-arg name="connectionNum" value="666"/></bean><bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl"/><bean id="bookService" class="com.itheima.service.impl.BookServiceImpl"><constructor-arg name="bookDao" ref="bookDao"/><constructor-arg name="userDao" ref="userDao"/></bean>
在实体类添加构造方法
public class BookDaoImpl implements BookDao {private String databaseName;private int connectionNum;public BookDaoImpl(String databaseName, int connectionNum) {this.databaseName = databaseName;this.connectionNum = connectionNum;}public void save() {System.out.println("book dao save ..."+databaseName+","+connectionNum);}
}
在配置类中,可以添加type属性,按照类型注入,也可以添加index属性,按照索引下标注入
<5> 自动装配
在配置文件中,可以直接添加autowire属性进行注入
<bean class="com.itheima.dao.impl.BookDaoImpl"/><bean id="bookService" class="com.itheima.service.impl.BookServiceImpl" autowire="byType"/>
autowire属性可以按照类型(byType),可以按照名称(byName)
使用按类型装配时(byType)必须保障容器中相同类型的bean唯一,推荐使用
使用按名称装配时(byName)必须保障容器中具有指定名称的bean,因变量名与配置耦合,不推荐使用
自动装配优先级低于setter注入与构造器注入,同时出现时自动装配配置失效
<6> 集合注入
在配置类中直接注入即可
注入数组类型数据
<property name="array"><array><value>100</value><value>200</value><value>300</value></array>
</property>
注入List类型数据
<property name="list"><list><value>itcast</value><value>itheima</value><value>boxuegu</value><value>chuanzhihui</value></list>
</property>
注入Set类型数据
<property name="set"><set><value>itcast</value><value>itheima</value><value>boxuegu</value><value>boxuegu</value></set>
</property>
注入Map类型数据
<property name="map"><map><entry key="country" value="china"/><entry key="province" value="henan"/><entry key="city" value="kaifeng"/></map>
</property>
注入Properties类型数据
<property name="properties"><props><prop key="country">china</prop><prop key="province">henan</prop><prop key="city">kaifeng</prop></props>
</property>
实现类
public class BookDaoImpl implements BookDao {private int[] array;private List<String> list;private Set<String> set;private Map<String,String> map;private Properties properties;public void save() {System.out.println("book dao save ...");System.out.println("遍历数组:" + Arrays.toString(array));System.out.println("遍历List" + list);System.out.println("遍历Set" + set);System.out.println("遍历Map" + map);System.out.println("遍历Properties" + properties);}
}
(2) 注解写法和注意事项
<1> 知识点速览
知识点1:@Autowired
| 名称 | @Autowired |
|---|---|
| 类型 | 属性注解 或 方法注解(了解) 或 方法形参注解(了解) |
| 位置 | 构造方法、setter方法、字段、配置方法上 或 方法形参前面 |
| 作用 | 为引用类型属性设置值 |
| 属性 | required:true/false,定义该属性是否允许为null |
知识点2:@Qualifier
| 名称 | @Qualifier |
|---|---|
| 类型 | 属性注解 或 方法注解(了解) |
| 位置 | 属性定义上方 或 标准set方法上方 或 类set方法上方 |
| 作用 | 为引用类型属性指定注入的beanId |
| 属性 | value(默认):设置注入的beanId |
知识点3:@Value
| 名称 | @Value |
|---|---|
| 类型 | 属性注解 或 方法注解(了解) |
| 位置 | 属性定义上方 或 标准set方法上方 或 类set方法上方 |
| 作用 | 为 基本数据类型 或 字符串类型 属性设置值 |
| 属性 | value(默认):要注入的属性值 |
知识点4:@PropertySource
| 名称 | @PropertySource |
|---|---|
| 类型 | 类注解 |
| 位置 | 类定义上方 |
| 作用 | 加载properties文件中的属性值 |
| 属性 | value(默认):设置加载的properties文件对应的文件名或文件名组成的数组 |
<2> 改进方案
通过使用注解,可以按照类型、名称、数据类型,甚至可以读取properties配置文件
按照类型注入
使用@Autowired注解开启自动装配模式
@Service
public class BookServiceImpl implements BookService{@Autowiredprivate BookDao bookDao;public void save(){System.out.println("save");bookDao.save();}
}
基于反射设计常见对象,无需提供setter方法,建议使用无参构造方法构建对象
按照名称注入
使用@Qualifier注解开启指定名称装配bean
@Service
public class BookServiceImpl implements BookService{@Autowired@Qualifier("bookDao")private BookDao bookDao;
}
@Qualifier注解无法单独使用,必须配合@Autowired注解使用
简单数据类型注入
使用@Value实现简单类型注入
@Repository("bookDao")
public class BookDaoImpl implements BookDao{@Value("100")private String connectionNum;
}
注解读取properties配置文件
对于如@Qualifier、@Value这种注解,为了防止写死,可以放到properties文件内
使用方法:
使用@PropertySource注解加载properties文件
@Configuration
@ComponentScan("com.itheima")
@PropertySource("classpath:jdbc.properties")
public class SpringConfig{
}
注意:
-
如果读取的properties配置文件有多个,可以使用
@PropertySource的属性来指定多个@PropertySource({"jdbc.properties","xxx.properties"}) -
@PropertySource注解属性中不支持使用通配符*,运行会报错@PropertySource({"*.properties"}) -
@PropertySource注解属性中可以把classpath:加上,代表从当前项目的根路径找文件@PropertySource({"classpath:jdbc.properties"})
4.AOP相关内容
AOP(Aspect Oriented Programming)面向切面编程,一种编程范式,指导开发者如何组织程序结构,它可以在不惊动原始设计的基础上为其进行功能增强。
在这里主要学习AOP核心概念和AOP作用,如AOP工作流程、AOP配置管理、AOP事务管理
(1) AOP的核心概念
概念:AOP(Aspect Oriented Programming)面向切面编程,一种编程范式
作用:在不惊动原始设计的基础上为方法进行功能增强
核心概念:
- 代理(Proxy):SpringAOP的核心本质是采用代理模式实现的
- 连接点(JoinPoint):在SpringAOP中,理解为任意方法的执行
- 切入点(Pointcut):匹配连接点的式子,也是具有共性功能的方法描述
- 通知(Advice):若干个方法的共性功能,在切入点处执行,最终体现为一个方法
- 切面(Aspect):描述通知与切入点的对应关系
- 目标对象(Target):被代理的原始对象成为目标对象
目标对象是要增强的类对应的对象,也叫原始对象。
SpringAOP中式不改变原有代码前提进行增强的,采用的是代理模式,因此对原始对象进行增强,需要对原始方法创建代理对象,在代理对象中的方法把通知加进去,实现了增强,这就是代理。
(2) 基于注解实现步骤
<1> 知识点一览
知识点1:@EnableAspectJAutoProxy
| 名称 | @EnableAspectJAutoProxy |
|---|---|
| 类型 | 配置类注解 |
| 位置 | 配置类定义上方 |
| 作用 | 开启注解格式AOP功能 |
知识点2:@Aspect
| 名称 | @Aspect |
|---|---|
| 类型 | 类注解 |
| 位置 | 切面类定义上方 |
| 作用 | 设置当前类为AOP切面类 |
知识点3:@Pointcut
| 名称 | @Pointcut |
|---|---|
| 类型 | 方法注解 |
| 位置 | 切入点方法定义上方 |
| 作用 | 设置切入点方法 |
| 属性 | value(默认):切入点表达式 |
知识点4:@Before
| 名称 | @Before |
|---|---|
| 类型 | 方法注解 |
| 位置 | 通知方法定义上方 |
| 作用 | 设置当前通知方法与切入点之间的绑定关系,当前通知方法在原始切入点方法前运行 |
<2> 准备工作
准备工作:创建Maven项目,添加依赖,实现类,配置类,运行类
思路分析:添加依赖,定义接口与实现类,定义通知类和通知,定义切入点,制作切面,将通知类配给容器,并标识为切面类,开启注解格式AOP功能
<3> 实现步骤
添加依赖
<dependency><groupId>org.aspectj</groupId><artifactId>aspectjweaver</artifactId><version>1.9.4</version>
</dependency>
在
spring-context中自动导入了spring-aop,这里是AspectJ,是AOP思想的具体实现
接口与实现类
public interface BookDao {public void save();public void update();
}@Repository
public class BookDaoImpl implements BookDao {public void save() {System.out.println(System.currentTimeMillis());System.out.println("book dao save ...");}public void update(){System.out.println("book dao update ...");}
}
定义通知类和通知
public class MyAdvice {public void method(){System.out.println(System.currentTimeMillis());}
}
定义切入点(增强update方法)
public class MyAdvice {@Pointcut("execution(void com.Coolipa.dao.BookDao.update())")private void pt(){}public void method(){System.out.println(System.currentTimeMillis());}
}
制作切面
public class MyAdvice {@Pointcut("execution(void com.itheima.dao.BookDao.update())")private void pt(){}@Before("pt()")public void method(){System.out.println(System.currentTimeMillis());}
}
将通知类配给容器并表示为切面类
@Component
@Aspect
public class MyAdvice {@Pointcut("execution(void com.itheima.dao.BookDao.update())")private void pt(){}@Before("pt()")public void method(){System.out.println(System.currentTimeMillis());}
}
开启注解AOP功能
@Configuration
@ComponentScan("com.itheima")
@EnableAspectJAutoProxy
public class SpringConfig {
}
运行程序即可
(3) AOP工作流程
- Spring容器启动,加载bean(需要被增强的类,通知类),此时bean还没创建
- 读取所有切面配置中的切入点
- 初始化bean,将要被实例化bean对象的类中的方法和切入点进行匹配
- 匹配失败,创建原始对象,直接调用原始对象的方法
- 匹配成功,创建原始对象的代理对象,对其中的方法进行加强
- 获取bean的执行方法
- 获取bean是原始对象时,调用方法并执行,完成操作
- 获取bean时代理对象是,运行原始方法与增强的内容,完成操作
(4) AOP配置管理
这里主要是切入点表达式、AOP通知类型、书写技巧
<1> 切入点表达式
- 切入点:要被增强的方法
- 切入点表达式:要进行增强方法的描述方式
切入点表达式格式:execution([修饰符] 返回类型 [声明类型].方法名(参数) [throws 异常])
[]表示可选部分- 修饰符:public、private等,通常省略
- 返回类型:方法返回值类型,使用
*匹配任意类型 - 包名.类名:完整的类路径,可以使用
..匹配多级包 - 方法名:具体方法名,可以使用
*通配符 - 参数:参数类型列表,
()表示无参数,(..)表示任意参数
execution(public User com.example.service.UserService.findById(int))
*:通配符,可以单独出现,可以作为前后缀..:通配符,多个连续的任意符号,用于简化包名与参数书写+:通配符,匹配子类类型
注意事项:
- 按标准规范开发
- 查询操作的返回值建议使用*匹配
- 减少使用…的形式描述包
- 对接口进行描述,使用*表示模块名,例如UserService的匹配描述为*Service
- 方法名书写保留动词,例如get,使用*表示名词,例如getById匹配描述为getBy*
- 参数根据实际情况灵活调整
示例:
execution(void com.itheima.dao.BookDao.update())
匹配接口,能匹配到
execution(void com.itheima.dao.impl.BookDaoImpl.update())
匹配实现类,能匹配到
execution(* com.itheima.dao.impl.BookDaoImpl.update())
返回值任意,能匹配到
execution(* com.itheima.dao.impl.BookDaoImpl.update(*))
返回值任意,但是update方法必须要有一个参数,无法匹配,要想匹配需要在update接口和实现类添加参数
execution(void com.*.*.*.*.update())
返回值为void,com包下的任意包三层包下的任意类的update方法,匹配到的是实现类,能匹配
execution(void com.*.*.*.update())
返回值为void,com包下的任意两层包下的任意类的update方法,匹配到的是接口,能匹配
execution(void *..update())
返回值为void,方法名是update的任意包下的任意类,能匹配
execution(* *..*(..))
匹配项目中任意类的任意方法,能匹配,但是不建议使用这种方式,影响范围广
execution(* *..u*(..))
匹配项目中任意包任意类下只要以u开头的方法,update方法能满足,能匹配
execution(* *..*e(..))
匹配项目中任意包任意类下只要以e结尾的方法,update和save方法能满足,能匹配
execution(void com..*())
返回值为void,com包下的任意包任意类任意方法,能匹配,*代表的是方法
execution(* com.itheima.*.*Service.find*(..))
将项目中所有业务层方法的以find开头的方法匹配
execution(* com.itheima.*.*Service.save*(..))
将项目中所有业务层方法的以save开头的方法匹配
<2> AOP通知类型
a.知识点总结
- 前置通知
- 后置通知
- 环绕通知(重点)
- 环绕通知依赖形参ProceedingJoinPoint才能实现对原始方法的调用
- 环绕通知可以隔离原始方法的调用执行
- 环绕通知返回值设置为Object类型
- 环绕通知中可以对原始方法调用过程中出现的异常进行处理
- 返回后通知
- 抛出异常后通知
知识点1:@After
| 名称 | @After |
|---|---|
| 类型 | 方法注解 |
| 位置 | 通知方法定义上方 |
| 作用 | 设置当前通知方法与切入点之间的绑定关系,当前通知方法在原始切入点方法后运行 |
知识点2:@AfterReturning
| 名称 | @AfterReturning |
|---|---|
| 类型 | 方法注解 |
| 位置 | 通知方法定义上方 |
| 作用 | 设置当前通知方法与切入点之间绑定关系,当前通知方法在原始切入点方法正常执行完毕后执行 |
知识点3:@AfterThrowing
| 名称 | @AfterThrowing |
|---|---|
| 类型 | 方法注解 |
| 位置 | 通知方法定义上方 |
| 作用 | 设置当前通知方法与切入点之间绑定关系,当前通知方法在原始切入点方法运行抛出异常后执行 |
知识点4:@Around
| 名称 | @Around |
|---|---|
| 类型 | 方法注解 |
| 位置 | 通知方法定义上方 |
| 作用 | 设置当前通知方法与切入点之间的绑定关系,当前通知方法在原始切入点方法前后运行 |
b. 实现
修改MyAdvice,在方法上加上对应注解即可,前置@Before、后置@After、环绕@Around、返回后通知@AfterReturning、异常后通知@AfterThrowing
前置通知示例:除了环绕通知,其他基本如下
@Component
@Aspect
public class MyAdvice {@Pointcut("execution(void com.itheima.dao.BookDao.update())")private void pt(){}@Before("pt()")//此处也可以写成 @Before("MyAdvice.pt()"),不建议public void before() {System.out.println("before advice ...");}
}
环绕通知:
@Around("pt2()")public Object aroundSelect(ProceedingJoinPoint pjp) throws Throwable {System.out.println("around before advice ...");//表示对原始操作的调用Object ret = pjp.proceed();System.out.println("around after advice ...");return ret;}
环绕通知注意事项
- 必须使用ProceedingJoinPoint:环绕通知必须依赖
ProceedingJoinPoint参数才能调用原始方法 - 必须调用proceed():如果不调用
pjp.proceed(),原始方法将被跳过 - 返回值类型:建议设置为
Object类型以兼容各种返回值 - 异常处理:必须处理
Throwable异常,因为无法预知原始方法是否会抛出异常 - void方法处理:即使原始方法返回void,环绕通知也可以返回Object或void
示例:
@Around("pt()")
public Object around(ProceedingJoinPoint pjp) throws Throwable {// 前置逻辑Object result = pjp.proceed(); // 调用原始方法// 后置逻辑return result;
}
<3> AOP通知获取参数
准备工作:Maven项目,依赖,实现类,配置类,通知类
思路分析:通过JoinPoint获得参数,通过getArgs()从环绕通知获得参数,通过AfterReturning和Around获得返回值,通过AfterThrowing和Around获得异常
(1) 获取参数(⭐)
非环绕通知获取方式
此方法适用于前置、后置、返回后、抛出异常后通知。
- JoinPoint对象描述了连接点方法的运行状态,可以获取到原始方法的调用参数
@Before("pt()")
public void before(JoinPoint jp){Object[] args = jp.getArgs();System.out.println(Arrays.toString(args));
}
环绕通知获取方式
通过getArgs()方法获取
- ProceedJointPoint是JoinPoint的子类
@Around("pt()")
public Object around(ProceedingJoinPoint pjp) throws Throwable {Object[] args = pjp.getArgs();System.out.println(Arrays.toString(args));Object ret = pjp.proceed();return ret;
}
(2) 获取返回值
只有返回后AfterReturing和环绕Around这两个通知类型可以获取
环绕通知获取返回值
ret是方法的返回值
@Around("pt()")public Object around(ProceedingJoinPoint pjp) throws Throwable{Object[] args = pjp.getArgs();System.out.println(Arrays.toString(args));args[0] = 666;Object ret = pjp.proceed(args);return ret;}
返回后通知获取返回值
@AfterReturning(value = "pt()",returning = "ret")public void afterReturning(Object ret) {System.out.println("afterReturning advice ..."+ret);}
(3) 获取异常(了解)
只有抛出异常后AfterThrowing和环绕Around这两个通知类型可以获取
环绕通知获取异常
将异常捕获即可
@Around("pt()")public Object around(ProceedingJoinPoint pjp){Object[] args = pjp.getArgs();System.out.println(Arrays.toString(args));args[0] = 666;Object ret = null;try{ret = pjp.proceed(args);}catch(Throwable throwable){t.printStackTrace();}return ret;}
获取异常
@AfterThrowing(value = "pt()",throwing = "t")public void afterThrowing(Throwable t) {System.out.println("afterThrowing advice ..."+t);}
(5) AOP事务管理
<1> 知识点总览
事务:在数据层或业务层保障一系列的数据库操作同成功失败
通过@Transactional注解实现,可拓展属性

<2> 概念
- 事务作用:在数据层保障一系列的数据库操作同成功同失败
- Spring事务作用:在数据层或业务层保障一系列的数据库操作同成功同失败
<3> 快速使用
在Spring中,提供了PlatformTransactionManager平台事务管理器
-
在方法上添加
@Transactional注解@Transactionalpublic void transfer(String out,String in ,Double money) {accountDao.outMoney(out,money);int i = 1/0;accountDao.inMoney(in,money);}
上面这些属性都可以在@Transactional注解的参数上进行设置。
-
readOnly:true只读事务,false读写事务,增删改要设为false,查询设为true。
-
timeout:设置超时时间单位秒,在多长时间之内事务没有提交成功就自动回滚,-1表示不设置超时时间。
-
rollbackFor:当出现指定异常进行事务回滚
-
noRollbackFor:当出现指定异常不进行事务回滚
- 在Config类中配置事务管理器
//配置事务管理器,mybatis使用的是jdbc事务@Beanpublic PlatformTransactionManager transactionManager(DataSource dataSource){DataSourceTransactionManager transactionManager = new DataSourceTransactionManager(dataSource);transactionManager.setDataSource(dataSource);return transactionManager;}
- 在配置类中开启即可
//开启注解式事务驱动
@EnableTransactionManagement
public class SpringConfig {
}
@Transactional可以写在接口类上、接口方法上、实现类上和实现类方法上
- 写在接口类上,该接口的所有实现类的所有方法都会有事务
- 写在接口方法上,该接口的所有实现类的该方法都会有事务
- 写在实现类上,该类中的所有方法都会有事务
- 写在实现类方法上,该方法上有事务
- 建议写在实现类或实现类的方法上
5. 结语
如果这篇文章对你有帮助,希望可以点赞收藏+关注,如果有什么问题,可以评论留言.
