【檀越剑指大厂—Spring】Spring高阶篇
创始人
2024-03-23 20:20:44

一.基础概念

1.模块

image-20221201113923696

image-20221201115352837

image-20221201115336442

2.Spring 框架概述。

  • Spring 是轻量级的开源的 JavaEE 框架

  • Spring 可以解决企业应用开发的复杂性

  • Spring 有两个核心部分: IOC 和 AOP

    • IOC:控制反转,把创建对象过程交给 Spring 进行管理

    • Aop:面向切面,不修改源代码进行功能增强

3.Spring 特点

  • 方便解耦,简化开发。
  • Aop 编程支持。
  • 方便程序测试。
  • 方便和其他框架进行整合。
  • 方便进行事务操作。
  • 降低 API 开发难度。

4.推断构造方法

先 bytype 再 byname

image-20221205174451491

5.属性注入具体实现

  1. 接口注入
  2. setter 注入
  3. 构造器注入

6.有状态的 bean 与无状态的 bean

有状态 bean:每个用户有自己特有的一个实例,在用户的生存期内,bean 保存了用户的信息,即有状态;一旦用户灭亡(调用结束或实例结束),bean 的生命期也告结束。即每个用户最初都会得到一个初始的 bean。

无状态 bean:bean 一旦实例化就被加进会话池中,各个用户都可以共用。即使用户已经消亡,bean 的生命期也不一定结束,它可能依然存在于会话池中,供其他用户调用。由于没有特定的用户,那么也就不能保持某一用户的状态,所以叫无状态 bean。但无状态会话 bean 并非没有状态,如果它有自己的属性(变量)。

有状态就是有数据存储功能。有状态对象(Stateful Bean),就是有实例变量的对象 ,可以保存数据,是非线程安全的。在不同方法调用间不保留任何状态。

无状态就是一次操作不能保存数据。无状态对象(Stateless Bean),就是没有实例变量的对象 ,不能保存数据是不变类,是线程安全的。

7.如何保证线程安全?

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()方法取得自己线程保存的那个对象,避免了将这个对象作为参数传递的麻烦。

8.常见依赖包

image-20221201155823064

9.扫描的实现过程

在 spring 中,spring 使用ClassPathBeanDefinitionScanner来实现包扫描,

org.springframework.context.annotation.ClassPathBeanDefinitionScanner#scan就是扫描方法的入口,实际的扫描逻辑是写在 doScan 方法中的

1.xml 方式



2.注解

