跳至主要內容

Spring

HeChuangJun约 3948 字大约 13 分钟

Spring?特性?模块?设计模式?

一个轻量级、非入侵式的控制反转IOC和面向切面AOP的框架

IOC和DI:管理对象生命周期和依赖关系
AOP:实现对程序进行权限拦截、运行监控等切面功能。
声明式事务:通过配置完成事务管理,不需要通过硬编码的方式
快捷测试:支持Junit注解测试Spring程序
快速集成各种优秀框架
复杂API模板封装:对JDBC、JavaMail等提供了模板化的封装,降低应用难度

Core:提供IoC和DI
Context:BeanFactory功能加强的子接口
Web:提供Web应用开发的支持
DAO:JDBC抽象,简化编码
ORM:整合ORM框架Hibernate、iBatis
AOP:面向切面编程

工厂模式:通过BeanFactory、ApplicationContext创建Bean对象
代理模式:用于实现AOP
单例模式:Bean默认为单例模式
模板方法:提高代码重用。RestTemplate、JdbcTemplate
观察者模式:驱动模型
适配器模式:AOP的增强或通知(Advice)、SpringMVC中适配Controller
策略模式:Resource接口会根据不同的策略去访问资源
前端控制器:SpringMVC的DispatcherServlet来对请求进行分发
依赖注入:

Spring注解?@Required、@Autowired、@Qualifier作用?@Autowired实现原理?

Spring MVC注解
Spring AOP注解
容器:
@Component:将类变为Spring管理的Bean
@Service:@Component注解,service层(业务逻辑层)
@Repository:~,dao层(数据访问层)
@Autowired:依赖注入工具(BeanPostProcessor、BeanFactoryPostProcessor)自动注入
@Configuration:声明配置类
@Value:在字段,构造器参数跟方法参数指定默认值,支持#{}${}方式。将配置文件值赋值给变量
@Bean:声明方法的返回值为Bean。@Bean(initMethod="init",destroyMethod="destroy")
@Scope:定义创建Bean的模式

事务:
@Transactional:在方法上使用声明式开启事务

@Required,用在setter方法。表示属性必须注入否则抛出BeanInitializationException异常
@Autowired:用在setter方法,构造函数,具有任意名称或多个参数的属性或方法上自动装配 Bean
@Qualifier:指定id装配Bean

在Bean的初始化阶段通过Bean后置处理器AutowiredAnnotationBeanPostProcessor
创建Bean的调用doCreateBean()方法,里面调用populateBean()方法为Bean进行属性填充
在populateBean()方法调用了两次后置处理器,第一次调用postProcessProperties判断是否需要属性填充,第二次调用AutowiredAnnotationBeanPostProcessor的postProcessPropertyValues()方法进行@Autowired注解的解析,实现自动装配

源码
//属性赋值
protected void populateBean(String BeanName, RootBeanDefinition mbd, @Nullable BeanWrapper bw) {
  if (hasInstAwareBpps) {
    if (pvs == null) { pvs = mbd.getPropertyValues();}

    PropertyValues pvsToUse;
    for(Iterator var9 = this.getBeanPostProcessorCache().instantiationAware.iterator(); var9.hasNext(); pvs = pvsToUse) {
        InstantiationAwareBeanPostProcessor bp = (InstantiationAwareBeanPostProcessor)var9.next();
        pvsToUse = bp.postProcessProperties((PropertyValues)pvs, bw.getWrappedInstance(), BeanName);
        if (pvsToUse == null) {
            if (filteredPds == null) {
                filteredPds = this.filterPropertyDescriptorsForDependencyCheck(bw, mbd.allowCaching);
            }
            //执行后处理器,填充属性,完成自动装配
            //调用InstantiationAwareBeanPostProcessor的postProcessPropertyValues()方法
            pvsToUse = bp.postProcessPropertyValues((PropertyValues)pvs, filteredPds, bw.getWrappedInstance(), BeanName);
            if (pvsToUse == null) {
                return;
            }
        }
    }
  }
}

public PropertyValues postProcessProperties(PropertyValues pvs, Object Bean, String BeanName) {
  //解析Bean的@Autowired注解、@Inject和@Value注解的属性和方法
  InjectionMetadata metadata = this.findAutowiringMetadata(BeanName, Bean.getClass(), pvs);

  try {
      //属性填充
      metadata.inject(Bean, BeanName, pvs);
      return pvs;
  } catch (BeanCreationException var6) {
      throw var6;
  } catch (Throwable var7) {
      throw new BeanCreationException(BeanName, "Injection of autowired dependencies failed", var7);
  }
}

IOC?优点?实现原理?di方式√

Inverse Of Control控制反转:设计模式,将对象的创建和控制权反转到容器或框架中,不由对象本身控制
简化代码、松耦合、支持懒加载Bean
IOC实现:di、事件驱动、服务定位器

