title: Spring
author: YangShen
tags:
- Spring
categories: - java学习
- Spring
abbrlink: 6c92115f
date: 2021-12-08 15:27:00
Spring IoC
1)Spring简介
1.1)什么是框架
- 源自于建筑学,隶属土木工程,后发展到软件工程领域
- 软件工程框架:经过验证的,具有一定功能的,半成品软件
- 经过验证
- 具有一定功能
- 半成品
1.2)框架的作用
1.3)Spring是什么
Spring是分层的JavaSE/EE应用full-stack轻量级开源框架
1.4)Spring的体系结构
1.5)Spring的发展历史
1.6)Spring优势
Spring的优势 | |
---|---|
方便解耦,简化开发 | 第一天 |
方便集成各种优秀框架 | 第一天 |
方便程序的测试 | 第二天 |
AOP编程的支持 | 第三天 |
声明式事务的支持 | 第四天 |
降低JavaEE API的使用难度 | 第四天 |
Java源码是经典学习范例 | 长期学习 |
2)IoC简介
2.1)优质程序代码的制作原则
2.2)耦合与内聚
- 耦合(Coupling):代码书写过程中所使用技术的结合紧密度,用于衡量软件中各个模块之间的互联程度
- 内聚(Cohesion):代码书写过程中单个模块内部各组成部分间的联系,用于衡量软件中各个功能模块内部的功能联系
- 程序书写的目标:高内聚,低耦合
- 就是同一个模块内的各个元素之间要高度紧密,但是各个模块之间的相互依存度却不要那么紧密
2.3)工厂模式发展史
2.4)Spring发展历程
2.5)IoC
- IoC(Inversion Of Control)控制反转,Spring反向控制应用程序所需要使用的外部资源
- Spring控制的资源全部放置在Spring容器中,该容器称为IoC容器
3)入门案例
3.1)案例环境说明
- 模拟三层架构中表现层调用业务层功能
- 表现层:UserApp模拟UserServlet(使用main方法模拟)
- 业务层:UserService
3.2)IoC入门案例制作步骤
1.导入spring坐标(5.1.9.release)
2.编写业务层与表现层(模拟)接口与实现类
3.建立spring配置文件
4.配置所需资源(Service)为spring控制的资源
5.表现层(App)通过spring获取资源(Service实例)
3.2.1)IoC入门案例制作步骤-1
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.1.9.RELEASE</version>
</dependency>
3.2.2)IoC入门案例制作步骤-2
public interface UserService {
//业务方法
void save();
}
3.2.3)IoC入门案例制作步骤-3
public class UserServiceImpl implements UserService {
public void save() {
System.out.println("user service running...");
}
}
3.2.4)IoC入门案例制作步骤-4
<?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
https://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 1.创建spring控制的资源-->
<bean id="userService" class="com.itheima.service.impl.UserServiceImpl"/>
</beans>
3.2.5)IoC入门案例制作步骤-5
public class UserApp {
public static void main(String[] args) {
//2.加载配置文件
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
//3.获取资源
UserService userService = (UserService) ctx.getBean("userService");
userService.save();
}
}
4)IoC配置(XML格式)
4.1)bean
- 名称:bean
- 类型:标签
- 归属:beans标签
- 作用:定义spring中的资源,受此标签定义的资源将受到spring控制
- 格式:
<beans>
<bean />
</beans>
- 基本属性:
<bean id="beanId" name="beanName1,beanName2" class="ClassName"></bean>
id:bean的名称,通过id值获取bean
class:bean的类型
name:bean的名称,可以通过name值获取bean,用于多人配合时给bean起别名
4.2)bean属性scope
- 名称:scope
- 类型:属性
- 归属:bean标签
- 作用:定义bean的作用范围
- 格式:
<bean scope="singleton"></bean>
- 取值:
- singleton:设定创建出的对象保存在spring容器中,是一个单例的对象
- prototype:设定创建出的对象保存在spring容器中,是一个非单例的对象
- request、session、application、 websocket :设定创建出的对象放置在web容器对应的位置
4.3)bean生命周期
- 名称:init-method,destroy-method
- 类型:属性
- 归属:bean标签
- 作用:定义bean对象在初始化或销毁时完成的工作
- 格式:
<bean init-method="init" destroy-method="destroy></bean>
- 取值:bean对应的类中对应的具体方法名
- 注意事项:
- 当scope=“singleton”时,spring容器中有且仅有一个对象,init方法在创建容器时仅执行一次
- 当scope=“prototype”时,spring容器要创建同一类型的多个对象,init方法在每个对象创建时均执行一次
- 当scope=“singleton”时,关闭容器会导致bean实例的销毁,调用destroy方法一次
- 当scope=“prototype”时,对象的销毁由垃圾回收机制gc()控制,destroy方法将不会被执行
4.4)bean对象创建方式(了解)
(1)factory-bean
- 名称:factory-bean
- 类型:属性
- 归属:bean标签
- 作用:定义bean对象创建方式,使用静态工厂的形式创建bean,兼容早期遗留系统的升级工作
- 格式:
<bean class="FactoryClassName" factory-method="factoryMethodName"></bean>
- 取值:工厂bean中用于获取对象的静态方法名
- 注意事项:
- class属性必须配置成静态工厂的类名
(2)factory-bean,factory-method
- 名称:factory-bean,factory-method
- 类型:属性
- 归属:bean标签
- 作用:定义bean对象创建方式,使用实例工厂的形式创建bean,兼容早期遗留系统的升级工作
- 格式:
<bean factory-bean="factoryBeanId" factory-method="factoryMethodName"></bean>
- 取值:工厂bean中用于获取对象的实例方法名
- 注意事项:
- 使用实例工厂创建bean首先需要将实例工厂配置bean,交由spring进行管理
- factory-bean是实例工厂的beanId
4.5)DI
- IoC(Inversion Of Control)控制反转,Spring反向控制应用程序所需要使用的外部资源
- DI(Dependency Injection)依赖注入,应用程序运行依赖的资源由Spring为其提供,资源进入应用程序的方式称为注入
IoC与DI的关系
- IoC与DI是同一件事站在不同角度看待问题
4.6)set注入(主流)
- 名称:property
- 类型:标签
- 归属:bean标签
- 作用:使用set方法的形式为bean提供资源
- 格式:
<bean>
<property />
</bean>
- 基本属性:
<property name="propertyName" value="propertyValue" ref="beanId"/>
- name:对应bean中的属性名,要求该属性必须提供可访问的set方法(严格规范为此名称是set方法对应名称)
- value:设定非引用类型属性对应的值,不能与ref同时使用
- ref:设定引用类型属性对应bean的id ,不能与value同时使用
- 注意:一个bean可以有多个property标签
4.7)构造器注入(了解)
- 名称:constructor-arg
- 类型:标签
- 归属:bean标签
- 作用:使用构造方法的形式为bean提供资源,兼容早期遗留系统的升级工作
- 格式:
<bean>
<constructor-arg />
</bean>
- 基本属性:
<constructor-arg name="argsName" value="argsValue />
- name:对应bean中的构造方法所携带的参数名
- value:设定非引用类型构造方法参数对应的值,不能与ref同时使用
其他属性:
<constructor-arg index="arg-index" type="arg-type" ref="beanId"/>
-
ref:设定引用类型构造方法参数对应bean的id ,不能与value同时使用
-
type :设定构造方法参数的类型,用于按类型匹配参数或进行类型校验
-
index :设定构造方法参数的位置,用于按位置匹配参数,参数index值从0开始计数
-
注意:一个bean可以有多个constructor-arg标签
4.8)集合类型数据注入
- 名称:array,list,set,map,props
- 类型:标签
- 归属:property标签 或 constructor-arg标签
- 作用:注入集合数据类型属性
- 格式:
<property>
<list></list>
</property>
(1)集合类型数据注入——list
<property name="al">
<list>
<value>itheima</value>
<value>66666</value>
</list>
</property>
(2)集合类型数据注入——props
<property name="properties">
<props>
<prop key="name">itheima666</prop>
<prop key="value">666666</prop>
</props>
</property>
(3)集合类型数据注入——array (了解)
<property name="arr">
<array>
<value>123456</value>
<value>66666</value>
</array>
</property>
(4)集合类型数据注入——set(了解)
<property name="hs">
<set>
<value>itheima</value>
<value>66666</value>
</set>
</property>
(5)集合类型数据注入——map(了解)
<property name="hm">
<map>
<entry key="name" value="itheima66666"/>
<entry key="value" value="6666666666"/>
</map>
</property>
4.9)使用p命名空间简化配置(了解)
- 名称:p:propertyName,p:propertyName-ref
- 类型:属性
- 归属:bean标签
- 作用:为bean注入属性值
- 格式:
<bean p:propertyName="propertyValue" p:propertyName-ref="beanId"/>
- 注意:使用p命令空间需要先开启spring对p命令空间的的支持,在beans标签中添加对应空间支持
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd">
后续课程中还将开启其他的命名空间,方式同上
- 案例:
<bean
id="userService"
class="com.itheima.service.impl.UserServiceImpl"
p:userDao-ref="userDao"
p:bookDao-ref="bookDao"
/>
4.10)SpEL (了解)
- Spring提供了对EL表达式的支持,统一属性注入格式
- 类型:属性值
- 归属:value属性值
- 作用:为bean注入属性值
- 格式:
<property value="EL"></bean>
- 注意:所有属性值不区分是否引用类型,统一使用value赋值
- 所有格式统一使用 value=“”
- 常量 #{10} #{3.14} #{2e5} #{‘itcast’}
- 引用bean #{beanId}
- 引用bean属性 #{beanId.propertyName}
- 引用bean方法 beanId.methodName().method2()
- 引用静态方法 T(java.lang.Math).PI
- 运算符支持 #{3 lt 4 == 4 ge 3}
- 正则表达式支持 #{user.name matches‘[a-z]{6,}’}
- 集合支持 #{likes[3]}
- 案例:
<bean id="userService" class="com.itheima.service.impl.UserServiceImpl">
<property name="userDao" value="#{userDao}"/>
<property name="bookDao" value="#{bookDao}"/>
<property name="num" value="#{666666666}"/>
<property name="version" value="#{'itcast'}"/>
</bean>
4.11)properties文件
- Spring提供了读取外部properties文件的机制,使用读取到的数据为bean的属性赋值
- 操作步骤
1.准备外部properties文件
2.开启context命名空间支持
xmlns:context="http://www.springframework.org/schema/context"
3.加载指定的properties文件
<context:property-placeholder location="classpath:filename.properties">
4.使用加载的数据
<property name="propertyName" value="${propertiesName}"/>
- 注意:如果需要加载所有的properties文件,可以使用
*.properties
表示加载所有的properties文件 - 注意:读取数据使用**${propertiesName}格式进行,其中propertiesName**指properties文件中的属性名
4.12)团队开发
- 名称:import
- 类型:标签
- 归属:beans标签
- 作用:在当前配置文件中导入其他配置文件中的项
- 格式:
<beans>
<import />
</beans>
- 基本属性:
<import resource=“config.xml"/>
resource:加载的配置文件名
- Spring容器加载多个配置文件
new ClassPathXmlApplicationContext("config1.xml","config2.xml");
- Spring容器中的bean定义冲突问题
- 同id的bean,后定义的覆盖先定义的
- 导入配置文件可以理解为将导入的配置文件复制粘贴到对应位置
- 导入配置文件的顺序与位置不同可能会导致最终程序运行结果不同
4.13)ApplicationContext
1.ApplicationContext是一个接口,提供了访问spring容器的API
2.ClassPathXmlApplicationContext是一个类,实现了上述功能
3.ApplicationContext的顶层接口是BeanFactory
4.BeanFactory定义了bean相关的最基本操作
5.ApplicationContext在BeanFactory基础上追加了若干新功能
对比BeanFactory
1.BeanFactory创建的bean采用延迟加载形式,使用才创建
2.ApplicationContext创建的bean默认采用立即加载形式
FileSystemXmlApplicationContext
可以加载文件系统中任意位置的配置文件,而ClassPathXmlApplicationContext只能加载类路径下的配置文件
BeanFactory
Resource res = new ClassPathResource("applicationContext.xml");
BeanFactory bf = new XmlBeanFactory(res);
UserService userService = (UserService)bf.getBean("userService");
4.14)第三方资源配置
- 阿里数据源方案Druid
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
<property name="url" value="jdbc:mysql://localhost:3306/spring_ioc"></property>
<property name="username" value="root"></property>
<property name="password" value="root"></property>
</bean>
5)综合案例
5.1)案例介绍
- 使用spring整合mybatis技术,完成账户模块(Account)的基础增删改查功能
- 账户模块对应字段
- 编号:id
- 账户名:name
- 余额:money
5.2)案例分析
非spring环境
1.实体类与表
2.业务层接口与实现
3.数据层接口
4.Mybatis核心配置
5.Mybatis映射配置
6.客户端程序测试功能
spring环境
1.实体类与表
2.业务层接口与实现(提供数据层接口的注入操作)
3.数据层接口
4.Mybatis核心配置(交给spring控制,该文件省略)
5.Mybatis映射配置
6.客户端程序测试功能(使用spring方式获取bean)
7.Spring核心配置文件
8.Druid数据源的应用(可选)
9.Spring整合MyBatis
5.3)案例制作步骤——基础准备工作
- 环境准备
1.导入Spring坐标,MyBatis坐标,MySQL坐标,Druid坐标
- 业务类与接口准备
2.创建数据库表,并制作相应的实体类Account
3.定义业务层接口与数据层接口
4.在业务层调用数据层接口,并实现业务方法的调用
- 基础配置文件
5.jdbc.properties
6.MyBatis映射配置文件
5.4)案例制作步骤——整合准备工作
- 整合前基础准备工作
1.spring配置文件,加上context命名空间,用于加载properties文件
2.开启加载properties文件
3.配置数据源druid(备用)
4.定义service层bean,注入dao层bean
5.dao的bean无需定义,使用代理自动生成
5.5)案例制作步骤——整合工作
- 整合工作
1.导入Spring整合MyBatis坐标
2.将mybatis配置成spring管理的bean(SqlSessionFactoryBean)
-将原始配置文件中的所有项,转入到当前配置中
数据源转换
映射转换
3.通过spring加载mybatis的映射配置文件到spring环境中
4.设置类型别名
- 测试结果
5.使用spring环境加载业务层bean,执行操作
小节
- 需要专用的spring整合mybatis的jar包
- Mybatis核心配置文件消失
- 环境environment转换成数据源对象
- 映射Mapper扫描工作交由spring处理
- 类型别名交由spring处理
- 业务发起使用spring上下文对象获取对应的bean
注解开发
1)注解驱动的意义
1.1)什么是注解驱动
注解启动时使用注解的形式替代xml配置,将繁杂的spring配置文件从工程中彻底消除掉,简化书写
1.2)注解驱动的弊端
- 为了达成注解驱动的目的,可能会将原先很简单的书写,变的更加复杂
- XML中配置第三方开发的资源是很方便的,但使用注解驱动无法在第三方开发的资源中进行编辑,因此会增大开发工作量
2)常用注解
2.1)启动注解功能
- 启动注解扫描,加载类中配置的注解项
<context:component-scan base-package="packageName"/>
- 说明:
- 在进行包所扫描时,会对配置的包及其子包中所有文件进行扫描
- 扫描过程是以文件夹递归迭代的形式进行的
- 扫描过程仅读取合法的java文件
- 扫描时仅读取spring可识别的注解
- 扫描结束后会将可识别的有效注解转化为spring对应的资源加入IoC容器
- 注意:
- 无论是注解格式还是XML配置格式,最终都是将资源加载到IoC容器中,差别仅仅是数据读取方式不同
- 从加载效率上来说注解优于XML配置文件
2.2)bean的定义
- 名称:@Component @Controller @Service @Repository
- 类型:类注解
- 位置:类定义上方
- 作用:设置该类为spring管理的bean
- 范例:
@Component
public class ClassName{}
- 说明:
- @Controller、@Service 、@Repository是@Component的衍生注解,功能同@Component
- 相关属性
- value(默认):定义bean的访问id
2.3)bean的作用域
- 名称:@Scope
- 类型:类注解
- 位置:类定义上方
- 作用:设置该类作为bean对应的scope属性
- 范例:
@Scope
public class ClassName{}
- 相关属性
- value(默认):定义bean的作用域,默认为singleton
2.4)bean的生命周期
- 名称:@PostConstruct、@PreDestroy
- 类型:方法注解
- 位置:方法定义上方
- 作用:设置该类作为bean对应的生命周期方法
- 范例:
@PostConstruct
public void init() { System.out.println("init..."); }
2.5)加载第三方资源
- 名称:@Bean
- 类型:方法注解
- 位置:方法定义上方
- 作用:设置该方法的返回值作为spring管理的bean
- 范例:
@Bean("dataSource")
public DruidDataSource createDataSource() { return ……; }
- 说明:
- 因为第三方bean无法在其源码上进行修改,使用@Bean解决第三方bean的引入问题
- 该注解用于替代XML配置中的静态工厂与实例工厂创建bean,不区分方法是否为静态或非静态
- @Bean所在的类必须被spring扫描加载,否则该注解无法生效
- 相关属性
- value(默认):定义bean的访问id
2.6)bean的非引用类型属性注入
- 名称:@Value
- 类型:属性注解、方法注解
- 位置:属性定义上方,方法定义上方
- 作用:设置对应属性的值或对方法进行传参
- 范例:
@Value("${jdbc.username}")
private String username;
- 说明:
- value值仅支持非引用类型数据,赋值时对方法的所有参数全部赋值
- value值支持读取properties文件中的属性值,通过类属性将properties中数据传入类中
- value值支持SpEL
- @value注解如果添加在属性上方,可以省略set方法(set方法的目的是为属性赋值)
- 相关属性
- value(默认):定义对应的属性值或参数值
2.7)bean的引用类型属性注入
- 名称:@Autowired、@Qualifier
- 类型:属性注解、方法注解
- 位置:属性定义上方,方法定义上方
- 作用:设置对应属性的对象或对方法进行引用类型传参
- 范例:
@Autowired(required = false)
@Qualifier("userDao")
private UserDao userDao;
- 说明:
- @Autowired默认按类型装配,指定@Qualifier后可以指定自动装配的bean的id
- 相关属性
- required:定义该属性是否允许为null
2.8)bean的引用类型属性注入
- 名称:@Primary
- 类型:类注解
- 位置:类定义上方
- 作用:设置类对应的bean按类型装配时优先装配
- 范例:
@Primary
public class ClassName{}
- 说明:
- @Autowired默认按类型装配,当出现相同类型的bean,使用@Primary提高按类型自动装配的优先级,多个@Primary会导致优先级设置无效
2.9)bean的引用类型属性注入
- 名称:@Inject、@Named、@Resource
- 说明:
- @Inject与@Named是JSR330规范中的注解,功能与@Autowired和@Qualifier完全相同,适用于不同架构场景
- @Resource是JSR250规范中的注解,可以简化书写格式
- @Resource相关属性
- name:设置注入的bean的id
- type:设置注入的bean的类型,接收的参数为Class类型
2.10)加载properties文件
- 名称:@PropertySource
- 类型:类注解
- 位置:类定义上方
- 作用:加载properties文件中的属性值
- 范例:
@PropertySource(value = "classpath:filename.properties")
public class ClassName {
@Value("${propertiesAttributeName}")
private String attributeName;
}
- 说明:
- 不支持*通配格式,一旦加载,所有spring控制的bean中均可使用对应属性值
- 相关属性
- value(默认):设置加载的properties文件名
- ignoreResourceNotFound:如果资源未找到,是否忽略,默认为false
2.11)纯注解格式
- 名称:@Configuration、@ComponentScan
- 类型:类注解
- 位置:类定义上方
- 作用:设置当前类为spring核心配置加载类
- 范例:
@Configuration
@ComponentScan("scanPackageName")
public class SpringConfigClassName{
}
- 说明:
- 核心配合类用于替换spring核心配置文件,此类可以设置空的,不设置变量与属性
- bean扫描工作使用注解@ComponentScan替代
AnnotationConfigApplicationContext
- 加载纯注解格式上下文对象,需要使用AnnotationConfigApplicationContext
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
2.12)第三方bean配置与管理
- 名称:@Import
- 类型:类注解
- 位置:类定义上方
- 作用:导入第三方bean作为spring控制的资源
- 范例:
@Configuration
@Import(OtherClassName.class)
public class ClassName {
}
- 说明:
- @Import注解在同一个类上,仅允许添加一次,如果需要导入多个,使用数组的形式进行设定
- 在被导入的类中可以继续使用@Import导入其他资源(了解)
- @Bean所在的类可以使用导入的形式进入spring容器,无需声明为bean
3)bean加载控制
3.1)依赖加载
(1)@DependsOn
- 名称:@DependsOn
- 类型:类注解、方法注解
- 位置:bean定义的位置(类上或方法上)
- 作用:控制bean的加载顺序,使其在指定bean加载完毕后再加载
- 范例:
@DependsOn("beanId")
public class ClassName {
}
- 说明:
- 配置在方法上,使@DependsOn指定的bean优先于@Bean配置的bean进行加载
- 配置在类上,使@DependsOn指定的bean优先于当前类中所有@Bean配置的bean进行加载
- 配置在类上,使@DependsOn指定的bean优先于@Component等配置的bean进行加载
- 相关属性
- value(默认):设置当前bean所依赖的bean的id
(2)@Order
- 名称:@Order
- 类型:配置类注解
- 位置:配置类定义的位置(类上)
- 作用:控制配置类的加载顺序
- 范例:
@Order(1)
public class SpringConfigClassName {
}
(3)@Lazy
- 名称:@Lazy
- 类型:类注解、方法注解
- 位置:bean定义的位置(类上或方法上)
- 作用:控制bean的加载时机,使其延迟加载
- 范例:
@Lazy
public class ClassName {
}
3.2)依赖加载应用场景
- 微信订阅号,发布消息和订阅消息的bean的加载顺序控制
- 双11活动期间,零点前是结算策略A,零点后是结算策略B,策略B操作的数据为促销数据。策略B加载顺序与促销数据的加载顺序
- 程序灾难出现后对应的应急预案处理是启动容器时加载时机
- 多个种类的配置出现后,优先加载系统级的,然后加载业务级的,避免细粒度的加载控制
4)整合第三方技术
4.1)综合案例改版(注解整合MyBatis)
4.2)注解整合MyBatis分析
- 业务类使用注解形式声明bean,属性采用注解注入
- 建立独立的配置管理类,分类管理外部资源,根据功能进行分类,并提供对应的方法获取bean
- 使用注解形式启动bean扫描,加载所有注解配置的资源(bean)
- 使用AnnotationConfigApplicationContext对象加载所有的启动配置类,内部使用导入方式进行关联
4.3)注解整合MyBatis步骤
1.修改mybatis外部配置文件格式为注解格式
2.业务类使用@Component声明bean,使用@Autowired注入对象
3.建立配置文件JDBCConfig与MyBatisConfig类,并将其导入到核心配置类SpringConfig
4.开启注解扫描
5.使用AnnotationConfigApplicationContext对象加载配置项
4.4)综合案例改版(注解整合Junit)
1.Spring接管Junit的运行权,使用Spring专用的Junit类加载器
2.为Junit测试用例设定对应的spring容器:
- 从Spring5.0以后,要求Junit的版本必须是4.12及以上
- Junit仅用于单元测试,不能将Junit的测试类配置成spring的bean,否则该配置将会被打包进入工程中
导入Spring整合Junit坐标
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.1.9.RELEASE</version>
</dependency>
Spring整合Junit测试用例注解格式
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfig.class)
public class UserServiceTest {
}
5)IoC底层核心原理
5.1)IoC核心接口
5.2)组件扫描器
- 开发过程中,需要根据需求加载必要的bean,排除指定bean
5.3)设定组件扫描加载过滤器
- 名称:@ComponentScan
- 类型:类注解
- 位置:类定义上方
- 作用:设置spring配置加载类扫描规则
- 范例:
@ComponentScan(
value="com.itheima", //设置基础扫描路径
excludeFilters = //设置过滤规则,当前为排除过滤
@ComponentScan.Filter( //设置过滤器
type= FilterType.ANNOTATION, //设置过滤方式为按照注解进行过滤
classes=Repository.class) //设置具体的过滤项,过滤所有@Repository修饰的bean
)
-
includeFilters:设置包含性过滤器
-
excludeFilters:设置排除性过滤器
-
type:设置过滤器类型
5.4)自定义组件过滤器
- 名称:TypeFilter
- 类型:接口
- 作用:自定义类型过滤器
- 范例:
public class MyTypeFilter implements TypeFilter {
public boolean match(MetadataReader mr, MetadataReaderFactory mrf) throws IOException {
ClassMetadata cm = metadataReader.getClassMetadata();
tring className = cm.getClassName();
if(className.equals("com.itheima.dao.impl.BookDaoImpl")){
return false;
}
return false;
}
}
5.5)自定义导入器
- bean只有通过配置才可以进入spring容器,被spring加载并控制
- 配置bean的方式如下:
- XML文件中使用标签配置
- 使用@Component及衍生注解配置
- 企业开发过程中,通常需要配置大量的bean,需要一种快速高效配置大量bean的方式
ImportSelector
- 名称: ImportSelector
- 类型:接口
- 作用:自定义bean导入器
- 范例:
public class MyImportSelector implements ImportSelector {
public String[] selectImports(AnnotationMetadata icm) {
return new String[]{"com.itheima.dao.impl.AccountDaoImpl"};
}
}
5.6)自定义注册器
- 名称:ImportBeanDefinitionRegistrar
- 类型:接口
- 作用:自定义bean定义注册器
- 范例:
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
public void registerBeanDefinitions(AnnotationMetadata icm, BeanDefinitionRegistry r) {
ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(r, false);
TypeFilter tf = new TypeFilter() {
public boolean match(MetadataReader mr, MetadataReaderFactory mrf) throws IOException {
return true;
}
};
scanner.addIncludeFilter(tf);
//scanner.addExcludeFilter(tf);
scanner.scan("com.itheima");
}
}
5.7)bean初始化过程解析
5.8)bean初始化过程解析
- BeanFactoryPostProcessor
- 作用:定义了在bean工厂对象创建后,bean对象创建前执行的动作,用于对工厂进行创建后业务处理
- 运行时机:当前操作用于对工厂进行处理,仅运行一次
- BeanPostProcessor
- 作用:定义了所有bean初始化前后进行的统一动作,用于对bean进行创建前业务处理与创建后业务处理
- 运行时机:当前操作伴随着每个bean的创建过程,每次创建bean均运行该操作
- InitializingBean
- 作用:定义了每个bean的初始化前进行的动作,属于非统一性动作,用于对bean进行创建前业务处理
- 运行时机:当前操作伴随着任意一个bean的创建过程,保障其个性化业务处理
- 注意:上述操作均需要被spring容器加载放可运行
5.9)bean初始化过程解析
5.10)繁琐的bean初始化过程处理
- FactoryBean
- 对单一的bean的初始化过程进行封装,达到简化配置的目的
FactoryBean与BeanFactory区别
- FactoryBean:封装单个bean的创建过程
- BeanFactory:Spring容器顶层接口,定义了bean相关的获取操作
AOP
1)AOP简介
1.1)OOP开发思路
1.2)AOP开发思想
1.3)AOP概念
- AOP(Aspect Oriented Programing)面向切面编程,一种编程范式,隶属于软工范畴,指导开发者如何组织程序结构
- AOP弥补了OOP的不足,基于OOP基础之上进行横向开发
- uOOP规定程序开发以类为主体模型,一切围绕对象进行,完成某个任务先构建模型
- uAOP程序开发主要关注基于OOP开发中的共性功能,一切围绕共性功能进行,完成某个任务先构建可能遇到的所有共性功能(当所有功能都开发出来也就没有共性与非共性之分)
- “AOP联盟”
1.4)AOP作用
- 伴随着AOP时代的降临,可以从各个行业的标准化、规范化开始入手,一步一步将所有共性功能逐一开发完毕,最终以功能组合来完成个别业务模块乃至整体业务系统的开发
- 目标:将软件开发由手工制作走向半自动化/全自动化阶段,实现“插拔式组件体系结构”搭建
1.5)AOP优势
- 提高代码的可重用性
- 业务代码编码更简洁
- 业务代码维护更高效
- 业务功能扩展更便捷
2)AOP入门案例
2.1)AOP相关概念
- Joinpoint(连接点):就是方法
- Pointcut(切入点):就是挖掉共性功能的方法
- Advice(通知):就是共性功能,最终以一个方法的形式呈现
- Aspect(切面):就是共性功能与挖的位置的对应关系
- Target(目标对象):就是挖掉功能的方法对应的类产生的对象,这种对象是无法直接完成最终工作的
- Weaving(织入):就是将挖掉的功能回填的动态过程
- Proxy(代理):目标对象无法直接完成工作,需要对其进行功能回填,通过创建原始对象的代理对象实现
- Introduction(引入/引介) :就是对原始对象无中生有的添加成员变量或成员方法
2.2)AOP开发过程
- **开发阶段(开发者完成) **
- 正常的制作程序
- 将非共性功能开发到对应的目标对象类中,并制作成切入点方法
- 将共性功能独立开发出来,制作成通知
- 在配置文件中,声明切入点
- 在配置文件中,声明切入点与通知间的关系(含通知类型),即切面
- **运行阶段(AOP完成) **
- Spring容器加载配置文件,监控所有配置的切入点方法的执行
- 当监控到切入点方法被运行,使用代理机制,动态创建目标对象的代理对象,根据通知类别,在代理对象的对应位置将通知对应的功能织入,完成完整的代码逻辑并运行
2.2)AOP开发方式
- XML方式
- XML+注解方式
- 注解方式
2.3)入门案例制作分析
1.导入相关坐标
2.确认要抽取的功能,并将其制作成方法保存到专用的类中,删除原始业务中对应的功能
3.将所有进行AOP操作的资源加载到IoC容器中
4.使用配置的方式描述被抽取功能的位置,并描述被抽取功能与对应位置的关系
5.运行程序
步骤一 导入坐标
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.4</version>
</dependency>
步骤二 在业务层抽取通用代码
步骤三 把通知加入spring容器管理
步骤四 在配置文件中配置aop的配置
<!--aop配置-->
<aop:config>
<!--配置切入点-->
<aop:pointcut id="pt" expression="execution(* *..*())"/>
<!--配置切面-->
<aop:aspect ref="myAdvice">
<!—通知与切入点之间的关系-->
<aop:before method="logAdvice" pointcut-ref="pt"/>
</aop:aspect>
</aop:config>
3)AOP配置(XML)
3.1)AspectJ
- Aspect(切面)用于描述切入点与通知间的关系,是AOP编程中的一个概念
- AspectJ是基于java语言对Aspect的实现
3.2)AOP配置
3.2.1)aop:config
- 名称:aop:config
- 类型:标签
- 归属:beans标签
- 作用:设置AOP
- 格式:
<beans>
<aop:config>……</aop:config>
<aop:config>……</aop:config>
</beans>
- 说明:一个beans标签中可以配置多个aop:config标签
3.2.2)aop:aspect
- 名称:aop:aspect
- 类型:标签
- 归属:aop:config标签
- 作用:设置具体的AOP通知对应的切入点
- 格式:
<aop:config>
<aop:aspect ref="beanId">……</aop:aspect>
<aop:aspect ref="beanId">……</aop:aspect>
</aop:config>
- 说明:
一个aop:config标签中可以配置多个aop:aspect标签 - 基本属性:
- ref :通知所在的bean的id
3.2.3)aop:pointcut
- 名称:aop:pointcut
- 类型:标签
- 归属:aop:config标签、aop:aspect标签
- 作用:设置切入点
- 格式:
<aop:config>
<aop:pointcut id="pointcutId" expression="……"/>
<aop:aspect>
<aop:pointcut id="pointcutId" expression="……"/>
</aop:aspect>
</aop:config>
- 说明:
一个aop:config标签中可以配置多个aop:pointcut标签,且该标签可以配置在aop:aspect标签内 - 基本属性:
- id :识别切入点的名称
- expression :切入点表达式
3.3)切入点
- 切入点描述的是某个方法
- 切入点表达式是一个快速匹配方法描述的通配格式,类似于正则表达式
3.4)切入点表达式的组成
- 切入点描述的是某个方法
- 切入点表达式是一个快速匹配方法描述的通配格式,类似于正则表达式
关键字(访问修饰符 返回值 包名.类名.方法名(参数)异常名)
-
关键字:描述表达式的匹配模式(参看关键字列表)
-
访问修饰符:方法的访问控制权限修饰符
-
类名:方法所在的类(此处可以配置接口名称)
-
异常:方法定义中指定抛出的异常
-
**范例: **
execution(public User com.itheima.service.UserService.findById(int))
3.4.1)切入点表达式——关键字
- execution :匹配执行指定方法
- args :匹配带有指定参数类型的方法
- within :……
- this :……
- target :……
- @within :……
- @target :……
- @args :……
- @annotation :……
- bean :……
- reference pointcut :……
3.4.2)切入点表达式——通配符
- *:单个独立的任意符号,可以独立出现,也可以作为前缀或者后缀的匹配符出现
execution(public * com.itheima.*.UserService.find*(*))
匹配com.itheima包下的任意包中的UserService类或接口中所有find开头的带有一个参数的方法
- … :多个连续的任意符号,可以独立出现,常用于简化包名与参数的书写
execution(public User com..UserService.findById(..))
匹配com包下的任意包中的UserService类或接口中所有名称为findById的方法
- +:专用于匹配子类类型
execution(* *..*Service+.*(..))
3.4.3)切入点表达式——逻辑运算符
- && :连接两个切入点表达式,表示两个切入点表达式同时成立的匹配
- || :连接两个切入点表达式,表示两个切入点表达式成立任意一个的匹配
- ! :连接单个切入点表达式,表示该切入点表达式不成立的匹配
3.4.4)切入点表达式——范例
execution(* *(..))
execution(* *..*(..))
execution(* *..*.*(..))
execution(public * *..*.*(..))
execution(public int *..*.*(..))
execution(public void *..*.*(..))
execution(public void com..*.*(..))
execution(public void com..service.*.*(..))
execution(public void com.itheima.service.*.*(..))
execution(public void com.itheima.service.User*.*(..))
execution(public void com.itheima.service.*Service.*(..))
execution(public void com.itheima.service.UserService.*(..))
execution(public User com.itheima.service.UserService.find*(..))
execution(public User com.itheima.service.UserService.*Id(..))
execution(public User com.itheima.service.UserService.findById(..))
execution(public User com.itheima.service.UserService.findById(int))
execution(public User com.itheima.service.UserService.findById(int,int))
execution(public User com.itheima.service.UserService.findById(int,*))
execution(public User com.itheima.service.UserService.findById(*,int))
execution(public User com.itheima.service.UserService.findById())
execution(List com.itheima.service.*Service+.findAll(..))
3.5)切入点的三种配置方式
<aop:config>
<!--配置公共切入点-->
<aop:pointcut id="pt1" expression="execution(* *(..))"/>
<aop:aspect ref="myAdvice">
<!--配置局部切入点-->
<aop:pointcut id="pt2" expression="execution(* *(..))"/>
<!--引用公共切入点-->
<aop:before method="logAdvice" pointcut-ref="pt1"/>
<!--引用局部切入点-->
<aop:before method="logAdvice" pointcut-ref="pt2"/>
<!--直接配置切入点-->
<aop:before method="logAdvice" pointcut="execution(* *(..))"/>
</aop:aspect>
</aop:config>
3.6)切入点配置经验
- 企业开发命名规范严格遵循规范文档进行
- 先为方法配置局部切入点
- 再抽取类中公共切入点
- 最后抽取全局切入点
- 代码走查过程中检测切入点是否存在越界性包含
- 代码走查过程中检测切入点是否存在非包含性进驻
- 设定AOP执行检测程序,在单元测试中监控通知被执行次数与预计次数是否匹配
- 设定完毕的切入点如果发生调整务必进行回归测试
(以上规则适用于XML配置格式)
3.7)通知类型
AOP的通知类型共5种:
- ** 前置通知**:原始方法执行前执行,如果通知中抛出异常,阻止原始方法运行
应用:数据校验 - 后置通知:原始方法执行后执行,无论原始方法中是否出现异常,都将执行通知
应用:现场清理 - 返回后通知:原始方法正常执行完毕并返回结果后执行,如果原始方法中抛出异常,无法执行
应用:返回值相关数据处理 - 抛出异常后通知:原始方法抛出异常后执行,如果原始方法没有抛出异常,无法执行
应用:对原始方法中出现的异常信息进行处理 - 环绕通知:在原始方法执行前后均有对应执行执行,还可以阻止原始方法的执行
应用:十分强大,可以做任何事情
3.7.1)aop:before
- 名称:aop:before
- 类型:标签
- 归属:aop:aspect标签
- 作用:设置前置通知
- 格式:
<aop:aspect ref="adviceId">
<aop:before method="methodName" pointcut="……"/>
</aop:aspect>
- 说明:一个aop:aspect标签中可以配置多个aop:before标签
- 基本属性:
- method :在通知类中设置当前通知类别对应的方法
- pointcut :设置当前通知对应的切入点表达式,与pointcut-ref属性冲突
- pointcut-ref :设置当前通知对应的切入点id,与pointcut属性冲突
3.7.2)aop:after
- 名称:aop:after
- 类型:标签
- 归属:aop:aspect标签
- 作用:设置后置通知
- 格式:
<aop:aspect ref="adviceId">
<aop:after method="methodName" pointcut="……"/>
</aop:aspect>
- 说明:一个aop:aspect标签中可以配置多个aop:after标签
- 基本属性:
- method :在通知类中设置当前通知类别对应的方法
- pointcut :设置当前通知对应的切入点表达式,与pointcut-ref属性冲突
- pointcut-ref :设置当前通知对应的切入点id,与pointcut属性冲突
3.7.3)aop:after-returning
- 名称:aop:after-returning
- 类型:标签
- 归属:aop:aspect标签
- 作用:设置返回后通知
- 格式:
<aop:aspect ref="adviceId">
<aop:after-returning method="methodName" pointcut="……"/>
</aop:aspect>
- 说明:一个aop:aspect标签中可以配置多个aop:after-returning标签
- 基本属性:
- method :在通知类中设置当前通知类别对应的方法
- pointcut :设置当前通知对应的切入点表达式,与pointcut-ref属性冲突
- pointcut-ref :设置当前通知对应的切入点id,与pointcut属性冲突
3.7.4)aop:after-throwing
- 名称:aop:after-throwing
- 类型:标签
- 归属:aop:aspect标签
- 作用:设置抛出异常后通知
- 格式:
<aop:aspect ref="adviceId">
<aop:after-throwing method="methodName" pointcut="……"/>
</aop:aspect>
- 说明:一个aop:aspect标签中可以配置多个aop:after-throwing标签
- 基本属性:
- method :在通知类中设置当前通知类别对应的方法
- pointcut :设置当前通知对应的切入点表达式,与pointcut-ref属性冲突
- pointcut-ref :设置当前通知对应的切入点id,与pointcut属性冲突
3.7.5)aop:around
- 名称:aop:around
- 类型:标签
- 归属:aop:aspect标签
- 作用:设置环绕通知
- 格式:
<aop:aspect ref="adviceId">
<aop:around method="methodName" pointcut="……"/>
</aop:aspect>
- 说明:一个aop:aspect标签中可以配置多个aop:around标签
- 基本属性:
- method :在通知类中设置当前通知类别对应的方法
- pointcut :设置当前通知对应的切入点表达式,与pointcut-ref属性冲突
- pointcut-ref :设置当前通知对应的切入点id,与pointcut属性冲突
环绕通知的开发方式
- 环绕通知是在原始方法的前后添加功能,在环绕通知中,存在对原始方法的显式调用
public Object around(ProceedingJoinPoint pjp) throws Throwable {
Object ret = pjp.proceed();
return ret;
}
- 环绕通知方法相关说明:
- 方法须设定Object类型的返回值,否则会拦截原始方法的返回。如果原始方法返回值类型为void,通知方也可以设定返回值类型为void,最终返回null
- 方法需在第一个参数位置设定ProceedingJoinPoint对象,通过该对象调用proceed()方法,实现对原始方法的调用。如省略该参数,原始方法将无法执行
- 使用proceed()方法调用原始方法时,因无法预知原始方法运行过程中是否会出现异常,强制抛出Throwable对象,封装原始方法中可能出现的异常信息
3.8)通知顺序(了解)
当同一个切入点配置了多个通知时,通知会存在运行的先后顺序,该顺序以通知配置的顺序为准
3.9)通知获取数据
- 参数
- 返回值
- 异常
3.9.1)通知获取参数数据
第一种情况:
- 设定通知方法第一个参数为JoinPoint,通过该对象调用getArgs()方法,获取原始方法运行的参数数组
public void before(JoinPoint jp) throws Throwable {
Object[] args = jp.getArgs();
}
- 所有的通知均可以获取参数
第二种情况:
- 设定切入点表达式为通知方法传递参数(锁定通知变量名)
- 原始方法
第三种情况
- 设定切入点表达式为通知方法传递参数(改变通知变量名的定义顺序)
- 原始方法
3.9.2)通知获取返回值数据
第一种:返回值变量名
- 设定返回值变量名
- 原始方法
public int save() {
System.out.println("user service running...");
return 100;
}
- AOP配置
<aop:aspect ref="myAdvice">
<aop:pointcut id="pt3" expression="execution(* *(..)) "/>
<aop:after-returning method="afterReturning" pointcut-ref="pt3" returning="ret"/>
</aop:aspect>
- 通知类
public void afterReturning(Object ret) {
System.out.println(ret);
}
- 适用于返回后通知(after-returning)
第二种:
- ** 在通知类的方法中调用原始方法获取返回值 **
- 原始方法
public int save() {
System.out.println("user service running...");
return 100;
}
- AOP配置l
<aop:aspect ref="myAdvice">
<aop:pointcut id="pt2" expression="execution(* *(..)) "/>
<aop:around method="around" pointcut-ref="pt2" />
</aop:aspect>
- 通知类
public Object around(ProceedingJoinPoint pjp) throws Throwable {
Object ret = pjp.proceed();
return ret;
}
- 适用于环绕通知(around)
3.9.3)通知获取异常数据
第一种:通知类的方法中调用原始方法捕获异常
- 在通知类的方法中调用原始方法捕获异常
- 原始方法
public void save() {
System.out.println("user service running...");
int i = 1/0;
}
- AOP配置
<aop:aspect ref="myAdvice">
<aop:pointcut id="pt4" expression="execution(* *(..)) "/>
<aop:around method="around" pointcut-ref="pt4" />
</aop:aspect>
- 通知类
public Object around(ProceedingJoinPoint pjp) throws Throwable {
Object ret = pjp.proceed(); //对此处调用进行try……catch……捕获异常,或抛出异常
return ret;
}
- 适用于环绕通知(around)
第二种:
- ** 设定异常对象变量名 **
- 原始方法
public void save() {
System.out.println("user service running...");
int i = 1/0;
}
- AOP配置
<aop:aspect ref="myAdvice">
<aop:pointcut id="pt4" expression="execution(* *(..)) "/>
<aop:after-throwing method="afterThrowing" pointcut-ref="pt4" throwing="t"/>
</aop:aspect>
- 通知类
public void afterThrowing(Throwable t){
System.out.println(t.getMessage());
}
- 适用于返回后通知(after-throwing)
4)AOP配置(注解)
4.1)AOP配置
4.2)注解开发AOP制作步骤
在XML格式基础上
- 导入坐标(伴随spring-context坐标导入已经依赖导入完成)
- 开启AOP注解支持
- 配置切面@Aspect
- 定义专用的切入点方法,并配置切入点@Pointcut
- 为通知方法配置通知类型及对应切入点@Before
4.3)注解开发AOP注意事项
1.切入点最终体现为一个方法,无参无返回值,无实际方法体内容,但不能是抽象方法
2.引用切入点时必须使用方法调用名称,方法后面的()不能省略
3.切面类中定义的切入点只能在当前类中使用,如果想引用其他类中定义的切入点使用“类名.方法名()”引用
4.可以在通知类型注解后添加参数,实现XML配置中的属性,例如after-returning后的returning属性
4.4)AOP注解详解
4.4.1)@Aspect
- 名称:@Aspect
- 类型:注解
- 位置:类定义上方
- 作用:设置当前类为切面类
- 格式:
@Aspect
public class AopAdvice {
}
- 说明:一个beans标签中可以配置多个aop:config标签
4.4.2)@Pointcut
- 名称:@Pointcut
- 类型:注解
- 位置:方法定义上方
- 作用:使用当前方法名作为切入点引用名称
- 格式:
@Pointcut("execution(* *(..))")
public void pt() {
}
- 说明:被修饰的方法忽略其业务功能,格式设定为无参无返回值的方法,方法体内空实现(非抽象)
4.4.3)@Before
- 名称:@Before
- 类型:注解
- 位置:方法定义上方
- 作用:标注当前方法作为前置通知
- 格式:
@Before("pt()")
public void before(){
}
- 特殊参数:
- 无
4.4.4)@After
- 名称:@After
- 类型:注解
- 位置:方法定义上方
- 作用:标注当前方法作为后置通知
- 格式:
@After("pt()")
public void after(){
}
- 特殊参数:
- 无
4.4.5)@AfterReturning
- 名称:@AfterReturning
- 类型:注解
- 位置:方法定义上方
- 作用:标注当前方法作为返回后通知
- 格式:
@AfterReturning(value="pt()",returning = "ret")
public void afterReturning(Object ret) {
}
- 特殊参数:
- returning :设定使用通知方法参数接收返回值的变量名
4.4.6)@AfterThrowing
- 名称:@AfterThrowing
- 类型:注解
- 位置:方法定义上方
- 作用:标注当前方法作为异常后通知
- 格式:
@AfterThrowing(value="pt()",throwing = "t")
public void afterThrowing(Throwable t){
}
- 特殊参数:
- throwing :设定使用通知方法参数接收原始方法中抛出的异常对象名
4.4.7)@Around
- 名称:@Around
- 类型:注解
- 位置:方法定义上方
- 作用:标注当前方法作为环绕通知
- 格式:
@Around("pt()")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
Object ret = pjp.proceed();
return ret;
}
- 特殊参数:
- 无
4.5)AOP注解开发通知执行顺序控制(了解)
1.AOP使用XML配置情况下,通知的执行顺序由配置顺序决定,在注解情况下由于不存在配置顺序的概念的概念,参照通知所配置的方法名字符串对应的编码值顺序,可以简单理解为字母排序
- 同一个通知类中,相同通知类型以方法名排序为准
- 不同通知类中,以类名排序为准
- 使用@Order注解通过变更bean的加载顺序改变通知的加载顺序
2.企业开发经验
- 通知方法名由3部分组成,分别是前缀、顺序编码、功能描述
- 前缀为固定字符串,例如baidu、itheima等,无实际意义
- 顺序编码为6位以内的整数,通常3位即可,不足位补0
- 功能描述为该方法对应的实际通知功能,例如exception、strLenCheck
- 制通知执行顺序使用顺序编码控制,使用时做一定空间预留
- 003使用,006使用,预留001、002、004、005、007、008
- 使用时从中段开始使用,方便后期做前置追加或后置追加
- 最终顺序以运行顺序为准,以测试结果为准,不以设定规则为准
4.6)AOP注解驱动
- 名称:@EnableAspectJAutoProxy
- 类型:注解
- 位置:Spring注解配置类定义上方
- 作用:设置当前类开启AOP注解驱动的支持,加载AOP注解
- 格式:
@Configuration
@ComponentScan("com.itheima")
@EnableAspectJAutoProxy
public class SpringConfig {
}
5)综合案例
5.1)案例介绍
对项目进行业务层接口执行监控,测量业务层接口的执行效率
public interface AccountService {
void save(Account account);
void delete(Integer id);
void update(Account account);
List<Account> findAll();
Account findById(Integer id);
}
5.2)案例分析
- 测量接口执行效率:接口方法执行前后获取执行时间,求出执行时长
- System.currentTimeMillis( )
- 对项目进行监控:项目中所有接口方法,AOP思想,执行期动态织入代码
- 环绕通知
- proceed()方法执行前后获取系统时间
5.3)案例制作步骤
- 定义切入点(务必要绑定到接口上,而不是接口实现类上)
- 制作AOP环绕通知,完成测量功能
- 注解配置AOP
- 开启注解驱动支持
5.4)案例制作核心代码
public class RunTimeMonitorAdvice {
//拦截所有的业务层接口中查询操作的执行
@Pointcut("execution(* com.itheima.service.*Service.find*(..))")
public void pt(){}
@Around("pt()")
public Object runtimeMonitor(ProceedingJoinPoint pjp) throws Throwable {
//获取执行签名信息
Signature signature = pjp.getSignature();
//通过签名获取执行类型(接口名)
String targetClass = signature.getDeclaringTypeName();
//通过签名获取执行操作名称(方法名)
String targetMethod = signature.getName();
//获取操作前系统时间beginTime
long beginTime = System.currentTimeMillis();
Object ret = pjp.proceed(pjp.getArgs());
//获取操作后系统时间endTime
long endTime = System.currentTimeMillis();
System.out.println(targetClass+" 中 "+targetMethod+" 运行时长 "+(endTime-beginTime)+"ms");
return ret;
}
}
5.5)案例后续思考与设计
- **测量真实性 **
- 开发测量是隔离性反复执行某个操作,是理想情况,上线测量差异过大
- 上线测量服务器性能略低于单机开发测量
- 上线测量基于缓存的性能查询要优于数据库查询测量
- 上线测量接口的性能与最终对外提供的服务性能差异过大
- 当外部条件发生变化(硬件),需要进行回归测试,例如数据库迁移
- **测量结果展示 **
- 测量结果无需每一个都展示,需要设定检测阈值
- 阈值设定要根据业务进行区分,一个复杂的查询与简单的查询差异化很大
- 阈值设定需要做独立的配置文件或通过图形工具配置(工具级别的开发)
- 配合图形界面展示测量结果
6)AOP底层原理
- 静态代理
- 动态代理——Proxy
- 动态代理——CGLIB
- 织入形式
6.1)静态代理
装饰者模式(Decorator Pattern):在不惊动原始设计的基础上,为其添加功能
public class UserServiceDecorator implements UserService{
private UserService userService;
public UserServiceDecorator(UserService userService) {
this.userService = userService;
}
public void save() {
//原始调用
userService.save();
//增强功能(后置)
System.out.println("刮大白");
}
}
6.2)动态代理——JDK Proxy
JDKProxy动态代理是针对对象做代理,要求原始对象具有接口实现,并对接口方法进行增强
public class UserServiceJDKProxy {
public UserService createUserServiceJDKProxy(final UserService userService){
//获取被代理对象的类加载器
ClassLoader classLoader = userService.getClass().getClassLoader();
//获取被代理对象实现的接口
Class[] classes = userService.getClass().getInterfaces();
//对原始方法执行进行拦截并增强
InvocationHandler ih = new InvocationHandler() {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//前置增强内容
Object ret = method.invoke(userService, args);
//后置增强内容
System.out.println("刮大白2");
return ret;
}
};
//使用原始被代理对象创建新的代理对象
UserService proxy = (UserService) Proxy.newProxyInstance(classLoader,classes,ih);
return proxy;
}
}
6.3)动态代理——CGLIB
- CGLIB(Code Generation Library),Code生成类库
- CGLIB动态代理不限定是否具有接口,可以对任意操作进行增强
- CGLIB动态代理无需要原始被代理对象,动态创建出新的代理对象
public class UserServiceImplCglibProxy {
public static UserServiceImpl createUserServiceCglibProxy(Class clazz){
//创建Enhancer对象(可以理解为内存中动态创建了一个类的字节码)
Enhancer enhancer = new Enhancer();
//设置Enhancer对象的父类是指定类型UserServerImpl
enhancer.setSuperclass(clazz);
Callback cb = new MethodInterceptor() {
public Object intercept(Object o, Method m, Object[] a, MethodProxy mp) throws Throwable {
Object ret = mp.invokeSuper(o, a);
if(m.getName().equals("save")) {
System.out.println("刮大白");
}
return ret;
}
};
//设置回调方法
enhancer.setCallback(cb);
//使用Enhancer对象创建对应的对象
return (UserServiceImpl)enhancer.create();
}
}
6.4)代理模式的选择
Spirng可以通过配置的形式控制使用的代理形式,默认使用jdkproxy,通过配置可以修改为使用cglib
- XML配置
<!--XMP配置AOP-->
<aop:config proxy-target-class="false"></aop:config>
- XML注解支持
<!--注解配置AOP-->
<aop:aspectj-autoproxy proxy-target-class="false"/>
- 注解驱动
//注解驱动
@EnableAspectJAutoProxy(proxyTargetClass = true)
6.5)织入时机
spring 事务
1)事务回顾
1.1)什么是事
事务指数据库中多个操作合并在一起形成的操作序列
1.2)事务的作用
1.当数据库操作序列中个别操作失败时,提供一种方式使数据库状态恢复到正常状态(A),保障数据库即使在异常状态下仍能保持数据一致性(C)(要么操作前状态,要么操作后状态)。
2.当出现并发访问数据库时,在多个访问间进行相互隔离,防止并发访问操作结果互相干扰(I)。
- **事务特征(ACID) **
- 原子性(Atomicity)指事务是一个不可分割的整体,其中的操作要么全执行或全不执行
- 一致性(Consistency)事务前后数据的完整性必须保持一致
- ** 隔离性**(Isolation)事务的隔离性是多个用户并发访问数据库时,数据库为每一个用户开启的事务,不能被其他事务的操作数据所干扰,多个并发事务之间要相互隔离
- 持久性(Durability)持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来即使数据库发生故障也不应该对其有任何影响
1.3)事务的隔离级
- ** 脏读**:允许读取未提交的信息
- 原因:Read uncommitted
解决方案: (表级读锁)
- 不可重复读:读取过程中单个数据发生了变化
- 解决方案: Repeatable read (行级写锁)
- 幻读:读取过程中数据条目发生了变化
- 解决方案: Serializable(表级写锁)
2)事务管理
2.1)Spring事务核心对象
- J2EE开发使用分层设计的思想进行,对于简单的业务层转调数据层的单一操作,事务开启在业务层或者数据层并无太大差别,当业务中包含多个数据层的调用时,需要在业务层开启事务,对数据层中多个操作进行组合并归属于同一个事务进行处理
- Spring为业务层提供了整套的事务解决方案
- PlatformTransactionManager
- TransactionDefinition
- TransactionStatus
2.2)PlatformTransactionManager
- 平台事务管理器实现类
- DataSourceTransactionManager 适用于Spring JDBC或MyBatis
- HibernateTransactionManager 适用于Hibernate3.0及以上版本
- JpaTransactionManager 适用于JPA
- JdoTransactionManager 适用于JDO
- JtaTransactionManager 适用于JTA
- JPA(Java Persistence API)Java EE 标准之一,为POJO提供持久化标准规范,并规范了持久化开发的统一API,符合JPA规范的开发可以在不同的JPA框架下运行
- JDO(Java Data Object )是Java对象持久化规范,用于存取某种数据库中的对象,并提供标准化API。与JDBC相比,JDBC仅针对关系数据库进行操作,JDO可以扩展到关系数据库、文件、XML、对象数据库(ODBMS)等,可移植性更强
- JTA(Java Transaction API)Java EE 标准之一,允许应用程序执行分布式事务处理。与JDBC相比,JDBC事务则被限定在一个单一的数据库连接,而一个JTA事务可以有多个参与者,比如JDBC连接、JDO 都可以参与到一个JTA事务中
此接口定义了事务的基本操作
- 获取事务 :
TransactionStatus getTransaction(TransactionDefinition definition)
- 提交事务 :
void commit(TransactionStatus status)
- 回滚事务 :
void rollback(TransactionStatus status)
2.3)TransactionDefinition
此接口定义了事务的基本信息
- 获取事务定义名称
String getName()
- 获取事务的读写属性
boolean isReadOnly()
- 获取事务隔离级别
int getIsolationLevel()
- 获事务超时时间
int getTimeout()
- 获取事务传播行为特征
int getPropagationBehavior()
2.4)TransactionStatus
此接口定义了事务在执行过程中某个时间点上的状态信息及对应的状态操作
2.5)事务控制方式
- 编程式
- 声明式(XML)
- 声明式(注解)
2.6)案例说明
2.6.1)案例说明
银行转账业务说明
银行转账操作中,涉及从A账户到B账户的资金转移操作。数据层仅提供单条数据的基础操作,未设计多账户间的业务操作。
2.6.2)案例环境(基于Spring、Mybatis整合)
- 业务层接口提供转账操作
/**
* 转账操作
* @param outName 出账用户名
* @param inName 入账用户名
* @param money 转账金额
*/
public void transfer(String outName,String inName,Double money);
- 业务层实现提供转账操作
public void transfer(String outName,String inName,Double money){
accountDao.inMoney(outName,money); accountDao.outMoney(inName,money);
}
- 数据层提供对应的入账与出账操作
<update id="inMoney">
update account set money = money + #{money} where name = #{name}
</update>
<update id="outMoney">
update account set money = money - #{money} where name = #{name}
</update>
2.6.3)编程式事务
public void transfer(String outName,String inName,Double money){
//创建事务管理器
DataSourceTransactionManager dstm = new DataSourceTransactionManager();
//为事务管理器设置与数据层相同的数据源
dstm.setDataSource(dataSource);
//创建事务定义对象
TransactionDefinition td = new DefaultTransactionDefinition();
//创建事务状态对象,用于控制事务执行
TransactionStatus ts = dstm.getTransaction(td);
accountDao.inMoney(outName,money);
int i = 1/0; //模拟业务层事务过程中出现错误
accountDao.outMoney(inName,money);
//提交事务
dstm.commit(ts);
}
2.7)使用AOP控制事务
将业务层的事务处理功能抽取出来制作成AOP通知,利用环绕通知运行期动态织入
public Object tx(ProceedingJoinPoint pjp) throws Throwable {
DataSourceTransactionManager dstm = new DataSourceTransactionManager();
dstm.setDataSource(dataSource);
TransactionDefinition td = new DefaultTransactionDefinition();
TransactionStatus ts = dstm.getTransaction(td);
Object ret = pjp.proceed(pjp.getArgs());
dstm.commit(ts);
return ret;
}
配置AOP通知类,并注入dataSource
<bean id="txAdvice" class="com.itheima.aop.TxAdvice">
<property name="dataSource" ref="dataSource"/>
</bean>
使用环绕通知将通知类织入到原始业务对象执行过程中
<aop:config>
<aop:pointcut id="pt" expression="execution(* *..transfer(..))"/>
<aop:aspect ref="txAdvice">
<aop:around method="tx" pointcut-ref="pt"/>
</aop:aspect>
</aop:config>
2.8声明式事务(XML)
AOP配置事务是否具有特例性?
public Object tx(ProceedingJoinPoint pjp) throws Throwable {
DataSourceTransactionManager dstm = new DataSourceTransactionManager();
dstm.setDataSource(dataSource);
TransactionDefinition td = new DefaultTransactionDefinition();
TransactionStatus ts = dstm.getTransaction(td);
Object ret = pjp.proceed(pjp.getArgs());
dstm.commit(ts);
return ret;
}
<bean id="txAdvice" class="com.itheima.aop.TxAdvice">
<property name="dataSource" ref="dataSource"/>
</bean>
使用tx命名空间配置事务专属通知类
<tx:advice id="txAdvice" transaction-manager="txManager">
<tx:attributes>
<tx:method name="*" read-only="false" />
<tx:method name="get*" read-only="true" />
<tx:method name="find*" read-only="true" />
</tx:attributes>
</tx:advice>
使用aop:advisor在AOP配置中引用事务专属通知类
<aop:config>
<aop:pointcut id="pt" expression="execution(* *..*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="pt"/>
</aop:config>
2.8.1)aop:advice与aop:advisor区别
- aop:advice配置的通知类可以是普通java对象,不实现接口,也不使用继承关系
- aop:advisor配置的通知类必须实现通知接口
- MethodBeforeAdvice
- AfterReturningAdvice
- ThrowsAdvice
- ……
2.8.2)tx配置----tx:advice
- 名称:tx:advice
- 类型:标签
- 归属:beans标签
- 作用:专用于声明事务通知
- 格式:
<beans>
<tx:advice id="txAdvice" transaction-manager="txManager">
</tx:advice>
</beans>
- 基本属性:
- id :用于配置aop时指定通知器的id
- transaction-manager :指定事务管理器bean
2.8.3)tx配置----tx:attributes
- 名称:tx:attributes
- 类型:标签
- 归属:tx:advice标签
- 作用:定义通知属性
- 格式:
<tx:advice id="txAdvice" transaction-manager="txManager">
<tx:attributes>
</tx:attributes>
</tx:advice>
- 基本属性:
- 无
2.8.4)tx配置----tx:method
- 名称:tx:method
- 类型:标签
- 归属:tx:attribute标签
- 作用:设置具体的事务属性
- 格式:
<tx:attributes>
<tx:method name="*" read-only="false" />
<tx:method name="get*" read-only="true" />
</tx:attributes>
- 说明:
通常事务属性会配置多个,包含1个读写的全事务属性,1个只读的查询类事务属性
tx:method属性
2.9)事务传播行为
- 事务管理员
- 事务协调员
- 事务传播行为描述的是事务协调员对事务管理员所携带事务的处理态度
2.10)事务传播行为
2.11)事务传播应用
- 场景A:生成订单业务
- 子业务S1:记录日志到数据库表X
- 子业务S2:保存订单数据到数据库表Y
- 子业务S3:……
- 如果S2或S3或……事务提交失败,此时S1是否回滚?如何控制?
- (S1需要新事务)
- 场景B:生成订单业务
- 背景1:订单号生成依赖数据库中一个专门用于控制订单号编号生成的表M获取
- 背景2:每次获取完订单号,表M中记录的编号自增1
- 子业务S1:从表M中获取订单编号
- 子业务S2:保存订单数据,订单编号来自于表M
- 子业务S3:……
- 如果S2或S3或……事务提交失败,此时S1是否回滚?如何控制?
- (S1需要新事务)
2.12)声明式事务(注解)
2.12.1)@Transactional
- 名称:@Transactional
- 类型:方法注解,类注解,接口注解
- 位置:方法定义上方,类定义上方,接口定义上方
- 作用:设置当前类/接口中所有方法或具体方法开启事务,并指定相关事务属性
- 范例:
@Transactional(
readOnly = false,
timeout = -1,
isolation = Isolation.DEFAULT,
rollbackFor = {ArithmeticException.class, IOException.class},
noRollbackFor = {},
propagation = Propagation.REQUIRES_NEW
)
2.12.2)tx:annotation-driven
- 名称:tx:annotation-driven
- 类型:标签
- 归属:beans标签
- 作用:开启事务注解驱动,并指定对应的事务管理器
- 范例:
<tx:annotation-driven transaction-manager="txManager"/>
2.13)声明式事务(纯注解驱动)
- 名称:@EnableTransactionManagement
- 类型:类注解
- 位置:Spring注解配置类上方
- 作用:开启注解驱动,等同XML格式中的注解驱动
- 范例:
@Configuration
@ComponentScan("com.itheima")
@PropertySource("classpath:jdbc.properties")
@Import({JDBCConfig.class,MyBatisConfig.class,TransactionManagerConfig.class})
@EnableTransactionManagement
public class SpringConfig {
}
3)模板对象
3.1)Spring模块对象
- TransactionTemplate
- JdbcTemplate
- RedisTemplate
- RabbitTemplate
- JmsTemplate
- HibernateTemplate
- RestTemplate
3.2)JdbcTemplate(了解)
提供标准的sql语句操作API
public void save(Account account) {
String sql = "insert into account(name,money)values(?,?)";
jdbcTemplate.update(sql,account.getName(),account.getMoney());
}
3.3)NamedParameterJdbcTemplate(了解)
提供标准的具名sql语句操作API
public void save(Account account) {
String sql = "insert into account(name,money)values(:name,:money)";
Map pm = new HashMap();
pm.put("name",account.getName());
pm.put("money",account.getMoney());
jdbcTemplate.update(sql,pm);
}
3.4)RedisTemplate
RedisTemplate对象结构
public void changeMoney(Integer id, Double money) {
redisTemplate.opsForValue().set("account:id:"+id,money);
}
public Double findMondyById(Integer id) {
Object money = redisTemplate.opsForValue().get("account:id:" + id);
return new Double(money.toString());
}
4)事务底层原理解析
4.1)策略模式应用
策略模式(Strategy Pattern)使用不同策略的对象实现不同的行为方式,策略对象的变化导致行为的变化。