@Configuration
@ComponentScan("com.example.spring.beans4")
public class ComponentScanConfig {
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CCR1RVAG-1670382012350)(http://120.79.36.53/blogImg/2022/12/spring%E6%89%AB%E6%8F%8F%E8%BF%87%E7%A8%8B.png)]

10.doScan 的具体流程

protected Set doScan(String... basePackages) {//首先,basePackages不能是空的Assert.notEmpty(basePackages, "At least one base package must be specified");Set beanDefinitions = new LinkedHashSet<>();//遍历所有要扫描的包for (String basePackage : basePackages) {//获取到待选的BeanDefinitionSet candidates = findCandidateComponents(basePackage);//遍历待选的BeanDefinitionfor (BeanDefinition candidate : candidates) {//设置ScopeScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);candidate.setScope(scopeMetadata.getScopeName());//生成beanNameString 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);//注册beanregisterBeanDefinition(definitionHolder, this.registry);}}}return beanDefinitions;
}

11.说说 MetadataReader

spring 通过读取 resource 中的 class 文件的字节码,生成了一个叫 MetadataReader 的对象。这个 MetadaReader 并不是 class 对象,但是可以读取到 class 上所有的元数据信息。这是因为 spring 使用了 ASM 技术,用流的方式读取了 class 文件。然后就是创建 ScannedGenericBeanDefinition 并返回了.

在这里插入图片描述

12.beanName 生成策略

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() ,这样我们这个生成策略就写好了。

13.单例与多例 Bean

scope 的值不止这两种,还包括了 request、session 等。但用的最多的还是 singleton 单态与 prototype 多态。



14.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

15.初始化过程

spring 初始化过程也叫 ioc 容器初始化过程、bean 生命周期。

public static void main(String[] args) {// 创建 ioc 容器ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");// 获取 beanUser 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();}
}

16.详解 refresh 方法

public void refresh() throws BeansException, IllegalStateException {synchronized(this.startupShutdownMonitor) {//判断系统要求的属性是否存在,不存在抛出异常this.prepareRefresh();/**创建DefaultListableBeanFactory对象,可以理解为就是ApplicationContext1.实例化了一个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();}}
}

17.Spring 是如何加载配置文件到应用程序的?

XmlBeanFactory 读取文件,改类已经为@Deprecated 废弃了,XmlBeanFactory 也是一个容器,目前改为 XmlBeanDefinitionReader 进行读取.

image-20221206104648321

18.XmlBeanDefinitionReader

  1. 首先 XML 文件通过 ResourceLoader 加载器加载为 Resource 对象
  2. XmlBeanDefinitionReader 将 Resource 对象解析为 Document 对象
  3. DefaultBeanDefinitionDocumentReader 将 Document 对象解析为 BeanDefinition 对象
  4. 将解析出来的 BeanDefinition 对象储存在 DefaultListableBeanFactory 工厂中

在这里插入图片描述

19.spring 容器有哪些?

Spring 中的两种容器类型:
分别是:BeanFactory 和 ApplicationContext

典型的上下文有如下几个

  • ClassPathXmlApplicationContext
  • FileSystemXmlApplicationContext
  • XmlWebApplicationContext
  • GroovyWebApplicationContext
  • AnnocationConfigWebApplicationContext

image-20221206120914252

共同点

它们都是 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 应用,将耗时的内容在系统启动的时候就完成

20.ApplicationContext

image-20221206145911405

  • FileSystemXmlApplicationContext:对应系统盘路径
  • ClassPathXmlApplicationContext:对应类路径

二.bean 的生命周期

1.bean 创建过程

image-20221201115109271

2.Bean 的创建的生命周期

  • UserService.class–>
  • 无参的构造方法–>对象–>依赖注入—>
  • 初始化前—>初始化—>初始化后(AOP) —>代理对象–>
  • 放入 Map(单例池)—>
  • Bean 对象

image-20221206122006464

3.DefaultListableBeanFactory

DefaultListableBeanFactory 是 Spring 注册加载 bean 的默认实现,它是整个 bean 加载的核心部分。

XmlBeanFactory 与它的不同点就是 XmlBeanFactory 使用了自定义的 XML 读取器 XmlBeanDefinitionReader,实现了自己的 BeanDefinitionReader 读取。 XmlBeanFactory 加载 bean 的关键就在于 XmlBeanDefinitionReader。

XmlBeanFactory 继承了 DefaultListableBeanFactory

image-20221130184100853

4.bf 和 fb 的区别?

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、事件传播。

BeanFactory

public interface FactoryBean {//返回的对象实例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本身

5.循环依赖 Bean 生命周期

**Spring循环依赖,只支持setter方式的依赖注入,不支持构造函数的依赖注入。**只针对单例bean.

  • 总结
    A 与B 对象相互依赖

  • A首先创建,并将A对象标记为正在创建中,然后将创建方法放到三级缓存中(factory)。创建过程中,在填充属性时发现需要B,于是去创建B(尝试从三级缓存中获取,但是获取不到),并将B也标记为正在创建中

  • B创建的时候,在填充属性的时候发现需要A,于是又去创建对象A,尝试从三级缓存中获取A,可以获取到,并将A放入二级缓存,于是B就成功创建了,并放入一级缓存,同时清空二三级缓存,同时返回B

  • 返回B以后,A也成功赋值创建,就完成了A和B的创建。

img

6.doGetBean

AbstractBeanFactory#doGetBean
doGetBean方法被getBean方法调用

该方法刚开始调用 getSingleton(beanName) 方法,该方法内使用三级缓存获取对象,由于该开始并没有对象被创建,所以开始时调用getSingleton(beanName) 方法返回的值为空

/** Cache of singleton objects: bean name to bean instance. */private final Map singletonObjects = new ConcurrentHashMap<>(256);/** Cache of singleton factories: bean name to ObjectFactory. */private final Map> singletonFactories = new HashMap<>(16);/** Cache of early singleton objects: bean name to bean instance. */private final Map earlySingletonObjects = new ConcurrentHashMap<>(16);

img

img

7.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 lockObject 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);}
}