工厂模式加反射机制
加载配置文件,解析成BeanDefinition放在Map里
BeanFactory调用getBean时,从BeanDefinition的Map取出Class对象实例化,如果有依赖关系,将递归调用getBean方法,完成依赖注入

dependency Injection依赖注入,允许将类所依赖的对象注入到类中,而不是在类内部创建依赖对象
注入方式:set方法、构造方法、工厂方法注入

代码
set方法注入
<Bean  name="user" class="cn.itcast.Bean.User" >
	<property name="name" value="tom" ></property>
	<property name="car"  ref="car" ></property>
	<property name="arr">
		<array>
			<value>tom</value>
			<ref Bean="user"/>
		<array>
	</property>
	<property name="list">
		<list>
			<value>jack</value>
			<ref Bean="user"/>
		<list>
	</property>
	<property name="prop">
		<props>
			<prop key="driverClass">com.jdbc.mysql.Driver</prop>
		<props>
	</property>
	<property name="map">
		<map>
			<entry key="url" value="jdbc:mysql:///crm"></entry>
		<map>
	</property>
</Bean>
<Bean name="car" class="cn.itcast.Bean.Car" >
	<property name="name" value="兰博基尼" ></property>
</Bean>


构造函数注入
<Bean name="user2" class="cn.itcast.Bean.User" >
	<constructor-arg name="name" index="0" type="java.lang.Integer" value="999"></constructor-arg>
	<constructor-arg name="car" index="1" ref="car"></constructor-arg>
</Bean>

ApplicationContext/BeanFactory?

BeanFactory接口:创建并管理类的对象。获取时才创建,如XmlBeanFactory根据XML文件创建对象
ApplicationContext:每次容器启动时就会创建容器中配置的所有对象
ClassPathXmlApplicationContext/FileSystemXmlApplicationContext/XmlWebApplicationContext
从ClassPath/文件系统/Web应用的XML文件中读取并生成上下文
Spring Boot的ConfigServletWebServerApplicationContext

Bean注册方式?生命周期?作用域scope类型√线程安全吗?√自动装配方式?循环依赖及解决?√为何使用三级缓存解决循环依赖而不是二级缓存?√

xml配置:Bean标签
注解配置:@Component、@Service、@Controller、@Bean
java config @Configuration、@Import(xxxConfig.class)
BeanFactory或ApplicationContext手动注册

实例化Bean对象: 根据配置中Bean Definition中实例化Bean对象
属性赋值
调用BeanNameAware的setBeanName(String name)方法
调用BeanFactoryAware的setBeanFactory(BeanFactory BeanFactory)方法
[调用ApplicationContextAware的setApplicationContext()方法]
初始化
@PostConstruct
调用BeanPostProcessor的preProcessBeforeInitialization(Object Bean, String BeanName) 方法。
调用InitializingBean的afterPropertiesSet()方法
调用init方法(例如 <Bean/> 的 init-method 属性)
调用BeanPostProcessor的postProcessAfterInitialization(Object Bean, String BeanName) 方法。
代理生成
通过BeanPostProcessor(如ProxyFactory)为目标Bean创建AOP代理对象。
销毁
@PreDestroy
调用DisposableBean的destroy()方法[afterPropertiesSet()方法]
调用destroy方法(例如 <Bean /> 的 destroy-method 属性)

singleton(默认):单例
prototype:每次获取都会创建新对象
request、session、Application:与request~~生命周期一致

单例Bean是无状态线程中的操作不会对Bean的成员执行查询以外的操作则线程安全
Bean有多种状态则线程不安全。将Bean的作用域改为"prototype"或者将变量放入ThreadLocal中

byName/byType/constructor/autodetect 根据名称/类型/函数入参类型自动匹配/Bean有默认构造函数用byType否则用constructor

两个或多个Bean互相依赖,导致无法正常创建和注入Bean。
允许循环依赖:spring.main.allow-circular-references:true

多例Bean不支持,无限创建对象,抛出BeanCurrentlylnCreationException异常
AB都采用构造器,依赖注入不支持,因为此时对象必须实例化时完成依赖注入,无法延迟依赖注入
AB均采用setter注入,支持
A中注入的B为setter注入,B中注入的A为构造器注入,支持
B中注入的A为setter注入,A中注入的B为构造器注入,不支持
第四种可以,第五种不可以是因为创建Bean时默认根据自然排序创建,A先于B创建

通过第三级缓存
singletonObjects 一级缓存保存实例化、属性赋值、初始化完成的Bean实例
earlySingletonObjects 二级缓存保存实例化完成的Bean实例
singletonFactories 三级缓存保存提前暴露的ObjectFactory,用于创建代理或者普通对象
关键在于第三级缓存ObjectFactory提前暴露对象的早期引用,通过这个早期引用,其他对象可以注入已经实例化但没赋值初始化的对象,使得依赖注入可以进行.而不需要等待Bean完全初始化。

