- 核心概念,降低企业级开发的复杂性
- 框架整合,高效整合其他技术,提高企业级应用开发与运行效率
- 官网:spring.io
- Spring 发展到今天已经形成了一种开发的生态圈,Spring 提供了若干个项目,每个项目用于完成特定的功能
- Spring系统架构
- Data Access:数据访问
- Data Intergration:数据集成
- Web:Web开发
- AOP:面向切面编程
- Aspects:AOP思想实现
- Core Container:核心容器
- Test:单元测试与集成测试
1. 核心概念
- 对象的创建控制权由程序转移到 外部,这种思想称为控制反转(不在业务层创建对象)(目的为了解耦) 业务层实现:
public class BookServiceImpl implements BookService {
private BookDao bookDao;
public void save(){
bookDao.save();
}
}
- 数据层实现
public class BookDaoImpl implements BookDao {
public void save() {
System.out.println("book dao save ... ");
}
}
- IOC控制反转:
- 使用对象时,由主动 new 产生对象转换为由 外部 提供对象,此过程中对象创建控制权由程序转移到 外部,此思想称为控制反转
- Spring 技术对 IOC 思想进行了实现
- Spring 提供了一个容器,称为 IOC 容器,用来充当 IOC 思想中的 “外部”
- IOC 容器负责对象的创建、初始化等一系列工作,被创建或被管理的对象在 IOC 容器中统称为 Bean
- DI 依赖注入:
- 在容器中建立 bean 与 bean 之间的依赖关系的整个过程,称为依赖注入
- (在 IOC 容器中,service 依赖 dao,IOC 容器会自动建立依赖,绑定好)
- 目的:充分解耦
- 使用 IOC 容器管理 bena (IOC)
- 在 IOC 容器内将有依赖关系的 bean 进行关系绑定(DI)
- 最终效果:
- 使用对象时不仅可以直接从 IOC 容器中获取,并且获取到的 bean 已经绑定了所有的依赖关系
2. 案例
2.1 IOC入门
- 导入 Spring 坐标
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.10.RELEASE</version>
</dependency>
2. 定义Spring管理的类(接口)
代码语言:java复制public interface BookService {
public void save();
}
代码语言:java复制public class BookServiceImpl implements BookService {
private BookDao bookDao = new BookDaoImpl();
public void save() {
bookDao.save();
}
}
3. 创建Spring配置文件,配置对应类作为 Spring 管理的 bean
代码语言:java复制<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--2.配置bean-->
<!--bean标签标示配置bean
id属性标示给bean起名字
class属性表示给bean定义类型-->
<bean id="bookDao" class="top.bigdata.dao.impl.BookDaoImpl"/>
</beans>
bean定义时id属性在同一个上下文中不能重复
4. 初始化 IOC 容器( Spring 核心容器 / Spring 容器),通过容器获取 bean
代码语言:java复制public class App {
public static void main(String[] args) {
//加载配置文件得到上下文对象,也就是容器对象
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
//4.获取bean
BookService bookService = (BookService) ctx.getBean("bookService");
bookService.save();
}
}
2.2 DI 入门
- 删除创建对象的代码,提供依赖项对应的setter方法
public class BookServiceImpl implements BookService {
//删除业务层中使用new的方式创建的dao对象
private BookDao bookDao;
public void save() {
System.out.println("book service save ...");
bookDao.save();
}
//提供对应的set方法
public void setBookDao(BookDao bookDao) {
this.bookDao = bookDao;
}
}
- 配置service与dao之间的关系
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="bookDao" class="top.bigdata.dao.impl.BookDaoImpl"/>
<bean id="bookService" class="top.bigdata.service.impl.BookServiceImpl">
<!--配置server与dao的关系-->
<!--
property标签表示配置当前bean的属性
name属性表示配置哪一个具体的属性
ref属性表示参照哪一个bean
-->
<property name="bookDao" ref="bookDao"/>
</bean>
</beans>
3. bean
3.1 bean 配置
- id 和 class
<bean id="" class=""/>
- id:bean 的 id ,使用容器可以通过 id 值获取对应的 bean,在一个容器中 id 值唯一
- class:bean 的类型,即配置的 bean 的全路径类名
- 案例:
<bean id="bookDao" class="top.bigdata.dao.impl.BookDaoImpl"/>
- name
- 功能:定义 bean 的别名,可以定义多个,使用逗号( , )分号( ; )空格( )分隔
<!--name:为bean指定别名,别名可以有多个,使用逗号,分号,空格进行分隔-->
<bean id="bookService" name="service service4 bookEbi" class="top.bigdata.service.impl.BookServiceImpl">
<property name="bookDao" ref="bookDao"/><!--这里ref的属性值可以是下面name的属性值dao,建议使用下面id进行注入-->
</bean>
<!--scope:为bean设置作用范围,可选值为单例singloton,非单例prototype-->
<bean id="bookDao" name="dao" class="top.bigdata.dao.impl.BookDaoImpl"/>
- 获取:
public class AppForName {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
//此处根据bean标签的id属性和name属性的任意一个值来获取bean对象
BookService bookService = (BookService) ctx.getBean("service4");
bookService.save();
}
}
- 作用范围:
- 默认情况下,Spring 创建的 bean 对象都是单例的(创建的多个对象地址值都相同)
- 功能:定义 bean 的作用范围,可选范围:
- singleton:单例(默认)
- prototype:非单例
- 案例:转换为非单例
<bean id="bookDao" name="dao" class="top.bigdata.dao.impl.BookDaoImpl" scope="prototype"/>
- bean 对象默认为单例的原因:
- bean 对象只有一个就避免了对象的频繁创建与销毁,达到了 bean 对象的复用,性能高
- 哪些bean对象适合交给容器进行管理?
- 表现层对象
- 业务层对象
- 数据层对象
- 工具对象
- 哪些bean对象不适合交给容器进行管理?
- 封装实例的域对象,因为会引发线程安全问题,所以不适合。
3.2 bean 实例化
- bean 本质上就是对象,创建 bean 使用构造方法完成
- 三种方式:
- 构造方法
- 静态工厂
- 实例工厂
构造方法
- 提供可访问的构造方法:
public class BookDaoImpl implements BookDao {
public BookDaoImpl() {
//spring创建bean的时候调用的是无参构造
System.out.println("book dao constructor is running ....");
}
public void save() {
System.out.println("book dao save ...");
}
}
- 配置:
<bean id="bookDao" class="top.bigdata.dao.impl.BookDaoImpl"/>
- 运行程序:
public class AppForInstanceBook {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
BookDao bookDao = (BookDao) ctx.getBean("bookDao");
bookDao.save();
}
}
- 无参构造方法如果不存在,将抛出异常 BeanCreationException
静态工厂
代码语言:javascript复制public interface OrderDao {
public void save();
}
public class OrderDaoFactory { //提供一个静态方法
public static OrderDao getOrderDao(){
System.out.println("factory setup....");
return new OrderDaoImpl();
}
}
配置:
代码语言:javascript复制<bean id="orderDao" class="top.bigdata.factory.OrderDaoFactory" factory-method="getOrderDao"/>
实例工厂
- 配置
<bean id="userFactory" class="top.bigdata.factory.UserDaoFactory"/> <!--先配置工厂的bean-->
<bean id="userDao" factory-method="getUserDao" factory-bean="userFactory"/>
- 实例:
public class UserDaoFactory {
public UserDao getUserDao(){
return new UserDaoImpl();
}
}
使用 FactoryBean 实例化 bean
- FactoryBean :
//FactoryBean创建对象
public class UserDaoFactoryBean implements FactoryBean<UserDao> { //代替上面 UserDaoFactory 中创建对象的方法
//代替原始实例工厂UserDaoFactory中创建对象的方法
public UserDao getObject() throws Exception {
return new UserDaoImpl();
}
public Class<?> getObjectType() {
return UserDao.class;
}
/*一般情况下都会采用单例,不需要重写 isSingleton 方法*/
public boolean isSingleton() {
return true; //此时是单例的bean对象,false为非单例对象
}
}
- 配置:
<bean id="userDao" class="top.bigdata.factory.UserDaoFactoryBean"/>
3.3 bean 生命周期
- bean 对象从创建到销毁的整体过程
- 生命周期控制:在 bean 创建后到销毁前做一些事情。
- 配置生命周期控制方法:
<bean id="bookDao" class="top.bigdata.dao.impl.BookDaoImpl" init-method="init" destroy-method="destory"/>
.
- 提供生命周期控制方法:
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...");
}
}
- 实现 InitializingBean,DisposableBean 接口
public class BookServiceImpl implements BookService, InitializingBean, DisposableBean {
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");
}
}
bean 销毁时机
- 容器关闭前会触发 bean 的销毁
- 关闭容器方式:
- 手工关闭容器:
- ClassPathXmlApplicationContext 接口 close() 操作
ctx.close();
- 注册关闭钩子,在虚拟机退出前先关闭容器再退出虚拟机
- ClassPathXmlApplicationContext 接口 registerShutdownHook() 操作
ctx.registerShutdownHook();
- 手工关闭容器:
public class AppForLifeCycle {
public static void main( String[] args ) {
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
BookDao bookDao = (BookDao) ctx.getBean("bookDao");
bookDao.save();
/*注册关闭钩子函数,在虚拟机退出之前回调此函数,先关闭容器*/
ctx.registerShutdownHook();
/*关闭容器*/
ctx.close();//暴力
}
}
- 两种方式的不同点:close() 是在调用的时候关闭,registerShutdownHook() 是在 JVM 退出前调用关闭。
4. 依赖注入
- 依赖注入方式:
- setter 注入
- 简单注入
- 引用注入
- 构造器注入
- 简单类型
- 引用类型
- setter 注入
4.1 setter 注入
- 引用类型:
- 在 bean 中定义引用类型属性并提供可访问的 set 方法
public class BookServiceImpl implements BookService {
private BookDao bookDao;
public void setBookDao(BookDao bookDao) {
this.bookDao = bookDao;
}
}
- 配置中使用 property 标签 ref 属性注入引用类型对象
<!--注入引用类型-->
<bean id="bookService" class="top.bigdata.service.impl.BookServiceImpl">
<!--property标签:设置注入属性-->
<!--name属性:设置注入的属性名,实际是set方法对应的名称(BookServiceImpl)中对象声明-->
<!--ref属性:设置注入引用类型bean的id或name-->
<property name="bookDao" ref="bookDao"/>
<property name="userDao" ref="userDao"/>
</bean>
- 简单类型:
- 在 bean 中定义引用类型属性并提供可访问的 set 方法
public class BookDaoImpl implements BookDao {
private String databaseName;
private int connectionNum;
//setter注入需要提供要注入对象的set方法
public void setConnectionNum(int connectionNum) {
this.connectionNum = connectionNum;
}
//setter注入需要提供要注入对象的set方法
public void setDatabaseName(String databaseName) {
this.databaseName = databaseName;
}
public void save() {
System.out.println("book dao save ..." databaseName "," connectionNum);
}
}
- 配置中使用 property 标签 value 属性注入简单类型数据
<!--注入简单类型-->
<bean id="bookDao" class="top.bigdata.dao.impl.BookDaoImpl">
<!--property标签:设置注入属性-->
<!--name属性:设置注入的属性名,实际是set方法对应的名称-->
<!--value属性:设置注入简单类型数据值-->
<property name="connectionNum" value="100"/>
<property name="databaseName" value="mysql"/>
</bean>
<bean id="userDao" class="top.bigdata.dao.impl.UserDaoImpl"/>
- 注意:value:后面跟的是简单数据类型,对于参数类型,Spring在注入的时候会自动转换,但是不能写成,如果这样,spring 在将 abc 转换成 int 类型的时候就会报错
<property name="connectionNum" value="abc"/>
4.2 构造器注入:
- 引用类型:
- 在 bean 中定义引用类型属性并提供可访问的构造方法
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();
}
}
- 配置中使用 property 标签 ref 属性注入引用类型对象
<bean id="userDao" class="top.bigdata.dao.impl.UserDaoImpl"/>
<bean id="bookService" class="top.bigdata.service.impl.BookServiceImpl">
<constructor-arg name="userDao" ref="userDao"/>
<constructor-arg name="bookDao" ref="bookDao"/> <!--此处的name是形参的名-->
</bean>
- 简单类型:
- 修改 BookDaoImpl 添加构造方法
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);
}
}
- 配置完成多个属性构造器注入
<bean id="bookDao" class="top.bigdata.dao.impl.BookDaoImpl">
<!--根据构造方法参数名称注入-->
<constructor-arg name="connectionNum" value="10"/>
<constructor-arg name="databaseName" value="mysql"/>
</bean>
- 构造器传参兼容性方案:
- 方式一:删除 name 属性,添加 type 属性,按照类型注入
- 这种方式可以解决构造函数形参名发生变化带来的耦合问题
- 但是如果构造方法参数中有类型相同的参数,这种方法就不能实现
- 方式一:删除 name 属性,添加 type 属性,按照类型注入
<!--解决形参名称的问题,与形参名不耦合-->
<bean id="bookDao" class="top.bigdata.dao.impl.BookDaoImpl">
<!--根据构造方法参数类型注入-->
<constructor-arg type="int" value="10"/>
<constructor-arg type="java.lang.String" value="mysql"/>
</bean>
<bean id="userDao" class="top.bigdata.dao.impl.UserDaoImpl"/>
<bean id="bookService" class="top.bigdata.service.impl.BookServiceImpl">
<constructor-arg name="userDao" ref="userDao"/>
<constructor-arg name="bookDao" ref="bookDao"/>
</bean>
- 方式二:删除 type 属性,添加 index 属性,按照索引下标注入,下标从0开始
- 这种方式可以解决参数类型重复问题
- 但是如果构造方法参数顺序发生变化后,这种方式又带来了耦合问题
<!--解决参数类型重复问题,使用位置解决参数匹配-->
<bean id="bookDao" class="top.bigdata.dao.impl.BookDaoImpl">
<!--根据构造方法参数位置注入-->
<constructor-arg index="0" value="mysql"/>
<constructor-arg index="1" value="100"/>
</bean>
<bean id="userDao" class="top.bigdata.dao.impl.UserDaoImpl"/>
<bean id="bookService" class="top.bigdata.service.impl.BookServiceImpl">
<constructor-arg name="userDao" ref="userDao"/>
<constructor-arg name="bookDao" ref="bookDao"/>
</bean>
4.3 注入方式选择
- 强制依赖使用构造器进行,使用setter注入有概率不进行注入导致null对象出现
- 强制依赖指对象在创建的过程中必须要注入指定的参数(bean 运行必须要的东西用构造器注入)
- 可选依赖使用 setter 注入进行,灵活性强
- 可选依赖指对象在创建过程中可以注入也可以不注入
- Spring 框架倡导使用构造器,第三方框架内部大多数采用构造器注入的形式进行数据初始化,相对严谨
- 如果有必要可以两者同时使用,使用构造器注入完成强制依赖的注入,使用setter注入完成可选依赖的注入
- 实际开发过程中还要根据实际情况分析,如果受控对象没有提供setter方法就必须使用构造器注入
- 自己开发的模块推荐使用setter注入
4.4 自动装配
- 依赖自动装配:IOC 容器根据 bean 所依赖的资源在容器中自动查找并注入到 bean 中的过程称为自动装配
- 自动装配方式:
- 按类型(常用)
- 按名称
- 按构造方法
- 不自动装配
- 配置中使用 bean 标签添加 autowire 属性设置自动装配的类型:
- 配置文件
<bean class="top.bigdata.dao.impl.BookDaoImpl"/>
<!--autowire属性:开启自动装配,通常使用按类型装配-->
<bean id="bookService" class="top.bigdata.service.impl.BookServiceImpl" autowire="byType"/>
- 代码层面需要加上 set 方法
public class BookServiceImpl implements BookService{
private BookDao bookDao;
public void setBookDao(BookDao bookDao) {
this.bookDao = bookDao;
}
public void save() {
System.out.println("book service save ...");
bookDao.save();
}
}
- 注意:
- 自动装配用于引用类型依赖注入,不能对简单类型进行操作
- 使用按类型装配时( byType )必须保障容器中相同类型的 bean 统一,推荐使用
- 使用按名称装配时( byName )必须保障容器中具有指定名称的 bean,因变量名与配置耦合,不推荐使用
- 自动装配优先级低于 setter 注入与构造器注入,同时出现时自动装配配置失效
4.5 集合注入
- 环境准备:
- 添加 BookDao、BookDaoImpl 类
public interface BookDao {
public void save();
}
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 setArray(int[] array) {
this.array = array;
}
public void setList(List<String> list) {
this.list = list;
}
public void setSet(Set<String> set) {
this.set = set;
}
public void setMap(Map<String, String> map) {
this.map = map;
}
public void setProperties(Properties properties) {
this.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);
}
}
- resources 下提供 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"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl">
</bean>
</beans>
- 编写运行类
public class AppForDICollection {
public static void main( String[] args ) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
BookDao bookDao = (BookDao) ctx.getBean("bookDao");
bookDao.save();
}
}
- 数组注入:
<!--数组注入-->
<property name="array">
<array>
<value>100</value>
<value>200</value>
<value>300</value>
</array>
</property>
- list 集合注入:
<!--list集合注入-->
<property name="list">
<list>
<value>itcast</value>
<value>itheima</value>
<value>boxuegu</value>
<value>chuanzhihui</value>
</list>
</property>
- set 集合注入:
<!--set集合注入-->
<property name="set">
<set>
<value>itcast</value>
<value>itheima</value>
<value>boxuegu</value>
<value>boxuegu</value>
</set>
</property>
- map 集合注入:
<!--map集合注入-->
<property name="map">
<map>
<entry key="country" value="china"/>
<entry key="province" value="henan"/>
<entry key="city" value="kaifeng"/>
</map>
</property>
- Properties 注入:
<!--Properties注入-->
<property name="properties">
<props>
<prop key="country">china</prop>
<prop key="province">henan</prop>
<prop key="city">kaifeng</prop>
</props>
</property>
说明:
- property 标签表示 setter 方式注入,构造方式注入 constructor-arg 标签内部也可以写
<array>
、<list>
、<set>
、<map>
、<props>
标签 - List 的底层也是通过数组实现的,所以
<list>
和<array>
标签是可以混用 - 集合中要添加引用类型,只需要把
<value>
标签改成<ref>
标签,这种方式用的比较少
5. 数据源对象管理
5.1 管理第三方资源
- 第三方的类:
- DruidDataSource
- C3P0
- 实现druid 管理:
- 添加依赖
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.16</version>
</dependency>
- 配置数据源对象作为 spring 管理的 bean
<!-- 管理DruidDataSource对象-->
<bean class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/> <!--driverClassName:数据库驱动-->
<property name="url" value="jdbc:mysql://localhost:3306/spring_db"/> <!--url:数据库连接地址-->
<property name="username" value="root"/> <!--username:数据库连接用户名-->
<property name="password" value="123456"/> <!--password:数据库连接密码-->
</bean>
- 实现C3P0管理
- 添加依赖:
<dependency>
<groupId>c3p0</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.1.2</version>
</dependency>
- 配置第三方 bean
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="com.mysql.jdbc.Driver"/>
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/spring_db"/>
<property name="user" value="root"/>
<property name="password" value="123456"/>
<property name="maxPoolSize" value="1000"/> <!--最大连接池的数量-->
</bean>
- pom 文件中添加 mysql 驱动
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
- 注意:
- ComboPooledDataSource 的属性是通过 setter 方式进行注入
- 想注入属性就需要在 ComboPooledDataSource 类或其上层类中有提供属性对应的 setter 方法
- C3P0 的四个属性和 Druid 的四个属性是不一样的
Druid 和 C3P0 在没导入 mysql 驱动前提下一个没报错一个报错,说明 Druid 在初始化的时候没有去加载驱动,而 C3P0 刚好相反
5.2 加载 properties 文件
- 将数据库四要素提取到 properties 配置文件中,spring 加载配置信息完成注入
- 准备 properties 配置文件
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://127.0.0.1:3306/spring_db
jdbc.username=root
jdbc.password=123456
- 开启 context 命名空间
<?xml version="1.0" encoding="UTF-8"?>
<!--xmlns:context开启一个新的命名空间,把上面的bean都换成context-->
<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/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
</beans>
- 使用 context 命名空间,加载指定 properties 文件:
<context:property-placeholder location="jdbc.properties"/>
- 使用 ${} 读取加载的属性值
<property name="username" value="${jdbc.username}"/>
- 完整依赖注入
<?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/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<context:property-placeholder location="jdbc.properties"/>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
</beans>
- 不加载系统属性
<context:property-placeholder location="jdbc.properties" system-properties-mode="NEVER"/>
- 加载多个 properties 文件
<context:property-placeholder location="jdbc.properties,jdbc2.properties"/>
- 加载所有 properties 文件
<context:property-placeholder location="*.properties"/>
- 加载所有 properties 文件标准格式
<context:property-placeholder location="classpath:*.properties"/>
- 从类路径或 jar 包中搜索并加载 properties 文件
<context:property-placeholder location="classpath*:*.properties"/>
6. 容器
6.1 创建容器
- 方式一:类路径加载配置文件
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
- 方式二:文件路径加载配置文件
ApplicationContext ctx = new FileSystemXmlApplicationContext("D:\applicationContext.xml");
- 加载多个配置文件:
ApplicationContext ctx = new ClassPathXmlApplicationContext("bean1.xml","bean2.xml");
6.2 获取 bean
- 方式一:使用 bean 名称获取
BookDao bookDao = (BookDao) ctx.getBean("bookDao");
- 方式二:使用 bean 名称获取并指定类型
BookDao bookDao = ctx.getBean("bookDao", BookDao.class);
- 方式三:使用 bean 类型获取
BookDao bookDao = ctx.getBean(BookDao.class);
- 容器中这个类型的 bean 只能有一个,多个的话会报错
6.3 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 均为延迟加载
- 所有容器类的顶层接口
6.4 总结
- BeanFactory 是 IOC 容器的顶层接口,初始化 BeanFactory 对象时,加载的 bean 延迟加载
- ApplicationContext 接口是 Spring 容器的核心接口,初始化时 bean 立即加载
- ApplicationContext 接口提供基础的 bean 操作相关方法,通过其他接口扩展其功能
- ApplicationContext 接口常用初始化类:
- ClassPathXmlApplicationContext
- FileSystemXmlApplicationContext
- bean 相关
<bean
id="bookDao" bean的Id
name="dao bookDaoImpl daoImpl" bean的别名
class="top.bigdata.dao.impl.BookDaoImpl" bean类型,静态工厂类,FactoryBean类
scope="singleton" 控制bean的实例数量
init-method="init" 生命周期初始化方法
destroy-method="destory" 生命周期销毁方法
autowire="byType" 自动装配类型
factory-method="getInstance" bean工厂方法,应用于静态工厂或实例工厂
factory-bean="top.bigdata.factory .BookDaoFactory" 实例工厂bean
lazy-init="true" 控制bean延迟加载
/>
7. 注解开发
7.1 注解定义 bean
- 使用 @Component 定义 bean
@Component
public class BookDaoImpl implements BookDao {
public void save() {
System.out.println("book dao save ...");
}
}
- 核心配置文件中通过组件扫描加载 bean
<context:component-scan base-package="top.bigdata"/>
- Spring 提供 @Component 注解的三个衍生注解:
- @Controller:用于表现层 bean 定义
- @Service:用于业务层 bean 定义
- @Repository:用于数据层 bean 定义
- 改进:
@Repository("bookDao")
public class BookDaoImpl implements BookDao {
public void save() {
System.out.println("book dao save ...");
}
}
@Service
public class BookServiceImpl implements BookService {
private BookDao bookDao;
public void setBookDao(BookDao bookDao) {
this.bookDao = bookDao;
}
public void save() {
System.out.println("book service save ...");
bookDao.save();
}
}
代码语言:java复制public class App {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
BookDao bookDao = (BookDao) ctx.getBean("bookDao");
System.out.println(bookDao);
//按类型获取bean
BookService bookService = ctx.getBean(BookService.class);
System.out.println(bookService);
}
}
7.2 纯注解开发
Spring3.0 升级了纯注解开发模式,使用 Java 类替代配置文件,开启了 Spring 快速开发赛道
- Java 类代替 Spring 核心配置文件,配置文件结构最终会转换成 @Configuration注解 ,配置文件中的扫描换成了@ComponentScan("com.itheima")
//声明当前类为Spring配置类,用这个类代替配置文件
@Configuration
//设置bean扫描路径,多个路径书写为字符串数组格式
@ComponentScan({"top.bigdata.service","top.bigdata.dao"})
public class SpringConfig {
}
代码语言:java复制public class AppForAnnotation {
public static void main(String[] args) {
//AnnotationConfigApplicationContext加载Spring配置类初始化Spring容器
ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
BookDao bookDao = (BookDao) ctx.getBean("bookDao");
System.out.println(bookDao);
//按类型获取bean
BookService bookService = ctx.getBean(BookService.class);
System.out.println(bookService);
}
}
- 注意:
- @Configuration 注解用于设定当前类为配置类
- @Configuration 注解用于设定扫描路径,此注解只能添加一次,多个数据用数组格式添加
@ComponentScan({"top.bigdata.service","top.bigdata.dao"})
- 读取 Spring 核心配置文件初始化容器对象切换为读取 Java 配置类初始化容器对象
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
//转换为
ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
7.3 bean 管理
- 使用 @Scope 定义 bean 作用范围,使用@PostConstruct、@PreDestroy 定义 bean 的生命周期
@Repository
//@Scope设置bean的作用范围
@Scope("singleton")//单例;换成prototype,非单例
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 ...");
}
}
7.4 依赖注入
- 使用 @Autowired 注解开启自动装配模式(按类型)
@Service
public class BookServiceImpl implements BookService {
//@Autowired:注入引用类型,自动装配模式,默认按类型装配
@Autowired
private BookDao bookDao;
public void save() {
System.out.println("book service save ...");
bookDao.save();
}
}
- 注意:
- 自动装配基于反射设计创建对象并暴力反射对应属性为私有属性初始化数据,因此无需提供 setter() 方法
- 自动装配建议使用无参构造方法创建对象(默认),如果不提供对应的构造方法,请提供唯一的构造方法
- 使用 @Qualifier 注解开启指定名称装配 bean
@Service
public class BookServiceImpl implements BookService {
//@Qualifier:自动装配bean时按bean名称装配,如果有相同类型的bean写
@Qualifier("bookDao")
private BookDao bookDao;
}
}
- 注意:
- @Qualifier 注解无法单独使用,必须配合 @Autowired 注解使用
使用 @Value 实现简单类型注入
代码语言:java复制@Repository("bookDao")
public class BookDaoImpl implements BookDao {
//@Value:注入简单类型(无需提供set方法)
@Value("${name}")
private String name;
}
使用 @PropertySource 注解加载 properties 文件
代码语言:java复制@Configuration
@ComponentScan("com.itheima")
//@PropertySource加载properties配置文件
@PropertySource({"jdbc.properties"})
public class SpringConfig {
}
- 注意:路径仅支持单一文件配置,多文件请使用数组格式配置,不允许使用通配符 *
7.5 管理第三方 bean
使用独立的配置类管理第三方 bean
代码语言:java复制@Configuration
public class JdbcConfig {
//1.定义一个方法获得要管理的对象
//2.添加@Bean,表示当前方法的返回值是一个bean
//@Bean修饰的方法,形参根据类型自动装配
@Bean
public DataSource dataSource(){
DruidDataSource ds = new DruidDataSource();
ds.setDriverClassName("com.mysql.jdbc.Driver");
ds.setUrl("jdbc:mysql://localhost:3306/spring_db");
ds.setUsername("root");
ds.setPassword("123456");
return ds;
}
}
- 导入式(建议)
- 使用 @Import 注解手动加入配置类到核心配置,此注解只能添加一次,多个数据使用数组格式
@Configuration
//@Import:导入配置信息
@Import({JdbcConfig.class})
public class SpringConfig {
}
- 扫描式
- 使用 @ComponentScan 注解扫描配置类所在的包,加载对应的配置类信息
@Configuration
@ComponentScan({"top.bigdata.config", "top.bigdata.service"})
public class SpringConfig {
}
- 简单类型依赖注入:
public class JdbcConfig {
@Value("com.mysql.jdbc.Driver")
private String driver;
@Value("jdbc:mysql://localhost:3306/spring_db")
private String url;
@Value("root")
private String userName;
@Value("123456")
private String password;
@Bean
public DataSource dataSource(BookDao bookDao){
System.out.println(bookDao);
DruidDataSource ds = new DruidDataSource();
ds.setDriverClassName(driver);
ds.setUrl(url);
ds.setUsername(userName);
ds.setPassword(password);
return ds;
}
}
- 引用类型依赖注入
@Bean
public DataSource dataSource(BookDao bookDao){
System.out.println(bookDao);
DruidDataSource ds = new DruidDataSource();
//属性设置
return ds;
}
7.6 总结
- 定义 bean:
- @Component
- @Controller:用于表现层 bean 定义
- @Service:用于业务层 bean 定义
- @Respository:用于数据层 bean 定义
- 上面三个名称只是给一个其他的名称帮助识别,和写 @Component 是一样的
- @ComponentScan:识别配置文件中定义的 bean,后面括号设置扫描对应的包,包里有这些注解的类才会被加载
- @Component
- 设置依赖注入:
- @Autowired :注入引用类型
- @Qualifier:使用名称去识别(很少用)
- @Value:解决简单类型注入值
- @Autowired :注入引用类型
- 配置第三方 bean
- @Bean
- 作用范围
- @Scope
- singleton:单例(默认)
- prototype:非单例
- @Scope
- 生命周期
- @PostConstructor:设置该方法为初始化方法
- @PreDestroy:设置该方法为销毁方法
- 这里两个注解找不到,需要导入下面的 jar 包:
<dependency>
<groupId>javax.annotation</groupId>
<artifactId>javax.annotation-api</artifactId>
<version>1.3.2</version>
</dependency>
8. 整合
8.1 整合 MyBatis
- MyBatis 程序核心对象分析
public class App {
public static void main(String[] args) throws IOException {
// 1. 创建SqlSessionFactoryBuilder对象
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
// 2. 加载SqlMapConfig.xml配置文件
InputStream inputStream = Resources.getResourceAsStream("SqlMapConfig.xml.bak");
// 3. 创建SqlSessionFactory对象
SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(inputStream);
// 4. 获取SqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();
// 5. 执行SqlSession对象执行查询,获取结果User
AccountDao accountDao = sqlSession.getMapper(AccountDao.class);
Account ac = accountDao.findById(2);
System.out.println(ac);
// 6. 释放资源
sqlSession.close();
}
}
- 整合MyBatis
- 原始配置文件:
<configuration>
<properties resource="jdbc.properties"></properties>
<!-- -->
<typeAliases>
<package name="top.bigdata.domain"/>
</typeAliases>
<environments default="mysql">
<environment id="mysql">
<transactionManager type="JDBC"></transactionManager>
<dataSource type="POOLED">
<property name="driver" value="${jdbc.driver}"></property>
<property name="url" value="${jdbc.url}"></property>
<property name="username" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
</dataSource>
</environment>
</environments>
<!-- -->
<mappers>
<package name="top.bigdata.dao"></package>
</mappers>
</configuration>
- 原来 typeAliases 转成set,原来环境数据源相关的配置转成 set
//定义bean,SqlSessionFactoryBean,用于产生SqlSessionFactory对象
@Bean
public SqlSessionFactoryBean sqlSessionFactory(DataSource dataSource){
SqlSessionFactoryBean ssfb = new SqlSessionFactoryBean();
ssfb.setTypeAliasesPackage("top.bigdata.domain");
ssfb.setDataSource(dataSource);
return ssfb;
}
- 原来映射配置读取 mappers 扫描转换为 MapperScannerConfigurer 对象,里面设置映射配置扫描的包
//定义bean,返回MapperScannerConfigurer对象
@Bean
public MapperScannerConfigurer mapperScannerConfigurer(){
MapperScannerConfigurer msc = new MapperScannerConfigurer();
msc.setBasePackage("top.bigdata.dao");
return msc;
}
- 总结
- SqlSessionFactoryBean
- MapperScannerConfigurer
8.2 整合 JUnit
- 使用 Spring 整合 JUnit 专用的类加载器
//设置类运行器
@RunWith(SpringJUnit4ClassRunner.class)
//设置Spring环境对应的配置类
@ContextConfiguration(classes = SpringConfig.class)
public class AccountServiceTest {
//支持自动装配注入bean
@Autowired
private AccountService accountService;
@Test
public void testFindById(){
System.out.println(accountService.findById(1)); //加载id为1的
}
@Test
public void testFindAll(){
System.out.println(accountService.findAll()); //加载全部
}
}
- 总结:
- @Runwith:设置专用的类运行器
- @ContextConfiguration:指定上下文的配置类
9. AOP
- AOP:面向切面编程,一种编程范式,指导开发者如何组织程序结构
- 作用:在不惊动原始设计的基础上为其进行功能增强
- Spring理念:无入侵式 / 无侵入式 编程
9.1 核心概念:
- 连接点:程序执行过程中的任意位置,执行方法、抛出异常、设置变量等
- 在 SpringAOP 中,理解为方法的执行
- 切入点:匹配连接点的式子
- 在 SpringAOP 中,一个切入点可以只描述一个具体方法,也可以匹配多个方法
- 一个具体方法:com.itheima.dao 包下的 BookDao 接口中的无形参返回值的 save 方法
- 匹配多个方法:所有的 save 方法,所有的 get 开头的方法,所有以 Dao 结尾的接口中的任意方法,所有带一个参数的方法
- 在 SpringAOP 中,一个切入点可以只描述一个具体方法,也可以匹配多个方法
- 通知:在切入点处执行的操作,也就是共性功能
- 在SpringAOP中,功能最终以方法的形式呈现
- 通知类:定义通知的类
- 切面:描述通知与切入点的对应关系
9.2 案例
- 思路:
- 导入坐标(pom.xml)
- 制作连接点方法(原始操作,Dao 接口与实现类)
- 制作共性功能(通知类与通知)
- 定义切入点
- 绑定切入点与通知关系
- 案例:
- 1. 导入 aop 相关坐标
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.4</version>
</dependency>
- 2. 定义 dao 接口与实现类
public interface BookDao {
public void save();
public void update();
}
代码语言:java复制@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 ...");
}
}
- 3. 定义通知类(写一个类,无返回值无参数,上面写一个功能)
public class MyAdvice {
public void method(){
System.out.println(System.currentTimeMillis());
}
}
- 4. 定义切入点
public class MyAdvice {
//设置切入点,要求配置在方法上方
@Pointcut("execution(void top.bigdata.dao.BookDao.update())")
private void pt(){}
}
- 5. 绑定切入点与通知关系,并指定通知添加到原始连接点的具体执行 位置
//通知类必须配置成Spring管理的bean
@Component
//设置当前类为切面类类
@Aspect
public class MyAdvice {
//设置切入点,要求配置在方法上方
@Pointcut("execution(void top.bigdata.dao.BookDao.update())")
private void pt(){}
//设置在切入点pt()的前面运行当前操作(前置通知)
@Before("pt()")
public void method(){
System.out.println(System.currentTimeMillis());
}
}
- 6. 开启 Spring 对 AOP 注解驱动支持
@Configuration
@ComponentScan("top.bigdata")
//开启注解开发AOP功能
@EnableAspectJAutoProxy
public class SpringConfig {
}
9.3 工作流程
- Spring 容器启动
- 读取所有切面配置的切入点
- 初始化 bean,判定 bean 对应的类中的方法是否匹配到任意切入点
- 匹配失败,创建对象
- 匹配成功,创建原始对象(目标对象)的代理对象
- 获取 bean 执行方法
- 获取 bean,调用方法并执行,完成操作
- 获取的 bean 是代理对象时,根据代理对象的运行模式运行原始方法与增强的内容,完成操作
- 目标对象:原始功能去掉共性功能对应的类产生的对象,这种对象是无法直接完成最终工作的
- 代理:目标对象无法直接完成工作,需要对其进行功能回填,通过原始对象的代理对象实现
9.4 切入点表达式
- 切入点:要进行增强的方法
- 切入点表达式:要进行增强的方法的描述方式
- 切入点表达式标准格式:动作关键字(访问修饰符 返回值 报名.类 / 接口名.方法名 (参数) 异常名)
execution(void top.bigdata.dao.BookDao.update())
- 动作关键字:描述切入点的行为动作,例如 execution 表示执行到指定切入点
- 访问修饰符:public,private 等,可以省略
- 返回值
- 包名
- 类 / 接口名
- 方法名
- 参数
- 异常名:方法定义中抛出指定异常,可以省略
- 例:
execution(public User top.bigdata.service.UserService.findById(int))
- execution:动作关键字,描述切入点的行为动作,例如execution表示执行到指定切入点
- public:访问修饰符,还可以是public,private等,可以省略
- User:返回值,写返回值类型
- com.itheima.service:包名,多级包使用点连接
- UserService:类/接口名称
- findById:方法名
- int:参数,直接写参数的类型,多个类型用逗号隔开
- 异常名:方法定义中抛出指定异常,可以省略
- 可以使用通配符描述切入点,快速描述
*
:单个独立的任意符号,可以独立出现,也可以作为前缀或者后缀的匹配符出现execution(public * top.bigdata.*.UserService.find* (*))
- 匹配 com.itheima 包下的任意包中的 UserService 类或接口中所有 find 开头的带有一个参数的方法
..
:多个连续的任意符号,可以独立出现,常用于简化报名与参数的书写execution(public User com..UserService.findById(..))
- 匹配 com 包下的任意包中的 UserService 类或接口中所有名称为 findById 的方法
execution(* *..*Service .*(..))
execution(void top.bigdata.dao.BookDao.update())
匹配接口,能匹配到
execution(void top.bigdata.dao.impl.BookDaoImpl.update())
匹配实现类,能匹配到
execution(* top.bigdata.dao.impl.BookDaoImpl.update())
返回值任意,能匹配到
execution(* top.bigdata.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(* top.bigdata.*.*Service.find*(..))
将项目中所有业务层方法的以find开头的方法匹配
execution(* top.bigdata.*.*Service.save*(..))
将项目中所有业务层方法的以save开头的方法匹配
- 书写技巧:
- 所有代码按照标准规范开发,否则以下技巧全部失效
- 描述切入点通常描述接口,而不描述实现类,如果描述到实现类,就出现紧耦合了
- 访问控制修饰符针对接口开发均采用public描述(可省略访问控制修饰符描述)
- 返回值类型对于增删改类使用精准类型加速匹配,对于查询类使用*通配快速描述
- ==包名==书写==尽量不使用..匹配==,效率过低,常用*做单个包描述匹配,或精准匹配
- ==接口名/类名==书写名称与模块相关的==采用*匹配==,例如UserService书写成*Service,绑定业务层接口名
- 方法名书写以动词进行精准匹配,名词采用匹配,例如getById书写成getBy,selectAll书写成selectAll
- 参数规则较为复杂,根据业务方法灵活调整
- 通常不使用异常作为==匹配==规则
9.5 通知类型
- AOP 通知描述了抽取的共性功能,根据共性功能抽取的位置不同,最终运行代码时要将其加入到合理的位置
- AOP 通知共分为 5 种类型:
- 前置通知 : @Before
- 后置通知 : @After
- 环绕通知(重点) : @Around
- 返回后通知(了解) : @AfterReturning
- 抛出异常后通知(了解) : @AfterThrowing
- 环绕通知:
@Pointcut("execution(void top.bigdata.dao.BookDao.update())")
private void pt(){}
代码语言:java复制 @Around("pt()")
public Object aroundSelect(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("around before advice ...");
//通过参数ProceedingJoinPoint对原始方法进行调用获取一个返回值
Integer ret = (Integer) pjp.proceed();
System.out.println("around after advice ...");
return ret;
}
- 注意:
- 环绕通知必须依赖形参 ProceedingJoinPoint 才能实现对原始方法的调用,进而实现原始方法调用前后同时添加通知
- 通知中如果未使用 ProceedingJoinPoint 对原始方法进行调用将跳过原始方法的执行
- 对原始方法的调用可以不接收返回值,通知方法设置成void即可,如果接收返回值,最好设定为 Object 类型
- 原始方法的返回值如果是 void 类型,通知方法的返回值类型可以设置成void,也可以设置成 Object
- 由于无法预知原始方法运行后是否会抛出异常,因此环绕通知方法必须要处理 Throwable 异常
- 总结:
名称 | @After |
---|---|
类型 | 方法注解 |
位置 | 通知方法定义上方 |
作用 | 设置当前通知方法与切入点之间的绑定关系,当前通知方法在原始切入点方法后运行 |
- 返回后通知: @AfterReturning
名称 | @AfterReturning |
---|---|
类型 | 方法注解 |
位置 | 通知方法定义上方 |
作用 | 设置当前通知方法与切入点之间绑定关系,当前通知方法在原始切入点方法正常执行完毕后执行 |
- 抛出异常后通知:@AfterThrowing
名称 | @AfterThrowing |
---|---|
类型 | 方法注解 |
位置 | 通知方法定义上方 |
作用 | 设置当前通知方法与切入点之间绑定关系,当前通知方法在原始切入点方法运行抛出异常后执行 |
- 环绕通知:@Around
名称 | @Around |
---|---|
类型 | 方法注解 |
位置 | 通知方法定义上方 |
作用 | 设置当前通知方法与切入点之间的绑定关系,当前通知方法在原始切入点方法前后运行 |
9.6 案例:
- 测量业务层接口万次执行效率
- 需求:任意业务层接口执行均可显示其执行效率(执行时长)
- 业务层接口执行前后分别记录时间,求差值得到执行效率
- 通知类型选择前后均可以增强的类型——环绕通知
//设置环绕通知,在原始操作的运行前后记录执行时间
@Around("ProjectAdvice.servicePt()")
public void runSpeed(ProceedingJoinPoint pjp) throws Throwable {
//获取执行的签名对象信息
Signature signature = pjp.getSignature();
String className = signature.getDeclaringTypeName();
String methodName = signature.getName();
long start = System.currentTimeMillis();
for (int i = 0; i < 10000; i ) {
pjp.proceed();
}
long end = System.currentTimeMillis();
System.out.println("万次执行:" className "." methodName "---->" (end-start) "ms");
}
- 当前测试的接口执行效率仅仅是一个理论值,并不是一次完整的执行过程
9.7 AOP 通知获取数据
- 获取切入点方法的参数:
- JoinPoint:适用于前置、后置、返回后、抛出异常后通知
- ProceedJointPoint:适用于环绕通知
- 获取切入点方法返回值
- 返回后通知
- 环绕通知
- 获取切入点方法运行异常信息
- 抛出异常后通知
- 环绕通知
- JoinPoint 对象描述了连接点方法的运行状态,可以获取到原始方法的调用函数
@Before("pt()")
public void before(JoinPoint jp) {
Object[] args = jp.getArgs();
System.out.println(Arrays.toString(args));
System.out.println("before advice ..." );
}
- ProceedJointPoint 是 JoinPoint 的子类
@Around("pt()")
public Object around(ProceedingJoinPoint pjp) {
Object[] args = pjp.getArgs();
System.out.println(Arrays.toString(args));
Object ret = pjp.proceed();
return ret;
}
- 抛出异常后通知可以获取切入点方法中出现的异常信息,使用形参可以接收对应的异常对象
//设置返回后通知获取原始方法的返回值,要求returning属性值必须与方法形参名相同
@AfterReturning(value = "pt()",returning = "ret")
public void afterReturning(JoinPoint jp,String ret) {
System.out.println("afterReturning advice ..." ret);
}
- 环绕通知中可以手工书写对原始方法的调用,得到的结果即为原始方法的返回值
@Around("pt()")
public Object around(ProceedingJoinPoint pjp) throws Throwable{
Object ret = pjp.proceed();
return ret;
}
抛异常:
- 抛出异常后通知可以获取切入点方法中出现的异常信息,使用形参可以接收对应的异常对象
//设置抛出异常后通知获取原始方法运行时抛出的异常对象,要求throwing属性值必须与方法形参名相同
@AfterThrowing(value = "pt()",throwing = "t")
public void afterThrowing(Throwable t) {
System.out.println("afterThrowing advice ..." t);
}
- 抛出异常后通知可以获取切入点方法运行的异常信息,使用形参可以接收运行时抛出的异常对象
@Around("pt()")
public Object around(ProceedingJoinPoint pjp) {
Object ret = null;
try {
ret = pjp.proceed(args);
} catch (Throwable t) {
t.printStackTrace();
}
return ret;
}
9.8 实际案例:
百度网盘密码数据兼容处理
代码语言:javascript复制 @Around("DataAdvice.servicePt()")
public Object trimStr(ProceedingJoinPoint pjp) throws Throwable {
Object[] args = pjp.getArgs();
for (int i = 0; i < args.length; i ) {
//判断参数是不是字符串
if(args[i].getClass().equals(String.class)){
args[i] = args[i].toString().trim();
}
}
Object ret = pjp.proceed(args);
return ret;
}
9.9 总结
AOP的核心概念
- 概念:AOP(Aspect Oriented Programming)面向切面编程,一种编程范式
- 作用:在不惊动原始设计的基础上为方法进行功能==增强==
- 核心概念
- 代理(Proxy):SpringAOP的核心本质是采用代理模式实现的
- 连接点(JoinPoint):在SpringAOP中,理解为任意方法的执行
- 切入点(Pointcut):匹配连接点的式子,也是具有共性功能的方法描述
- 通知(Advice):若干个方法的共性功能,在切入点处执行,最终体现为一个方法
- 切面(Aspect):描述通知与切入点的对应关系
- 目标对象(Target):被代理的原始对象成为目标对象
切入点表达式
- 切入点表达式标准格式:动作关键字(访问修饰符 返回值 包名.类/接口名.方法名(参数)异常名)
execution(* top.bigdata.service.*Service.*(..))
- 切入点表达式描述通配符:
- 作用:用于快速描述,范围描述
*
:匹配任意符号(常用)..
:匹配多个连续的任意符号(常用)
- 切入点表达式书写技巧 1.按==标准规范==开发 2.查询操作的返回值建议使用*匹配 3.减少使用..的形式描述包 4.==对接口进行描述==,使用*表示模块名,例如UserService的匹配描述为*Service 5.方法名书写保留动词,例如get,使用*表示名词,例如getById匹配描述为getBy* 6.参数根据实际情况灵活调整
五种通知类型
- 前置通知
- 后置通知
- 环绕通知(重点)
- 环绕通知依赖形参ProceedingJoinPoint才能实现对原始方法的调用
- 环绕通知可以隔离原始方法的调用执行
- 环绕通知返回值设置为Object类型
- 环绕通知中可以对原始方法调用过程中出现的异常进行处理
- 返回后通知
- 抛出异常后通知
通知中获取参数
- 获取切入点方法的参数,所有的通知类型都可以获取参数
- JoinPoint:适用于前置、后置、返回后、抛出异常后通知
- ProceedingJoinPoint:适用于环绕通知
- 获取切入点方法返回值,前置和抛出异常后通知是没有返回值,后置通知可有可无,所以不做研究
- 返回后通知
- 环绕通知
- 获取切入点方法运行异常信息,前置和返回后通知是不会有,后置通知可有可无,所以不做研究
- 抛出异常后通知
- 环绕通知
10. 事务
- 事务作用:在数据层保障一系列的数据库操作同成功同失败
- Spring 事务作用:在数据库或 业务层 保障一系列的数据库操作同成功同失败
10.1 案例:
银行账户转账
- 在业务层接口上添加 Spring 事务管理
public interface AccountService {
/**
* 转账操作
* @param out 传出方
* @param in 转入方
* @param money 金额
*/
//配置当前接口方法具有事务
@Transactional //开启Spring事务
public void transfer(String out,String in ,Double money) ;
}
- 注意:
- Spring 注解式事务通常添加在业务层 接口 中而不会添加到业务层实现类中,降低耦合
- @Transactional 注解可以写在方法上,也可以写在类和接口上
- 注解式事务可以添加到业务方法上表示当前方法开启事务,也可以添加到接口上表示当前接口所有方法开启事务
- 设置事务管理器:
//配置事务管理器,mybatis使用的是jdbc事务
@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource){
DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
transactionManager.setDataSource(dataSource);
return transactionManager;
}
- 注意:
- 事务管理器要根据实现技术进行选择
- MyBatis 框架使用的是 JDBC 事务
- 开启注解式事务驱动
@Configuration
@ComponentScan("com.itheima")
@PropertySource("classpath:jdbc.properties")
@Import({JdbcConfig.class,MybatisConfig.class})
//开启注解式事务驱动
@EnableTransactionManagement
public class SpringConfig {
}
10.2 事务角色
- 事务角色:
- 事务管理员:发起事务方,在 Spring 中通常指代业务层开启事务的方法
- 事务协调员:加入事务方,在 Spring 中通常指代数据层方法,也可以是业务层方法
- 针对上面案例:
- 在这个方法调用下面两个方法。
@Transactional //开启Spring事务
public void transfer(String out,String in ,Double money) { //将 Spring 开启的事务叫事务管理员
accountDao.outMoney(out,money);
accountDao.inMoney(in,money);
}
- 下面这两个都在上面的控制范围内,下面两个方法各自开启的事务全部改成和上面一样的事务,加入到上面的事务中
@Update("update tbl_account set money = money #{money} where name = #{name}")
void inMoney(@Param("name") String name, @Param("money") Double money);
//加入到Spring事务中的成员叫 事务协调员
@Update("update tbl_account set money = money - #{money} where name = #{name}")
void outMoney(@Param("name") String name, @Param("money") Double money);
10.3 事务属性
相关配置
属性 | 作用 | 实例 |
---|---|---|
readOnly | 设置是否为只读事务 | readOnly=true 只读事务 |
timeout | 设置事务超时时间 | timeout = -1 (永不超时) |
rollbackFor | 设置事务回滚异常(class) | rollbackFor={NullPointException.class} |
rollbackForClassName | 设置事务回滚异常(String) | 同上格式为字符串 |
noRollbackFor | 设置事务不回滚异常(class) | noRollbackFor={NullPointException.class} |
noRollbackForClassName | 设置事务不回滚异常(String) | 同上格式为字符串 |
propagation | 设置事务传播行为 | …… |
10.4 传播行为
- 传播行为:事务协调员对事务管理员所携带事务的处理态度
- 在业务层接口上添加 Spring 事务,设置事务传播行为 REQUIRES_NEW(需要新事务)
@Service
public class LogServiceImpl implements LogService {
@Autowired
private LogDao logDao;
//propagation设置事务属性:传播行为设置为当前操作需要开启新事务
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void log(String out,String in,Double money ) {
logDao.log("转账操作由" out "到" in ",金额:" money);
}
}
Spring基础好像也就这么多了,欢迎大家留言指正呦~~