img

img

img

8.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 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;
}

img

img

9.三级缓存的作用

  • 一级缓存:存放的已经经历了完整生命周期的bean
  • 二级缓存:存放早期暴露的对象,bean的生命周期还没有完全结束(属性还没填充完整的),是一个载体,B对象在三级缓存获取到创建A的方法,创建好A后,将A放入二级缓存
  • 三级缓存:存放可以生成bean的工厂,是一个兰姆达函数

三.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 {String OBJECT_TYPE_ATTRIBUTE = "factoryBeanObjectType";@NullableT getObject() throws Exception;@NullableClass getObjectType();default boolean isSingleton() {return true;}
}

5.为什么不用工厂模式用 ioc

本质上来说,IOC是通过反射机制实现的,当我们需求变动的时,工厂模式会需要进行相应的变化。但是Ioc的反射机制允许我们不用重新编译代码,因为它的对象都是动态生成的。

工厂模式属于创建类型模式,本质上就是灵活创建实现了某个接口的不同对象实例,即可以根据需求灵活创建某一类对象。

springioc是为了解决众多对象之间的相互依赖关系,利用反射与动态代理技术通过配置文件或者注解的方式来实例化对象。动态代理通常有jdk自带的InvicationHandler,和第三方代码生成工具cglib。

6.哪些循环依赖解决不了?

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 ProxyProcessorSupportimplements SmartInstantiationAwareBeanPostProcessor, BeanFactoryAware {......
}

4.@Async 增强的 Bean 的循环依赖

描述:

A --> B --> A, 且 A 是被 @Async 标记的类

@Service
public class A {@Autowiredprivate B b;@Asyncpublic void m1(){}
}@Service
public class B {@Autowiredprivate 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

7.为什么@Lazy 注解可以用来解决循环依赖

@Resource与@Autowired的区别在处理@Lazy注解时,被 @Lazy 标记的属性,在 populateBean 注入依赖时,会直接注入一个 proxy 对象。并且,不会触发注入对象的加载。这样的话,就不会产生 bean 的循环加载问题了。

protected Object buildLazyResourceProxy(final LookupElement element, final @Nullable String requestingBeanName) {TargetSource ts = new TargetSource() {@Overridepublic Class getTargetClass() {return element.lookupType;}@Overridepublic boolean isStatic() {return false;}@Overridepublic Object getTarget() {return getResource(element, requestingBeanName);}@Overridepublic 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 容器中存放的对象不是同一个对象!!!

8.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(初始化 后)

9.什么是 beanDefinition?

BeanDefinition 是定义 Bean 的配置元信息接口,包含:

  • Bean 的类名
  • 设置父 bean 名称、是否为 primary、
  • Bean 行为配置信息,作用域、自动绑定模式、生命周期回调、延迟加载、初始方法、销毁方法等
  • Bean 之间的依赖设置,dependencies
  • 构造参数、属性设置

image-20221206160516265

作用是什么 相当于容器中的水

10.@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对象是否在容器中,保持单实例。

四.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 操作与实现

1.Spring 框架一般都是基于 Aspect实现 AOP 操作
什么是 Aspectj

  • Aspect不是 Spring 组成部分,独立 AOP 框架,一般把 Aspect和 Spring 框架一起使用,进行 AOP 操作
  • 基于 AspectJ 实现 AOP 操作
    • 基于 xml 配置文件实现
    • 基于注解方式实现(使用)

切入点表达式写法

pointcut="execution( *..*.*(..))//切到业务层实现类下的所有方法
* cn.luis.service.impl.*.*(..)

6.aop 的 api 常用话术

1.连接点

类里面哪些方法可以被增强,这些方法称为连接点

2.切入点

实际被真正增强的方法,称为切入点

3.通知(增强)

  • 实际增强的逻辑部分称为通知(增强)
  • 通知有多钟类型
    • 前置通知
    • 后置通知
    • 环绕通知
    • 异常通知
    • 最终通知

7.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();}
}

五.事务

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可以进入事务的拦截器
  • 打印日志,会开启一个新的事物

原理:

  1. @EnableTransactionManagement

    1. 利用TransactionManagementConfigurationSelector给容器中会导入组件
    2. 导入两个组件
      • AutoProxyRegistrar
      • ProxyTransactionManagementConfiguration
  2. AutoProxyRegistrar:

    • 给容器中注册一个 InfrastructureAdvisorAutoProxyCreator 组件;

    • InfrastructureAdvisorAutoProxyCreator:

    • 利用后置处理器机制在对象创建以后,包装对象,返回一个代理对象(增强器),代理对象执行方法利用拦截器链进行调用;

  3. ProxyTransactionManagementConfiguration 做了什么?

    • 给容器中注册事务增强器;
      • 事务增强器要用事务注解的信息,AnnotationTransactionAttributeSource解析事务注解
      • 事务拦截器:
        • TransactionInterceptor;
          • 保存了事务属性信息,事务管理器;
          • 他是一个 MethodInterceptor;
          • 在目标方法执行的时候;
            • 执行拦截器链;
            • 事务拦截器:
            1. 先获取事务相关的属性
            2. 再获取PlatformTransactionManager,如果事先没有添加指定任何transactionmanger,最终会从容器中按照类型获取一个PlatformTransactionManager;
            3. 执行目标方法
              • 如果异常,获取到事务管理器,利用事务管理回滚操作;
              • 如果正常,利用事务管理器,提交事务

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来创建代理* proxyTargetClasstrue目标对象实现了接口 – 使用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代理机制

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[] rollbackFor() default {};/*** 和 rollbackFor 作用一样,只是这个地方使用的是类名*/String[] rollbackForClassName() default {};/*** 定义零(0)个或更多异常类,这些异常类必须是Throwable的子类,当方法抛出这些异常的时候,事务不会回滚*/Class[] 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 {@Transactionalpublic 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;@Transactionalpublic void test(){//开启事务// 1、事务管理器新建一个数据库连接conn// 2.conn.autocommit = falsetarget.test(); // 普通对象.test()conn.commit conn.rollback();}
}

不生效写法

可以看到只创建了一个事物ServiceA.test方法的事务,但是a方法却没有被事务增强;

分析原因:Spring事务生成的对象也是被Cglib或JDK代理的对象,就区别于该对象本身了,代理的对象执行方法之前会走拦截器链,就不能同this方法.

之前Aop可以将代理对象暴露到当前线程局部变量中;

“true”/>

通过尝试发现,SpringTx也可以使用该配置,将创建的对象加入到当前线程局部变量;

也许觉得SpringAop和SpringTx不一样啊,但其实两者都实现了AbstractAutoProxyCreator类,同样设置expose-proxy也能生效,绑定到线程局部变量上;

image-20221201142355668

生效写法

确实初始化了a方法的事务;

image-20221201142448606

7.5.数据源没有配置事务管理器

@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource) {return new DataSourceTransactionManager(dataSource);
}

7.6.不支持事务

@Service
public class OrderServiceImpl implements OrderService {@Transactionalpublic 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 {@Transactionalpublic void updateOrder(Order order) {try {// update order} catch {     }}  
}

7.8.抛出异常

这样事务也是不生效的,因为默认回滚的是:RuntimeException,如果你想触发其他异常的回滚,需要在注解上配置一下,如:@Transactional(rollbackFor = Exception.class)
这个配置仅限于 Throwable 异常类及其子类。

@Service
public class OrderServiceImpl implements OrderService {@Transactionalpublic void updateOrder(Order order) {try {// update order} catch {throw new Exception("更新错误");}}  
}

六.注解说明

1.spring 常用注解

image-20221201115611411

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 [] value();
}

value:Condition类型的数组,Condition是一个接口,表示一个条件判断,内部有个方法返回true或false,当所有Condition都成立的时候,@Conditional的结果才成立

Condition接口:
内部有个match方法,判断条件是否成立的。

@FunctionalInterface
public interface Condition {//判断条件是否匹配 context:条件判断上下文boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
}

有两个参数:contextmetadata