实例化过程
A实例化并把A的ObjectFactory加入第三级缓存
A填充属性需要注入B => B实例化并把B的ObjectFactory加入第三级缓存
B填充属性需要注入A => 从第三级缓存移除A的ObjectFatory,A的ObjectFactory生成代理对象A'加入第二级缓存(A还是半成品)
B属性注入A',创建B代理对象(完成品) => 从第三级缓存移除B对象,B代理对象加入第一级缓存
A填充属性注入B代理对象,从第二级缓存移除A代理对象,A代理对象加入第一级缓存

为什么要三级缓存?而不是二级?
在循环依赖的场景下,Bean可能需要代理对象,而不是目标对象。如果只使用二级缓存,无法动态生成这些代理对象。三级缓存通过ObjectFactory动态调用BeanPostProcessor(AbstractAutoProxyCreator的getEarlyBeanReference方法)来生成代理对象。确保依赖关系的正确性。(即循环依赖时提前AOP。原本是属性赋完值之后,再由后置处理器处理AOP)

读取配置的方式?bootstrap\application.properties区别?配置文件读取顺序?√区分环境?配置文件外置

@PropertySource(value="x")指定配置文件
@Value读取配置到属性。
@ConfigurationProperties(prefix="x")读取配置到类

bootstrap.yml/properties:由父ApplicationContext加载,属性不能被覆盖,用于SpringCloud Config、Nacos
application.yml/properties:由ApplicatonContext加载

同目录先读取properties后yml
同属性默认使用第1个读取的

config/application.properties|yml(项目根目录中config目录下)
application.properties|yml
resources/config/application.properties|yml(项目resources目录中config目录下)
resources/application.properties|yml

Spring.profiles.active=dev => application-dev.properties

java -Dfile.encoding=utf-8 -Xbootclasspath/a:/config/ -jar a.jar外置配置文件优先
/config/根目录,config/相对当前目录
指定编码

Spring AOP?原理?场景?通知类型?

Aspect-Oriented Programming面向切面编程:通过动态代理将通知织入目标对象,把相同业务逻辑中抽取到一个模块中,提高代码的重用性

动态代理:基于接口,目标对象必须实现接口
cglib代理:基于继承。目标对象不被final修饰。通过ASM读取目标类的字节码并修改生成

增强方法,权限认证、日志、事务、参数校验

Joinpoint连接点:目标对象中被拦截的方法
Poincut切入点:目标对象中已经增强的方法。给满足规则(使用 AspectJ pointcut expression language)的匹配joinpoint添加Advice@PointCut:在配置类上用@EnableAspectJAutoProxy注解开启AspectJ代理
Advice通知/增强:连接点要执行的增强代码
aspect切面:切入点+通知 使用@Aspect的类
前置通知Before advice:在JoinPoint之前执行的通知,但不能阻止连接点前的执行。ApplicationContext中在< aop:aspect >嵌套< aop:before >标签声明@Before
后置通知After advice:~之后(不论是正常返回还是异常退出)。 ~< aop:after > @After
返回后通知After return advice:~正常完成后,不包括抛出异常的情况。~< after-returning >
抛出异常后通知After throwing advice:~抛出异常退出时执行的通知。~< aop:after-throwing >
环绕通知Around advice:之前与之后都执行的 advice。~< aop:around > @Around
Target目标对象:被代理的对象
Weaving织入:将aspect和其他对象连接起来创建adviced object的过程
Proxy代理:将通知织入到目标对象之后,形成代理对象

Spring事务配置方式?隔离级别?传播行为作用和实现?原理?失效情况?√集成数据持久层框架MyBatis原理?为什么Spring事务不能切换数据源?

声明式事务:使用@Transactional或XML。@Transactional可用在接口、接口方法、类、类方法上。方法级别注解覆盖类级别的注解。但无法用到代码块级别
属性:propagation事务传播行为,isolation事务隔离级别、rollbackFor事务回滚的异常类数组、timeout事务超时回滚、readonly读写或只读事务

编程式事务:通过TransactionTemplate和PlatformTransactionManager。灵活性高,维护困难

demo
public class AccountService {
  private TransactionTemplate transactionTemplate;

  public void setTransactionTemplate(TransactionTemplate transactionTemplate) {
    this.transactionTemplate = transactionTemplate;
  }

  public void transfer(final String out, final String in, final Double money) {
    transactionTemplate.execute(new TransactionCallbackWithoutResult() {
        @Override
        protected void doInTransactionWithoutResult(TransactionStatus status) {
            // 转出
            accountDao.outMoney(out, money);
            // 转入
            accountDao.inMoney(in, money);
        }
    });
  }
}

