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
<?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调用内部的方法
配置文件关键点:
xml
<bean id="bookDao" class="com.Coolipa.Dao.Impl.BookDaoImpl" />
启动类:
java
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可能会由于命名习惯而产生分歧,可以利用别名去解决这个问题
配置文件关键点:
xml
<bean id="bookDao" class="com.Coolipa.Dao.Impl.BookDaoImpl" name="bookDao2"/>
启动类:
java
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
xml
<bean id ="bookService" name="service service2" class="com.Coolipa.Dao.Impl.BookDaoImpl">
<property name="bookDao" ref="dao"/>
</bean>

<5>测试单例/非单例(scope的使用)
在默认情况下,Spring创建的Bean对象都是单例了,所以需要手动更改
配置文件关键点:
xml
<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文件
配置类:
java
@Configuration
@Scope("singleton")
@ComponentScan("com.Coolipa")
public class SpringConfig {
}
被替换部分:
java
<bean id="bookDao" class="com.Coolipa.Dao.Impl.BookDaoImpl" scope="singleton" name="bookDao2"/>
<bean id="bookService" class="com.Coolipa.Service.Impl.BookServiceImpl" scope="prototype"/>
启动类:
java
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();
}
}
被替换部分对比:
java
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> 构造方法实例化
在类中添加无参构造函数即可
java
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:具体工厂类中创建对象的方法名
xml
<bean id="orderDao" class="com.itheima.factory.OrderDaoFactory" factory-method="getOrderDao"/>
编写运行类运行
java
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
实例工厂:
配置类内容
xml
<bean id="userFactory" class="com.itheima.factory.UserDaoFactory"/>
<bean id="userDao" factory-method="getUserDao" factory-bean="userFactory"/>
factory-bean:工厂的实例对象
factory-method:工厂对象中的具体创建对象的方法名
编写运行类,在类中通过工厂获取对象
java
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接口,重写方法
java
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,观察是否为同一个实例来验证单例行为。
java
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. 正常实现
添加初始化和销毁方法
java
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...");
}
}
在配置文件添加配置,如下:
xml
<bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl" init-method="init" destroy-method="destory"/>
由于在普通Java应用中,JVM退出时不会自动调用bean的销毁方法,可以通过以下两种方式触发:
-
显式关闭 :使用
ClassPathXmlApplicationContext的close()方法javaClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml"); // 使用bean... ctx.close(); // 显式关闭容器,触发销毁方法 -
注册关闭钩子 :使用
registerShutdownHook()方法,在JVM关闭时自动调用销毁方法javaClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml"); ctx.registerShutdownHook(); // 注册JVM关闭钩子
区别:
close():需要手动调用,立即触发销毁registerShutdownHook():自动在JVM关闭时触发,更适合生产环境
b. 基于接口实现
在实现类添加两个接口InitializingBean, DisposableBean并实现接口中的两个方法afterPropertiesSet和destroy
java
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生命周期
java
@Repository
//@Scope设置bean的作用范围
@Scope("singleton")
public class BookDaoImpl implements BookDao {
public void save() {
System.out.println("book dao save ...");
}
//@PostConstruct设置bean的初始化方法
@PostConstruct
public void init() {
System.out.println("init ...");
}
//@PreDestroy设置bean的销毁方法
@PreDestroy
public 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配置文件
java
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
文件系统下的XML配置文件
java
ApplicationContext ctx = new FileSystemXmlApplicationContext("D:\\workspace\\spring\\spring_10_container\\src\\main\\resources\\applicationContext.xml");
(3) Bean的三种获取方式
方式一:
java
BookDao bookDao = (BookDao) ctx.getBean("bookDao");
缺点:需要强转
方式二:
java
BookDao bookDao = ctx.getBean("bookDao",BookDao.class);
缺点:需要多加一个参数
方式三:
java
BookDao bookDao = ctx.getBean(BookDao.class);
缺点:IOC容器中对应的bean对象只能有一个
(4) BeanFactory的使用
java
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注入引用类型对象
java
<bean id="bookService" class="com.itheima.service.impl.BookServiceImpl">
<!--property标签:设置注入属性-->
<!--name属性:设置注入的属性名,实际是set方法对应的名称-->
<!--ref属性:设置注入引用类型bean的id或name-->
<property name="bookDao" ref="bookDao"/>
</bean>
在bean中定义引用数据属性并提供set方法即可
java
public class BookServiceImpl implements BookService{
private BookDao bookDao;
//setter注入需要提供要注入对象的set方法
public void setBookDao(BookDao bookDao) {
this.bookDao = bookDao;
}
}
简单类型
在配置类中使用propety和value属性注入简单类型数据
xml
<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属性注入引用类型对象
xml
<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
实现类中定义引用类型属性,并提供构造方法
java
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属性注入简单类型数据
xml
<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>
在实体类添加构造方法
java
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属性进行注入
java
<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> 集合注入
在配置类中直接注入即可
注入数组类型数据
xml
<property name="array">
<array>
<value>100</value>
<value>200</value>
<value>300</value>
</array>
</property>
注入List类型数据
xml
<property name="list">
<list>
<value>itcast</value>
<value>itheima</value>
<value>boxuegu</value>
<value>chuanzhihui</value>
</list>
</property>
注入Set类型数据
xml
<property name="set">
<set>
<value>itcast</value>
<value>itheima</value>
<value>boxuegu</value>
<value>boxuegu</value>
</set>
</property>
注入Map类型数据
xml
<property name="map">
<map>
<entry key="country" value="china"/>
<entry key="province" value="henan"/>
<entry key="city" value="kaifeng"/>
</map>
</property>
注入Properties类型数据
xml
<property name="properties">
<props>
<prop key="country">china</prop>
<prop key="province">henan</prop>
<prop key="city">kaifeng</prop>
</props>
</property>
实现类
java
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注解开启自动装配模式
java
@Service
public class BookServiceImpl implements BookService{
@Autowired
private BookDao bookDao;
public void save(){
System.out.println("save");
bookDao.save();
}
}
基于反射设计常见对象,无需提供setter方法,建议使用无参构造方法构建对象
按照名称注入
使用@Qualifier注解开启指定名称装配bean
java
@Service
public class BookServiceImpl implements BookService{
@Autowired
@Qualifier("bookDao")
private BookDao bookDao;
}
@Qualifier注解无法单独使用,必须配合@Autowired注解使用
简单数据类型注入
使用@Value实现简单类型注入
java
@Repository("bookDao")
public class BookDaoImpl implements BookDao{
@Value("100")
private String connectionNum;
}
注解读取properties配置文件
对于如@Qualifier、@Value这种注解,为了防止写死,可以放到properties文件内
使用方法:
使用@PropertySource注解加载properties文件
java
@Configuration
@ComponentScan("com.itheima")
@PropertySource("classpath:jdbc.properties")
public class SpringConfig{
}
注意:
-
如果读取的properties配置文件有多个,可以使用
@PropertySource的属性来指定多个java@PropertySource({"jdbc.properties","xxx.properties"}) -
@PropertySource注解属性中不支持使用通配符*,运行会报错java@PropertySource({"*.properties"}) -
@PropertySource注解属性中可以把classpath:加上,代表从当前项目的根路径找文件java@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> 实现步骤
添加依赖
xml
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.4</version>
</dependency>
在
spring-context中自动导入了spring-aop,这里是AspectJ,是AOP思想的具体实现
接口与实现类
java
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 ...");
}
}
定义通知类和通知
java
public class MyAdvice {
public void method(){
System.out.println(System.currentTimeMillis());
}
}
定义切入点(增强update方法)
java
public class MyAdvice {
@Pointcut("execution(void com.Coolipa.dao.BookDao.update())")
private void pt(){}
public void method(){
System.out.println(System.currentTimeMillis());
}
}
制作切面
java
public class MyAdvice {
@Pointcut("execution(void com.itheima.dao.BookDao.update())")
private void pt(){}
@Before("pt()")
public void method(){
System.out.println(System.currentTimeMillis());
}
}
将通知类配给容器并表示为切面类
java
@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功能
java
@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等,通常省略
- 返回类型:方法返回值类型,使用
*匹配任意类型 - 包名.类名:完整的类路径,可以使用
..匹配多级包 - 方法名:具体方法名,可以使用
*通配符 - 参数:参数类型列表,
()表示无参数,(..)表示任意参数
arduino
execution(public User com.example.service.UserService.findById(int))
*:通配符,可以单独出现,可以作为前后缀..:通配符,多个连续的任意符号,用于简化包名与参数书写+:通配符,匹配子类类型
注意事项:
- 按标准规范开发
- 查询操作的返回值建议使用*匹配
- 减少使用..的形式描述包
- 对接口进行描述,使用*表示模块名,例如UserService的匹配描述为*Service
- 方法名书写保留动词,例如get,使用*表示名词,例如getById匹配描述为getBy*
- 参数根据实际情况灵活调整
示例:
java
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
前置通知示例:除了环绕通知,其他基本如下
java
@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 ...");
}
}
环绕通知:
java
@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
示例:
java
@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对象描述了连接点方法的运行状态,可以获取到原始方法的调用参数
java
@Before("pt()")
public void before(JoinPoint jp){
Object[] args = jp.getArgs();
System.out.println(Arrays.toString(args));
}
环绕通知获取方式
通过getArgs()方法获取
- ProceedJointPoint是JoinPoint的子类
java
@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是方法的返回值
java
@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;
}
返回后通知获取返回值
java
@AfterReturning(value = "pt()",returning = "ret")
public void afterReturning(Object ret) {
System.out.println("afterReturning advice ..."+ret);
}
(3) 获取异常(了解)
只有抛出异常后AfterThrowing和环绕Around这两个通知类型可以获取
环绕通知获取异常
将异常捕获即可
java
@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;
}
获取异常
java
@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注解java@Transactional public 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类中配置事务管理器
java
//配置事务管理器,mybatis使用的是jdbc事务
@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource){
DataSourceTransactionManager transactionManager = new DataSourceTransactionManager(dataSource);
transactionManager.setDataSource(dataSource);
return transactionManager;
}
- 在配置类中开启即可
java
//开启注解式事务驱动
@EnableTransactionManagement
public class SpringConfig {
}
@Transactional可以写在接口类上、接口方法上、实现类上和实现类方法上
- 写在接口类上,该接口的所有实现类的所有方法都会有事务
- 写在接口方法上,该接口的所有实现类的该方法都会有事务
- 写在实现类上,该类中的所有方法都会有事务
- 写在实现类方法上,该方法上有事务
- 建议写在实现类或实现类的方法上