  • context:ConditionContext 接口类型的,用来获取容器中的bean的信息
  • metadata:用来获取被@Conditional标注的对象上的所有注解信息。

ConditionContext接口

public interface ConditionContext {//返回bean定义注册器,可以通过注册器获取bean定义的各种配置信息BeanDefinitionRegistry getRegistry();//返回ConfigurableListableBeanFactory类型的bean工厂,相当于一个ioc容器对象@NullableConfigurableListableBeanFactory getBeanFactory();//返回当前spring容器的环境配置信息对象Environment getEnvironment();//返回资源加载器ResourceLoader getResourceLoader();//返回类加载器@NullableClassLoader 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;}@Overridepublic 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,配置类生效.@Beanpublic FilterRegistrationBean getFilterRegistration() {FilterRegistrationBean filterRegistration  = new FilterRegistrationBean(new LoginFilter());filterRegistration.addUrlPatterns("/*");return filterRegistration;}
}

总结:
通过@ConditionalOnProperty控制配置类是否生效,可以将配置与代码进行分离,实现了更好的控制配置.
@ConditionalOnProperty实现是通过havingValue与配置文件中的值对比,返回为true则配置类生效,反之失效.

七.扩展

1.Spring5 的新特性?

  • 整合日志Log4j-2
  • @Nullable注解
  • 函数式注册对象
  • 整合junit5
  • webFlux

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框架的应该很好理解)

image-20221201115837370

3.什么是响应式

目的,响应式编程的目是帮助应用在不同的环境条件和负载下仍保持响应性。

响应式系统需要具备这些特征:及时响应性(Responsive)、恢复性(Resilient)、有弹性(Elastic)以及消息驱动(Message Driven)。我们把具有上面四个特性的系统就叫做响应式系统。

上面的四个特性中,及时响应性(Responsive)是系统最终要达到的目标,恢复性(Resilient)和有弹性(Elastic)是系统的表现形式,而消息驱动(Message Driven)则是系统构建的手段。

4.SpringMVC 和 SpringWebFlux 的对比?

image-20221201114257539

5.spring 中设计模式

  • 简单工厂模式 BeanFactory
  • 工厂方法模式 FactoryBean
  • 单例模式
  • 适配器模式
  • 包装器:一种是类名中含有Wrapper,另一种是类名中含有Decorator
  • 代理模式
  • 观察者模式:当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新
  • 策略模式
  • 模版方法 JdbcTemplate
  • 发布订阅模式

Spring中Observer模式常用的地方是listener的实现。如ApplicationListener

6.SpringMVC 流程

image-20221201115149181

7.Spring Security 实现多重角色鉴权,URL 级别权限管理

基于 RBAC,五表

RBAC 是基于角色访问控制

五表 user user_role role role_resource resource

资源表就是存储 url

8.存在 2 个 bean 名称相同会如何

如果是xml的形式获取bean,则会直接报错,xml不允许定义2个名称相同的bean

如果是注解的方式,不会报错,但是第二个bean不会继续创建,单例bean

单例 bean 里面的多例 bean 只会有一个值

存在相同 id 的 bean 会不会报错,xml 报错,不是 xml 不会报错,但只会初始化一个 bean,第一个 bean

9.什么是 Aot?

JIT(Just-in-Time,实时编译)一直是Java语言的灵魂特性之一,与之相对的AOT(Ahead-of-Time,预编译)方式,似乎长久以来和Java语言都没有什么太大的关系。但是近年来随着Serverless、云原生等概念和技术的火爆,Java JVM和JIT的性能问题越来越多地被诟病,使用Aot可以很大程度上提高编译速度,jar启动更快.

AOT的优点

  • 在程序运行前编译,可以避免在运行时的编译性能消耗和内存消耗

  • 可以在程序运行初期就达到最高性能,程序启动速度快

