- 对于 Spring 框架而言,一切 Java 对象都是 Bean
- Spring 的 IoC 容器降低了业务对象替换的复杂性,提高了组件之间的解耦
- Spring 的 AOP 支持允许将一些通用任务如安全、事务、日志等进行集中式处理,从而提供了更好的复用
- Spring 的 ORM 和 DAO 提供了与第三方持久层框架的良好整合,并简化了底层的数据库访问
- 注意:Spring 的核心容器必须依赖于 common-logging.jar
- https://docs.spring.io/spring/docs/current/spring-framework-reference/index.html
# Spring 整合 JUnit 测试
// 让 JUnit 直接启动 Spring 容器,测试类运行在容器中
@RunWith(SpringJUnit4ClassRunner.class)
// 启动 Spring 容器时去哪加载配置文件,默认加载当前包下的 "当前类名-context.xml"
@ContextConfiguration("classpath:applicationContext.xml")
// 声明为集成测试加载的 ApplicationContext 是 WebApplicationContext
// @WebAppConfiguration
2
3
4
5
6
# Spring 容器
// AbstractApplicationContext#refresh
// 初始化 BeanFactory
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// 调用容器后处理器
invokeBeanFactoryPostProcessors(beanFactory);
// 注册 Bean 后处理器
registerBeanPostProcessors(beanFactory);
// 初始化信息源
initMessageSource();
// 初始化应用上下文事件广播器
initApplicationEventMulticaster();
// 初始化其它特殊的 Bean:由具体子类实现
onRefresh();
// 注册事件监听器
registerListeners();
// 初始化所有单例的 Bean,使用懒加载模式的 Bean 除外
// 1. Trigger initialization of all non-lazy singleton beans
// 2. Trigger post-initialization callback for all applicable beans
finishBeanFactoryInitialization(beanFactory);
// 完成刷新并发布容器刷新事件
finishRefresh();
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
- 接口层描述了容器的重要组件及组件间的协作关系
- 继承体系逐步实现组件的各项功能
# Resource 接口(资源访问)
常见的前缀及对应的访问策略:
classpath:
——使用 ClassPathResource 从类路径中加载资源。classpath:
和classpath:/
是等价的,都是相对于类的根路径。classpath:
只会在第一个加载的包路径下查找,而classpath*
会扫描所有这些 JAR 包及路径下出现的相同类路径。资源文件可以在标准的文件系统中,也可以在 JAR 或 ZIP 的类包中file:
——使用 FileSystemResource 从本地文件系统加载资源,以斜杠开头表示绝对路径,否则表示相对路径http://
或ftp://
——使用 UrlResource 访问基于 HTTP 或 FTP 协议的网络资源(无前缀)——由 ApplicationContext 的实现类来决定访问策略
用 Resource 操作文件时,如果不是本地文件系统中存在的文件(如 JAR 包中的文件、网络文件),不能使用 getFile() 方法,而应使用 getInputStream() 方法
// ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
// Resource resource = resolver.getResource("classpath:applicationContext.xml");
Resource resource = new ClassPathResource("applicationContext.xml");
ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
Resource[] resources = resourcePatternResolver.getResources("classpath*:/mapper/**/*.xml");
2
3
4
5
6
# Spring 容器的创建方式
# BeanFactory 接口
- 负责配置、创建、管理 Bean
- 在初始化容器时,并未实例化 Bean,直到第一次访问某个 Bean 时才实例化目标 Bean
- 常见实现类:DefaultListableBeanFactory(DefaultListableBeanFactory#preInstantiateSingletons)、ConfigurationClassPostProcessor、XmlBeanFactory
- 常用方法
boolean containsBean(String name)
:判断 Spring 容器是否包含 id 为 name 的 Bean 实例T getBean(Class<T> requiredType)
:获取 Spring 容器中属于 requiredType 类型的、唯一的 Bean 实例Object getBean(String name)
:返回容器 id 为 name 的 Bean 实例T getBean(String name, Class requiredType)
:返回容器中 id 为 name,并且类型为 requiredType 的 BeanClass<?> getType(String name)
:返回容器中 id 为 name 的 Bean 实例的类型
Resource resource = new ClassPathResource("applicationContext.xml");
// BeanFactory factory = new XmlBeanFactory(resource);
DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);
reader.loadBeanDefinitions(resource);
2
3
4
5
# ApplicationContext 接口
- Spring 上下文,BeanFactory 的子接口
- 常用实现类:FileSystemXmlApplicationContext、ClassPathXmlApplicationContext 和 AnnotationConfigApplicationContext
- 在初始化应用上下文时就实例化所有单实例的 Bean
- 系统前期创建 ApplicationContext 时将有较大的系统开销,但一旦 ApplicationContext 初始化完成,程序后面获取 singleton Bean 实例时将拥有较好的性能
// 使用 XML 配置文件提供 Bean 定义信息启动容器
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
// 使用 Java 配置类提供 Bean 定义信息启动容器
// ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
ApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.register(AppConfig.class); // 注册配置类
ctx.refresh(); // 刷新容器以应用这些注册的配置类
2
3
4
5
6
7
- AbstractApplicationContext 抽象类中的方法:
Map<String, Object> getBeansWithAnnotation(Class<? extends Annotation> annotationType)
:获取所有带有指定注解的 Beans 集合
T getBean(String name, Class<T> requiredType)
:根据容器中 Bean 的 id 来获取指定 Bean
# WebApplicationContext 接口
- Spring Web 上下文,ApplicationContext 的子接口
# 父子容器
- 通过 HierarchicalBeanFactory 接口,Spring 的 IoC 容器可以建立父子层级关联的容器体系
- 子容器可以访问父容器中的 Bean,但父容器不能访问子容器中的 Bean
- 在容器内,Bean 的 id 必须是唯一的,但子容器可以拥有一个和父容器 id 相同的 Beam
# Aware 容器感知
- BeanFactoryAware、ApplicationContextAware:获取 Spring 容器
- 自定义 Bean 实现 BeanFactoryAware 接口,重写该接口中的
setBeanFactory(BeanFactory beanFactory)
方法,当 Spring 调用该方法时会将 Spring 容器作为参数传入该方法 - 自定义 Bean 实现 ApplicationContextAware 接口,重写该接口中的
setApplicationContext(ApplicationContext applicationContext)
方法,当 Spring 容器调用该方法时会把自身作为参数传入该方法
- 自定义 Bean 实现 BeanFactoryAware 接口,重写该接口中的
- EnvironmentAware:获取环境 Environment
- BeanNameAware:获取当前 Bean 的名称
- ResourceLoaderAware:获取资源加载器 ResourceLoader
- ServletContextAware:获取 ServletContext
- ImportAware:获取到被 @Import 导入的配置类的 AnnotationMetadata
- ApplicationEventPublisher:获取事件发布器
# ApplicationContextInitializer
- 回调接口,用于在 ConfigurableApplicationContext 类型(或其子类型)的容器进行刷新 refresh 之前,初始化 ConfigurableApplicationContext 实例
# BeanDefinitionRegistry
- BeanDefinitionRegistry 接口提供了向容器手动注册 BeanDefinition 对象的方法
# Spring Environment
- Environment 是一种在容器内以 Profile 和 Property 为模型的应用环境抽象整合:
- 针对 Property,又抽象出各种 PropertySource 类代表配置源。一个环境下可能有多个配置源,每个配置源中有诸多配置项。在查询配置信息时,需要按照配置源优先级进行查询。常见的配置源 ConfigurationPropertySourcesPropertySource、PropertiesPropertySource(JVM 系统配置)、OriginAwareSystemEnvironmentPropertySource(配置文件配置) 等。
- Profile 定义了场景的概念。通常,我们会定义类似 dev、test 和 prod 等环境作为不同的 Profile,用于按照场景对 Bean 进行逻辑归属。同时,Profile 和配置文件也有关系,每个环境都有独立的配置文件,但我们只会激活某一个环境来生效特定环境的配置文件。
- Spring Framework 提供了两种 Environment 的实现(ConfigurableEnvironment 子接口的实现类)
- StandardEnvironment(一般应用),包含的属性源名称:systemProperties(JVM 系统属性)、systemEnvironment(操作系统环境变量)
- StandardServletEnvironment(Web 应用),包含的属性源名称: servletContextInitParams、servletConfigInitParams、jndiProperties
# applicationContext.xml
<import resource="classpath:其它 xml 配置文件"/>
,引入其它分支配置文件
# IoC 和 DI
IoC:Inverse of Control(控制反转):将原本在程序中手动创建对象的控制权,交由 Spring 框架来管理,即由 Spring 容器负责创建 Bean(即 Java 对象)
DI:Dependency Injection(依赖注入):Spring 容器管理容器中 Bean 之间依赖关系的方法
# 后置处理器
- Spring 提供了两种常用的后置处理器:BeanPostProcessor、BeanFactoryPostProcessor
# BeanPostProcessor(Bean 后置处理器)
- 对容器中所有 Bean 进行后处理,对 Bean 进行额外加强
- 方法:
- postProcessBeforeInitialization(初始化前执行)
- postProcessAfterInitialization(初始化后执行)
- 例如:为容器中的目标 Bean 生成代理(AnnotationAwareAspectJAutoProxyCreator)、增加对 IoC 和 DI 注解的支持(如 AutowiredAnnotationBeanPostProcessor 用于处理 @Autowired 和 @Value)等
- 子接口 InstantiationAwareBeanPostProcessor
- 方法:
- postProcessBeforeInstantiation(实例化前执行)
- postProcessAfterInstantiation(实例化后执行)
- postProcessPropertyValues(属性注入)
- 子接口 SmartInstantiationAwareBeanPostProcessor
- 方法:
- BeanPostProcessor Bean 后置处理器(对象初始化前后的回调)
- postProcessBeforeInitialization:实例化、依赖注入后,在调用显式的初始化(init-method、InitializingBean 等)之前执行。如:
- BeanValidationPostProcessor 完成 JSR-303 @Valid 注解 Bean 的验证
- InitDestroyAnnotationBeanPostProcessor 完成 @PostConstruct 注解的初始化方法调用
- ApplicationContextAwareProcessor 完成一些 Aware 接口的注入(如 EnvironmentAware、ResourceLoaderAware、ApplicationContextAware)
- postProcessAfterInitialization:在调用显式的初始化之后执行,如
- AspectJAwareAdvisorAutoProxyCreator 完成 xml 风格的 AOP 配置(aop:config)的目标对象包装到 AOP 代理对象
- AnnotationAwareAspectJAutoProxyCreator:完成 @Aspectj 注解风格(aop:aspectj-autoproxy)的 AOP 配置的目标对象包装到 AOP 代理对象
- AsyncAnnotationBeanPostProcessor:完成 @Async 标注的目标对象包装到 AOP 代理对象,对应的切面类AnnotationAsyncExecutionInterceptor
- MethodValidationPostProcessor:完成 @Validated 标注的目标对象包装到 AOP 代理对象,用于支持对方法级别数据(方法参数/方法返回值)进行验证,对应的切面类 MethodValidationInterceptor
- postProcessBeforeInitialization:实例化、依赖注入后,在调用显式的初始化(init-method、InitializingBean 等)之前执行。如:
- InstantiationAwareBeanPostProcessor 实例化 Bean(对象实例化前后以及实例化后设置 propertyValues 的回调)
- postProcessBeforeInstantiation:实例化之前执行,可以用来在对象实例化前直接返回一个对象(如代理对象)来代替通过内置的实例化流程创建对象
- postProcessAfterInitialization:实例化完毕后、属性注入前
populateBean()
执行。若方法返回 false,后续的 postProcessPropertyValues 不再执行,Spring 也不再对对应的 Bean 实例进行自动依赖注入 - postProcessPropertyValues:紧接着上面 postProcessAfterInitialization 执行。如:
- AutowiredAnnotationBeanPostProcessor 执行 @Autowired 注解注入
- CommonAnnotationBeanPostProcessor 执行 @Resource 等注解的注入
- PersistenceAnnotationBeanPostProcessor 执行 @PersistenceContext 等 JPA 注解的注入
- RequiredAnnotationBeanPostProcessor 执行 @Required 注解的检查等
- SmartInstantiationAwareBeanPostProcessor 智能实例化 Bean
- 抽象子类:InstantiationAwareBeanPostProcessorAdapter、AbstractAutoProxyCreator
- predictBeanType:预测 Bean 的类型
- determineCandidateConstructors:获取 Bean 的候选构造器,若返回 null,则使用空构造函数去实例化。如:
- AutowiredAnnotationBeanPostProcessor:扫描 Bean 中使用了 @Autowired 或 @Value 注解的构造器从而完成构造器注入
- getEarlyBeanReference:获取要提前暴露的 Bean 的引用,用来支持单例对象的循环引用
- AspectJAwareAdvisorAutoProxyCreator 或 AnnotationAwareAspectJAutoProxyCreator
当需要实现 SmartInstantiationAwareBeanPostProcessor 或者 InstantiationAwareBeanPostProcessor 中的某个方法,可以通过继承 InstantiationAwareBeanPostProcessorAdapter(做了所有方法的空实现)
MergedBeanDefinitionPostProcessor 合并 Bean 定义
- postProcessMergedBeanDefinition:执行 Bean 定义的合并
DestructionAwareBeanPostProcessor 销毁 Bean
- postProcessBeforeDestruction:销毁后处理回调方法,该回调只应用于单例 Bean。如:
- InitDestroyAnnotationBeanPostProcessor 完成 @PreDestroy 注解的销毁方法调用
- postProcessBeforeDestruction:销毁后处理回调方法,该回调只应用于单例 Bean。如:
# BeanFactoryPostProcessor(容器后置处理器)
- 对 IoC 容器进行后处理,用于增强容器功能,如动态添加 Bean(DefaultListableBeanFactory#registerBeanDefinition、DefaultListableBeanFactory#registerSingleton)
- 在应用上下文装配配置文件后立即调用
- 方法:
void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)
- 例如:PropertyPlaceholderConfigurer(属性占位符配置器)、ConfigurationClassPostProcessor(@Configuration 配置文件的解析)
# FactoryBean<T> 接口
可以通过实现该工厂类接口定制实例化 Bean 的逻辑
接口方法:
T getObject()
:返回由 FactoryBean 创建的 Bean 实例,如果isSingleton()
返回 true,则该实例会放到 Spring 容器的单实例缓存池中boolean isSingleton()
:确定由 FactoryBean 创建的 Bean 的作用域是 singleton 还是 prototypeClass<?> getObjectType()
:返回 FactoryBean 创建 Bean 的类型
常见实现类:MapperFactoryBean<T>
# ObjectFactory<T> 接口
- 对象工厂,其实现通常被定义为作为 API(通过注入)提供给其它 Bean
- 用途:
- 从对应的域中获取到指定名称的对象:Scope 接口中的
Object get(String name, ObjectFactory<?> objectFactory);
- 允许注入点能够被延迟注入:ConfigurableListableBeanFactory 接口中
void registerResolvableDependency(Class<?> dependencyType, Object autowiredValue);
- 从对应的域中获取到指定名称的对象:Scope 接口中的
- 接口方法:
T getObject()
- 常见实现类:RequestObjectFactory、ResponseObjectFactory、SessionObjectFactory、WebRequestObjectFactory
# ObjectProvider<T> 接口
ObjectFactory<T> 的子接口,ObjectFactory<T> 的一种变体
用途(
DefaultListableBeanFactory#resolveDependency
):使用构造函数注入时,如果注入实例为空时,使用 ObjectProvider 可避免了强依赖导致的依赖对象不存在异常;如果有多个实例,ObjectProvider 的方法可以根据 Bean 实现的 Ordered 接口或 @Order 注解指定的先后顺序获取一个 Bean@Component public class IndexService { private A a; private B b; public IndexService(ObjectProvider<A> a, ObjectProvider<B> b) { this.b = b.getIfAvailable(); this.b = b.orderedStream().findFirst().orElse(null); } }
1
2
3
4
5
6
7
8
9
10
# 使用 XML 文件配置 IoC
<bean>,定义一个 Bean,需指定的属性
调用构造器:id、class(该 Bean 的实现类)、parent(所继承 Bean 的 id)
调用静态工厂方法:id、class(静态工厂类)、factory-method
调用实例工厂方法:id、factory-bean(实例工厂 Bean 的 id)、factory-method
调用实例工厂方法:id、class(实现了 FactoryBean<T> 的实例工厂类型)
配置 Bean 时,可以不设置 id,也可以不设置 name,Spring 默认会使用类的全限定名作为 Bean 的标识符
如果使用 id 属性来设置 Bean 的标识符,那么 id 在 Spring 容器中必需唯一
如果使用 name 属性来设置,那么设置的其实就是 Bean 的标识符,必需在容器中唯一
如果同时设置 id 和 name,那么 id 设置的是标识符(identifier),name 设置的是别名(aliases)
name 属性设置多个值(多个 name 用,
;
或 空格分割),当不设置 id 时,name 属性值的第一个被用作标识符,其它的被视为别名;如果设置了 id,那么 name 的所有值都是别名当配置文件中 <bean> 的 class 属性配置的实现类是 FactoryBean 时,通过
BeanFactory#getBean()
方法返回的不是 FactoryBean 本身,而是FactoryBean#getObject()
方法所返回的对象,相当于FactoryBean#getObject()
代理了BeanFactory#getBean()
方法
如果需要获取 FactoryBean 实例,则需要在使用getBean(beanName)
方法时显式地在 beanName 前加上 '&' 前缀
其它属性:
- scope:定义 Bean 的作用域,属性值:singleton(缺省)、prototype(每次获取都新建一个 Bean 的实例)、session、request
- init-method:初始化后执行的方法
- destroy-method:销毁前执行的方法(只适用于 singleton Bean)
- abstract="true":定义成抽象 Bean
- lazy-init="true”:阻止 Spring 容器预初始化容器中的 singleton Bean
# 使用 XML 文件配置 DI
# 设值注入(属性注入)
需提供对应的 setter 方法及存在无参构造器
属性变量名的前两个字母要么全部大写,要么全部小写
<property> 子标签
属性:name、value(普通的属性值或属性占位符)、ref(所引用 Bean 的 id)
子标签:
- <bean>,嵌套 Bean,容器不能获取,无须指定 id 属性
- <list>,每个子标签 <value>、<ref> 或 <bean> 配置一个 List 元素
- <set>,每个子标签 <value>、<ref> 或 <bean> 配置一个 Set 元素
- <array>,每个子标签 <value>、<ref> 或 <bean> 配置一个数组元素
- <map>,子标签 <entry>,属性:key、key-ref、value、value-ref
- <props>,子标签 <prop>,属性 key 指定 key 的值,内容指定 value 的值
- <value>,"key"="value"
value 的属性值可以使用属性占位符
- PropertyPlaceholderConfigurer 是一个容器后处理器,负责加载 Properties 属性文件里的属性值,并将这些属性值设置成 Spring 配置文件的数据
<!-- 引入 属性占位符 --> <context:property-placeholder location="classpath:db.properties" system-properties-mode="NEVER"/> <!-- 定义数据源 Bean,使用 Druid 数据源实现 --> <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close"> <property name="driverClassName" value="${driverClassName}"/> <property name="url" value="${url}"/> <property name="username" value="${username}"/> <property name="password" value="${password}"/> </bean>
1
2
3
4
5
6
7
8
9
10
# 构造函数注入
需提供对应的、带参数的构造器
构造函数入参引用的对象必须已经准备就绪,所以如果两个 Bean 都采用构造函数注入,而且都通过构造函数入参引用对方,这种类型导致的循环依赖问题 Spring 无法解决
- <constructor-arg> 子标签,代表一个构造器参数,属性:name、value、index、type、ref
# 使用注解配置 IoC 和 DI
Spring 通过使用 Bean 后处理器(BeanPostProcessor)增加对注解的支持
启动包扫描功能,以注册在指定包下带有 IoC 和 DI 相关注解的 Bean:
<context:component-scan base-package="包1,包2, ..."/>
启用 4 个 Bean 后处理器(当启动了包扫描功能后,在 Spring 测试环境中可以省略):
<context:annotation-config />
# IoC 相关注解
- 标注 Bean 类,指定该 Java 类作为 Spring Bean,Bean 实例的名称默认是 Bean 类的首字母小写,其它部分不变
@Component("bean 的 id") // 缺省的 Bean 名称为首字母小写的 Bean 类名(AnnotationBeanNameGenerator,但当第一个和第二个字符都是大写字符时保留不变)
@Repository:在数据访问层(dao 层)使用
@Service:在业务逻辑层(service 层)使用
@Controller:在展现层(MVC→Spring MVC)使用
@Primary:@Autowired 自动装配找到多个匹配的 Bean时,首选该 Bean - 指定 Bean 的作用域、代理方式,如
@Scope(value = "prototype", proxyMode = ScopedProxyMode.DEFAULT)
- 指定 Bean 的加载顺序,如
@Order(1)
(值越大优先级反而越低) - 标注方法
@PostConstruct:指定 Bean 的初始化方法
@PreDestroy:指定 Bean 销毁之前的方法 - DefaultListableBeanFactory#allowBeanDefinitionOverriding,是否允许重新注册具有相同名称的不同 Bean 定义,默认 true
当为单例的 Bean 注入 prototype 的 Bean 时,由于单例的 Bean 注入的依赖 Bean 是一次性创建的,所以即使依赖 Bean 本身设置的 Scope 属性为 prototype,也不会生效,
@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
,修复方式:
- 让依赖 Bean 以代理方式注入,
@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE, proxyMode = ScopedProxyMode.TARGET_CLASS)
- 修改 Scope 属性为 prototype,且每次直接从 ApplicationContext 中获取依赖 Bean
# DI 相关注解(配置依赖)
@Autowired 自动装配
- 可修饰实例变量、setter 方法、普通方法(方法的参数可以有多个)、构造器参数等
- 自动搜索容器中类型匹配的 Bean 实例
- 匹配顺序(by type 自动装配策略):先根据类型找到对应的 Bean,如果对应类型的 Bean 不是唯一的,再根据其属性名称和 Bean 的名称进行匹配
- 若找不到或找到多个相同类型、相同名称的 Bean 则抛出异常,可设置
@Autowired(required=false)
- 消除歧义性——@Primary 和 @Qualifier
- 也可用在数组、List、Set 等数据结构上,目标 Bean 可通过实现 org.springframework.core.Ordered 接口或使用 @Order 注解指定其在数组、List 中的顺序(默认为注册顺序)
- 也可用在 Map 上(Map 的 key 必须为 String 类型,此时 key 是 Bean 的名字)
@Qualifier("指定 Bean 的 id") 精确装配
- 可修饰实例变量、方法的形参
- 若找不到则不注入
@Resource(name="指定 Bean 的 id")
- JavaEE 的注解
- 可修饰实例变量或 setter 方法
- 默认匹配顺序:属性的名称、属性的类型(如果 name 属性一旦指定,就只会按照名称进行装配)
@Value:属性占位符需要放到
${key:defaultValue}
之中,SpEL 表达式要放到#{ ... }
之中@Value("#{T(System).currentTimeMillis()}") private Long initTime; // 赋值字符串 @Value("#{'使用 Spring EL 赋值字符串'}") private String str; // 科学计数法赋值 @Value("#{9.3E3}") private double d; // 赋值浮点数 @Value("#{3.14}") private float pi; // 这里的 beanName 是 Spring IoC 容器 Bean 的名称,str 是其属性,? 含义是判断这个属性是否为空,如果不为空才去执行 toUpperCase 方法 @Value("#{beanName.str?.toUpperCase()}") private String otherBeanProp; // 数学运算 @Value("#{1+2}") private int run; // #浮点数比较运算 @Value("#{beanName.pi == 3.14f}") private boolean piFlag; // 字符串比较运算 @Value("#{beanName.str eq 'Spring Boot'}") private boolean strFlag; // 字符串连接 @Value("#{beanName.str + ' 连接字符串'}") private String strApp; // 三元运算 @Value("#{beanName.d > 1000 ? '大于' : '小于'}") private String resultDesc; @#{person.age?:20} private int age;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# Bean 的生命周期
DefaultSingletonBeanRegistry 中的缓存:
- singletonObjects:一级缓存,缓存完全初始化好的单例 Bean
- earlySingletonObjects:二级缓存,缓存提前暴露的单例 Bean(即还未完成属性注入的 Bean)
- singletonFactories:三级缓存,缓存需要提前暴露对象的对象工厂(用于解决循环依赖)
- singletonsCurrentlyInCreation:缓存正在创建中的 Bean 的名称
AbstractBeanFactory 中的缓存:
- alreadyCreated:缓存至少已创建一次的 Bean 的名称
DefaultListableBeanFactory#preInstantiateSingletons
AbstractBeanFactory#getBean
AbstractBeanFactory#doGetBean
- 调用 DefaultSingletonBeanRegistry#getSingleton(beanName, true),此时允许引用提前暴露的 Bean(解决循环引用):先从 singletonObjects 中获取 Bean,不存在或对象正在创建中,再从 earlySingletonObjects 中获取 Bean,不存在,从 singletonFactories 中获取 Bean,存在,添加到 earlySingletonObjects,并从 singletonFactories 中移除(即从三级缓存移动到二级缓存)
- 不存在,调用 DefaultSingletonBeanRegistry#getSingleton(beanName, singletonFactory),singletonFactory.getObject() 创建后再添加到 singletonObjects。
singletonFactory.getObject()
实际上是调用 AbstractAutowireCapableBeanFactory#createBean --> Spring 创建 Bean 过程可分为三步(AbstractAutowireCapableBeanFactory#doCreateBean):- 实例化 AbstractAutowireCapableBeanFactory#createBeanInstance,实例化后使用该 Bean 创建对象工厂并添加到 singletonFactories,并从 earlySingletonObjects 中移除,如果该 Bean 需要被代理,
bp instanceof SmartInstantiationAwareBeanPostProcessor
,此时添加的是 AbstractAutoProxyCreator#getEarlyBeanReference 返回的代理对象(即提前创建代理) - 属性注入 AbstractAutowireCapableBeanFactory#populateBean
- 初始化 AbstractAutowireCapableBeanFactory#initializeBean
- 实例化 AbstractAutowireCapableBeanFactory#createBeanInstance,实例化后使用该 Bean 创建对象工厂并添加到 singletonFactories,并从 earlySingletonObjects 中移除,如果该 Bean 需要被代理,
- Spring 启动时读取应用程序提供的 Bean 配置信息(XML、注解、Java Config、Groovy DSL),并在 Spring 容器中生成一份相应的 Bean 配置注册表 BeanDefinitionRegistry,然后根据这张注册表实例化 Bean,装配好 Bean 之间的依赖关系,为上层应用提供准备就绪的运行环境
# SpEL
- Spring Expression Language (SpEL) (opens new window)
- SpEL 支持一系列功能特性,如方法调用、属性调用及构造器调用等
ExpressionParser parser = new SpelExpressionParser(); // 创建表达式解析器(可重用、线程安全)
// Expression expression = parser.parseExpression("'Hello World'.concat('!')"); // 对表达式字符串进行解析
// String message = expression.getValue(String.class); // Hello World!
String expressionStr = "Hello, #{#user} --> #{T(System).getProperty('user.home')}"; // 变量,使用 # + 变量名引用,默认对 java.lang 包可见
Expression expression = parser.parseExpression(expressionStr, ParserContext.TEMPLATE_EXPRESSION); // 对模版表达式字符串进行解析
StandardEvaluationContext context = new StandardEvaluationContext(); // 如果需要解析引用,在计算时设置一个表达式上下文对象
context.setVariable("user", "Aspire"); // 在上下文中设定变量
String message = expression.getValue(context, String.class); // Hello, Aspire --> C:\Users\Aspire
2
3
4
5
6
7
8
9
# Spring 的事件
- ApplicationContext 的事件机制:当 ApplicationContext 发布 ApplicationEvent 时,容器主控程序将调用事件广播器 ApplicationEventMulticaster (AbstractApplicationContext#applicationEventMulticaster)将事件通知给事件监听器注册表中的 ApplicationListener,ApplicationListener 分别对事件进行响应
- 在 Spring 中的事件,默认是同步处理的(SimpleApplicationEventMulticaster#taskExecutor 为 null)
自定义事件,继承 ApplicationEvent,并在其构造函数中指定事件源 source 以及事件关联的对象
ApplicationEvent 两个子类:ApplicationContextEvent(容器事件,拥有 4 个子类,分别表示容器启动 ContextStartedEvent、刷新 ContextRefreshedEvent、停止 ContextStoppedEvent 及关闭 ContextClosedEvent 的事件)、RequestHandledEvent(与 Web 应用相关的事件,当一个 HTTP 请求被处理后, 产生该事件)定义事件监听器
方式 1:实现 ApplicationListener<E extends ApplicationEvent> 接口,并指定监听的事件类型,重写该接口中的
void onApplicationEvent(E event)
方法(判断该事件对象是否是想要监听的事件,并对消息进行接收处理)方式 2:在处理事件的 Bean 的方法上添加 @EventListener,并在该方法参数上指定要监听的事件,或者用 value 属性指定
使用容器发布事件
方式 1:注入 ApplicationContext,使用 ApplicationContext.publishEvent() 方法来发布事件
方式 2:实现 ApplicationEventPublisherAware 接口,使用 ApplicationEventPublisher.publishEvent() 方法来发布事件
- Spring Boot 中的事件(org.springframework.boot.context.event.SpringApplicationEvent 子类):
ApplicationStartingEvent(开始启动中)、ApplicationEnvironmentPreparedEvent(环境已准备好)、ApplicationContextInitializedEvent(上下文已实例化)、ApplicationPreparedEvent(上下文已准备好)、ApplicationStartedEvent(应用已启动)、ApplicationReadyEvent(应用已准备好)、ApplicationFailedEvent
# AOP 思想
- AOP(Aspect Orient Programming),面向切面编程,将程序运行过程分解成各个切面
- AOP 的作用:为系统中业务组件的多个业务方法添加某种通用功能(在执行目标方法之前、之后插入一些通用处理)
- AOP 的过程:把业务方法中与业务无关的、却为业务模块所共同调用的操作抽离到不同的对象的方法中,最后使用代理的方式组合起来
- 使用场景:日志、用户鉴权、全局性异常处理、性能监控、事务处理等
- 术语:
- 切面(Aspect):用于组织多个 Advice,Advice 放在切面中定义,在实际应用中通常是一个存放通用功能实现的普通 Java 类,如日志切面、权限切面、事务切面等
- 通知/增强处理(Advice):AOP 框架在特定的切入点执行的增强处理,处理有 around、before 和 after 等类型,在实际应用中通常是切面类中的一个方法
- 连接点(Joinpoint):程序在运行过程中能够插入切面的点,如方法的调用、异常的抛出或成员变量的访问和更新等(Spring AOP 只支持将方法调用作为连接点)
- 切入点(Pointcut):可以插入增强处理的连接点,当某个连接点满足指定要求(由切入点的正则表达式来定义)时,该连接点将被添加增强处理,该连接点也就变成了切入点
- 织入(Weaving):将增强添加到目标对象(Target)中,并创建一个被增强的对象——AOP 代理(Proxy)的过程(Spring AOP 在运行时完成织入)
# AspectJ 切入点语法
- Supported Pointcut Designators (opens new window)
- Spring AOP 常用的切入点指示符(pointcut designators,PCD)
- execution:用于匹配执行方法的连接点
execution(<修饰符>? <返回值类型> <所属类>?<方法名>(形参类型列表) <声明抛出的异常>?)
,?
表示该部分可省略- 通配符:
*
代表一个任意类型的参数..
代表零个或多个任意类型的参数,在表示类时,必须和*
联合使用,而在表示入参时则单独使用+
表示按类型匹配指定类的所有类,必须跟在类名后面,如 com.smart.Car+ 继承或扩展指定类的所有类,同时还包括指定类本身
- 如
execution(* com.example.app.service.impl.*.*(..))
,匹配 com.example.app.service.impl 包中任意类的任意方法的执行
- within:用于限定匹配特定域下的连接点。当使用 Spring AOP 的时候,只能匹配方法执行的连接点。
- 如
within(com.example.app.service..*)
,匹配在 com.example.app.service 包或其子包中的任意连接点
- 如
- this:用于限定 AOP 代理对象必须是指定类型的实例,匹配该对象的所有连接点
this(com.example.app.service.AccountService)
,匹配实现了 com.example.app.service.AccountService 接口的 AOP 代理的所有连接点
- target:用于限定目标对象必须是指定类型的实例,匹配该对象的所有连接点
target(com.example.app.service.AccountService)
,匹配实现了 com.example.app.service.AccountService 接口的目标对象的所有连接点
- args:
args(参数类型列表)
,用于对连接点的参数类型进行限制,要求参数是指定类型的实例 - bean:
bean(Bean的id或name)
,用于限定只匹配指定 Bean 实例内方法的连接点,支持使用*
通配符,注意:bean 切入点表达式是 Spring AOP 额外支持的 - @annotation:
@annotation(注解类型)
,用于匹配标注有指定注解的方法 - @within:用于匹配标注有指定注解的类内所有方法
- 如
@within(feign.Client+)
,切入 feign.Client 的实现类
- 如
- @target:用于匹配标注有指定注解的类的目标对象内所有方法
- @args:用于匹配入参标注有指定注解的方法
- execution:用于匹配执行方法的连接点
- 注意:当使用 Spring AOP 的时候,只能匹配方法执行的连接点
- Spring 支持使用如下三个逻辑运算符来组合切入点表达式
&&
:要求连接点同时匹配两个切入点表达式||
:只要连接点匹配任意一个切入点表达式!
:要求连接点不匹配指定的切入点表达式
# AOP 的实现
静态 AOP 实现:在编译阶段对程序进行修改(编译时增强),以 AspectJ 为代表,需要使用特殊的编译器
AspectJ 定义了如何表达、定义 AOP 编程中的语法规范,以及提供了编译、运行 AspectJ 的一些工具命令动态 AOP 实现:在内存中以 JDK 动态代理或 cglib 动态地生成 AOP 代理类(运行时增强),以 Spring AOP 为代表
# Spring 的 AOP
- 依赖的 jar 包:aopalliance.jar、aspectjweaver.jar
- Spring 使用 AspectJ 方式来定义切入点和增强处理(没有使用 AspectJ 的编译器,底层使用的是动态代理技术)
- 如果目标类有实现的接口,Spring 会使用 JDK 动态代理生成代理类,该代理类与目标类实现相同的接口(必须确保要拦截的目标方法在接口中有定义,方法只能使用 public 修饰)
- 如果目标类没有实现接口,Spring 会使用 cglib 代理生成代理类,该代理类是目标类的子类(所以必须确保要拦截的目标方法可被子类访问,方法需使用 public 或 protected 修饰)
- Spring 只能在方法级别上织入增强
- 相关类:MethodInterceptor
- Spring 只能切入由自己管理的 Bean
- Spring Boot 2.x 默认使用 cglib 的方式生成代理类,
spring.aop.proxy-target-class=true
- ObjenesisCglibAopProxy#createProxyClassAndInstance,当通过 Objenesis 创建代理对象(此时无需调用类的构造函数)失败时,才通过默认构造函数创建代理对象。从 Spring 4 开始默认使用。
- CglibAopProxy.CglibMethodInvocation#invokeJoinpoint,当调用的方法可以被代理时,就使用目标对象进行调用,否则使用代理对象进行调用
# 使用 XML 配置 AOP
<aop:config >
- <aop:aspect >:配置切面,属性:id 该切面的标识名,ref 引用的切面 Bean,order 该切面 Bean 的优先级,子标签:<aop:pointcut >、<aop:before >、<aop:after >、<aop:after-retuming >、<aop:after-throwing >、<aop:around >,子标签属性:method、pointcut、pointcut-ref、throwing、returning
- <aop:pointcut >:配置切入点,属性:id 定该切入点的标识名,expression 该切入点关联的切入点表达式
- <aop:advisor >:将单独配置的增强处理和切入点绑定在一起,属性:advice-ref、pointcut-ref、pointcut、order、id
<aop:config>
<!-- what: 定义切面 -->
<aop:aspect ref="txManager">
<!-- where: 定义切入点 -->
<aop:pointcut expression="execution(* com.example.tx.service.*Service.*(..))" id="pc"/>
<!-- when: 定义在什么时机做增强处理,以及具体做什么增强处理 -->
<aop:before method="begin" pointcut-ref="pc"/> <!-- 前置增强 -->
<aop:after-returning method="commit" pointcut-ref="pc"/> <!-- 后置增强 -->
<aop:after-throwing method="rollback" pointcut-ref="pc" throwing="ex"/> <!-- 异常增强 -->
<aop:after method="close" pointcut-ref="pc"/> <!-- 最终增强 -->
<aop:around method="allInOne" pointcut-ref="pc"/> <!-- 环绕增强 -->
</aop:aspect>
<aop:config>
2
3
4
5
6
7
8
9
10
11
12
13
# 使用注解配置 AOP (opens new window)
- 需在 XML 文件中开启 AOP 注解解析器
<aop:aspectj-autoproxy/>
,启动 @AspectJ 支持;或者在启动类上添加 @EnableAspectJAutoProxy - 定义切面类 Bean @Aspect
- 定义切入点 @Pointcut
@Pointcut("execution(* com.example.tx.service.*Service.*(..))")
使用一个返回值为 void、方法体为空的方法来命名切入点,public void pointCut() {}
- 定义增强处理,指定切入点,即 value 属性值
@Around("pointCut()")
@Before("pointCut()")
@After("pointCut()"):不管目标方法如何结束(包括成功完成和遇到异常中止两种情况)都会被织入
@AfterReturning("pointCut()") :在目标方法正常完成后被织入
@AfterThrowing(value="pointCut()", throwing="ex") - 当定义 Around 增强处理方法时,该方法的第一个形参必须是 ProceedingJoinPoint 类型(JoinPoint 类型的子类,代表了织入增强处理的连接点),在增强处理方法体内调用 ProceedingJoinPoint 参数的
proceed()
方法才会执行目标方法 - JoinPoint 接口中常用的方法
Object[] getArgs()
:返回执行目标方法时的参数
Signature getSignature()
:返回被增强的方法的相关信息
Object getTarget()
:返回被织入增强处理的目标对象
Object getThis()
:返回 AOP 框架为目标对象生成的代理对象 - ProceedingJoinPoint 接口(JoinPoint 的子接口)中常用的方法
Object proceed()
:执行目标方法
Object proceed(Object[] args)
:args 中的值被传入目标方法作为执行方法的实参 - @DeclareParents:引入新的类来增强功能
切面 Bean 优先级越高(Order 值越小),越先执行入操作(越后执行出操作)
// 定义一个切面 Bean
@Aspect
@Component
@Slf4j
public class FourAdviceTest {
@Pointcut("execution(* com.example.app.service.impl.*.*(..))")
public void pointCut() }{}
// @Around("execution(* com.example.app.service.impl.*.*(..))")
@Around("pointCut()")
public Object processTx(ProceedingJoinPoint jp) throws Throwable {
log.info("[Around 增强]执行目标方法之前");
// 访问执行目标方法的参数
Object[] args = jp.getArgs();
// 当执行目标方法的参数存在,且第一个参数是字符串时
if (args != null && args.length > 0 && args[0].getClass() == String.class) {
// 修改目标方法调用参数的第一个参数
args[0] = "【增加的前缀】" + args[0];
}
// 执行目标方法,并保存目标方法执行后的返回值
Object rvt = jp.proceed(args);
log.info("[Around 增强]执行目标方法之后");
// 如果 rvt 的类型是 Integer,将 rvt 改为它的平方
if (rvt instanceof Integer)
rvt = (Integer) rvt * (Integer) rvt;
return rvt;
}
@Before("pointCut()")
public void authority(JoinPoint jp) {
log.info("[Before 增强]");
}
@After("pointCut()")
public void release(JoinPoint jp) {
log.info("[After 增强]");
}
@AfterReturning(pointcut = "pointCut()", returning = "rvt")
public void log(JoinPoint jp, Object rvt) {
log.info("[AfterReturning 增强]");
}
@AfterThrowing(pointcut = "pointCut()", throwing="ex")
// 声明 ex 时指定的类型会限制目标方法必须抛出指定类型的异常
// 此处将 ex 的类型声明为 Throwable,意味着对目标方法抛出的异常不加限制
public void recovery(Throwable ex) {
log.info("[AfterThrowing 增强]");
}
}
// 输出
[Around 增强]执行目标方法之前
[Before 增强]
[Around 增强]执行目标方法之后
[After 增强]
[AfterReturning 增强] 或 [AfterThrowing 增强]
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
# Spring 的 JDBC
# JdbcTemplate 类
构造器:JdbcTemplate(DataSource dataSource)(调用该构造器时,dataSource 不能为 null,否则抛出异常)
实例方法:query、queryForObject、queryForList、update、batchUpdate、execute
int update(String sql, Object... args)
T queryForObject(String sql, RowMapper<T> rowMapper, Object... args)
List<T> query(String sql, RowMapper<T> rowMapper, Object... args)
SimpleJdbcInsert 类、NamedParameterJdbcTemplate 类
通过实现 RowMapper 接口完成 JdbcTemplate 的映射关系
# JdbcDaoSupport 抽象类
- 实例方法:
void setDataSource(DataSource dataSource)
JdbcTemplate getJdbcTemplate()
# JDBC 异常抽象
- Spring JDBC 会将数据操作的异常转换为 DataAccessException
- 通过 SQLErrorCodeSQLEXceptionTranslator 解析错误码
- ErrorCode 定义:
- org/springframework/jdbc/support/sql-error-codes.xml
- classpath 下的 sql-error-codes.xml(可定制错误码)
# Spring 的事务管理
# 相关接口/类
TransactionDefinition,定义了一个事务规则:事务隔离、事务传播、事务超时、只读状态
- 事务隔离级别:默认值为 ISOLATION_DEFAULT(-1),使用数据库的默认隔离级别
- 事务的传播方式:当一个事务方法调用另外一个方法时,应该怎么处理自身的事务。枚举类 Propagation 中定义了 7 种传播规则:
- REQUIRED:需要事务,它是默认传播行为,如果当前存在事务,就沿用当前事务,否则新建一个事务运行子方法
- SUPPORTS:支持事务,如果当前存在事务,就沿用当前事务,如果不存在,则继续采用无事务的方式运行子方法
- MANDATORY:必须使用事务,如果当前没有事务,则会抛出异常,如果存在当前事务,就沿用当前事务
- REQUIRES_NEW:无论当前事务是否存在,都会创建新事务运行方法,这样新事务就可以拥有新的锁和隔离级别等特性,与当前事务相互独立
- NOT_SUPPORTED:不支持事务,当前存在事务时,将挂起事务,运行方法
- NEVER:不支持事务,如果当前方法存在事务,则抛出异常,否则继续使用无事务机制运行
- NESTED:在当前方法调用子方法时,如果子方法发生异常,只回滚子方法执行过的 SQL,而不回滚当前方法的事务(需要在当前方法调用子方法时捕获子方法抛出的异常)
- 嵌套事务,将创建一个依赖于外层事务的子事务,子事务执行成功后是否能提交由外层事务控制,但它无法影响外层事务。即子事务失败,而外层事务成功时,子事务回滚,而外层事务提交;子事务成功,而外层事务失败时,子事务、外层事务都回滚。
- 在大部分的数据库中,一段 SQL 语句中可以设置一个标志位,然后后面的代码执行时如果有异常,只是回滚到这个标志位的数据状态,而不会让这个标志位之前的代码也回滚。这个标志位,在数据库的概念中被称为保存点(save point)。
- Spring 使用保存点技术来完成让子事务回滚而不致使当前事务回滚。当数据库支持保存点技术时,就启用保存点技术;如果不能支持,就新建一个事务去运行代码,即等价于 REQUIRES_NEW 传播行为。
- 事务超时时间
- 是否只读
PlatformTransactionManager,Spring 具体的事务管理由 PlatformTransactionManager 的不同实现类来完成,常用实现类:DataSourceTransactionManager,接口方法:
TransactionStatus getTransaction(@Nullable TransactionDefinition definition)
:开始事务,transactionDefinition 可从容器中获取void commit(TransactionStatus status)
:提交事务void rollback(TransactionStatus status)
:回滚事务
TransactionStatus,表示一个事务
ProxyTransactionManagementConfiguration、TransactionInterceptor
TransactionAspectSupport#invokeWithinTransaction
@TransactionalEventListener:根据 TransactionPhase 调用的 EventListener
TransactionSynchronizationManager,事务同步管理器,管理每个线程的资源和事务同步,包括资源绑定、激活事务同步等。类方法:
Object getResource(Object key)
void bindResource(Object key, Object value)
Object unbindResource(Object key)
boolean isSynchronizationActive()
:判断当前事务是否为活跃,对于被挂起的线程返回 trueboolean isActualTransactionActive()
:判断当前事务是否为实际活跃的事务,对于被挂起的线程返回 falsevoid registerSynchronization(TransactionSynchronization synchronization)
:注册事务同步
// org.springframework.cache.transaction.TransactionAwareCacheDecorator#put public void put(final Object key, @Nullable final Object value) { if (TransactionSynchronizationManager.isSynchronizationActive()) { TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() { @Override public void afterCommit() { this.targetCache.put(key, value); } }); } else { this.targetCache.put(key, value); } }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 配置事务管理器
<!-- 配置 JDBC 数据源的事务管理器,使用 DataSourceTransactionManager 实现类 -->
<!-- 配置 DataSourceTransactionManager 时需要依赖注入 DataSource 的引用 -->
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
2
3
4
5
# 声明式事务
# 使用 XML 配置事务
- <tx:method > 子标签的常用属性
- name:指定对哪些方法起作用
- propagation:事务的传播方式,属性值:REQUIRED(默认值,要求在事务环境中执行该方法,如果当前执行线程已处于事务环境中,则直接调用,如果当前执行线程不处于事务环境中,则启动新的事务)、SUPPORTS(如果当前执行线程处于事务环境中,则使用当前事务,否则不使用事务)
- read-only:是否是只读事务,属性值:false(默认值)、true
- rollback-for:触发事务回滚的 Exception,默认是所有 Runtime 异常回滚
- no-rollback-for:不触发事务回滚的 Exception,默认是所有 checked 异常不回滚
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 配置事务增强处理 -->
<tx:advice id="txAdvice" transaction-manager="txManager">
<tx:attributes>
<tx:method name="get*" read-only="true"/>
<tx:method name="list*" read-only="true"/>
<tx:method name="select*" read-only="true"/>
<tx:method name="query*" read-only="true"/>
<tx:method name="*" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
<!-- 配置 AOP -->
<aop:config>
<!-- 定义一个切入点,通过 expression 指定对应的切入点表达式 -->
<aop:pointcut expression="execution(* com.example.ssm.service.*Servcie.*(..))" id="pc"/>
<!-- 将增强处理和切入点绑定在一起:指定在 pc 切入点应用 txAdvice 事务增强处理 -->
<aop:advisor advice-ref="txAdvice" pointcut-ref="pc"/>
</aop:config>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 使用注解配置事务
- 开启事务注解解析器,并指定使用的事务管理器 Bean 的 id(默认为 transactionManager):
<tx:annotation-driven transaction-manager="txManager"/>
- @Transactional 建议标注在实现类(或实现类的方法)上,而不是所实现的接口
- @Transactional 可用属性
- transactionManager:指定使用的事务管理器 Bean 的 id
- isolation:用于指定事务的隔离级别,默认为底层事务的隔离级别 Isolation.DEFAULT
- propagation:指定事务传播行为,默认 Propagation.REQUIRED
- readOnly:指定事务是否只读,默认 false
- rollbackFor:指定遇到特定异常时强制回滚事务,默认
ex instanceof RuntimeException || ex instanceof Error
(RuntimeException 或 Error)时才回滚 - rollbackForClassName:指定遇到特定的多个异常时强制回滚事务
- noRollbackFor:指定遇到特定异常时强制不回滚事务
- noRollbackForClassName:指定遇到特定的多个异常时强制不回滚事务
- timeout:指定事务的超时时长
- TransactionAspectSupport#invokeWithinTransaction
- TransactionAspectSupport#completeTransactionAfterThrowing
- RuleBasedTransactionAttribute#rollbackOn(获胜规则是最浅的规则,即继承层次结构中最接近异常的规则)
# 编程式事务
- 方式一:获取容器中的 transactionManager(PlatformTransactionManager 的实例),通过该接口提供的方法来开始事务、提交事务和回滚事务
- 方式二:使用 TransactionTemplate 类来进行事务操作 ,
T execute(TransactionCallback<T> action)
重写 TransactionCallback、TransactionCallbackWithoutResult 中的 doInTransaction 或 doInTransactionWithoutResult 方法
# 注意事项
- 自调用失效问题
- 一个类自身方法之间的调用,称为自调用
- 在自调用过程中,是类自身
this
的调用,而不是代理对象去调用,不会产生 AOP - 解决方法:访问增强后的代理类的方法,而非直接访问自身的方法,如:
- 用一个 service 去调用另一个 service
- 从 Spring IoC 容器中获取 service 代理对象去启用 AOP
- 如果其代理通过使用 AbstractAutoProxyCreator 子类创建(如类标注有 @Transactional 或者 @Caching 等注解,只标注 @Async 的类除外),可以在被代理方法内使用
AopContext.currentProxy();
获取当前代理对象,但需先设置 ProxyConfig.exposeProxy 为 true,默认 false - 使用 AspectJ 进行增强
mode=AdviceMode.ASPECTJ
- 只有 public 方法事务有效:AbstractFallbackTransactionAttributeSource#computeTransactionAttribute
- 部分事务失败全局回滚
- 默认情况下,发生异常后当前的事务被标记为 rollback-only,外层事务管理器再 commit 时就会抛出 UnexpectedRollbackException(Transaction rolled back because it has been marked as rollback-only)(可以在 catch 块里加上
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
用于显式控制回滚) - AbstractPlatformTransactionManager.globalRollbackOnParticipationFailure,设置在参与事务失败后是否将现有事务全局标记为 rollback-only
- true(默认):如果参与事务(事务传播规则为 PROPAGATION_REQUIRED 或 PROPAGATION_SUPPORTS)失败,则该事务将全局标记为 rollback-only,这种事务唯一可能的结果是回滚,因此事务发起者不能再提交事务
- false:表示让事务发起者决定是否回滚,如果参与的事务因异常而失败,调用者可以处理异常并决定回滚,独立于子事务的回滚规则
- 在嵌套事务中处理子事务失败,推荐使用 PROPAGATION_NESTED
- 见 AbstractPlatformTransactionManager#setGlobalRollbackOnParticipationFailure 注释
- 默认情况下,发生异常后当前的事务被标记为 rollback-only,外层事务管理器再 commit 时就会抛出 UnexpectedRollbackException(Transaction rolled back because it has been marked as rollback-only)(可以在 catch 块里加上
- 避免长事务:对事务方法进行拆分,尽量让事务变小,变快,减小事务的颗粒度;使用编程式事务手动控制事务范围
注意确认调用 @Transactional 注解标记的方法是 public 且非静态的,并且是通过 Spring 注入的 Bean 进行调用的
手动设置让当前事务处于回滚状态
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
# Spring 的缓存抽象
- Spring 可以支持多种缓存管理机制,如 ConcurrentMap、EhCache、Redis、Caffeine、JCache 等,并提供了缓存处理器的接口 CacheManager 和与之相关的类
# 工具类
# StringUtils
boolean isEmpty(Object str)
:字符串是否为空或者空字符串boolean hasLength(CharSequence str)
:字符串是否为空,或者长度为 0boolean hasText(String str)
:字符串是否有内容(不为空,且不全为空格)boolean containsWhitespace(String str)
:字符串是否包含空格String trimWhitespace(String str)
:去掉字符串前后的空格String trimAllWhitespace(String str)
:去掉字符串中所有的空格String unqualify(String qualifiedName)
:得到以 . 分割的最后一个值(可以用来获取类名或者文件后缀)String unqualify(String qualifiedName, char separator)
:得到以给定字符分割的最后一个值(可以用来获取文件名File.separatorChar
)String getFilename(String path)
:获取文件名String getFilenameExtension(String path)
:获取文件后缀名String capitalize(String str)
:首字母大写String uncapitalize(String str)
:取消首字母大写boolean substringMatch(CharSequence str, int index, CharSequence substring)
:判断从指定索引开始,是否匹配子字符串int countOccurrencesOf(String str, String sub)
:判断子字符串在字符串中出现的次数String replace(String inString, String oldPattern, String newPattern)
:在字符串中使用子字符串替换String delete(String inString, String pattern)
:删除所有匹配的子字符串String deleteAny(String inString, String charsToDelete)
:删除子字符串中任意出现的字符String quote(String str)
:在字符串前后增加单引号,比较适合在日志时候使用String[] addStringToArray(String[] array, String str)
:把一个字符串添加到一个字符串数组中String[] concatenateStringArrays(String[] array1, String[]array2)
:连接两个字符串数组String[] mergeStringArrays(String[] array1, String[] array2)
:连接两个字符串数组,去掉重复元素String[] sortStringArray(String[] array)
:字符串数组排序String[] tokenizeToStringArray(String str, String delimiters)
:对每一个元素执行 trim 操作,并去掉空字符串String[] delimitedListToStringArray(String str, String delimiter)
:分割字符串,以 delimiter 作为整体分隔符Set<String> commaDelimitedListToSet(String str)
:使用逗号分割字符串,并放到 set 中去重String collectionToDelimitedString(Collection<?> coll, String delim, String prefix, String suffix)
:将集合中的每个元素使用前缀、后缀、分隔符连接String collectionToDelimitedString(Collection<?> coll, String delim)
:将集合中的每个元素使用指定字符串连接String arrayToDelimitedString(Object[] arr, String delim)
:数组使用指定字符串连接String[] split(String toSplit, String delimiter)
:在第一次出现分隔符时分割Properties splitArrayElementsIntoProperties(String[] array, String delimiter)
:把字符串数组中的每一个字符串按照给定的分隔符装配到一个 Properties 中
# StopWatch
- 秒表,可用于查看多个任务的耗时情况,线程不安全
- 构造器:StopWatch(String id)
void start(String taskName)
void stop()
long getTotalTimeMillis()
、long getLastTaskTimeMillis()
TaskInfo[] getTaskInfo()
、TaskInfo getLastTaskInfo()
String shortSummary()
# NumberUtils
T convertNumberToTargetClass(Number number, Class<T> targetClass)
T parseNumber(String text, Class<T> targetClass)
T parseNumber(String text, Class<T> targetClass, NumberFormat numberFormat)
# CollectionUtils
# ObjectUtils
boolean isCheckedException(Throwable ex)
boolean isArray(Object obj)
boolean isEmpty(Object[] array)
boolean isEmpty(Object obj)
Object unwrapOptional(Object obj)
boolean containsElement(Object[] array, Object element)
A[] addObjectToArray(A[] array, O obj)
boolean nullSafeEquals(Object o1, Object o2)
int nullSafeHashCode(Object obj)
String nullSafeToString(Object obj)
String nullSafeToString(Object[] array)
# FileCopyUtils
- 用于文件和流复制,所有复制方法都使用 4096 字节的块大小,并在完成后关闭所有受影响的流
int copy(File in, File out)
void copy(byte[] in, File out)
int copy(InputStream in, OutputStream out)
void copy(byte[] in, OutputStream out)
int copy(Reader in, Writer out)
void copy(String in, Writer out)
String copyToString(Reader in)
byte[] copyToByteArray(File in)
byte[] copyToByteArray(InputStream in)
# StreamUtils
- 用于处理流的简单实用方法,此类的复制方法与 FileCopyUtils 中定义的复制方法类似,只是在完成后所有受影响的流都保持打开状态
# FileSystemUtils
- boolean deleteRecursively(File root):递归删除
- boolean deleteRecursively(Path root)
- void copyRecursively(File src, File dest):递归复制
- void copyRecursively(Path src, Path dest)
# ResourceUtils
- 将给定的资源位置解析为文件系统中的文件
- 支持的资源位置前缀:
file:
File getFile(String resourceLocation)
:将给定的资源位置解析为文件系统中的文件,不检查文件是否实际存在File getFile(URL resourceUrl)
File getFile(URI resourceUri)
jar 包中的文件只能通过类加载器以文件流的方式读取
InputStream inputStream = new ClassPathResource("applicationContext.xml").getInputStream();
# PropertiesLoaderUtils
主要用于加载 Properties 文件
Properties loadProperties(Resource resource)
:从一个资源文件加载 PropertiesProperties loadProperties(EncodedResource resource)
:加载资源文件,传入的是提供了编码的资源类(EncodedResource)void fillProperties(Properties props, Resource resource)
:从一个资源类中加载资源,并填充到指定的 Properties 对象中void fillProperties(Properties props, EncodedResource resource)
:从一个编码资源类中加载资源,并填充到指定的 Properties 对象中Properties loadAllProperties(String resourceName)
:根据资源文件名称,加载并合并 classpath 中的所有资源文件Properties loadAllProperties(String resourceName, ClassLoader classLoader)
:从指定的 ClassLoader 中,根据资源文件名称,加载并合并 classpath 中的所有资源文件
# BeanUtils
void copyProperties(Object source, Object target, String... ignoreProperties)
:浅克隆(原理:反射)boolean isSimpleProperty(Class<?> clazz)
:判断给定的类型是否表示简单属性:八大基本类型/包装类型、字符串或其它 CharSequence、Number、Enum、Date、URI、URL、Locale、Class 或对应的数组T instantiateClass(Class<T> clazz)
:使用无参构造器实例化Method findDeclaredMethod(Class<?> clazz, String methodName, Class<?>... paramTypes)
# BeanCopier
- org.springframework.cglib.beans.BeanCopier
- 原理:修改字节码
- 类方法:
BeanCopier create(Class source, Class target, boolean useConverter)
:通过操作字节码生成用于两个 JavaBean 间进行复制的类 - 抽象方法:
void copy(Object var1, Object var2, Converter var3)
:对两个 Bean 间属性名和类型完全相同的变量进行拷贝
# BeanMap
- org.springframework.cglib.beans.BeanMap implements Map
- 类方法:
BeanMap create(Object bean)
:JavaBean 对象转 Map 对象,BeanMap.create(bean).keySet();
- 实例方法:
void putAll(Map t)
:Map 对象转 JavaBean 对象,BeanMap.create(bean).putAll(map);
# AopUtils
org.springframework.aop.support.AopUtils
boolean isAopProxy(Object object)
:是否是代理对象boolean isJdkDynamicProxy(Object object)
:判断是否是 JDK 代理对象boolean isCglibProxy(Object object)
:判断是否是 cglib 代理对象Class<?> getTargetClass(Object candidate)
:获取对象的真实类型Method getMostSpecificMethod(Method method, Class<?> targetClass)
:获取真实对象上对应的方法Object invokeJoinpointUsingReflection(Object target, Method method, Object[] args)
:在 target 对象上,使用 args 参数列表执行 method
# ClassUtils
# AnnotationUtils
# AnnotatedElementUtils
A findMergedAnnotation(AnnotatedElement element, Class<A> annotationType)
:找到指定的注解类型的第一个注释(包括父类和接口、父类方法和接口方法上的注解)
# ReflectionUtils
Field findField(Class<?> clazz, String name)
Field findField(Class<?> clazz, String name, Class<?> type)
Object getField(Field field, Object target)
void setField(Field field, Object target, Object value)
Method findMethod(Class<?> clazz, String name)
Method findMethod(Class<?> clazz, String name, Class<?>... paramTypes)
Object invokeMethod(Method method, Object target)
Object invokeMethod(Method method, Object target, Object... args)
# UUID 生成器
new AlternativeJdkIdGenerator().generateId()
# Assert 断言工具类
如果参数值被视为无效,则将抛出 IllegalArgumentException (通常)
Assert.notNull(Object object, "object is required")
:对象非空Assert.isTrue(Object object, "object must be true")
:对象必须为 trueAssert.notEmpty(Collection collection, "collection must not be empty")
:集合非空Assert.hasLength(String text, "text must be specified")
:字符不为 null 且字符长度不为 0Assert.hasText(String text, "text must not be empty")
:text 不为 null 且必须至少包含一个非空格的字符Assert.isInstanceOf(Class clazz, Object obj, "clazz must be of type [clazz]")
:obj 必须能被正确造型成为 clazz 指定的类
# AntPathMatcher
- 构造器:AntPathMatcher()(默认的路径分隔符 "/")、AntPathMatcher(String pathSeparator)
- 常用实例方法:
boolean isPattern(String path)
:判断传入的 path 是否可以作为 pattern 使用,即判断 path 是否包含 '*' 或 '?'boolean match(String pattern, String path)
:使用 pattern 匹配 pathString extractPathWithinPattern(String pattern, String path)
:提取 path 中匹配到的部分Map<String,String> extractUriTemplateVariables(String pattern, String path)
:提取 URI 模板变量,如pattern(/hotels/{id:[0-9]+}), path(/hotels/1)
,解析出{id=1}
String combine(String pattern1, String pattern2)
:合并 pattern,即 pattern1 然后 pattern2
# Base64Utils
byte[] encode(byte[] src)
:Base64 编码byte[] decode(byte[] src)
:Base64 解码String encodeToString(byte[] src)
byte[] decodeFromString(String src)
byte[] encodeUrlSafe(byte[] src)
:Base64 编码(using the RFC 4648 "URL and Filename Safe Alphabet")static byte[] decodeUrlSafe(byte[] src)
:Base64 解码(using the RFC 4648 "URL and Filename Safe Alphabet")String encodeToUrlSafeString(byte[] src)
byte[] decodeFromUrlSafeString(String src)
# DigestUtils
- byte[] md5Digest(byte[] bytes):计算 MD5 摘要
- byte[] md5Digest(InputStream inputStream)
- String md5DigestAsHex(byte[] bytes):返回十六进制的 MD5 摘要字符串
- String md5DigestAsHex(InputStream inputStream)
# MultiValueMap
- MultiValueMap<K, V> extends Map<K, List<V>>,一个 Key 对应多个 Value
- 常用实现类:LinkedMultiValueMap、HttpHeaders
V getFirst(K key)
void add(K key, V value)
void addAll(K key, List<? extends V> values)
void addAll(MultiValueMap<K, V> values)
void set(K key, V value)
void setAll(Map<K, V> values)
Map<K, V> toSingleValueMap()
:Return a Map with the first values contained in this MultiValueMap
# ConcurrentReferenceHashMap
# SerializationUtils
byte[] serialize(Object object)
Object deserialize(byte[] bytes)
# NestedExceptionUtils
Throwable getMostSpecificCause(Throwable original)
:获取异常最内部的原因(根本原因)或异常本身
# PropertyPlaceholderHelper
- 属性占位符替换
- 构造器:
- PropertyPlaceholderHelper(String placeholderPrefix, String placeholderSuffix)
- PropertyPlaceholderHelper(String placeholderPrefix, String placeholderSuffix, String valueSeparator, boolean ignoreUnresolvablePlaceholders)
- 实例方法:
String replacePlaceholders(String value, Properties properties)
String replacePlaceholders(String value, PlaceholderResolver placeholderResolver)
# ParameterNameDiscoverer
- 获取非抽象方法的参数名称
- 常用实现类 DefaultParameterNameDiscoverer
ParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer();
parameterNameDiscoverer.getParameterNames(method);
2