概述
一.基础概念
1.Spring 模块
2.Spring 框架概述
Spring 框架概述
-
Spring 是轻量级的开源的 JavaEE 框架
-
Spring 可以解决企业应用开发的复杂性
-
Spring 有两个核心部分: IOC 和 AOP
-
IOC:控制反转,把创建对象过程交给 Spring 进行管理
-
Aop:面向切面,不修改源代码进行功能增强
-
为了降低 Java 开发的复杂性,Spring 采取了以下 4 种关键策略
- 基于 POJO 的轻量级和最小侵入性编程;
- 通过依赖注入和面向接口实现松耦合;
- 基于切面和惯例进行声明式编程;
- 通过切面和模板减少样板式代码。
3.Spring 特点
- 方便解耦,简化开发。
- Aop 编程支持。
- 方便程序测试。
- 方便和其他框架进行整合。
- 方便进行事务操作。
- 降低 API 开发难度。
4.常见依赖包
5.spring 中设计模式
- 简单工厂模式 BeanFactory
- 工厂方法模式 FactoryBean
- 单例模式
- 适配器模式
- 包装器:一种是类名中含有 Wrapper,另一种是类名中含有 Decorator
- 代理模式
- 观察者模式:当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新
- 策略模式
- 模版方法 JdbcTemplate
- 发布订阅模式
Spring 中 Observer 模式常用的地方是 listener 的实现。如 ApplicationListener
- 工厂模式:BeanFactory 就是简单工厂模式的体现,用来创建对象的实例;
- 单例模式:Bean 默认为单例模式。
- 代理模式:Spring 的 AOP 功能用到了 JDK 的动态代理和 CGLIB 字节码生成技术;
- 模板方法:用来解决代码重复的问题。比如. RestTemplate, JmsTemplate, JpaTemplate。
- 观察者模式:定义对象键一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都会得到通知被制动更新,如 Spring 中 listener 的实现–ApplicationListener。
6.Spring5 的新特性?
- 整合日志 Log4j-2
- @Nullable 注解
- 函数式注册对象
- 整合 junit5
- webFlux
7.Spring6 新特性?
SpringBoot3 和 Spring6 的最低依赖就是 JDK17,为什么是 JDK17 呢?
主要是因为他是一个 Oracle 官宣可以免费商用的 LTS 版本,所谓 LTS,是 Long Term Support,也就是官方保证会长期支持的版本。
- AOT 支持
- 虚拟线程
- 提供基于
@HttpExchange
服务接口的 HTTP 接口客户端 - 对 RFC 7807 问题详细信息的支持
- Spring HTTP 客户端提供基于 Micrometer 的可观察性
二.bean 的生命周期
1.bean 创建过程
Spring 启动过程大致如下:
- 创建 beanFactory,加载配置文件
- 解析配置文件转化 beanDefination,获取到 bean 的所有属性、依赖及初始化用到的各类处理器等
- 刷新 beanFactory 容器,初始化所有单例 bean
- 注册所有的单例 bean 并返回可用的容器,一般为扩展的 applicationContext
对 Bean 的创建最为核心三个方法解释如下:
- createBeanInstance:例化,其实也就是调用对象的构造方法实例化对象
- populateBean:填充属性,这一步主要是对 bean 的依赖属性进行注入(@Autowired)
- initializeBean:回到一些形如 initMethod、InitializingBean 等方法
2.Bean 的创建的生命周期
- UserService.class–>
- 无参的构造方法–>对象–>依赖注入—>
- 初始化前—>初始化—>初始化后(AOP) —>代理对象–>
- 放入 Map(单例池)—>
- Bean 对象
bean 生命周期有七步 (正常生命周期为五步,而配置后置处理器后为七步)
(1)通过构造器创建 bean 实例(无参数构造)
(2)为 bean 的属性设置值和对其他 bean 引用(调用 set 方法)
(3)把 bean 实例传递 bean 后置处理器的方法 postProcessBeforeInitialization
(4)调用 bean 的初始化的方法(需要进行配置初始化的方法)
(5)把 bean 实例传递 bean 后置处理器的方法 postProcessAfterInitialization
(6)bean 可以使用了(对象获取到了)
(7)当容器关闭时候,调用 bean 的销毁的方法(需要进行配置销毁的方法)
3.DefaultListableBeanFactory
DefaultListableBeanFactory 是 Spring 注册加载 bean 的默认实现,它是整个 bean 加载的核心部分。
XmlBeanFactory 与它的不同点就是 XmlBeanFactory 使用了自定义的 XML 读取器 XmlBeanDefinitionReader,实现了自己的 BeanDefinitionReader 读取。 XmlBeanFactory 加载 bean 的关键就在于 XmlBeanDefinitionReader。
XmlBeanFactory 继承了 DefaultListableBeanFactory
beanDefinitionMap 是 DefaultListableBeanFactory 的成员
DefaultListableBeanFactory 作为 BeanFactory 默认是维护这一张 beanDefinition 的表。
/** Map of bean definition objects, keyed by bean name. */
private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>(256);
4.doGetBean
AbstractBeanFactory#doGetBean
doGetBean 方法被 getBean 方法调用
该方法刚开始调用 getSingleton(beanName) 方法,该方法内使用三级缓存获取对象,由于该开始并没有对象被创建,所以开始时调用 getSingleton(beanName) 方法返回的值为空
/** Cache of singleton objects: bean name to bean instance. */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
/** Cache of singleton factories: bean name to ObjectFactory. */
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
/** Cache of early singleton objects: bean name to bean instance. */
private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);
5.getSingleton
DefaultSingletonBeanRegistry#getSingleton(beanName, BeanFactory<?>)
该方法在 doGetBean 方法中被调用(开始 getSingleton(beanName) 返回的值为空,会走到这里)
该方法传入两个参数一个为 当前的 beanName 令一个参数时 beanFactory,在里面会调用传入 beanFactory 的 getObject 方法返回该 beanName 的对象
最后将创建好的对象通过 addSingleton(beanName, singletonObject);方法将 bean 放入到一级缓存中。
//通过名称返回一个原生的单例对象,查找已经存在的实例化单例,也同时允许一个提早引用,只向一个正在创建的单实例对象 (解决循环引用)
@Nullable
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
// Quick check for existing instance without full singleton lock
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference) {
synchronized (this.singletonObjects) {
//一致性创建一个可提早引用的对象,通过锁
// Consistent creation of early reference within full singleton lock
//1级缓存,singletonObjects (单例对象集)
singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
//2级缓存,earlySingletonObjects (早产单例对象集)
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null) {
//3级缓存,单例对象工厂集
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
//单例对象工厂集,得到对象,放到二级缓存,清空三级缓存
singletonObject = singletonFactory.getObject();
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
}
}
return singletonObject;
}
protected void addSingleton(String beanName, Object singletonObject) {
synchronized (this.singletonObjects) {
this.singletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
this.earlySingletonObjects.remove(beanName);
this.registeredSingletons.add(beanName);
}
}
6.doCreateBean
AbstractAutowireCapableBeanFactory#doCreateBean(beanName, mbdToUse, args)
该方法被 createBean 方法调用
方法一开始调用 createBeanInstance(beanName, mbd, args)方法,通过反射创建原始对象,并用 instanceWrapper 包装
protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) throws BeanCreationException {
BeanWrapper instanceWrapper = null;
if (mbd.isSingleton()) {
instanceWrapper = (BeanWrapper)this.factoryBeanInstanceCache.remove(beanName);
}
if (instanceWrapper == null) {
instanceWrapper = this.createBeanInstance(beanName, mbd, args);
}
Object bean = instanceWrapper.getWrappedInstance();
Class<?> beanType = instanceWrapper.getWrappedClass();
if (beanType != NullBean.class) {
mbd.resolvedTargetType = beanType;
}
synchronized(mbd.postProcessingLock) {
if (!mbd.postProcessed) {
try {
this.applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName);
} catch (Throwable var17) {
throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Post-processing of merged bean definition failed", var17);
}
mbd.postProcessed = true;
}
}
boolean earlySingletonExposure = mbd.isSingleton() && this.allowCircularReferences && this.isSingletonCurrentlyInCreation(beanName);
if (earlySingletonExposure) {
if (this.logger.isTraceEnabled()) {
this.logger.trace("Eagerly caching bean '" + beanName + "' to allow for resolving potential circular references");
}
this.addSingletonFactory(beanName, () -> {
return this.getEarlyBeanReference(beanName, mbd, bean);
});
}
Object exposedObject = bean;
try {
this.populateBean(beanName, mbd, instanceWrapper);
exposedObject = this.initializeBean(beanName, exposedObject, mbd);
} catch (Throwable var18) {
if (var18 instanceof BeanCreationException && beanName.equals(((BeanCreationException)var18).getBeanName())) {
throw (BeanCreationException)var18;
}
throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Initialization of bean failed", var18);
}
if (earlySingletonExposure) {
Object earlySingletonReference = this.getSingleton(beanName, false);
if (earlySingletonReference != null) {
if (exposedObject == bean) {
exposedObject = earlySingletonReference;
} else if (!this.allowRawInjectionDespiteWrapping && this.hasDependentBean(beanName)) {
String[] dependentBeans = this.getDependentBeans(beanName);
Set<String> actualDependentBeans = new LinkedHashSet(dependentBeans.length);
String[] var12 = dependentBeans;
int var13 = dependentBeans.length;
for(int var14 = 0; var14 < var13; ++var14) {
String dependentBean = var12[var14];
if (!this.removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
actualDependentBeans.add(dependentBean);
}
}
if (!actualDependentBeans.isEmpty()) {
throw new BeanCurrentlyInCreationException(beanName, "Bean with name '" + beanName + "' has been injected into other beans [" + StringUtils.collectionToCommaDelimitedString(actualDependentBeans) + "] in its raw version as part of a circular reference, but has eventually been wrapped. This means that said other beans do not use the final version of the bean. This is often the result of over-eager type matching - consider using 'getBeanNamesOfType' with the 'allowEagerInit' flag turned off, for example.");
}
}
}
}
try {
this.registerDisposableBeanIfNecessary(beanName, bean, mbd);
return exposedObject;
} catch (BeanDefinitionValidationException var16) {
throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Invalid destruction signature", var16);
}
}
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
Assert.notNull(singletonFactory, "Singleton factory must not be null");
synchronized (this.singletonObjects) {
if (!this.singletonObjects.containsKey(beanName)) {
this.singletonFactories.put(beanName, singletonFactory);
this.earlySingletonObjects.remove(beanName);
this.registeredSingletons.add(beanName);
}
}
}
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
Object exposedObject = bean;
if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
for (BeanPostProcessor bp : getBeanPostProcessors()) {
if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
}
}
}
return exposedObject;
}
7.BeanFactory 整体类图
8.什么是 BeanDefinition?
BeanDefinition 是定义 Bean 的配置元信息接口,包含:
- Bean 的类名
- 设置父 bean 名称、是否为 primary、
- Bean 行为配置信息,作用域、自动绑定模式、生命周期回调、延迟加载、初始方法、销毁方法等
- Bean 之间的依赖设置,dependencies
- 构造参数、属性设置
9.DefaultSingletonBeanRegistry
- 类 DefaultSingletonBeanRegistry 实现了接口 SingletonBeanRegistry,是通用注册表的基础实现类:
- ①、它允许注册 bean 对象实例(这些对象实例都是单例模式)。
- ②、已经注册的 bean 对象实例是注册表的所有调用者共享的(所以特别需要注意线程安全)。
- ③、可以通过 bean 名称获得目标 bean 对象实例。
- 类 DefaultSingletonBeanRegistry 还支持类 DisposableBean 对象实例的注册(在关闭注册表时销毁),该实例可能与已注册的 bean 对象实例相对应。bean 对象实例之间的依赖关系也可以注册,以此控制销毁 bean 对象实例的顺序。
- 类 DefaultSingletonBeanRegistry 主要作为实现接口 BeanFactory 的基类,它分解了单例 bean 对象实例的常见管理(比如注册,获取等等,但不包含创建)。另一个接口 ConfigurableBeanFactory 扩展了接口 SingletonBeanRegistry。
- 类 DefaultSingletonBeanRegistry 与类 AbstractBeanFactory 和类 DefaultListableBeanFactory(继承自类 DefaultSingletonBeanRegistry)相比,既不涉及 bean 对象实例概念的定义也不涉及 bean 对象实例的创建过程,还可以用作委托的嵌套助手(涉及 Java 设计模式)。
10.说说 MetadataReader
spring 通过读取 resource 中的 class 文件的字节码,生成了一个叫 MetadataReader 的对象。这个 MetadaReader 并不是 class 对象,但是可以读取到 class 上所有的元数据信息。这是因为 spring 使用了 ASM 技术,用流的方式读取了 class 文件。然后就是创建 ScannedGenericBeanDefinition 并返回了.
11.Aware 回调
Aware 接口从字面上翻译过来是感知捕获的含义。单纯的 bean(未实现 Aware 系列接口)是没有知觉的;实现了 Aware 系列接口的 bean 可以访问 Spring 容器。这些 Aware 系列接口增强了 Spring bean 的功能,但是也会造成对 Spring 框架的绑定,增大了与 Spring 框架的耦合度。(Aware 是“意识到的,察觉到的”的意思,实现了 Aware 系列接口表明:可以意识到、可以察觉到)
对于 bean 中非依赖注入的属性,也可以在创建 bean 的时候也可以去设置值,spring 中提供了 Aware 回调接口,用于到 bean 生命周期中依赖注入之后设置其他属性值。Aware 系列接口,主要用于辅助 Spring bean 访问 Spring 容器
回调顺序
- BeanNameAware
- BeanClassLoaderAware
- BeanFactoryAware
- EnvironmentAware
- EmbeddedValueResolverAware
- ResourceLoaderAware
- ApplicationEventPublisherAware
- MessageSourceAware
- ApplicationContextAware
12.初始化过程
spring 初始化过程也叫 ioc 容器初始化过程、bean 生命周期。
public static void main(String[] args) {
// 创建 ioc 容器
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
// 获取 bean
User user = (User) context.getBean("user");
user.test();
}
public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent) throws BeansException {
super(parent);
// 获取xml文件并存储到String[] configLocations中
this.setConfigLocations(configLocations);
if (refresh) {
// 重点方法,功能较复杂,继续往下看
this.refresh();
}
}
13.详解 refresh 方法
public void refresh() throws BeansException, IllegalStateException {
synchronized(this.startupShutdownMonitor) {
//判断系统要求的属性是否存在,不存在抛出异常
this.prepareRefresh();
/**
创建DefaultListableBeanFactory对象,可以理解为就是ApplicationContext
1.实例化了一个DefaultListableBeanFactory对象,即创建bean工厂applicationContext.
2.实例化了一个XmlBeanDefinitionReader对象,解析xml配置文件。将xml中的bean信息解析封装为BeanDefinition对象。
3.通过一个工具类将解析好的BeanDefinition对象注册到BeanFactory中,以key-value的形式保存在BeanFactory中,其中key为beanName.
*/
ConfigurableListableBeanFactory beanFactory = this.obtainFreshBeanFactory();
/**
该方法作用,主要给beanFactory设置相关属性:
1、类加载器、类表达式解析器、属性编辑注册器
2、添加bean后处理器等
3、资源注册
4、注册默认的environment beans
*/
this.prepareBeanFactory(beanFactory);
try {
this.postProcessBeanFactory(beanFactory);
//查询当前在BeanFactory中是否有指定类型的Bean信息。如果有则获取Bean实体并执行相关方法
this.invokeBeanFactoryPostProcessors(beanFactory);
//查询当前在BeanFactory中是否有指定类型的Bean信息。如果有则获取Bean实体注册到beanFactory中
this.registerBeanPostProcessors(beanFactory);
this.initMessageSource();
//判断bean工厂是否包含应用事件广播器,没有的话,创建一个,然后,把它放入bean工厂中。
this.initApplicationEventMulticaster();
this.onRefresh();
this.registerListeners();
this.finishBeanFactoryInitialization(beanFactory);
this.finishRefresh();
} catch (BeansException var9) {
if (this.logger.isWarnEnabled()) {
this.logger.warn("Exception encountered during context initialization - cancelling refresh attempt: " + var9);
}
this.destroyBeans();
this.cancelRefresh(var9);
throw var9;
} finally {
this.resetCommonCaches();
}
}
}
14.推断构造方法
先 bytype 再 byname
三.IOC
1.什么是 IOC
控制反转,把对象创建和对象之间的调用过程,交给 Spring 进行管理。
依赖控制反转:对象的依赖关系交由 spring 来管理
ioc:是实现这个模式的载体
2.IOC 作用?
通过 spring 提供的 ioc 容器,可以将对象间的依赖关系交给 spring 管理,避免硬编码造成的程序过渡耦合
- 为了耦合度降低
- 简化开发
3.IOC 原理
底层实现
- XML 解析
- 工厂模式
- 反射
IOC 思想基于 IOC 容器完成,IOC 容器底层就是对象工厂。
Spring 提供 IOC 容器实现两种方式: (两个接口) 。
- BeanFactory: IOC 容器基本实现,是 Spring 内部的使用接口,不提供开发人员进行使用。
- ApplicationContext: BeanFactory 接口的子接口,提供更多更强大的功能,一般由开发人员进行使用。
总结:IOC 的原理其实包含两部分
1、Spring 通过 Bean 工厂来创建 Bean 对象(包含 id,class,和 property 三个属性)
2、Spring 通过反射机制来获取 class 对象的实例,利用实例来操作该 class 对象的相关属性和方法。
4.对象创建的三种方式
- new
- 工厂模式
- ioc 模式 xml 解析 工厂模式 反射
实现 FactoryBean,重写 getObject 方法,得到需要的 bean,反射
public interface FactoryBean<T> {
String OBJECT_TYPE_ATTRIBUTE = "factoryBeanObjectType";
@Nullable
T getObject() throws Exception;
@Nullable
Class<?> getObjectType();
default boolean isSingleton() {
return true;
}
}
5.为什么不用工厂模式用 ioc
本质上来说,IOC 是通过反射机制实现的,当我们需求变动的时,工厂模式会需要进行相应的变化。但是 Ioc 的反射机制允许我们不用重新编译代码,因为它的对象都是动态生成的。
工厂模式属于创建类型模式,本质上就是灵活创建实现了某个接口的不同对象实例,即可以根据需求灵活创建某一类对象。
springioc 是为了解决众多对象之间的相互依赖关系,利用反射与动态代理技术通过配置文件或者注解的方式来实例化对象。动态代理通常有 jdk 自带的 InvicationHandler,和第三方代码生成工具 cglib。
6.Spring ioc 创建过程?
1.利用反射, 利用该类的构造方法来实例化得到一个对象(但是如果一个类中有多个构造方法, Spring 则会进行选择,这个叫做推断构造方法,[有参构造和无参构造, 有参构造需要导入的属性应该为 Spring 的 Bean])
2.得到一个对象后,Spring 会判断该对象中是否存在被@Autowired 注解了的属性,把这些属性找出来并由 Spring 进行赋值(依赖注入)
3.依赖注入后,Spring 会判断该对象是否实现了 BeanNameAware 接口、 BeanClassLoaderAware 接口、BeanFactoryAware 接口,如果实现了,就表示当前 对象必须实现该接口中所定义的 setBeanName()、setBeanClassLoader()、 setBeanFactory()方法,那 Spring 就会调用这些方法并传入相应的参数
4.Aware 回调后,Spring 会判断该对象中是否存在某个方法被**@PostConstruct**注解 了,如果存在,Spring 会调用当前对象的此方法(初始化前)
5.紧接着,Spring 会判断该对象是否实现了 InitializingBean 接口,如果实现了,就表示当前对象必须实现该接口中的 afterPropertiesSet()方法,那 Spring 就会调用当
前对象中的 afterPropertiesSet()方法(初始化)
6.最后,Spring 会判断当前对象需不需要进行 AOP,如果不需要那么 Bean 就创建完 了,如果需要进行 AOP,则会进行动态代理并生成一个代理对象做为 Bean(初始化 后)
7.扫描的实现过程
在 spring 中,spring 使用ClassPathBeanDefinitionScanner
来实现包扫描,
org.springframework.context.annotation.ClassPathBeanDefinitionScanner#scan
就是扫描方法的入口,实际的扫描逻辑是写在 doScan 方法中的
1.xml 方式
<!--配置扫描com.example.spring.beans下的所有bean-->
<context:component-scan base-package="com.example.spring.beans"/>
2.注解
@Configuration
@ComponentScan("com.example.spring.beans4")
public class ComponentScanConfig {
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ETJonn5M-1670764773333)(http://120.79.36.53/blogImg/2022/12/spring%E6%89%AB%E6%8F%8F%E8%BF%87%E7%A8%8B.png)]
8.@ComponentScan
@ComponentScan 主要就是定义扫描的路径从中找出标识了需要装配的类自动装配到 spring 的 bean 容器中,都有用过@Controller,@Service,@Repository 注解,查看其源码你会发现,他们中有一个共同的注解@Component,没错@ComponentScan 注解默认就会装配标识了@Controller,@Service,@Repository,@Component 注解的类到 spring 容器中
@ComponentScan 的常用方式如下
- 自定扫描路径下边带有@Controller,@Service,@Repository,@Component 注解加入 spring 容器
- 通过 includeFilters 加入扫描路径下没有以上注解的类加入 spring 容器
- 通过 excludeFilters 过滤出不用加入 spring 容器的类
- 自定义增加了@Component 注解的注解方式
9.doScan 的具体流程
protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
//首先,basePackages不能是空的
Assert.notEmpty(basePackages, "At least one base package must be specified");
Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();
//遍历所有要扫描的包
for (String basePackage : basePackages) {
//获取到待选的BeanDefinition
Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
//遍历待选的BeanDefinition
for (BeanDefinition candidate : candidates) {
//设置Scope
ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
candidate.setScope(scopeMetadata.getScopeName());
//生成beanName
String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
if (candidate instanceof AbstractBeanDefinition) {
//默认值处理
postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
}
if (candidate instanceof AnnotatedBeanDefinition) {
//@Lazy @Primary @DependsOn @Role @Description这些注解支持
AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
}
//bean冲突校验
if (checkCandidate(beanName, candidate)) {
BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
definitionHolder =
AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
beanDefinitions.add(definitionHolder);
//注册bean
registerBeanDefinition(definitionHolder, this.registry);
}
}
}
return beanDefinitions;
}
10.Spring 是如何加载配置文件到应用程序的?
XmlBeanFactory 读取文件,改类已经为@Deprecated 废弃了,XmlBeanFactory 也是一个容器,目前改为 XmlBeanDefinitionReader 进行读取.
11.XmlBeanDefinitionReader
- 首先 XML 文件通过 ResourceLoader 加载器加载为 Resource 对象
- XmlBeanDefinitionReader 将 Resource 对象解析为 Document 对象
- DefaultBeanDefinitionDocumentReader 将 Document 对象解析为 BeanDefinition 对象
- 将解析出来的 BeanDefinition 对象储存在 DefaultListableBeanFactory 工厂中
12.spring 容器有哪些?
Spring 中的两种容器类型:
分别是:BeanFactory 和 ApplicationContext
典型的上下文有如下几个
- ClassPathXmlApplicationContext
- FileSystemXmlApplicationContext
- XmlWebApplicationContext
- GroovyWebApplicationContext
- AnnocationConfigWebApplicationContext
共同点:
它们都是 Spring 中的两个接口,用来获取 Spring 容器中的 bean
不同点:
1.bean 的加载方式不同
- 前者 BeeanFactory 是使用的懒加载的方式,只有在调用 getBean()时才会进行实例化。
- 后者 ApplicationContext 是使用预加载的方式,即在应用启动后就实例化所有的 bean。常用的 API 比如 XmlBeanFactory
2.特性不同:
BeanFactory 接口只提供了 IOC/DI 的支持,常用的 API 是 XMLBeanFactory
- ApplicationContext 是整个 Spring 应用中的中央接口,翻看源码就会知道,它继承了 BeanFactory 接口,具备 BeanFactory 的所有特性,还有一些其他特性比如:AOP 的功能,事件发布/响应(ApplicationEvent)等。常用的 API 比如 ClassPathXmlApplication
3.应用场景不同:
- BeanFactory 适合系统资源(内存)较少的环境中使用延迟实例化,比如运行在移动应用中
- ApplicationContext 适合企业级的大型 web 应用,将耗时的内容在系统启动的时候就完成
13.ApplicationContext
为什么建议使用 ApplicationContext,而不是直接使用 BeanFactory?
因为在使用 BeanFactory 时,注解的处理、AOP 代理、BeanPostProcessor 扩展点等容器特性功能是不会自动开启的,需要手动调用方法来实现。而使用 ApplicationContext 时,这些容器特性会自动开启。
- FileSystemXmlApplicationContext:对应系统盘路径
- ClassPathXmlApplicationContext:对应类路径
目前,我们基本很少直接使用上面这种方式来用 Spring,而是使用 Spring MVC,或者 Spring Boot,但是它们都是基于上面这种方式的,都需要在内部去创建一个 ApplicationContext 的,只不过:
- Spring MVC 创建的是 XmlWebApplicationContext,和 ClassPathXmlApplicationContext 类似,都是基于 XML 配置的
- Spring Boot 创建的是 AnnotationConfigApplicationContext
14.BeanDefinition
RootBeanDefinition 本质上是 Spring BeanFactory 运行时统一的 BeanDefinition 视图。
GenericBeanDefinition 是以编程方式注册 BeanDefinition 的首选类。
ChildBeanDefinition 它是一种可以继承 parent 配置的 BeanDefinition。
对 BeanDefinition 进行 merge 操作时,会将 child 的属性与 parent 的属性进行合并,当有相同属性时,以 child 的为准。而类似 propertyValues、constructor args 这种集合类的参数属性的话,就会取并集。所以,上面 ChildBeanDefinition 例举的例子中,ChildBeanDefinition 中并没有 name 这个属性,而通过子容器获取到的 FooService bean 中的 name 却有了值,并且值是 parent BeanDefinition 中的属性值。这也证明了 child 与 parent 的 BeanDefinition 属性会进行合并。
15.GenericApplicationContext 解析
GenericApplicationContext 通用应用程序上下文实现,该实现内部有一个 DefaultListableBeanFactory 实例。可以采用混合方式处理 bean 的定义,而不是采用特定的 bean 定义方式来创建 bean。
GenericApplicationContext 基本就是对 DefaultListableBeanFactory 做了个简易的封装,几乎所有方法都是使用了 DefaultListableBeanFactory 的方法去实现。
GenericApplicationContext 更多是作为一个通用的上下文(通用的 IOC 容器)而存在,BeanFactory 本质上也就是一个 IOC 容器,一个用来生产和获取 beans 的工厂。
几乎可以把 GenericApplicationContext 等价于 DefaultListableBeanFactory+上下文刷新等其他功能
public class GenericApplicationContext extends AbstractApplicationContext implements BeanDefinitionRegistry {
//内部的beanFactory
private final DefaultListableBeanFactory beanFactory;
@Nullable
private ResourceLoader resourceLoader;
private boolean customClassLoader = false;
private final AtomicBoolean refreshed = new AtomicBoolean();
/**
* Create a new GenericApplicationContext.
* @see #registerBeanDefinition
* @see #refresh
*/
public GenericApplicationContext() {
this.beanFactory = new DefaultListableBeanFactory();
}
//......
}
16.Spring 中的自动装配
在 spring 中,对象无需自己查找或创建与其关联的其他对象,由容器负责把需要相互协作的对象引用赋予各个对象,使用 autowire 来配置自动装载模式。
在 Spring 框架 xml 配置中共有 5 种自动装配:
- no:默认的方式是不进行自动装配的,通过手工设置 ref 属性来进行装配 bean。
- byName:通过 bean 的名称进行自动装配,如果一个 bean 的 property 与另一 bean 的 name 相同,就进行自动装配。
- byType:通过参数的数据类型进行自动装配。
- constructor:利用构造函数进行装配,并且构造函数的参数通过 byType 进行装配。
- autodetect:自动探测,如果有构造方法,通过 construct 的方式自动装配,否则使用 byType 的方式自动装配。
17.beanName 生成策略
- DefaultBeanNameGenerator
- AnnotationBeanNameGenerator
- FullyQualifiedAnnotationBeanNameGenerator
AnnotationBeanNameGenerator
是 Spring 的默认生成策略,buildDefaultBeanName
方法是生成名称的实现
String shortClassName = ClassUtils.getShortName(definition.getBeanClassName());
return Introspector.decapitalize(shortClassName);
这个默认的生成策略其实就是取首字母小写后的类名称,作为 Bean 名称。
这里 definition.getBeanClassName()
是获取全限定名称的,ClassUtils.getShortName()
是获取类名的,下面的 Introspector.decapitalize()
实际上就是把首字母变小写的。
这里要设置为全限定名称,我们可以新写一个类,例如 SherlockyAnnotationBeanNameGenerator
,继承 AnnotationBeanNameGenerator
之后重写buildDefaultBeanName
方法,返回 definition.getBeanClassName()
,这样我们这个生成策略就写好了。
public String generateBeanName(BeanDefinition definition, BeanDefinitionRegistry registry) {
if (definition instanceof AnnotatedBeanDefinition) {
// @Component、@Service 如果指定了 value 的话,那么 bean name 就是指定的值
String beanName = determineBeanNameFromAnnotation((AnnotatedBeanDefinition) definition);
if (StringUtils.hasText(beanName)) {
// Explicit bean name found.
return beanName;
}
}
// @Component、@Service 没有指定 value 的话,就使用默认的生成规则来生成 bean name
// Fallback: generate a unique default bean name.
return buildDefaultBeanName(definition, registry);
}
/**
* 默认生成的 bean name 是类名小写:例如“mypackage.MyJdbcDao” -> “myJdbcDao”。
* 请注意:
* 1. 如果是内部类的话,因此将会是 “outerClassName.InnerClassName” 形式的名称
* 2. 类名如果是连续2个首字母大写的话,bean name 就是类名:例如 “mypackage.MYJdbcDao” -> “MYJdbcDao”
*/
protected String buildDefaultBeanName(BeanDefinition definition) {
String beanClassName = definition.getBeanClassName();
Assert.state(beanClassName != null, "No bean class name set");
String shortClassName = ClassUtils.getShortName(beanClassName);
// 根据类名来生成 bean name
return Introspector.decapitalize(shortClassName);
}
@Service、@Componet 定义的 bean name 的生成规则如下:
- 优先取注解中的 value 指定的名字做为 bean name。
- 如果注解中没有指定的话,默认情况下是类名小写,例如: “mypackage.MyJdbcDao” -> “myJdbcDao”
- 注意有两种特殊的情况:
- 如果 bean 是内部类的话,因此将会是 “outerClassName.InnerClassName” 形式的名称
- 如果类名是连续 2 个首字母大写的话,bean name 就是类名,例如:“mypackage.MYJdbcDao” -> “MYJdbcDao”
@Bean 定义的 bean name 的生成规则是:
- 优先取注解中 name 指定的名字做为 bean name。
- 如果注解中没有指定的话,就取 methodName 做为 bean name。
18.属性注入具体实现
- 接口注入
- setter 注入
- 构造器注入
四.AOP
1.什么是 AOP?
AOP(Aspect Oriented Programming):面向切面编程,一种编程范式,隶属于软件工程范畴,指导开发者如何组织程序结构,AOP 弥补了 OOP 的不足,基于 OOP 基础之上进行横向开发。
- AOP 底层原理:动态代理,有接口(JDK 动态代理),没有接口(CGLIB 动态代理)。
- 术语:切入点、增强(通知)、切面。
- 基于 Aspect 实现 AOP 操作
2.AOP 作用?
-
提高代码的可重用性
-
业务代码编码更简洁
-
业务代码维护更高效
-
业务功能拓展更便捷
3.什么是动态代理
-
UserServiceProxy 对象–>UserService 代理对象—>UserService 代理对象.target=普通对象
-
UserService 代理对象.test();
-
多个增强类 用 order 排序
class UserServiceProxy extends UserService {
UserService target;
public void test(){
// 切面逻辑@Before
// target.test();
}
}
4.spring 的代理为何是动态代理?
静态代理是编译期间增强,通过修改字节码。
SpringAOP 是运行期间创建代理对象,用反射机制,JDK 和 CGLIB 都是动态代理
5.AOP 处理
6.AOP 操作与实现
Spring 框架一般都是基于 Aspect 实现 AOP 操作
什么是 Aspectj
- Aspect 不是 Spring 组成部分,独立 AOP 框架,一般把 Aspect 和 Spring 框架一起使用,进行 AOP 操作
- 基于 AspectJ 实现 AOP 操作
- 基于 xml 配置文件实现
- 基于注解方式实现(使用)
切入点表达式写法
pointcut="execution( *..*.*(..))
//切到业务层实现类下的所有方法
* cn.luis.service.impl.*.*(..)
7.aop 的常用话术
1.连接点
类里面哪些方法可以被增强,这些方法称为连接点
2.切入点
实际被真正增强的方法,称为切入点
3.通知(增强)
- 实际增强的逻辑部分称为通知(增强)
- 通知有多钟类型
- 前置通知
- 后置通知
- 环绕通知
- 异常通知
- 最终通知
8.newProxyInstance
newProxyInstance 方法有三个参数:
- loader: 用哪个类加载器去加载代理对象
- interfaces:动态代理类需要实现的接口
- h:动态代理方法在执行时,会调用 h 里面的 invoke 方法去执行
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
public class VehicalInvacationHandler implements InvocationHandler {
private final IVehical vehical;
public VehicalInvacationHandler(IVehical vehical){
this.vehical = vehical;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("---------before-------");
Object invoke = method.invoke(vehical, args);
System.out.println("---------after-------");
return invoke;
}
}
public class App {
public static void main(String[] args) {
IVehical car = new Car();
IVehical vehical = (IVehical)Proxy.newProxyInstance(car.getClass().getClassLoader(), Car.class.getInterfaces(), new VehicalInvacationHandler(car));
vehical.run();
}
}
9.没有 AspectJ,如何实现 AOP?
当添加 @EnableAspectJAutoProxy 时,使用的是 AnnotationAwareAspectJAutoProxyCreator 来产生 AOP 代理类。
不添加 @EnableAspectJAutoProxy 时,使用的是 InfrastructureAdvisorAutoProxyCreator 来产生 AOP 代理类。
-
InfrastructureAdvisorAutoProxyCreator: 获取 application context 中的 advisor 来产生 AOP 代理类
-
AnnotationAwareAspectJAutoProxyCreator: 获取 application context 中的 advisor 和 aspectj 注解风格的 Aspect 来产生 AOP 代理类
当我们不依赖 aspectjweaver.jar 时,可以通过定义 Advisor 的方式来使用 Spring AOP 的功能!
总而言之:
- 不引入 aspectjweaver.jar 时,我们也可以正常的使用 @Transactional、@Async 等 AOP 的功能;
- 引入 aspectjweaver.jar 时,我们也可以使用,只是两种情况下创建 AOP 时使用的 AutoProxyCreator 不一样而已。
- Spring 框架提供了三种 AutoProxyCreator,只会有一个会生效,它按照引入 jar 的不同来做了一个简单的升级协议,来保证只选用其中的一个。这是一种可以参考的框架封装的思路。
10.joinPoint 方法
被代理对象 vs 代理对象
- getTarget()被代理对象,即连接点所在的目标对象(周杰伦),该方法返回被织入增强处理的目标对象
- getThis()代理对象(经纪人)
五.循环依赖
1.循环依赖 Bean 生命周期
**Spring 循环依赖,只支持 setter 方式的依赖注入,不支持构造函数的依赖注入。**只针对单例 bean.
-
总结
A 与 B 对象相互依赖 -
A 首先创建,并将 A 对象标记为正在创建中,然后将创建方法放到三级缓存中(factory)。创建过程中,在填充属性时发现需要 B,于是去创建 B(尝试从三级缓存中获取,但是获取不到),并将 B 也标记为正在创建中
-
B 创建的时候,在填充属性的时候发现需要 A,于是又去创建对象 A,尝试从三级缓存中获取 A,可以获取到,并将 A 放入二级缓存,于是 B 就成功创建了,并放入一级缓存,同时清空二三级缓存,同时返回 B
-
返回 B 以后,A 也成功赋值创建,就完成了 A 和 B 的创建。
2.三级缓存?
/** Cache of singleton objects: bean name to bean instance. */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
/** Cache of singleton factories: bean name to ObjectFactory. */
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
/** Cache of early singleton objects: bean name to bean instance. */
private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);
3.三级缓存的作用
-
一级缓存:
- 存放的已经经历了完整生命周期的 bean
- 该缓存是对外使用的,指的就是使用 Spring 框架的程序员。
-
二级缓存:
- 存放早期暴露的对象,bean 的生命周期还没有完全结束(属性还没填充完整的)
- 该缓存是对内使用的,指的就是 Spring 框架内部逻辑使用该缓存。
- 是一个载体,B 对象在三级缓存获取到创建 A 的方法,创建好 A 后,将 A 放入二级缓存
-
三级缓存:存放可以生成 bean 的工厂,是一个兰姆达函数
- 通过 ObjectFactory 对象来存储单例模式下提前暴露的 Bean 实例的引用(正在创建中)。
- 该缓存是对内使用的,指的就是 Spring 框架内部逻辑使用该缓存
- 此缓存是解决循环依赖最大的功臣
4.详解三级缓存
第三级缓存在添加时,是通过 addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean)); 添加到 singletonFactories 中的。
当 bean 被循环依赖时,第一级缓存 singletonObjects 中还没有完全创建好的 bean;此时,第二级缓存 earlySingletonObjects 中也没有 bean 的早期引用;
所以,这时只能通过第三级缓存 singletonFactories 来获取 bean 的早期引用。
第三级缓存对应的 ObjectFactory 的实现是通过 lambda 表达式: () -> getEarlyBeanReference(beanName, mbd, bean) 来实现的,具体代码如下:
// AbstractAutowireCapableBeanFactory#getEarlyBeanReference()
/**
* Obtain a reference for early access to the specified bean, typically for the purpose of resolving a circular reference.
* 获取指定 bean 的早期引用,通常用于解析循环引用。
*/
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
Object exposedObject = bean;
if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
for (SmartInstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().smartInstantiationAware) {
exposedObject = bp.getEarlyBeanReference(exposedObject, beanName);
}
}
return exposedObject;
}
可以看到,第三级缓存会将 bean 的原始引用通过 SmartInstantiationAwareBeanPostProcessor#getEarlyBeanReference()
来进行处理。
它只有一个实现,如下:
// AbstractAutoProxyCreator#getEarlyBeanReference()
public Object getEarlyBeanReference(Object bean, String beanName) {
Object cacheKey = getCacheKey(bean.getClass(), beanName);
this.earlyProxyReferences.put(cacheKey, bean);
// 如果有需要的话,对 bean 生成代理
return wrapIfNecessary(bean, beanName, cacheKey);
}
如果 bean 是需要被 AOP 增强的话,getEarlyBeanReference() 就会提前给 bean 的原始引用生成代理。
如果 bean 不需要被 AOP 增强的话,getEarlyBeanReference() 就不会做任何操作,直接返回原始引用。
所以,第三级缓存真正起作用的是针对 AOP 代理 bean 被循环引用的情况,这种 bean 需要提前通过第三级缓存生成 AOP 代理对象,然后放入二级缓存中。
三级缓存是为了解决 AOP 代理 bean 被循环依赖的场景。
5.哪些循环依赖解决不了?
1.prototype 类型的循环依赖
分析:
A 实例创建后,populateBean 时,会触发 B 的加载。
B 实例创建后,populateBean 时,会触发 A 的加载。由于 A 的 scope=prototype,从缓存中获取不到 A,要创建一个全新的 A。
这样,就会进入一个死循环。Spring 肯定是解决不了这种情况下的循环依赖的。所以,提前进行了 check,并抛出了异常。
解决方案:在需要循环注入的属性上添加 @Lazy
2.constructor 注入的循环依赖
分析:
A 实例在创建时(createBeanInstance),由于是构造注入,这时会触发 B 的加载。
B 实例在创建时(createBeanInstance),又会触发 A 的加载,此时,A 还没有添加到三级缓存中,所以就会创建一个全新的 A。
这样,就会进入一个死循环。Spring 是解决不了这种情况下的循环依赖的。所以,提前进行了 check,并抛出了异常。
解决:
在需要循环注入的属性上添加 @Lazy
例如:public A(@Lazy B b){...}
3.普通的 AOP 代理 Bean 的循环依赖–(默认是可以的)
描述:
A --> B --> A, 且 A,B 都是普通的 AOP Proxy 类型的 bean
普通的 AOP proxy 类型指:通过用户自定义的 @Aspect 切面生成的代理 bean,区别于 @Async 标记的类产生的 AOP 代理
Spring 默认解决了 普通的 AOP 代理 Bean 的循环依赖 问题,这里单独拿出来,是为了与 @Async 增强的代理 Bean 场景 进行对比。
分析:
通常情况下, AOP proxy 的创建是在 initializeBean 的时候,通过 BeanPostProcessor 处理的。
A 在 createBeanInstance 之后,添加到三级缓存。populateBean 时触发 B 的加载。
B 在 createBeanInstance 之后,添加到三级缓存。populateBean 时触发 A 的加载,这时,三级缓存中有 A,那么通过三级缓存 ObjectFactory#get() 可以获取到 bean 的早期引用。
// AbstractAutowireCapableBeanFactory#getEarlyBeanReference()
/**
* Obtain a reference for early access to the specified bean, typically for the purpose of resolving a circular reference.
* 获取指定 bean 的早期引用,通常用于解析循环引用。
*/
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
Object exposedObject = bean;
if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
for (SmartInstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().smartInstantiationAware) {
exposedObject = bp.getEarlyBeanReference(exposedObject, beanName);
}
}
return exposedObject;
}
普通的 AOP 代理都是通过 AbstractAutoProxyCreator 来生成代理类的,而 AbstractAutoProxyCreator 实现了 SmartInstantiationAwareBeanPostProcessor,所以,在通过三级缓存 getEarlyBeanReference() 的时候,就可以提前获取到最终暴露到 Spring 容器中的代理 bean 的早期引用。
public abstract class AbstractAutoProxyCreator extends ProxyProcessorSupport
implements SmartInstantiationAwareBeanPostProcessor, BeanFactoryAware {
......
}
4.@Async 增强的 Bean 的循环依赖
描述:
A --> B --> A, 且 A 是被 @Async 标记的类
@Service
public class A {
@Autowired
private B b;
@Async
public void m1(){
}
}
@Service
public class B {
@Autowired
private A a;
}
@Async 产生的代理和普通的 AOP 代理有什么区别了?
分析:
proxy 的创建是在 initializeBean 的时候,通过 BeanPostProcessor 处理的。
A 在 createBeanInstance 之后,添加到三级缓存。populateBean 时触发 B 的加载。
B 在 createBeanInstance 之后,添加到三级缓存。populateBean 时触发 A 的加载,这时,三级缓存中有 A,那么通过三级缓存 ObjectFactory#get() 可以获取到 bean 的早期引用。
普通的 AOP 代理都是通过 AbstractAutoProxyCreator 来生成代理类的,AbstractAutoProxyCreator 实现了 SmartInstantiationAwareBeanPostProcessor。
而 @Async 标记的类是通过 AbstractAdvisingBeanPostProcessor 来生成代理的,AbstractAdvisingBeanPostProcessor 没有实现 SmartInstantiationAwareBeanPostProcessor。
public abstract class AbstractAdvisingBeanPostProcessor extends ProxyProcessorSupport implements BeanPostProcessor {
.......
}
所以,这时通过 A 的三级缓存来获取 bean 的早期引用时,获取到的是 bean 的原始对象的引用,而不会提前生成代理对象。
这时 B 中注入的 A 对象不是代理对象。最后会导致 B 中持有的 A 对象与 Spring 容器中的 bean A 不是同一个对象。
这种情况显然是有问题的,跟我们的预期是不相符的,所以,Spring 在 initializeBean 之后,做了 check,检验二级缓存中的 bean 与最终暴露到 Spring 容器中的 bean 是否是相同的,如果不同,就会报错。
二级缓存中存放的是 bean 的早期引用,与最终暴露到容器中的 bean 的引用必须是相同的。
如果最终暴露的 AOP 代理 bean 与 三级缓存中获取到的早期引用 不是同一个对象引用的话,那就说明被循环依赖注入的 bean 与最终暴露到 Spring 容器中的 bean 不相同,这样是不被允许的。
Spring 通过检查的机制,check 检验二级缓存中的 bean 与最终暴露到 Spring 容器中的 bean 是否是相同的,如果不同,就会报错。
解决:
在需要循环注入的属性上添加 @Lazy
6.为什么@Lazy 注解可以用来解决循环依赖
@Resource 与@Autowired 的区别在处理@Lazy 注解时,被 @Lazy 标记的属性,在 populateBean 注入依赖时,会直接注入一个 proxy 对象。并且,不会触发注入对象的加载。这样的话,就不会产生 bean 的循环加载问题了。
protected Object buildLazyResourceProxy(final LookupElement element, final @Nullable String requestingBeanName) {
TargetSource ts = new TargetSource() {
@Override
public Class<?> getTargetClass() {
return element.lookupType;
}
@Override
public boolean isStatic() {
return false;
}
@Override
public Object getTarget() {
return getResource(element, requestingBeanName);
}
@Override
public void releaseTarget(Object target) {
}
};
ProxyFactory pf = new ProxyFactory();//代理对象
pf.setTargetSource(ts);
if (element.lookupType.isInterface()) {
pf.addInterface(element.lookupType);
}
ClassLoader classLoader = (this.beanFactory instanceof ConfigurableBeanFactory ?
((ConfigurableBeanFactory) this.beanFactory).getBeanClassLoader() : null);
return pf.getProxy(classLoader);
}
假设 A 先加载,在创建 A 的实例时,会触发依赖属性 B 的加载,在加载 B 时发现它是一个被 @Lazy 标记过的属性。
那么,就不会去直接加载 B,而是产生了一个代理对象注入到了 A 中,这样 A 就能正常的初始化完成放入一级缓存了。
自然,B 加载时,再去注入 A 就能直接从一级缓存中获取到 A,这样 B 也能正常初始化完成了。
所以,循环依赖的问题就解决了!
@Lazy 的本质就是将注入的依赖变成了一个代理对象。使用 @Lazy 时,不会触发依赖 bean 的加载。
通过调试代码,我们会发现 A 中注入的 @Lazy B 是一个代理对象。在 A 中,通过 B 的代理对象去调用 B 的方法时,才会去触发 B 的加载。
@Service
public class A {
private B b;
public A(@Lazy B b) {
this.b = b;
}
}
注意:被 @Lazy 标记的依赖属性比较特殊,实际注入的对象与 Spring 容器中存放的对象不是同一个对象!!!
7.objectProvider
ObjectProvider 提供的是通过 ObjectProvider#getObject() 或者 ObjectProvider#getIfAvailable() 获取 Object (即: bean) 的能力。
也就是说,在依赖注入时,注入的是一个 ObjectProvider 对象,想要获取到真正的 bean 时,可以通过调用 ObjectProvider#getObject() 或者 ObjectProvider#getIfAvailable()。
所以,它跟 @Lazy 起到的作用是很相似的。
可以看到,如果是 ObjectFactory 和 ObjectProvider 类型的依赖,那么会直接 return new DependencyObjectProvider(descriptor, requestingBeanName);,而不会触发依赖 bean 的加载。
等到真正使用 ObjectProvider#getObject() 获取 bean 的时候,才会触发 bean 的加载。
@Lazy 只能延迟注入,但如果容器中没有这个 bean 的话,在使用时,是会报错的。
所以,ObjectFactory 提供的功能是同 @Lazy 等价的。而 ObjectProvider 可以额外提供 required=false 的能力
六.事务
1.什么是事务?
事务就是用户定义的一系列数据库操作,这些操作可以视为一个完成的逻辑处理工作单元,要么全部执行,要么全部不执行,是不可分割的工作单元
begin transaction commit/rollback.
-
begin transaction 表示事务的开启标记,
-
commit 表示事务的提交操作,表示该事务的结束,此时将事务中处理的数据刷到磁盘中物理数据库磁盘中去。
-
rollback:表示事务的回滚操作,表示事务异常结束,此时将事务中已经执行的操作撤销回原来的状态。
2.事务的四大特性?
为了保证数据库的正确性与一致性事务具有四个特征:
2.1.原子性(Atomicity)
事务的原子性保证事务中包含的一组更新操作是原子的,不可分割的,不可分割是事务最小的工作单位,所包含的操作被视为一个整体,执行过程中遵循“要么全部执行,要不都不执行”,不存在一半执行,一半未执行的情况。
2.2.一致性(Consistency)
事务的一致性要求事务必须满足数据库的完整性约束,且事务执行完毕后会将数据库由一个一致性的状态变为另一个一致性的状态。事务的一致性与原子性是密不可分的,如银行转账的例子 A 账户向 B 账户转 1000 元钱,首先 A 账户减去 1000 元钱,然后 B 账户增加 1000 元钱,这两动作是一个整体,失去任何一个操作数据的一致性状态都会遭到破坏,所以这两个动作是一个整体,要么全部操作,要么都不执行,可见事务的一致性与原子性息息相关。
2.3.隔离性(Isolation)
事务的隔离性要求事务之间是彼此独立的,隔离的。及一个事务的执行不可以被其他事务干扰。具体到操作是指一个事务的操作必须在一个事务 commit 之后才可以进行操作。多事务并发执行时,相当于将并发事务变成串行事务,顺序执行,如同串行调度般的执行事务。这里可以思考事务如何保证它的可串行化的呢?答案锁,接下来会讲到。
2.4.持续性(Durability)
事物的持续性也称持久性,是指一个事务一旦提交,它对数据库的改变将是永久性的,因为数据刷进了物理磁盘了,其他操作将不会对它产生任何影响。
3.Spring 的事务?
spring 事务有 2 种用法:编程式事务和声明式事务。
所谓声明式事务,就是通过配置的方式,比如通过配置文件(xml)或者注解的方式,告诉 spring,哪些方法需要 spring 帮忙管理事务,然后开发者只用关注业务代码,而事务的事情 spring 自动帮我们控制。
声明式事务的 2 种实现方式
- 配置文件的方式,即在 spring xml 文件中进行统一配置,开发者基本上就不用关注事务的事情了,代码中无需关心任何和事务相关的代码,一切交给 spring 处理。
- 注解的方式,只需在需要 spring 来帮忙管理事务的方法上加上@Transaction 注解就可以了,注解的方式相对来说更简洁一些,都需要开发者自己去进行配置,可能有些同学对 spring 不是太熟悉,所以配置这个有一定的风险,做好代码 review 就可以了。
如何验证事务生效:
- debug 可以进入事务的拦截器
- 打印日志,会开启一个新的事物
原理:
-
@EnableTransactionManagement
- 利用 TransactionManagementConfigurationSelector 给容器中会导入组件
- 导入两个组件
- AutoProxyRegistrar
- ProxyTransactionManagementConfiguration
-
AutoProxyRegistrar:
-
给容器中注册一个 InfrastructureAdvisorAutoProxyCreator 组件;
-
InfrastructureAdvisorAutoProxyCreator:
-
利用后置处理器机制在对象创建以后,包装对象,返回一个代理对象(增强器),代理对象执行方法利用拦截器链进行调用;
-
-
ProxyTransactionManagementConfiguration 做了什么?
- 给容器中注册事务增强器;
- 事务增强器要用事务注解的信息,AnnotationTransactionAttributeSource 解析事务注解
- 事务拦截器:
- TransactionInterceptor;
- 保存了事务属性信息,事务管理器;
- 他是一个 MethodInterceptor;
- 在目标方法执行的时候;
- 执行拦截器链;
- 事务拦截器:
- 先获取事务相关的属性
- 再获取 PlatformTransactionManager,如果事先没有添加指定任何 transactionmanger,最终会从容器中按照类型获取一个 PlatformTransactionManager;
- 执行目标方法
- 如果异常,获取到事务管理器,利用事务管理回滚操作;
- 如果正常,利用事务管理器,提交事务
- TransactionInterceptor;
- 给容器中注册事务增强器;
4.@EnableTransactionManagement
@EnableTransactionManagement 配置类 开启 spring 事务管理功能
@Transaction 业务类
当 spring 容器启动的时候,发现有@EnableTransactionManagement 注解,此时会拦截所有 bean 的创建,扫描看一下 bean 上是否有@Transaction 注解(类、或者父类、或者接口、或者方法中有这个注解都可以),如果有这个注解,spring 会通过 aop 的方式给 bean 生成代理对象,代理对象中会增加一个拦截器,拦截器会拦截 bean 中 public 方法执行,会在方法执行之前启动事务,方法执行完毕之后提交或者回滚事务。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(TransactionManagementConfigurationSelector.class)
public @interface EnableTransactionManagement {
/**
* spring是通过aop的方式对bean创建代理对象来实现事务管理的
* 创建代理对象有2种方式,jdk动态代理和cglib代理
* proxyTargetClass:为true的时候,就是强制使用cglib来创建代理
* proxyTargetClass
true
目标对象实现了接口 – 使用CGLIB代理机制
目标对象没有接口(只有实现类) – 使用CGLIB代理机制
false
目标对象实现了接口 – 使用JDK动态代理机制(代理所有实现了的接口)
目标对象没有接口(只有实现类) – 使用CGLIB代理机制
*/
boolean proxyTargetClass() default false;
/**
* 用来指定事务拦截器的顺序
* 我们知道一个方法上可以添加很多拦截器,拦截器是可以指定顺序的
* 比如你可以自定义一些拦截器,放在事务拦截器之前或者之后执行,就可以通过order来控制
*/
int order() default Ordered.LOWEST_PRECEDENCE;
}
proxyTargetClass
- true
- 目标对象实现了接口 – 使用 CGLIB 代理机制
- 目标对象没有接口(只有实现类) – 使用 CGLIB 代理机制
- false
- 目标对象实现了接口 – 使用 JDK 动态代理机制(代理所有实现了的接口)
- 目标对象没有接口(只有实现类) – 使用 CGLIB 代理机制
proxyTargetClass | 目标对象特征 | 代理效果 |
---|---|---|
true | 目标对象实现了接口 | 使用 CGLIB 代理机制 |
true | 目标对象没有接口(只有实现类) | 使用 CGLIB 代理机制 |
false | 目标对象实现了接口 | 使用 JDK 动态代理机制(代理所有实现了的接口) |
false | 目标对象没有接口(只有实现类) | 使用 CGLIB 代理机制 |
上述规则同样适用于 SpringAOP,ProxyFactory 中进行设置.
5.@Transaction
需使用事务的目标上加@Transaction 注解
- @Transaction 放在接口上,那么接口的实现类中所有 public 都被 spring 自动加上事务
- @Transaction 放在类上,那么当前类以及其下无限级子类中所有 pubilc 方法将被 spring 自动加上事务
- @Transaction 放在 public 方法上,那么该方法将被 spring 自动加上事务
- 注意:@Transaction 只对 public 方法有效
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Transactional {
/**
* 指定事务管理器的bean名称,如果容器中有多事务管理器PlatformTransactionManager,
* 那么你得告诉spring,当前配置需要使用哪个事务管理器
*/
@AliasFor("transactionManager")
String value() default "";
/**
* 同value,value和transactionManager选配一个就行,也可以为空,如果为空,默认会从容器中按照类型查找一个事务管理器bean
*/
@AliasFor("value")
String transactionManager() default "";
/**
* 事务的传播属性
*/
Propagation propagation() default Propagation.REQUIRED;
/**
* 事务的隔离级别,就是制定数据库的隔离级别,数据库隔离级别大家知道么?不知道的可以去补一下
*/
Isolation isolation() default Isolation.DEFAULT;
/**
* 事务执行的超时时间(秒),执行一个方法,比如有问题,那我不可能等你一天吧,可能最多我只能等你10秒
* 10秒后,还没有执行完毕,就弹出一个超时异常吧
*/
int timeout() default TransactionDefinition.TIMEOUT_DEFAULT;
/**
* 是否是只读事务,比如某个方法中只有查询操作,我们可以指定事务是只读的
* 设置了这个参数,可能数据库会做一些性能优化,提升查询速度
*/
boolean readOnly() default false;
/**
* 定义零(0)个或更多异常类,这些异常类必须是Throwable的子类,当方法抛出这些异常及其子类异常的时候,spring会让事务回滚
* 如果不配做,那么默认会在 RuntimeException 或者 Error 情况下,事务才会回滚
*/
Class<? extends Throwable>[] rollbackFor() default {};
/**
* 和 rollbackFor 作用一样,只是这个地方使用的是类名
*/
String[] rollbackForClassName() default {};
/**
* 定义零(0)个或更多异常类,这些异常类必须是Throwable的子类,当方法抛出这些异常的时候,事务不会回滚
*/
Class<? extends Throwable>[] noRollbackFor() default {};
/**
* 和 noRollbackFor 作用一样,只是这个地方使用的是类名
*/
String[] noRollbackForClassName() default {};
}
参数介绍
参数 | 描述 |
---|---|
value | 指定事务管理器的 bean 名称,如果容器中有多事务管理器 PlatformTransactionManager,那么你得告诉 spring,当前配置需要使用哪个事务管理器 |
transactionManager | 同 value,value 和 transactionManager 选配一个就行,也可以为空,如果为空,默认会从容器中按照类型查找一个事务管理器 bean |
propagation | 事务的传播属性,下篇文章详细介绍 |
isolation | 事务的隔离级别,就是制定数据库的隔离级别,数据库隔离级别大家知道么?不知道的可以去补一下 |
timeout | 事务执行的超时时间(秒),执行一个方法,比如有问题,那我不可能等你一天吧,可能最多我只能等你 10 秒 10 秒后,还没有执行完毕,就弹出一个超时异常吧 |
readOnly | 是否是只读事务,比如某个方法中只有查询操作,我们可以指定事务是只读的 设置了这个参数,可能数据库会做一些性能优化,提升查询速度 |
rollbackFor | 定义零(0)个或更多异常类,这些异常类必须是 Throwable 的子类,当方法抛出这些异常及其子类异常的时候,spring 会让事务回滚 如果不配做,那么默认会在 RuntimeException 或者 Error 情况下,事务才会回滚 |
rollbackForClassName | 同 rollbackFor,只是这个地方使用的是类名 |
noRollbackFor | 定义零(0)个或更多异常类,这些异常类必须是 Throwable 的子类,当方法抛出这些异常的时候,事务不会回滚 |
noRollbackForClassName | 同 noRollbackFor,只是这个地方使用的是类名 |
6.事务传播行为?
事务传播行为(propagation behavior)指的就是当一个事务方法被另一个事务方法调用时,这个事务方法应该如何运行。
例如:methodA 方法调用 methodB 方法时,methodB 是继续在调用者 methodA 的事务中运行呢,还是为自己开启一个新事务运行,这就是由 methodB 的事务传播行为决定的。
事务的 7 种传播行为
Spring 在 TransactionDefinition 接口中规定了 7 种类型的事务传播行为。
事务传播行为是 Spring 框架独有的事务增强特性。
7 种:(required / supports / mandatory / requires_new / not supported / never / nested)
- PROPAGATION_REQUIRED:如果当前没有事务,就创建一个新事务,如果当前存在事务,就加入该事务,这是最常见的选择,也是 Spring 默认的事务传播行为。(required 需要,没有新建,有加入)
- PROPAGATION_SUPPORTS:支持当前事务,如果当前存在事务,就加入该事务,如果当前不存在事务,就以非事务执行。(supports 支持,有则加入,没有就不管了,非事务运行)
- PROPAGATION_MANDATORY:支持当前事务,如果当前存在事务,就加入该事务,如果当前不存在事务,就抛出异常。(mandatory 强制性,有则加入,没有异常)
- PROPAGATION_REQUIRES_NEW:创建新事务,无论当前存不存在事务,都创建新事务。(requires_new 需要新的,不管有没有,直接创建新事务)
- PROPAGATION_NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。(not supported 不支持事务,存在就挂起)
- PROPAGATION_NEVER:以非事务方式执行,如果当前存在事务,则抛出异常。(never 不支持事务,存在就异常)
- PROPAGATION_NESTED:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则按 REQUIRED 属性执行。(nested 存在就在嵌套的执行,没有就找是否存在外面的事务,有则加入,没有则新建)
对事务的要求程度可以从大到小排序:mandatory / supports / required / requires_new / nested / not supported / never
7.事务注解不生效
自身调用、异常被吃、异常抛出类型不对这三种最为常见
7.1.数据库引擎不支持事务
以 MySQL 为例,其 MyISAM 引擎是不支持事务操作的,InnoDB 才是支持事务的引擎,一般要支持事务都会使用 InnoDB。
从 MySQL 5.5.5 开始的默认存储引擎是:InnoDB,之前默认的都是:MyISAM,所以要注意的是:底层引擎不支持事务的情况下事务也不会生效。
7.2.没有被 Spring 管理
// @Service
public class OrderServiceImpl implements OrderService {
@Transactional
public void updateOrder(Order order) {
// update order
}
}
7.3.方法不是 public 的
以下来自 Spring 官方文档:
When using proxies, you should apply the @Transactional annotation only to methods with public visibility. If you do annotate protected, private or package-visible methods with the @Transactional annotation, no error is raised, but the annotated method does not exhibit the configured transactional settings. Consider the use of AspectJ (see below) if you need to annotate non-public methods.
意思大概就是 @Transactional 只能用于 public 的方法上,否则事务不会失效,如果要用在非 public 方法上,可以开启 AspectJ 代理模式。
7.4.自身调用问题
发生自身调用时,就调该类自己的方法,但是没有经过 Spring 的代理类,默认只有在外部调用事务才会生效,这也是经典的事务问题了。
事务注解不生效,一定要考虑是哪个对象在调用?是代理对象还是普通对象?只有
生效写法
class UserServiceProxy extends UserService {
UserService target;
@Transactional
public void test(){
//开启事务
// 1、事务管理器新建一个数据库连接conn
// 2.conn.autocommit = false
target.test(); // 普通对象.test()
conn.commit conn.rollback();
}
}
可以看到只创建了一个事物 ServiceA.test 方法的事务,但是 a 方法却没有被事务增强;
分析原因:Spring 事务生成的对象也是被 Cglib 或 JDK 代理的对象,就区别于该对象本身了,代理的对象执行方法之前会走拦截器链,就不能同 this 方法.
之前 Aop 可以将代理对象暴露到当前线程局部变量中;
<aop:aspectj-autoproxy expose-proxy=“true”/>
通过尝试发现,SpringTx 也可以使用该配置,将创建的对象加入到当前线程局部变量;
也许觉得 SpringAop 和 SpringTx 不一样啊,但其实两者都实现了 AbstractAutoProxyCreator 类,同样设置 expose-proxy 也能生效,绑定到线程局部变量上;
不生效写法
生效写法
确实初始化了 a 方法的事务;
7.5.没有事务管理器
数据源没有配置事务管理器
@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
7.6.不支持事务
@Service
public class OrderServiceImpl implements OrderService {
@Transactional
public void update(Order order) {
updateOrder(order);
}
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void updateOrder(Order order) {
// update order
}
}
Propagation.NOT_SUPPORTED: 表示不以事务运行,当前若存在事务则挂起
7.7.异常被吃
// @Service
public class OrderServiceImpl implements OrderService {
@Transactional
public void updateOrder(Order order) {
try {
// update order
} catch {
}
}
}
7.8.抛出异常
这样事务也是不生效的,因为默认回滚的是:RuntimeException,如果你想触发其他异常的回滚,需要在注解上配置一下,如:@Transactional(rollbackFor = Exception.class)
这个配置仅限于 Throwable 异常类及其子类。
@Service
public class OrderServiceImpl implements OrderService {
@Transactional
public void updateOrder(Order order) {
try {
// update order
} catch {
throw new Exception("更新错误");
}
}
}
8.自身调用详解
此处的 this 指向目标对象,因此调用 this.b()将不会执行 b 事务切面,即不会执行事务增强,因此 b 方法的事务定义@Transactional(propagation = Propagation.REQUIRES_NEW)将不会实施.
在一个 Service 内部,事务方法之间的嵌套调用,普通方法和事务方法之间的嵌套调用,都不会开启新的事务.是因为 spring 采用动态代理机制来实现事务控制,而动态代理最终都是要调用原始对象的,而原始对象在去调用方法时,是不会再触发代理了!
解决方法:
1、可以把方法 B 放到另外一个 service 或者 dao,然后把这个 server 或者 dao 通过@Autowired 注入到方法 A 的 bean 里面,这样即使方法 A 没用事务,方法 B 也可以执行自己的事务了。
2、在 java 配置类上添加注解@EnableAspectJAutoProxy(exposeProxy = true)方式暴漏代理对象,然后在 service 中通过代理对象 AopContext.currentProxy()去调用方法。
9.Spring 的事务隔离?
spring 有五大隔离级别,默认值为 ISOLATION_DEFAULT(使用数据库的设置),其他四个隔离级别和数据库的隔离级别一致:
-
ISOLATION_DEFAULT:用底层数据库的设置隔离级别,数据库设置的是什么我就用什么;
-
ISOLATION_READ_UNCOMMITTED:未提交读,最低隔离级别、事务未提交前,就可被其他事务读取(会出现幻读、脏读、不可重复读);
-
ISOLATION_READ_COMMITTED:提交读,一个事务提交后才能被其他事务读取到(会造成幻读、不可重复读),SQL server 的默认级别;
-
ISOLATION_REPEATABLE_READ:可重复读,保证多次读取同一个数据时,其值都和事务开始时候的内容是一致,禁止读取到别的事务未提交的数据(会造成幻读),MySQL 的默认级别;
-
ISOLATION_SERIALIZABLE:序列化,代价最高最可靠的隔离级别,该隔离级别能防止脏读、不可重复读、幻读。
脏读 :表示一个事务能够读取另一个事务中还未提交的数据。比如,某个事务尝试插入记录 A,此时该事务还未提交,然后另一个事务尝试读取到了记录 A。
不可重复读 :是指在一个事务内,多次读同一数据。
幻读 :指同一个事务内多次查询返回的结果集不一样。比如同一个事务 A 第一次查询时候有 n 条记录,但是第二次同等条件下查询却有 n+1 条记录,这就好像产生了幻觉。发生幻读的原因也是另外一个事务新增或者删除或者修改了第一个事务结果集里面的数据,同一个记录的数据内容被修改了,所有数据行的记录就变多或者变少了。
七.注解说明
1.spring 常用注解
2.Autowired 和 Resource 和 Qualifier 对比
@Autowired:@Autowired 可以单独使用。如果单独使用,它将按类型装配。
@Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Autowired {
boolean required() default true;
}
@Qualifier:@Qualifier 与 @Autowired 一起,通过指定 bean 名称来阐明实际装配的 bean (按姓名连线)
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Qualifier {
String value() default "";
}
@Resource:@Resource(这个注解属于 J2EE 的),默认按照名称进行装配,名称可以通过 name 属性进行指定, 如果没有指定 name 属性,当注解写在字段上时,默认取字段名进行按照名称查找,如果注解写在 setter 方法上默认取属性名进行装配。 当找不到与名称匹配的 bean 时才按照类型进行装配。但是需要注意的是,如果 name 属性一旦指定,就只会按照名称进行装配。
package javax.annotation;
@Target({TYPE, FIELD, METHOD})
@Retention(RUNTIME)
public @interface Resource {
String name() default "";
String lookup() default "";
Class<?> type() default java.lang.Object.class;
enum AuthenticationType {
CONTAINER,
APPLICATION
}
AuthenticationType authenticationType() default AuthenticationType.CONTAINER;
boolean shareable() default true;
String mappedName() default "";
String description() default "";
}
3.注解的底层原理是什么?
-
通过键值对的方式为注解的属性赋值.
-
编译器会检查注解的使用范围. 将注解的信息, 写入元素的属性表
-
程序运行时, JVM 将 RUNTIME 的所有注解属性都取出最终存入 map 里.
-
JVM 会创建 AnnotationInvocationHandler 实例, 并传递上一步的 map
-
JVM 会使用 JDK 动态代理为注解生成代理类, 并初始化 AnnotationInvocationHandler
-
调用 invoke 方法, 通过传入方法名, 返回注解对应的属性值.
4.@Conditional (ZhovyuCondition.class)
该注解是 Spring4.0 之后才有的,该注解可以放在任意类型或者方法上。通过@Conditional 可以配置一些条件判断,当所有条件都满足时,被该@Conditional 注解标注的目标才会被 Spring 处理。
@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Document
public @interface Confitional{
Class<? extend Condition> [] value();
}
value:Condition 类型的数组,Condition 是一个接口,表示一个条件判断,内部有个方法返回 true 或 false,当所有 Condition 都成立的时候,@Conditional 的结果才成立
Condition 接口:
内部有个 match 方法,判断条件是否成立的。
@FunctionalInterface
public interface Condition {
//判断条件是否匹配 context:条件判断上下文
boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
}
有两个参数:context和metadata
- context:ConditionContext 接口类型的,用来获取容器中的 bean 的信息
- metadata:用来获取被@Conditional 标注的对象上的所有注解信息。
ConditionContext 接口:
public interface ConditionContext {
//返回bean定义注册器,可以通过注册器获取bean定义的各种配置信息
BeanDefinitionRegistry getRegistry();
//返回ConfigurableListableBeanFactory类型的bean工厂,相当于一个ioc容器对象
@Nullable
ConfigurableListableBeanFactory getBeanFactory();
//返回当前spring容器的环境配置信息对象
Environment getEnvironment();
//返回资源加载器
ResourceLoader getResourceLoader();
//返回类加载器
@Nullable
ClassLoader getClassLoader();
}
@Conditional 使用的步骤
- 自定义一个类,实现 Condition 或 ConfigurationCondition 接口,实现 matches 方法
- 在目标对象上使用@Conditional 注解,并指定 value 的指为自定义的 Condition 类型
- 启动 spring 容器加载资源,此时@Conditional 就会起作用了
总结
- @Conditional 注解可以标注在 spring 需要处理的对象上(配置类、@Bean 方法),相当于加了个条件判断,通过判断的结果,让 spring 觉得是否要继续处理被这个注解标注的对象
- spring 处理配置类大致有 2 个过程:解析配置类、注册 bean,这两个过程中都可以使用@Conditional 来进行控制 spring 是否需要处理这个过程
- Condition 默认会对 2 个过程都有效
- ConfigurationCondition 控制得更细一些,可以控制到具体那个阶段使用条件判断
5.什么是配置类?
类上面有@Configuration,@Component,@ComponentScan,@Import,@Bean,@ImportResource 这些注解时,被标注的类就是配置类.
6.@Lookup(“orderService”)
@Lookup 用于单例组件引用 prototype 组件。单例组件使用@Autowired 方式注入 prototype 组件时,被引入 prototype 组件也会变成单例的。@Lookup 可以保证被引入的组件保持 prototype 模式。
@Bean 与@Lookup 不能一起使用的原因
@Lookup 注解下的当前类不能通过@Bean 方式注入,这样@Lookup 不起作用。必须通过@Component 注解标注类然后通过扫描的方式注入。
7.ScopedProxyMode 介绍与底层原理
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Scope {
@AliasFor("scopeName")
String value() default "";
@AliasFor("value")
String scopeName() default "";
ScopedProxyMode proxyMode() default ScopedProxyMode.DEFAULT;
}
ScopedProxyMode 是一个枚举类,该类共定义了四个枚举值,分别为 NO、DEFAULT、INTERFACE、TARGET_CLASS,其中 DEFAULT 和 NO 的作用是一样的。INTERFACES 代表要使用 JDK 的动态代理来创建代理对象,TARGET_CLASS 代表要使用 CGLIB 来创建代理对象。
8.BeanFactoryPostProcessor
BeanFactoryPostProcessor 和 BeanPostProcessor 类似,可以对 beanDefinition 进行处理,也就是说 SpringIOC 容器允许 BeanFactoryPostProcessor 在容器实际实例化任何 bean 之前读取 beanDefinition,并有可能修改他.并且我们还可以配置自定义的 BeanFactoryPostProcessor.如果想改变 bean,那么使用 beanPostProcessor
9.@PostConstruct
@PostConstruct 和@PreDestroy,这两个注解被用来修饰一个非静态的 void()方法。
@PostConstruct 注解的方法在项目启动的时候执行这个方法,也可以理解为在 spring 容器启动的时候执行,可作为一些数据的常规化加载,比如数据字典之类的。
10.afterPropertiesSet
在 spring 的 bean 的生命周期中,实例化->生成对象->属性填充后会进行 afterPropertiesSet 方法,这个方法可以用在一些特殊情况中,也就是某个对象的某个属性需要经过外界得到,比如说查询数据库等方式,这时候可以用到 spring 的该特性,只需要实现 InitializingBean 即可:
@Component("a")
public class A implements InitializingBean {
private B b;
public A(B b) {
this.b = b;
}
@Override
public void afterPropertiesSet() throws Exception {
}
}
11.@ConditionalOnProperty
@ConditionalOnProperty(prefix = “file”, value = “model”, havingValue = “local”, matchIfMissing = true)
在 spring boot 中有时候需要控制配置类是否生效,可以使用@ConditionalOnProperty 注解来控制@Configuration 是否生效.
@Configuration
@ConditionalOnProperty(prefix = "filter",name = "loginFilter",havingValue = "true")
public class FilterConfig {
//prefix为配置文件中的前缀,
//name为配置的名字
//havingValue是与配置的值对比值,当两个值相同返回true,配置类生效.
@Bean
public FilterRegistrationBean getFilterRegistration() {
FilterRegistrationBean filterRegistration = new FilterRegistrationBean(new LoginFilter());
filterRegistration.addUrlPatterns("/*");
return filterRegistration;
}
}
总结:
通过@ConditionalOnProperty 控制配置类是否生效,可以将配置与代码进行分离,实现了更好的控制配置.
@ConditionalOnProperty 实现是通过 havingValue 与配置文件中的值对比,返回为 true 则配置类生效,反之失效.
12.@ConditionalOnProperty
@ConditionalOnProperty 注解结合@Configuration 使用来控制配置类是否生效
@Configuration
@ConditionalOnProperty(prefix = "filter",name = "loginFilter",havingValue = "true")
public class FilterConfig {
//prefix为配置文件中的前缀,
//name为配置的名字
//havingValue是与配置的值对比值,当两个值相同返回true,配置类生效.
@Bean
public FilterRegistrationBean getFilterRegistration() {
FilterRegistrationBean filterRegistration = new FilterRegistrationBean(new LoginFilter());
filterRegistration.addUrlPatterns("/*");
return filterRegistration;
}
}
配置文件中的代码:
filter.loginFilter=true
总结:通过@ConditionalOnProperty 控制配置类是否生效,可以将配置与代码进行分离,实现了更好的控制配置.
@ConditionalOnProperty 实现是通过 havingValue 与配置文件中的值对比,返回为 true 则配置类生效,反之失效.
13.@Configuration
- 底层代码就两个属性,一个用于声明配置类的名称,一个用于声明是否是代理对象方法(重点)。
- 由于有@Component 注解的加持,那么被声明的配置类本身也是一个组件!
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Configuration {
@AliasFor(annotation = Component.class)
String value() default "";
boolean proxyBeanMethods() default true;
}
基础使用
@Configuration 注解常常一起搭配使用的注解有@Bean、@Scope、@Lazy 三个比较常见:
-
@Bean:等价于 Spring 中的 bean 标签用于注册 bean 对象的,内部有一些初始化、销毁的属性
-
@Scope:用于声明该 bean 的作用域,作用域有 singleton、prototype、request、session。
-
@Lazy:标记该 bean 是否开启懒加载。
proxyBeanMethods是及其重要的,设置 true/false 会得到不同的效果。
- 值为 false:那么 MyConfig 类是一个 lite 的配置类,没有代理功能
- 不进行检查 IOC 容器中是否存在,而是简单的调用方法进行创建对象。
- 通过直接 getBean 方式获取 IOC 容器中的对象获取还是一样的
- 无法保证 bean 的单一,失去了单例的意义!
- 值为 true:该类是一个 Full 的配置类,使用 cglib 代理
- 通过配置类调用方法,还是通过 getBean 直接从 IOC 容器中获取对象,获取到的都是同一个!(单例)
- 外部无论对配置类中的这个组件注册方法调用多少次都是直接注册到容器中的单实例对象!
- 代理对象调用方法时 SpringBoot 会检查这个 bean 对象是否在容器中,保持单实例。
14.scope 注解
scope 生效的地方是在 bean 加载的时候,调用 AbstractBeanFactory#doGetBean()
时生效的。
单例与多例 Bean
scope 的值不止这两种,还包括了 request、session 等。但用的最多的还是 singleton 单态与 prototype 多态。
<bean id="testManager" class="com.sw.TestManagerImpl" scope="singleton" />
<bean id="testManager" class="com.sw.TestManagerImpl" scope="prototype" />
scope=singleton 是单独的处理逻辑,产生的 bean 会放入一级缓存
scope=prototype 是单独的处理逻辑,产生的 bean 不会放入缓存
其他 scope 是另一套单独的处理逻辑,而且它使用了 prototype 的方式在处理,产生的 bean 不会放入缓存
八.常见问题
1.什么是响应式
目的,响应式编程的目是帮助应用在不同的环境条件和负载下仍保持响应性。
响应式系统需要具备这些特征:及时响应性(Responsive)、恢复性(Resilient)、有弹性(Elastic)以及消息驱动(Message Driven)。我们把具有上面四个特性的系统就叫做响应式系统。
上面的四个特性中,及时响应性(Responsive)是系统最终要达到的目标,恢复性(Resilient)和有弹性(Elastic)是系统的表现形式,而消息驱动(Message Driven)则是系统构建的手段。
2.webflux 的核心使用与原理
- 是 Spring5 添加的新模块,用于 web 开发的,功能 SpringMvc 类似,Webflux 使用当前流行的响应式编程出现的框架。
- 传统的 web 框架,比如 SpringMVC,这些都是基于 Servlet 容器来实现的,Webflux 是一种异步非阻塞的框架,异步非阻塞的框架在 Servlet3.1 以后才支持,核心是基于 Reactor 的相关 API 实现的,但是同时它也支持 Servlet
- 什么是异步:同步异步其实就是针对的是调用者,你在吃饭,然后准备去打球,要给辅导员请假,同步就是你必须等到它同意,而且异步就是可以说了就去打球了,其他的不管。
- 什么是非阻塞是针对对象的:A 是调用者,B 是被调用者,B 得到一个被调用,直接给他反馈,这就是非阻塞,不反馈就是阻塞。
- WebFlux 的优势:第一步非阻塞(有限的资源下可以提高吞吐量和伸缩性,以 Reactor 实现的响应式编程),Spring5 框架基于 Java8,WebFlux 使用 Java8 函数式编程方式实现路由请求(学过 Vue 框架的应该很好理解)
3.SpringWebFlux
Mono 和 Flux 是 spring webflux 用来处理响应式工作流的核心 api,
-
Mono 表示 0 或 1 个元素
-
Flux 表示 0 至 N 和元素
-
新的 spring-webflux 模块,一个基于 reactive 的 spring-webmvc,完全的异步非阻塞,旨在使用 enent-loop 执行模型和传统的线程池模型。
-
Reactive 说明在 spring-core 比如编码和解码
-
spring-core 相关的基础设施,比如 Encode 和 Decoder 可以用来编码和解码数据流;DataBuffer 可以使用 java ByteBuffer 或者 Netty ByteBuf;ReactiveAdapterRegistry 可以对相关的库提供传输层支持。在 spring-web 包里包含 HttpMessageReade 和 HttpMessageWrite
4.SpringWebflux 中处理器
WebSpringFlux 基于 Reactor 默认容器为 Netty, Netty 是高性能的 NIO 框架,异步非阻塞框架。
SpringWebflux 核心控制器 DispatchHandler,实现接口 WebHandler
- HandlerMapping:请求查询到处理的方法
- HandlerAdapter:真正负责请求处理
- HandlerResultHandler:响应结果处理
SpringWebflux 实现函数式编程 :
两个接口 :
- RouterFunction(路由处理)
- HandlerFunction (函数处理)
5.SpringMVC 和 SpringWebFlux 的对比?
6.存在 2 个 bean 名称相同会如何
如果是 xml 的形式获取 bean,则会直接报错,xml 不允许定义 2 个名称相同的 bean
如果是注解的方式,不会报错,但是第二个 bean 不会继续创建,单例 bean
单例 bean 里面的多例 bean 只会有一个值
存在相同 id 的 bean 会不会报错,xml 报错,不是 xml 不会报错,但只会初始化一个 bean,第一个 bean
7.什么是 Aot?
JIT(Just-in-Time,实时编译)一直是 Java 语言的灵魂特性之一,与之相对的 AOT(Ahead-of-Time,预编译)方式,似乎长久以来和 Java 语言都没有什么太大的关系。但是近年来随着 Serverless、云原生等概念和技术的火爆,Java JVM 和 JIT 的性能问题越来越多地被诟病,使用 Aot 可以很大程度上提高编译速度,jar 启动更快.
AOT 的优点
-
在程序运行前编译,可以避免在运行时的编译性能消耗和内存消耗
-
可以在程序运行初期就达到最高性能,程序启动速度快
-
运行产物只有机器码,打包体积小
AOT 的缺点
- 由于是静态提前编译,不能根据硬件情况或程序运行情况择优选择机器指令序列,理论峰值性能不如 JIT
- 没有动态能力
- 同一份产物不能跨平台运行
8.Spring Security
Spring Security 实现多重角色鉴权,URL 级别权限管理
基于 RBAC,五表
RBAC 是基于角色访问控制
五表 user user_role role role_resource resource
资源表就是存储 url
9.什么是 ASM 技术?
ASM 是一个 Java 字节码操控框架。它能被用来动态生成类或者增强既有类的功能。ASM 可以直接产生二进制 class 文件,也可以在类被加载入 Java 虚拟机之前动态改变类行为。Java class 被存储在严格格式定义的 .class 文件里,这些类文件拥有足够的元数据来解析类中的所有元素:类名称、方法、属性以及 Java 字节码(指令)。ASM 从类文件中读入信息后,能够改变类行为,分析类信息,甚至能够根据用户要求生成新类。
ASM 能够通过改造既有类,直接生成需要的代码。增强的代码是硬编码在新生成的类文件内部的,没有反射带来性能上的付出。同时,ASM 与 Proxy 编程不同,不需要为增强代码而新定义一个接口,生成的代码可以覆盖原来的类,或者是原始类的子类。它是一个普通的 Java 类而不是 proxy 类,甚至可以在应用程序的类框架中拥有自己的位置,派生自己的子类。
相比于其他流行的 Java 字节码操纵工具,ASM 更小更快。ASM 具有类似于 BCEL 或者 SERP 的功能,而只有 33k 大小,而后者分别有 350k 和 150k。同时,同样类转换的负载,如果 ASM 是 60% 的话,BCEL 需要 700%,而 SERP 需要 1100% 或者更多。
10.ApplicationEvent
public abstract class ApplicationEvent extends EventObject {
private static final long serialVersionUID = 7099057708183571937L;
private final long timestamp = System.currentTimeMillis();
public ApplicationEvent(Object source) {
super(source);
}
public final long getTimestamp() {
return this.timestamp;
}
}
类的注释是,被应用事件继承的类,抽象的不能初始化;
作用
- 事件携带一个 Objecgt 对象,可以被发布;
- 事件监听者,监听到这个事件后,触发自定义逻辑 ( 操作 Object 对象)
- 发布事件的时候,不仅会被 MyEventListener 监听,其他只要监听了 MyApplicationEvent 的监听器 都会收到事件
定义事件
public class MyApplicationEvent extends ApplicationEvent {
public MyApplicationEvent(Object source) {
super(source);
System.out.println("MyApplicationEvent create");
}
}
定义时间监听器
public class MyEventListener implements ApplicationListener<MyApplicationEvent> {
@Override
public void onApplicationEvent(MyApplicationEvent myApplicationEvent) {
int[] source = (int[]) myApplicationEvent.getSource();
System.out.println(Arrays.toString(source));
}
}
发布事件
@SpringBootApplication
public class AntServiceApplication {
public static void main(String[] args) {
//启动SpringBoot 并拿到配置信息
ConfigurableApplicationContext context =
SpringApplication.run(AntServiceApplication.class, args);
JdbcTemplate jdbcTemplate = context.getBean(JdbcTemplate.class);
List<Map<String, Object>> result = jdbcTemplate.queryForList("SELECT * FROM t_class_table");
// jdbcTemplate.execute();
System.out.println(result);
// 启动SpringBoot
ConfigurableApplicationContext run = SpringApplication.run(AntServiceApplication.class, args);
int[] array = {1, 2, 3, 4};
run.publishEvent(new MyApplicationEvent(array))
}
}
11.ApplicationContext 延迟加载?
两种方式:
- bean 元素配置 lazy-init=true
- beans 元素中配置 default-lazy-init=true 让这个 beans 中的所有 bean 延迟实例化
12.Advice、Advisor、Advised 区别?
Advice、Advisor、Advised 都是 Spring AOP 相关的基本接口,理解这些接口的作用,对于更好的理解 Spring AOP 有很大的好处:
Advice: org.aopalliance.aop.Advice
“通知”,表示 Aspect 在特定的 Join point 采取的操作。包括 “around”, “before” and “after 等
Advice 大体上分为了三类:BeforeAdvice、MethodInterceptor、AfterAdvice
MethodInterceptor 是功能最强大的,是一个通用的方法拦截接口,它能够处理 BeforeAdvice、AroundAdvice、AfterAdvice、ThrowsAdvice、@Valid 方法参数校验、@Async 异步等
Advisor: org.springframework.aop.Advisor
“通知者”,它持有 Advice,是 Spring AOP 的一个基础接口。
它的子接口 PointcutAdvisor 是一个功能完善接口,它涵盖了绝大部分的 Advisor。
Advised: org.springframework.aop.framework.Advised
AOP 代理工厂配置类接口。提供了操作和管理 Advice 和 Advisor 的能力。
它的实现类 ProxyFactory 是 Spring AOP 主要用于创建 AOP 代理类的核心类。
13.@Component 和@Bean
Spring 5.0.7.RELEASE 支持@Configuration + @Bean
与@Component
同时作用于同一个类
@Component
修饰的 UserManager 定义直接被覆盖成了 @Configuration + @Bean
修饰的 UserManager 定义,Bean 定义类型也由 ScannedGenericBeanDefinition
替换成了 ConfigurationClassBeanDefinition
后续通过 BeanDefinition
创建实例的时候,创建的自然就是 @Configuration + @Bean
修饰的 UserManager ,也就是会反射调用 UserManager 的有参构造方法
也许 Spring 团队意识到了上述处理不太合适,于是在 Spring 5.1.2.RELEASE
做出了优化处理
增加了配置项:allowBeanDefinitionOverriding
,将主动权交给了开发者,由开发者自己决定是否允许覆盖
14.bf 和 fb 的区别?
Spring 中的 beanFactory and FactoryBean 常常容易混淆,特别拿出来对比一下
BeanFactory
是一个负责生产和管理 bean 的一个工厂类(接口)。
在 Spring 中,BeanFactory 是 IOC 容器的核心接口,它的职责包括:实例化、定位、配置应用程序中的对象及建立这些对象间的依赖。
我们通过 getBean()方法,传入参数——bean 的名称或者类型,便可以从 Spring 容器中来获取 bean。
BeanFactory 是用于访问 Spring 容器的根接口,是从 Spring 容器中获取 Bean 对象的基础接口,提供了 IOC 容器最基本的形式,给具体的 IOC 容器的实现提供了规范。
BeanFactory 只是个接口,并不是 IOC 容器的具体实现,Spring 容器给出了很多种 BeanFactory 的 扩展实现,如:DefaultListableBeanFactory、XmlBeanFactory、ApplicationContext 等。
原始的 BeanFactory 无法支持 spring 的许多插件,如 AOP 功能、Web 应用等。ApplicationContext 接口,它由 BeanFactory 接口派生而来。现在一般使用 ApplicationnContext,其不但包含了 BeanFactory 的作用,同时还进行更多的扩展。
ApplicationContext 包含 BeanFactory 的所有功能,还提供了以下更多的功能:
1、MessageSource, 提供国际化的消息访问 ;
2、资源访问,如 URL 和文件;
3、事件传播。
FactoryBean
public interface FactoryBean<T> {
//返回的对象实例
T getObject() throws Exception;
//Bean的类型
Class<?> getObjectType();
//true是单例,false是非单例
boolean isSingleton();
}
和 BeanFactory 一样,FactoryBean 也是接口。
FactoryBean 是为 IOC 容器中的 Bean 的实现提供了更加灵活的方式,FactoryBean 在 IOC 容器的基础上,给 Bean 的实现加上了一个简单工厂模式和装饰模式。
一般情况下实例化一个 Bean 对象:Spring 通过反射机制利用 bean 的 class 属性指定实现类实例化 Bean,在某些情况下,实例化 Bean 过程比较复杂,如果按照传统的方式,则需要在 bean 中提供大量的配置信息。配置方式的灵活性是受限的,这时采用编码的方式可能会得到一个简单的方案。
Spring 为此提供了一个 org.springframework.bean.factory.FactoryBean的工厂类接口,用户可以通过实现该接口,然后在 getObject()方法中灵活配置,来定制实例化 Bean 的逻辑。
FactoryBean 接口对于 Spring 框架来说占用重要的地位,Spring 自身就提供了 70 多个 FactoryBean 的实现——(xxxFactoryBean)。它们隐藏了实例化一些复杂 Bean 的细节,给上层应用带来了便利。使用场景如:Mybatis 中的 SqlSessionFactoryBean,可以让我们自定义 Bean 的创建过程。
从 BeanFactory 及其实现类的 getBean()方法中获取到的 Bean 对象,实际上是 FactoryBean 的 getObject()方法创建并返回的 Bean 对象,而不是 FactoryBean 本身。
getObject():
- 返回 Bean 对象;
- 当通过 getBean()方法获取到一个 Bean 时,返回的并不是 xxxFactoryBean 本身,而是其创建的 Bean 对象;
- 如果要获取 xxxFactoryBean 对象本身,请在参数前面加一个&符号来获取,即:getBean(&BeanName)。
从 getBean()方法 到 getObject()方法:
从 BeanFactory 及其实现类的 getBean()方法中获取到的 Bean 对象,实际上是 FactoryBean 的 getObject()方法创建并返回的 Bean 对象,而不是 FactoryBean 本身。
15.有状态的 bean 与无状态的 bean
有状态 bean:每个用户有自己特有的一个实例,在用户的生存期内,bean 保存了用户的信息,即有状态;一旦用户灭亡(调用结束或实例结束),bean 的生命期也告结束。即每个用户最初都会得到一个初始的 bean。
无状态 bean:bean 一旦实例化就被加进会话池中,各个用户都可以共用。即使用户已经消亡,bean 的生命期也不一定结束,它可能依然存在于会话池中,供其他用户调用。由于没有特定的用户,那么也就不能保持某一用户的状态,所以叫无状态 bean。但无状态会话 bean 并非没有状态,如果它有自己的属性(变量)。
有状态就是有数据存储功能。有状态对象(Stateful Bean),就是有实例变量的对象 ,可以保存数据,是非线程安全的。在不同方法调用间不保留任何状态。
无状态就是一次操作不能保存数据。无状态对象(Stateless Bean),就是没有实例变量的对象 ,不能保存数据是不变类,是线程安全的。
16.如何保证线程安全?
Spring 的单例不是线程安全的话,那它怎么保证线程安全的呢?
将有状态的 bean 配置成 singleton 会造成资源混乱问题(线程安全问题),而如果是 prototype 的话,就不会出现资源共享的问题,即不会出现线程安全的问题。
如果你的代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码。如果每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,那么代码就是线程安全的。
无状态的 Bean 适合用不变模式,技术就是单例模式,这样可以共享实例,提高性能。有状态的 Bean,多线程环境下不安全,那么适合用 Prototype 原型模式(解决多线程问题),每次对 bean 的请求都会创建一个新的 bean 实例。
bean 只会实例化一次,无状态 Bean 共享,Spring 是用了 threadlocal 来解决了这个问题。
ThreadLocal 的机制:
- 每个 Thread 线程内部都有一个 Map。
- Map 里面存储线程本地对象(key)和线程的变量副本(value)
- 但是,Thread 内部的 Map 是由 ThreadLocal 维护的,由 ThreadLocal 负责向 map 获取和设置线程的变量值。
- 所以对于不同的线程,每次获取副本值时,别的线程并不能获取到当前线程的副本值,形成了副本的隔离,互不干扰。
- 每个线程中都有一个自己的 ThreadLocalMap 类对象,可以将线程自己的对象保持到其中,各管各的,线程可以正确的访问到自己的对象。
- 将一个共用的 ThreadLocal 静态实例作为 key,将不同对象的引用保存到不同线程的 ThreadLocalMap 中,然后在线程执行的各处通过这个静态 ThreadLocal 实例的 get()方法取得自己线程保存的那个对象,避免了将这个对象作为参数传递的麻烦。
最后
以上就是傲娇方盒为你收集整理的【檀越剑指大厂—Spring】Spring高阶篇的全部内容,希望文章能够帮你解决【檀越剑指大厂—Spring】Spring高阶篇所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复