  • 运行产物只有机器码,打包体积小

AOT的缺点

  • 由于是静态提前编译,不能根据硬件情况或程序运行情况择优选择机器指令序列,理论峰值性能不如JIT
  • 没有动态能力
  • 同一份产物不能跨平台运行

11.SpringWebFlux

  • 新的spring-webflux模块,一个基于reactive的spring-webmvc,完全的异步非阻塞,旨在使用enent-loop执行模型和传统的线程池模型。
  • Reactive说明在spring-core比如编码和解码
  • spring-core相关的基础设施,比如Encode和Decoder可以用来编码和解码数据流;DataBuffer可以使用java ByteBuffer或者Netty ByteBuf;ReactiveAdapterRegistry可以对相关的库提供传输层支持。在spring-web包里包含HttpMessageReade和HttpMessageWrite

12.SpringWebflux中处理器

里面 DispatcherHandler,负责请求的处理。

  • HandlerMapping:请求查询到处理的方法
  • HandlerAdapter:真正负责请求处理
  • HandlerResultHandler:响应结果处理

13.Spring6 新特性?

SpringBoot3 和 Spring6 的最低依赖就是 JDK17,为什么是 JDK17 呢?

主要是因为他是一个 Oracle 官宣可以免费商用的 LTS 版本,所谓 LTS,是 Long Term Support,也就是官方保证会长期支持的版本。

  • AOT支持
  • 虚拟线程
  • 提供基于 @HttpExchange 服务接口的 HTTP 接口客户端
  • 对 RFC 7807 问题详细信息的支持
  • Spring HTTP 客户端提供基于 Micrometer 的可观察性

14.什么是 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% 或者更多。

15.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 {@Overridepublic 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> result = jdbcTemplate.queryForList("SELECT * FROM t_class_table");
//        jdbcTemplate.execute();System.out.println(result);
//        启动SpringBootConfigurableApplicationContext run = SpringApplication.run(AntServiceApplication.class, args);int[] array = {1, 2, 3, 4};run.publishEvent(new MyApplicationEvent(array))}
}

16.ApplicationContext 延迟加载?

两种方式:

  • bean 元素配置 lazy-init=true
  • beans 元素中配置 default-lazy-init=true 让这个 beans 中的所有 bean 延迟实例化

相关内容

热门资讯

埃菲尔铁塔在哪 中国仿建埃菲尔... 2019年4月26日,广西南宁市,街头惊现一座巨型山寨版埃菲尔铁塔,高约20米,白色塔身,造型逼真,...
北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...
苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
长白山自助游攻略 吉林长白山游... 昨天介绍了西坡的景点详细请看链接:一个人的旅行,据说能看到长白山天池全凭运气,您的运气如何?今日介绍...
世界上最漂亮的人 世界上最漂亮... 此前在某网上,选出了全球265万颜值姣好的女性。从这些数量庞大的女性群体中,人们投票选出了心目中最美...
猫咪吃了塑料袋怎么办 猫咪误食... 你知道吗?塑料袋放久了会长猫哦!要说猫咪对塑料袋的喜爱程度完完全全可以媲美纸箱家里只要一有塑料袋的响...
应用未安装解决办法 平板应用未... ---IT小技术,每天Get一个小技能!一、前言描述苹果IPad2居然不能安装怎么办?与此IPad不...
脚上的穴位图 脚面经络图对应的... 人体穴位作用图解大全更清晰直观的标注了各个人体穴位的作用,包括头部穴位图、胸部穴位图、背部穴位图、胳...
demo什么意思 demo版本... 618快到了,各位的小金库大概也在准备开闸放水了吧。没有小金库的,也该向老婆撒娇卖萌服个软了,一切只...
埃菲尔铁塔在哪 中国仿建埃菲尔... 2019年4月26日,广西南宁市,街头惊现一座巨型山寨版埃菲尔铁塔,高约20米,白色塔身,造型逼真,...
北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...
苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
长白山自助游攻略 吉林长白山游... 昨天介绍了西坡的景点详细请看链接:一个人的旅行,据说能看到长白山天池全凭运气,您的运气如何?今日介绍...