spring概述
提供两个开发技术:IOC、AOP+事务处理
框架整合:MyBatis、MyBatis-Plus、Hibernate、Structs、Structs2等
技术:底层框架Spring Framework;简化开发框架spring boot;分布式开发框架spring cloud
spring framework
IOC - 控制反转
假设有一个需求要我们开发一个Controller-Service-Dao的后端架构,由Controller负责处理web请求,调用对应的service执行具体操作,通过Dao查询数据库
如果没有IOC,我们的开发方式是:
每个Controller主动创建Service实例,Service再创建Dao实例
如果Controller多了,且都在复用Service,且Service还有多种实现类,创建的实例就会非常多,总结开发问题可能有:
创建了许多重复对象,造成大量资源浪费;
耦合性强,更换实现类需要改动多个地方;
创建和配置组件工作繁杂,给组件调用方带来极大不便
其实问题的根源在于:调用方主动进行参与了组件的创建和配置工作
IOC控制反转的含义就是,调用方不再主动进行组件创建,而是交给第三方容器完成,在使用时被动由第三方容器注入一个
相当于齿轮,原本两个齿轮是互相卡死的,一个坏掉,另一个也坏掉,有了IOC容器,则齿轮全都与IOC容器耦合,如果一个齿轮坏掉了,另一个齿轮不至于全部崩盘
为了控制对象创建,spring提供了控制创建对象的容器,即IoC容器,用于存放对象,IoC容器就是IoC思想中的外部,被创建或管理在IoC容器中的对象统称为bean
如果bean之间存在依赖关系,spring的ioc容器还能提供依赖注入的能力(Dependency Injection-DI)
DI - 依赖注入
spring的IOC容器提供的依赖注入有:
setter注入:在bean标签中通过配置property标签给属性属性赋值,实际上就是通过反射调用set方法完成属性的注入
构造器注入:使用constructor-arg标签进行属性注入即可
注解属性注入:例如@Autowired等
循环依赖和三级缓存
因为springIOC支持依赖注入,就有可能出现A依赖B,B依赖A这种场景,就是循环依赖
除此之外,还有可能是更大的环,只要是闭环就有可能最终产生循环依赖
解决循环依赖的方案是三级缓存,具体可以参考三级缓存相关源码部分
IOC容器 - bean的创建流程
bean创建流程中的扩展点
在refresh流程对ApplicationContext进行扩展 - ApplicationContextInitializer#initialize
实现方法:实现
ApplicationContextInitializer#initialize
接口,仅针对ConfigureableApplicationContext类型触发时机:
ApplicationContext#refresh()
流程之前,允许我们对ConfigurableApplicationContext的实例做进一步的设置或者处理调用点:常用于SpringMVC的servlet初始化流程FrameworkServlet#configureAndRefreshWebApplicationContext
在refresh流程中新增BeanPostProcessor - AbstractApplicationContext#postProcessBeanFactory
实现方法:
AbstractApplicationContext#postProcessBeanFactory
是一个默认空实现的方法,是AbstractApplicationContext提供的一个上下文级别的方法,需要自行重写AbstractApplicationContext触发时机:在加载了BeanDefinition之后执行。往往用于一些顶层设计,例如补充一些BeanPostProcessor进去
调用点:
AbstractApplicationContext#refresh
方法直接调用
在refresh流程中对BeanDefinition进行扩展 - BeanDefinitionRegistryPostProcessor#postProcessBeanDefinitionRegistry
实现方法:实现
BeanDefinitionRegistryPostProcessor#postProcessBeanDefinitionRegistry
,触发时机:在beanDefinition加载后,bean创建之前对bd做一次扩展,常用于添加一些组件
调用点:在
ApplicationContext#refresh()
方法中调用AbstractApplicationContext#invokeBeanFactoryPostProcessors
方法,会触发BeanDefinitionRegistryPostProcessor#postProcessBeanDefinitionRegistry
方法的调用
在refresh流程中对BeanFactory进行扩展 - BeanFactoryPostProcessor#postProcessBeanFactory
实现方法:实现
BeanFactoryPostProcessor#postProcessBeanFactory
触发时机:在beanDefinition加载后,bean创建之前对BeanFactory做一次扩展,用来定制和修改BeanFactory的内容
调用点:和上面一个一样,在
ApplicationContext#refresh
方法中调用AbstractApplicationContext#invokeBeanFactoryPostProcessors
方法,会触发BeanFactoryPostProcessor#postProcessBeanFactory
方法的调用与上面一个的区别:一个注重bd的调整,补充组件,一个注重BeanFactory的定制
在doCreateBean流程之前对bean初始化流程的短路扩展 - InstantiationAwareBeanPostProcessor#postProcessBeforeInstantiation
实现方法:实现
InstantiationAwareBeanPostProcessor#postProcessBeforeInstantiation
触发时机:加载完bd,要创建bean,在doCreateBean(创建三步)流程之前,createBean方法中还有两次短路的机会。这里是前置短路。AOP的targetSource创建流程是从这里实现的。
调用点:在
AbstractAutowireCapableBeanFactory#createBean
方法中调用AbstractAutowireCapableBeanFactory#resolveBeforeInstantiation
方法,进而调用AbstractAutowireCapableBeanFactory#applyBeanPostProcessorsBeforeInstantiation
触发
在doCreateBean流程中对bean初始化流程的前置扩展 - BeanPostProcessor#postProcessBeforeInitialization
实现方法:实现
BeanPostProcessor#postProcessBeforeInitialization
触发时机:bean实例化方法
AbstractAutowireCapableBeanFactory#doCreatebean
的三步流程中,完成bean实例化、属性注入,还没有进行初始化时调用调用点:是在doCreateBean第三步
AbstractAutowireCapableBeanFactory#initializeBean
中调用AbstractAutowireCapableBeanFactory#applyBeanPostProcessorsBeforeInitialization
引入的
在doCreateBean的第三步初始化流程中执行的初始化完成后扩展 - InitializingBean#afterPropertiesSet
实现方法:实现
InitializingBean#afterPropertiesSet
接口触发时机:bean实例化方法
AbstractAutowireCapableBeanFactory#doCreatebean
的三步流程中,完成bean实例化、属性注入,初始化流程开始后完成了默认初始化方法后调用,用于对刚初始化的bean做一些自定义的初始化扩展调用点:在doCreateBean(创建三步)的第三步
AbstractAutowireCapableBeanFactory#initializeBean
方法中,会调用AbstractAutowireCapableBeanFactory#invokeInitMethods
对bean进行初始化,其中调用此扩展注意:此扩展的执行时机是在postProcessBeforeInitialization前置处理器和postProcessAfterInitialization后置处理器之间,算是一种对bean初始化方法的扩展
bean初始化完成后 - BeanPostProcessor#postProcessAfterInitialization
实现方法:实现
BeanPostProcessor#postProcessAfterInitialization
触发时机:所有可以完成bean加载的场景后面都会调用,例如
断路操作
InstantiationAwareBeanPostProcessor#postProcessBeforeInstantiation
完成后会触发正常调用
AbstractAutowireCapableBeanFactory#initializeBean
完成bean加载也会触发,用于对完全填充好的bean做后置扩展,例如AOP的创建
调用点:较多
doCreateBean完成后的扩展操作 - SmartInitializingSingleton#afterSingletonsInstantiated
实现方法:实现
SmartInitializingSingleton#afterSingletonsInstantiated
方法的单例bean触发时机:在doCreateBean完成后,用于在Spring容器启动完成时进行扩展操作
调用点:在
DefaultListableBeanFactory#preInstantiateSingletons
中触发,点我跳转
spring配置文件中的常用标签
beans、bean、alias、import是最常用的四个标签,四大标签解析主题框架可以参考后文:
beans标签
spring配置文件的主体
profile属性
profile属性是做环境配置用的,在java启动时指定当前启动环境的值,这样可以快速实现切换部署环境变量、比如数据库配置等,就会根据对应profile加载对应的bean,例如:
<beans profile="dev,qa">
<bean id="hadoopSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource" >
<property name="driverClassName" value="${hadoopDriverName}"></property>
<property name="url" value="${hadoopUrl}"></property>
<property name="username" value="${hadoopUserName}"></property>
<property name="password" value="${hadoopPass}"></property>
</bean>
</beans>
而在web应用中,web.xml中做如下配置:
<context-param>
<param-name>Spring.profiles.active</param-name>
<param-value>dev</param-value>
</context-param>
profile属性的加载参考源码部分:
bean标签
最常用的标签,用于配置spring托管的bean
默认情况下它调用的是类中的无参构造函数。如果没有无参构造函数则不能创建成功
bean标签的常见属性
id:给对象在容器中提供一个唯一标识。用于获取对象。
class:指定类的全限定类名。用于反射创建对象。默认情况下调用无参构造函数。
scope:指定对象的作用范围。
singleton :默认值,单例的.
prototype :多例的.
request :WEB 项目中,Spring 创建一个 Bean 的对象,将对象存入到 request 域中.
session :WEB 项目中,Spring 创建一个 Bean 的对象,将对象存入到 session 域中.
global session :WEB 项目中,应用在 Portlet 环境.没有 Portlet 环境 globalSession 相当于 session.
init-method:指定类中的初始化方法名称。
destroy-method:指定类中销毁方法名称。
factory-bean关键字表示如果该bean用工厂创建,那工厂类是哪个bean
factory-method关键字表示如果该bean是工厂创建,那工厂类哪个方法创建
beans中的default-lazy-init属性代表懒加载,即只有使用这个实例才会加载他,如果只是spring创建一个对象,相当于只创建了一个空壳。如果修饰的类只使用了其static方法,也就是没有使用实例,那这种场景就会导致NoClassDefFound报错
constructor-arg:注入属性,该标签自动调用构造方法
index关键字表示构造方法注入的参数顺序,从0开始
ref关键字表示用哪个bean注入
如果bean是工厂类工厂方法构造的,那么constructor-arg表示入参
property:也可以注入属性,该标签自动调用的是set方法
作用域
在bean标签可以通过scope属性指定对象的的作用域
scope=“singleton” 表示当前bean是单例模式(默认饿汉模式,Spring容器初始化阶段就会完成此对象的创建;当在bean标签中设置 lazy-init="true"变为懒汉模式)
scope=“prototype” 表示当前bean为非单例模式,每次通过Spring容器获取此bean的对象时都会创建一个新的对象
id、name、alias属性 - 名称相关
id用于指定这个bean被get的时候的名字是什么,一个接口多个实现类,调用方调用接口,又要指定注入时,可以使用。要注意在一个xml中bean的id是不可重复的
name和alias指定名称和别名。
解析流程参考源码部分:
class、parent属性 - 类定义相关
class用来定义类的全限定名,子bean不用定义该属性。
parent属性是子类bean定义它所引用的父类bean的,有parent时class会失效,子类bean继承父类bean所有属性。
abstract属性默认为false,用来定义bean是否是抽象bean。它表示这个bean一般不会被实例化。
解析流程参考源码部分:
singleton、scope 、lazy-init、abstract、depends-on属性 - 加载相关
singleton默认为true,是老版本属性,现在已不推荐使用。
scope属性是现在使用的,值有singleton和prototype,singleton为默认值,代表单例模式,prototype非单例,即每个注入和getBean都是创建一个新的对象。
<bean id="prototypeTest" class="com.gty.bean.PrototypTest" scope="prototype"/>
lazy-init
为懒加载,默认为false,当设置为true时将不会在spring启动时ApplicationContext启动时实例化,而是在第一次getBean的时候实例化。懒加载仅针对scope=”singleton”
的bean起作用。
depends-on指向的bean先于此bean加载,晚于此bean销毁。尽管可能二者没有什么实际的依赖或注入关系,但使用depends-on依旧可以强制获得加载时间上的先后顺序。
解析流程参考源码部分:
autowire、autowire-candidate、primary属性 - 依赖注入相关
autowire和@Autowired作用相同,只不过是在xml中配置的。
autowire-candidate属性默认为true,如果设置为false,则该bean不能通过autowire注入到别的bean中。
primary属性是用来反屏蔽autowire-candidate的,通过primary注解,可以让autowire-candidate在指定的类上面不生效。
解析流程参考源码部分:
init-method、destroy-method、factory-method、factory-bean属性 - 初始化相关
init-method它的作用是在创建一个bean之后调用该方法,初始化方法必须是一个无参方法。
destroy-method的作用是在销毁bean之前可以执行指定的方法。注意:必须满足scope=“singleton”
,并且destroy方法参数个数不能超过1,并且参数类型只能为boolean。
factory-method、factory-bean是针对使用工厂模式生产的bean。指定工厂类和工厂方法后可以不使用class属性标注bean的类型,就可以返回工厂方法返回对象。
解析流程参考源码部分:
meta子标签
是bean的额外声明,以键值对的形式存储,不存入bean的属性中,当需要使用其中信息可以通过BeanDefinition::getAttribute(key)
方法获取。
<bean id="myTestBean" class="bean.MyTestBean">
<meta key="testStr" value="aaaaaaaa"/>
</bean>
解析流程参考源码部分:
look-method子标签
方法查找。通过@Autowired可以把bean注入到对象中,通过lookup-method可以把bean当作返回对象注入到方法结果中。甚至可以对抽象方法生效。案例如下:
// 调用方
public abstract class GetBeanTest {
public void showMe(){
this.getBean().showMe();
}
public abstract User getBean();
}
// 注入方
package test.lookup.bean;
public class Teacher {
public void showMe(){
System.out.println("i am Teacher");
}
}
// xml配置
<bean id="getBeanTest" class="test.lookup.app.GetBeanTest">
<lookup-method name="getBean" bean="teacher"/>
</bean>
<bean id="teacher" class="test.lookup.bean.Teacher"/>
这样就把Teacher注入到了getBean方法种,使方法被调用时,自动获取到Teacher类对象。如果需要修改,只需修改teacer bean对应的class映射即可。
解析流程参考源码部分:
replaced-method子标签
当一个类实现了MethodReplacer接口,实现reimplement方法,就具备了替换的能力。例如:
// 要被替换的方法
public class TestChangeMethod {
public void changeMe(){
System.out.println("changeMe");
}
}
public class TestMethodReplacer implements MethodReplacer{
@Override
public Object reimplement(Object obj, Method method, Object[] args)throws Throwable {
System.out.println("我替换了原有的⽅法");
return null;
}
}
// xml配置
<bean id="testChangeMethod" class="test.replacemethod.TestChangeMethod">
<replaced-method name="changeMe" replacer="replacer"/>
</bean>
<bean id="replacer" class="test.replacemethod.TestMethodReplacer"/>
解析流程参考源码部分:
constructor-arg子标签
<beans>
<!-- 默认的情况下是按照参数的顺序注⼊,当指定index索引后就可以改变注⼊参数的顺序 -->
<bean id="helloBean" class="com.HelloBean">
<constructor-arg index="0">
<value>注入1</value>
</constructor-arg>
<constructor-arg index="1">
<value>注入2</value>
</constructor-arg>
</bean>
... ...
</beans>
这是一个最简单的构造函数的例子,HelloBean类的构造函数需要注入两个String参数,分别是注入1和注入2。
解析流程参考源码部分:
还有比较复杂的构造函数,即使用子元素作为填充的例如:
<constructor-arg>
<map>
<entry key="key" value="value" />
</map>
</constructor-arg>
解析流程参考源码部分:
也有直接使用属性和值的例如:
<constructor-arg value="a" >
解析流程参考源码部分:
idref子标签
constructor-arg和 property的子标签,idref元素用来将容器内其它bean的id传给<constructor-arg>
或 <property>
标签,同时提供错误验证功能。
<bean id="p2" class="com.pojo.Person">
<property name="pname">
<idref bean="c3"/>
</property>
</bean>
解析流程参考源码部分:
merge属性
merge属性用于将子类的数组元素和父类的相同元素合并,而非覆盖。例如:
// 父类
public class Student {
private String name;
private List<Integer> nums;
……
}
<!-- xml配置-->
<bean id="baseStudent" class="Student">
<property name="name"><value>zhangsan</value></property>
<property name="nums">
<list>
<value>1</value>
<value>2</value>
<value>3</value>
</list>
</property>
</bean>
<bean id="Student" parent="baseStudent">
<property name="nums" >
<list>
<value>1</value>
<value>2</value>
</list>
</property>
</bean>
这种场景下,子类Student获取到name是zhangsan,nums是12,如果修改Student这个bean的配置,改为<list merge=true>
,则name是zhangsan,nums是12312
array、list、set标签
用于注入数组、列表、集合类型,以array为例:
value-type使用其他类型,数组子元素用ref引用bean名称
<bean class="test.ParentBean">
<property name="subBeans">
<array value-type="test.SubBean">
<ref bean = “subBean1”/>
<ref bean = “subBean2”/>
</array>
</property>
</bean>
value-type不加默认为String类型,子元素可以直接用value
<bean class="test.ParentBean">
<property name="subBeanNames">
<array>
<value>subBean1</value>
<value>subBean2</value>
</array>
</property>
</bean>
value使用type属性具有相同的功效
<bean class="test.ParentBean">
<property name="subBeanNames">
<array>
<value type="int">1</value>
<value type="int">2</value>
</array>
</property>
</bean>
解析流程参考源码部分:
map、property标签
<property name="map">
<map>
<entry key="1" value-ref="li"/>
<entry key="2" value-ref="wang"/>
</map>
</property>
先看一个property使用map子元素的样例,map子元素下面是entry子元素,有key和value-ref两种属性,通过value-ref可以把bean注入到mapvalue里面,如果是普通字符串,也可以直接使用value
qualifier子标签
一般@Autowired注解用的比较多,当bean注入时是参考类,如果实现类有多个,spring会抛BeanCreationException异常。使用qualifier指定bean的id,可以消除歧义。等同于@Autowired加beanname的形式
<bean id="myTestBean" class="bean.MyTestBean">
<qualifier type="org.Springframework.beans.factory.annotation.Qualifier" value="qf"/>
</bean>
alias标签
对bean配置别名可能不是在bean创建时就要立即配置的场景,有可能会有单独引入的需要。因此产生了alias标签。使用bean标签配置name属性和使用alias标签配置效果相同,name属性是bean的id,alias属性是对应别名:
<!-- 使用bean标签-->
<bean id="testBean" name="testBean,testBean2" class="com.test"/>
<!-- 使用alias标签-->
<bean id="testBean" class="com.test"/>
<alias name="testBean" alias="testBean,testBean2"/>
使用alias标签也可以做到为一个bean配置两个不同别名,方便其在不同场景下进行引用,常见场景就是一个数据源给多个环境使用。
解析流程参考源码部分:
import标签
用于引用其他spring配置文件,就跟引java一样
<beans>
<import resource="customerContext.xml" />
<import resource="systemContext.xml" />
... ...
</beans>
解析流程参考源码部分:
AOP相关标签
namespace
依赖的namespace是:
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.0.xsd"
基本标签
<aop:config> 用于声明开始aop的配置
|--- <aop:aspect> 用于配置切面
|--- |--- 属性: id 给切面提供一个唯一标识。
|--- |--- 属性: ref 引用配置好的通知类bean的id。
|--- |--- <aop:pointcut> 用于配置切入点表达式
|--- |--- |--- 属性: expression 用于定义切入点表达式。
|--- |--- |--- 属性: id 用于给切入点表达式提供唯一标识。
|--- |--- <aop:before> 用于配置前置通知
|--- |--- |--- 属性: method 指定通知中方法的名称。
|--- |--- |--- 属性: pointcut 定义切入点表达式。
|--- |--- |--- 属性: pointcut-ref 用于给切入点表达式提供唯一标识。
|--- |--- <aop:after-returning> 用于配置后置通知,出现异常不调用
|--- |--- |--- 属性: method 指定通知中方法的名称。
|--- |--- |--- 属性: pointcut 定义切入点表达式。
|--- |--- |--- 属性: pointcut-ref 用于给切入点表达式提供唯一标识。
|--- |--- <aop:after-returning> 用于配置异常通知
|--- |--- |--- 属性: method 指定通知中方法的名称。
|--- |--- |--- 属性: pointcut 定义切入点表达式。
|--- |--- |--- 属性: pointcut-ref 用于给切入点表达式提供唯一标识。
|--- |--- <aop:after> 用于配置最终通知
|--- |--- |--- 属性: method 指定通知中方法的名称。
|--- |--- |--- 属性: pointcut 定义切入点表达式。
|--- |--- <aop:around> 用于环绕通知
|--- |--- |--- 属性: invokeMethod 指定通知中方法的名称。
|--- |--- |--- 属性: pointcut 定义切入点表达式。
|--- |--- |--- 属性: pointcut-ref 用于给切入点表达式提供唯一标识。
proxy-target-class
还有一个全局标签proxy-target-class,在aop和事务中都生效的,属性值决定是基于接口的还是基于类的代理被创建,为true则是基于类的代理将起作用(需要cglib库),为false或者省略这个属性,则标准的JDK 基于接口的代理将起作用
<aop:config proxy-target-class="true">
它的解析参考aop部分
spring-jdbc相关标签
依赖的namespace是
xmlns:jdbc="http://www.springframework.org/schema/jdbc"
xsi:schemaLocation="http://www.springframework.org/schema/jdbc
http://www.springframework.org/schema/jdbc/spring-jdbc.xsd"
标签包括:
数据源初始化相关
<jdbc:initialize-database data-source="test01db" enabled="true" ignore-failures="NONE"
separator="@@" >
<jdbc:script location="classpath:init/test01db_1.sql" />
<jdbc:script location="classpath:init/test01db_2.sql" />
</jdbc:initialize-database>
通过
<jdbc:script location>
标签指定数据脚本,在初始化的时候可以自动执行。数据脚本指定支持通配符,例如:classpath*:/com/foo/**/sql/*-data.sql
data-source参数用于选定由spring管理的数据源,可以通过注入的方式获取。
enabled表明初始化数据库是否执行,设置方法有两种:
直接设置,例如
enabled=”true”
配置文件读取,例如
enabled=’#{systemProperties.INITIALIZE_DATABASE}’
ignore-failures属性有三个值:NONE、DROPS、ALL
NONE表示不忽略任何错误,sql报错,初始化终止
DROPS表示忽略删除错误,当删除语法的表不存在,忽略该错误
ALL表示忽略任何错误
separator属性是sql语句的分隔标识,可配置,会对script起作用,也可以在
jdbc:script
中单个配置。优先级是script级别>jdbc级别>默认级别。默认分隔符为“;”,即sql语句之间用分号隔开,没有分号算一句。
spring中的常用工具类
ApplicationListener
由应用程序事件侦听器实现的接口。
内含onApplicationEvent方法。实现该方法,会在类加载时被执行。
但该方法可能会被加载多次,因此可以用synchronized进行保护
InitializingBean
扩展方法里面最常用的一个,提供了初始化方法的方式,它只包括afterPropertiesSet方法,凡是继承该接口的类,在初始化bean的时候都会执行该方法
import org.springframework.beans.factory.InitializingBean;
@Component
public class AfterPropertiesSetTest implements InitializingBean {
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("ceshi InitializingBean");
}
}
添加配置文件:
<bean id="testInitializingBean" class="com.TestInitializingBean" ></bean>
Main函数如下
public class Main {
public static void main(String[] args){
ApplicationContext context = new FileSystemXmlApplicationContext("/src/main/java/com/beans.xml");
}
}
需要注意的是:如果执行afterPropertiesSet方法抛异常出错了,则无法继续执行bean的初始化,会导致bean初始化失败
spring定时任务
使用@EnableScheduling和@Scheduled(cron = “表达式”)配置在方法上可以实现定时启动任务。
开启定时任务注解要使用@EnableScheduling加到@Configuration定义的配置类上面,或者使用xml配置文件增加<task:annotation-driven>
@Scheduled不安全。在默认情况下,spring只启动一个线程开启定时任务,所有的scheduled会按先后顺序进行执行。如果任务1本次启动阻塞了,那么任务n也会无限等待。因此要防止阻塞,可以需要搞一个线程池,给每次自启动都起一条线程
可以通过@Async、@EnableAsync注解将线程托管给spring
@Async修饰方法,可以让spring自己捞一个线程执行该方法。
@EnableAsync表示开启对异步任务的支持,可以放在springboo启动类上也可以放在自定义线程池配置上
@EnableAsync加在启动类上,@Async修饰方法时,spring自己从默认线程池捞一个线程执行方法,默认线程池为SimpleAsyncTaskExecutor
@EnableAsync加在自定义线程池上,可以将线程池托管给spring用于@Async方法捞取线程
@Configuration
@EnableAsync
public class ScheduleConfig {
@Bean(name = “pool1”)
public TaskScheduler taskScheduler() {
ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler();
taskScheduler.setPoolSize(50);
return taskScheduler;
}
@EnableScheduling
public class TaskFileScheduleService {
@Async(“pool1”)
@Scheduled(cron="0 */1 * * * ?")
public void task1(){
.......
}
@Async
@Scheduled(cron="0 */1 * * * ?")
public void task2(){
.......
}
spring事务
spring事务有三种开启方法:
@Transactional注解开启
实现TransactionTemplate
使用jdbcTemplate自带的事务方案
实现TransactionTemplate
@Autowired
private TransactionTemplate transactionTemplate;
public void testTransaction() {
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
try {
// .... 业务代码
} catch (Exception e){
//回滚
transactionStatus.setRollbackOnly();
}
}
});
}
使用jdbcTemplate自带事务方案
原生jdbc支持事务
DataSource dataSource;
Connection connection = dataSource.getConnection();
connection.setAutoCommit(false);
PreparedStatement ps = connection.prepareStatement(sql);
ps.addBatch();
affectRow = ps.executeBatch();
connection.commit();
connection.setAutoCommit(true);
spring提供的JdbcTemplate中,只需要从JdbcTemplate中获取connection就可以开启原生事务
String sql1 = "INSERT INTO user_tmp(`id`, `username`) VALUES(22, 222)";
String sql2 = "INSERT INTO user_tmp(`id`, `username`) VALUES(1, 111)";
Connection connection = jdbcTemplate.getDataSource().getConnection();
connection.setAutoCommit(false);
jdbcTemplate.batchUpdate(new String[] {sql1, sql2});
注意:NamedParameterJdbcTemplate这种支持具名参数的JdbcTemplate就不支持多sql批处理了,那么开启事务实际上就没啥用了
使用@Transactional注解
最常用,最简单,一般Mybatis等ORM组件都可以用这种方案处理
常用的属性包括:
propagation 对应 TransactionDefinition 中的 getPropagationBehavior,默认值为
Propagation.REQUIRED(TransactionDefinition.PROPAGATION_REQUIRED)。
isolation 对应 TransactionDefinition 中的 getIsolationLevel,默认值为 DEFAULT(TransactionDefinition.ISOLATION_DEFAULT)。
timeout 对应 TransactionDefinition 中的 getTimeout,默认值为TransactionDefinition.TIMEOUT_DEFAULT。
readOnly 对应 TransactionDefinition 中的 isReadOnly,默认值为 false。
事务传播
当事务方法被另外一个事务方法调用时,必须指定事务应该如何传播,例如,方法可能继续在当前事务中执行,也可以开启一个新的事务,在自己的事务中执行。可以通过 @Transactional 注解中的 propagation 属性来定义
PROPAGATION_REQUIRED:默认值,当前存在事务,则加入该事务;如果当前没有事务,则创建一个
新的事务
PROPAGATION_REQUIRES_NEW:创建一个新的事务,如果当前存在事务,则把当前事务挂起。
PROPAGATION_NESTED:如果当前存在事务,就在当前事务内执行
PROPAGATION_SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
PROPAGATION_NOT_SUPPORTED:以非事务方式运行,如果当前存在事务,则把当前事务挂起
PROPAGATION_MANDATORY:如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。
PROPAGATION_NEVER:以非事务方式运行,如果当前存在事务,则抛出异常。
回滚策略
回滚策略 rollbackFor ,用于指定能够触发事务回滚的异常类型,可以指定多个异常类型。默认情况下,事务只在出现运行时异常(Runtime Exception)时回滚,以及 Error,出现检查异常(checked exception,需要主动捕获处理或者向上抛出)时不回滚。
spring管理事务边界是调用业务方法之前开始的,业务方法执行完毕后才执行提交或回滚操作。
@Transactional事务失效场景
作用在非public方法上失效,但在protected和private上加不报错
propagation设置错误导致失效
rollbackFor指定回滚异常类型导致失效
同个类中this调用被@transactional修饰的方法会失效,spring只对被所在类以外的代码调用,才做事务管理
try catch捕获异常会导致回滚失效,除非主动抛出runtimeException。此时要保证事务内容全部在try里,但catch也不能有操作,因为catch内容也会一起回滚
数据库不支持事务
spring深拷贝 - BeanCopier
A. 创建BeanCopier对象
public static BeanCopier create(Class source, Class target, boolean useConverter)
第一个参数source:我们要拷贝的对象
第二个参数target:我们要拷贝成什么样的对象
第三个参数useConverter:用户控制转换器,在下文对这个参数做解释,因为CGLIB是动态代理的,所以有控制反转
用户控制转换器,如果选择为false,它会对拷贝的对象和被拷贝的对象的类型进行判断,如果类型不同就不会拷贝,如果要使他会拷贝,就需要设置为true,自己拿到控制权自己对其进行处理,一般情况下我们都是使用的false
BeanCopier studentCopier = BeanCopier.create(Student.class, Student.class, false);
Student baseStudent = new Student(“name”, 19);
Student student = new Student();
studentCopier.copy(baseStudent, student, null);
spring上下文感知 - ApplicationContextAware
通过实现ApplicationContextAware接口让类获得上下文感知的能力
Aware顾名思义,就是意识到,一个类能意识到ApplicationContext,也就是说它可以获取spring上下文环境了
public class MyApplicationContextAware implements ApplicationContextAware {
private ApplicationContext applicationContext;
public void setApplicationContext(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}
// 支持对本身、父类、抽象类、接口的获取
public Object getBean(String name, Class<?> type) {
return applicationContext.getBean(name, type);
}
}
ReflectionUtils - 反射工具类
使用spring的反射工具类可以极大简化反射代码,据说性能比jdk原生反射要好哦
MethodFilter - 方法过滤器
方法过滤器用于创建一个在所有方法中找到想要方法的过滤器。
spring提供了一个基础过滤器,可以帮开发找到用户声明方法:
public static final MethodFilter USER_DECLARED_METHODS =
(method -> !method.isBridge() && !method.isSynthetic() && (method.getDeclaringClass() != Object.class));
同时提供and方法用于过滤器条件拼接,在and方法中只需要写lambda表达式即可,例如:
ReflectionUtils.USER_DECLARED_METHODS
.and(method -> (AnnotationUtils.getAnnotation(method, Pointcut.class) == null));
创建方法过滤器后,spring提供了doWithMethods方法
public static void doWithMethods(Class<?> clazz, MethodCallback mc, @Nullable MethodFilter mf)
参数1代表要找的类,参数2是找到后的回调方法,参数3是方法过滤器。
spring反射工具在AOP流程中有很典型的使用:ReflectiveAspectJAdvisorFactory#getAdvisorMethods
ReflectionUtils.doWithMethods(aspectClass, methods::add, adviceMethodFilter);
其中是在aspectClass中找符合adviceMethodFilter的方法,找到加入到列表methods中。
AnnotationUtils - 注解工具类
findAnnotation - 找注解
public static <A extends Annotation> A findAnnotation(Method method, @Nullable Class<A> annotationType)
参数1是方法,参数2是注解类型
在spring-aop中的使用示例:AbstractAspectJAdvisorFactory#isAspect
A result = AnnotationUtils.findAnnotation(method, toLookFor);
isCandidateClass - 查看是否匹配
public static boolean isCandidateClass(Class<?> clazz, @Nullable Class<? extends Annotation> annotationType)
AnnotatedElementUtils - 注解属性工具类
findMergedAnnotationAttributes - 找方法上的注解中的属性
AnnotationAttributes findMergedAnnotationAttributes(AnnotatedElement element,
Class<? extends Annotation> annotationType, boolean classValuesAsString, boolean nestedAnnotationsAsMap)
入参AnnotatedElement实际上可以传入Method对象,是它的顶层接口
参数Class就是注解类型。
返回值是AnnotationAttributes是由spring封装的注解属性。
其经典调用点就是spring事务中的调用:SpringTransactionAnnotationParser#parseTransactionAnnotation
hasAnnotation
判断类上有没有某个注解
boolean b = AnnotatedElementUtils.hasAnnotation(AspectJTest.class, Pointcut.class);
AnnotatedMetadata - 注解元数据类
// 此方法是否标注有此注解,annotationName:注解全类名
boolean isAnnotated(String annotationName);
//取得指定类型注解的所有的属性 - 值(k-v)
// classValuesAsString:若是true表示 Class用它的字符串的全类名来表示。可以避免Class被提前加载
@Nullable
Map<String, Object> getAnnotationAttributes(String annotationName);
@Nullable
Map<String, Object> getAnnotationAttributes(String annotationName, boolean classValuesAsString);
// 参见这个方法的含义:AnnotatedElementUtils.getAllAnnotationAttributes
@Nullable
MultiValueMap<String, Object> getAllAnnotationAttributes(String annotationName);
@Nullable
MultiValueMap<String, Object> getAllAnnotationAttributes(String annotationName, boolean classValuesAsString);
AnnotatedTypeMetadata - 被注解类的元数据类
这个类是被注解的类的元数据类,提供的方法是找这个类上面的注解
// 判断这个类是否被某个特定注解注解了
default boolean isAnnotated(String annotationName)
// 获取这个类上面所有注解
MergedAnnotations getAnnotations();
// 获取这个类的特定注解的所有注解属性
default Map<String, Object> getAnnotationAttributes(String annotationName)
AopUtils - aop工具类
判断切面是否应用于方法
public static List<Advisor> findAdvisorsThatCanApply(List<Advisor> candidateAdvisors, Class<?> clazz)
BeanFactoryUtils - Bean工厂工具类
BeanFactoryUtils工具类是在 bean 工厂上运行的便捷方法,可以方便地返回 bean 计数、bean 名称或 bean 实例
例如beansOfTypeIncludingAncestors方法可以返回给定类型或子类型的所有 bean,使用案例参考MVC部分
spring源码架构
DefaultListableBeanFactory - 核心bean工厂的血缘
可以看到一路继承下来的一些能力,包括:
AliasRegistry:定义对alias的简单增删改等操作。
SimpleAliasRegistry:主要使用map作为alias的缓存,并对接口AliasRegistry进行实现。
SingletonBeanRegistry:定义对单例的注册及获取。
BeanFactory:定义获取bean及bean的各种属性。
DefaultSingletonBeanRegistry:对接口SingletonBeanRegistry各函数的实现。
HierarchicalBeanFactory:继承BeanFactory,也就是在BeanFactory定义的功能的基础上增加了对parentFactory的支持。
BeanDefinitionRegistry:定义对BeanDefinition 的各种增删改操作。
FactoryBeanRegistrySupport:在DefaultSingletonBeanRegistry基础上增加了对 FactoryBean的特殊处理功能。
ConfigurableBeanFactory:提供配置Factory的各种方法。
ListableBeanFactory:根据各种条件获取bean的配置清单。
AbstractBeanFactory:综合FactoryBeanRegistrySupport和 ConfigurableBeanFactory的功能。
AutowireCapableBeanFactory:提供创建bean、自动注入、初始化以及应用bean的后处理器。
AbstractAutowireCapableBeanFactory:综合AbstractBeanFactory并对接口AutowireCapable BeanFactory进行实现。
ConfigurableListableBeanFactory:BeanFactory配置清单,指定忽略类型及接口等。
DefaultListableBeanFactory:综合上面所有功能,主要是对bean注册后的处理。
其实这些内容基本上就是spring容器提供的全部核心功能了
可以说,DefaultListableBeanFactory是一个很成熟的Bean工厂,可以提供完善的Bean创建流程的支持,同时它也是自定义BeanFactory的父类,其他有各类需求的BeanFactory都是基于DefaultListableBeanFactory衍生的
评论区