TransactionDefinition接口定义隔离级别的常量
ISOLATION_DEFAULT/READ_UNCOMMITTED/READ_COMMITTED/REPEATABLE_READ/SERIALIZABLE:数据库默认的隔离级别/读未提交/读已提交/可重复读/串行化

传播行为propagation:一个事务方法中调用其他类事务方法时,事务的范围和属性等。
支持当前事务
PROPAGATION_REQUIRED/SUPPORTS/MANDATORY如果当前存在事务则使用。没有则新建(默认)/不使用事务/抛出异常
不支持当前事务的情况
PROPAGATION_REQUIRES_NEW创建新事务,如果有事务存在,挂起当前事务,
PROPAGATION_NOT_SUPPORTED以非事务方式运行,如果有事务存在,挂起当前事务
PROPAGATION_NEVER以非事务方式运行,如果有事务存在,抛出异常
PROPAGATION_NESTED如果当前事务存在则新建事务做内嵌事务,没有则等价PROPAGATION_REQUIRED,外部的事务提交和回滚会使内部事务的提交和回滚

用ThreadLocal实现,调用其他线程的事务方法时事务传播会失效

通过AOP和动态代理实现
Bean初始化时会遍历所有的BeanPostProcessor实现类并执行postProcessAfterInitialization方法。遍历容器中所有的切面,查找与当前Bean匹配的切面并获取@Transactional注解及其属性值。
然后创建代理对象,如果目标类是接口,则使用JDK动态代理,否则使用Cglib。
当通过代理对象调用Bean方法时触发AOP增强拦截器接口MethodInterceptor的实现类TransactionInterceptor。调用DynamicAdvisedInterceptor(CglibAopProxy的内部类)的intercept方法或JdkDynamicAopProxy的invoke方法,调用AbstractFallbackTransactionAttributeSource的computeTransactionAttribute方法,获取Transactional注解的事务配置信息。再通过调用父类TransactionAspectSupport的invokeWithinTransaction方法处理事务

protectedTransactionAttributecomputeTransactionAttribute(Methodmethod,Class<?>targetClass){
  if(allowPublicMethodsOnly()&&!Modifier.isPublic(method.getModifiers())){
    return null;
  }
}

@Transactional
public方法
propagation要用支持事务NOT_SUPPORTED、SUPPORTS、NEVER
rollbackFor默认抛出未检查unchecked异常(继承自RuntimeException的异常)或者Error才回滚事务,其他异常不会触发回滚事务
调用其他方法时必须是不同类,AOP才生成代理对象管理事务

实现org.Springframework.transaction.PlatformTransactionManager接口
HibernateTransactionManager集成Hibernate5
DataSourceTransactionManager集成JDBC、MyBatis

public interface PlatformTransactionManager {
  //根据事务定义TransactionDefinition获得TransactionStatus。 
  //为什么不创建事务呢?因为如果已经有事务则不会再创建,跟当前线程进行绑定。
  //为什么返回TransactionStatus对象?因为TransactionStatus中包含事务属性等信息,例如是否只读、是否为新创建的事务等
  TransactionStatus getTransaction(@Nullable TransactionDefinition definition) throws TransactionException;
  //为什么根据TransactionStatus情况进行提交?例如A方法调用B方法,在B方法结束后执行commit方法时不能提交事务,而是A方法结束后执行commit方法才提交事务
  void commit(TransactionStatus status) throws TransactionException;
  // 为什么根据 TransactionStatus 情况,进行回滚?原因同上
  void rollback(TransactionStatus status) throws TransactionException;
}

数据库连接和当前线程绑定
多个数据源会带来多事务一致性的问题

延迟加载?热部署?容器创建过程?Spring启动过程?

容器启动后不需要默认创建作用域为单例的Bean而是在获得该Bean时才创建。设置lazy-init="true"即可

Spring-boot-devtools、Spring Loaded实现原理,nio的WatchService监听文件夹变化,同时实现classLoader的findclass方法重新将class加载进内存

AbstractApplicationContext调用refresh()方法通过调用obtainFreshBeanFactory()方法创建Bean工厂AnnotatedBeanDefinitionReader扫描注解doScan方法和XmlBeanDefinitionReader的doLoadBeanDefinitions方法sax方式解析xml将其封装成document对象,使用BeanDefinitionReaderUtils.registerBeanDefinition并注册到BeanDefinitionRegistry缓存中
然后调用postProcessBeanFactory调用子类的BeanFactory的后置处理器,然后调用invokeBeanFactoryPostProcessors()执行后置处理器
第三注册BeanPostProcessors()到BeanFactory中
第四注册监听器
第五实例化所有的Bean放到singletonObjects单例池里面

容器启动阶段:加载并分析配置文件,装配到BeanDefinition,其他后处理
Bean实例化阶段:实例化对象,装配依赖, 生命周期回调,对象其他处理, 注册回调接口