SpringBoot2——自动装配(笔记)

POJO :普通的 java

MVC:将软件划分为模型,视图,控制器划分的架构。

  • M:model,模型层。指工程中的 JavaBeanJavaBean :主要用来传递数据,即把一组数据组合成一个类便于传输 )
    • 一类 JavaBean 称为实体类 Bean :存储业务数据(Student,User等)。
    • 一类被称为业务处理 Bean :指的就是 Service 或者 Dao (DataAccessobjectsbase)对象,专用于处理业务逻辑和数据访问
  • V:View,视图层。指工程中的 JSP 或者 HTML 等页面,作用是与用户交互,展示数据。
  • C:Controller,控制层。指工程中的 Servlet,作用是接收请求和相应服务器。即在 VM 之间起到连接作用。

而又有一种经典的三层分层结构。三层架构分为
  • 表述层(或表示层):,表述层表示前台页面和后台 Servlet
  • 业务逻辑层:Servlet传来的数据进行处理的部分,Servlet 一般只是作为一个传递者作用。
  • 数据访问层:该层接收业务逻辑层的指令、数据,对数据库进行增删改查。

SpringMVC:为Spring表达层开发提供一套完备的解决方案。在表述层框架历经Struts、WebWork、Struts2等诸多产品迭代后,的一种普遍使用的表述层开发首选方案。

流程:

1.用户请求
12.响应请求
2.请求查询
3.返回处理器执行链HandlerExecutioinChain
4.请求执行Handler
5.执行Handler
6.返回ModelAndView
7.返回ModelAndView
8.请求解析视图
9.返回View对象
10.渲染视图
11.渲染视图
客户端
前端控制器(DispatcherServlet)
处理器映射器(HandlerMapping)
处理器适配器(HandlerAdapter)
处理器(Handler)
视图解析器(ViewResolver)
Jsp,freemaker

另外:前端控制器 所能处理的请求中,jsp不会被 SpringMVC 处理,因为 jsp 本质就是一种 Servlet ,需要服务器的特殊指定的 Servlet处理。

一、SpringBoot 特点

1.1 依赖管理

parent

maven中的父项目一般都是用来作为依赖管理的。子项目继承了父项目后,依赖将不再需要版本号。

我们查看我们导入的 starter 场景启动器会发现它也是一个继承父类的项目。

本项目的父项目:

通过按住 ctrl 键点击,我们可找到其父项目。

父项目的父项目:

再次点入会发现,其几乎声明了开发中我们常用的jar包版本号。


因此我们的 spring boot 相关依赖,不再需要在关注版本号,这就是它的自动版本仲裁 机制。

如果jar不在范围内,仍然需要些版本依赖。

如果我们想要修改jar包而不用自动仲裁的部分,我们需要在我们 pom.xml 中写入如下代码:

<project>
...
  <properties>
  	...
  	<property>
  	<!--如果我们对MySQL版本不满意
  	 --我们就需要在父类中,找到MySQL对应的key
  	 --然后在我们当前项目中,写入我们想要的版本进行重写
  	 --这是maven的特性,就近使用设置的版本号-->
 	 	<mysql.version>5.1.43</mysql.version>
  	</property>
  </properties>
</project>

starter

我们在项目中还引入了一个依赖:

starter 是一组依赖的集合描述。
一般来说,一个starter的引入,代表着某个场景的全部依赖被导入。

命名规范:

  • 官方 :spring-boot-starter-*

  • 第三方: *-spring-boot-starter

所有场景启动器最底层的依赖(自动配置核心依赖)

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter</artifactId>
    <version>2.6.5</version>
</dependency>

1.2 自动配置

  • 自动配好了 Tomcat
    • 引入 Tomcat 依赖
    • 配置 Tomcat
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-tomcat</artifactId>
    <version>2.6.5</version>
</dependency>
  • 自动配好了MVC

    • 引入了 SpringMVC 全套组件
    • 自动配好了 SpringMVC 常用组件(功能)
  • 配好的 Web 常见功能,如:字符编码

    • SpringBoot 帮助我们配置好了所有 Web 开发的场景
  • 默认的包结构

    • 主程序所在包及其下面的所有子包里面的组件都会被默认扫描进来
    • 无需像以前一样对包扫描进行配置
    • 当然也可以配置扫描范围,通过修改下列代码:
      @SpringBootApplication(scanBasePackages="路径")
      
    • 或者用下列代码指定扫描路径
      @ComponentScan("包路径")
      /*
       *但当你要单独使用时,@ComponentScan与@SpringBootApplication不能同时存在
       *因为其中也含有@ComponentScan,这样会导致重复。
       *此外也不能不写@SpringBootApplication,因此需要将@SpringBootApplication中包含的其它注释也单独写
       */
      
  • 各种配置拥有默认值

    • 默认配置最终都是映射到 MultipartProperties
    • 配置文件的值最终会绑定到某个类上,这个类会在容器中创建对象
  • 按需加载所有自动配置项

    • 非常多的starter 不会同时被配置
    • 引入哪些场景,哪些场景的自动配置才会开启
    • 所有自动配置功能都在
    <dependency>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-autoconfigure</artifactId>
       <version>2.6.5</version>
    </dependency>
    

二、SpringApplication

传入当前启动类,参数args
执行
执行
返回ConfigurableApplicationContext
返回ConfigurableApplicationContext
准备工作
标记好对应的启动类
解析启动类上的注解
创建SpringApplication
执行run核心逻辑
run(new Class< ?>[] { 当前启动类}, args)
new SpringApplication(primarySources).run(args)

2.1SpringApplication的初始化

  • SpringApplication的构造方法
初始化
初始化
初始化
推断当前开发环境
推断当前开发环境
推断当前开发环境
设置初始化器
设置监听器
通过deduceMainApplicationClass()初始化
public SpringApplication(当前启动类,参数args)
resourceLoader
primarySources
webApplicationType
webApplicationType.Reactive
webApplicationType.NONE
webApplicationType.Servlet(默认)
传入(Collection)getSpringFactoriesInstances(ApplicationContextInitializer.class)
传入(Collection)getSpringFactoriesInstances(ApplicationListener.class)
mainApplicationClass
  • deduceMainApplicationClass方法
    通过异常信息(new runtimeException().getStackTrace())判断main函数,找到具体的类,返回。

  • getSpringFactoriesInstances方法

获取类加载器getClassLoader
设置名字集合
=
通过获取到的names,type等数据初始化
return
getSpringFactoriesInstances
ClassLoader classLoader
Set names
new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type,classLoader))
List< T> instances
  • loadFactoryFactoryNames方法
factoryType.getName()
执行
返回Collection< T>
loadFactoryFactoryNames(Class factoryType,@Nullable ClassLoader)
factoryTypeName
loadSpringFactories(classLoader).getOrDefault(factoryTypeName,Collections.emptyList())
  • loadSpringFactories方法
从缓存中读取cache.getName()
读到了
没读到
classLoader.getResources(FACTORIES_RESOURCE_LOCATION)
或者classLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION)
即是
加载资源new UrlResource(url)
载入PropertiesLoderUtils.loadProperties(resource)
通过key寻找合适的资源
放入cache
loadSpringFactories
MultiValueMap< String,String > result
return result
try
Enumeration< URL> urls
FACTORIES_RESOURCE_LOCATION
META-INF/spring.factories
UrlResource resource
Properties properties
result.add(找到的键值对)
返回result

源码

/*
 * 初始化时,这里时,先调用下方代码
 */
public SpringApplication(Class<?>... primarySources) {
	this(null, primarySources);
}
/*
*随后,调用下方代码
*此时resourceLoader为空
*获取
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
	public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
		this.resourceLoader = resourceLoader;
		Assert.notNull(primarySources, "PrimarySources must not be null");
		this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
		this.webApplicationType = WebApplicationType.deduceFromClasspath();
		this.bootstrapRegistryInitializers = new ArrayList<>(
				getSpringFactoriesInstances(BootstrapRegistryInitializer.class));
		setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
		setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
		this.mainApplicationClass = deduceMainApplicationClass();
	}

2.2 Run方法

基础信息

new SpringApplication(primarySources).run(args)
long startTime = System.nanoTime();
记录开始时间
DefaultBootstrapContext bootstrapContext = createBootstrapContext();
默认boot的运行上下文对象,存储当前操作的环境对象、资源对象、属性值等
configureHeadlessProperty()
配置Headless属性
ConfigurableApplicationContext context = null
Application的上下文对象
SpringApplicationRunListeners listeners = getRunListeners(args)
获取监听器,也即是我们之前生成的
listeners.starting(bootstrapContext, this.mainApplicationClass)
观察者模式

源码

ic ConfigurableApplicationContext run(String... args) {
		long startTime = System.nanoTime();
		DefaultBootstrapContext bootstrapContext = createBootstrapContext();
		ConfigurableApplicationContext context = null;
		configureHeadlessProperty();
		SpringApplicationRunListeners listeners = getRunListeners(args);
		listeners.starting(bootstrapContext, this.mainApplicationClass);
	try{
	...
	}
}

try部分

//main参数作为ApplicationArguments
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
//将所有我们获得的boot相关数据封装起来,并存入。方便读取和加载。
ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
configureIgnoreBeanInfo(environment);
//springboot的图标打印
Banner printedBanner = printBanner(environment);
//现阶段Application上下文对象创建
context = createApplicationContext();
context.setApplicationStartup(this.applicationStartup);
/*将我们的boot上下文,application上下文,boot环境,监听器、args参数,springboot的图标
/*prepareContext
 *在其中进行了,
 *将context设置了环境environment
 *然后通过postProcessApplicationContext处理context
 *应用于初始化器中,为其初始化。initializer.initialize(context);
 *告知观察者准备完成listeners.contextPrepared(context)
 *在bootstrapContext中关闭该对象(因为完成了)bootstrapContext.close(context);
 *等操作
 */
/*
 *ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
 *这里获取了beanFactory,并在之后注册了三个BeanFacotryPostProcessor
 * 以及beanFactory在此处被强转为DefaultListableBeanFactory类,进行了相应的配置,方便日后使用
 *Set<Object> sources = getAllSources();
 *我们在application初始化的primarySource(启动类)会被getAllSources()调用,方便之后解析启动类注解
 *load(context, sources.toArray(new Object[0]));
 *其中会创建一个BD(BeanDefinition)加载器,BS保存了Bean的定义信息,属性配置
 *通过加载器加载启动类,匹配类的类型然后加载
 *判断是否有@Component注解(启动类肯定有,不过被包含在其他注解里面了)
 *如果可以找到,会被注册到annotatedReder,完成标识,方便之后调用
 */
prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
/*
 * 有refresh前缀,一定会调用refresh(核心)方法
 * 用于Bean的创建
 * 里面就是Spring部分
 * 见后面
 */
refreshContext(context);
afterRefresh(context, applicationArguments);
Duration timeTakenToStartup = Duration.ofNanos(System.nanoTime() - startTime);
if (this.logStartupInfo) {
	new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), timeTakenToStartup);
}
/*
 * 告知已启动
 */
listeners.started(context, timeTakenToStartup);

/*
 *runners.addAll(context.getBeansOfType(ApplicationRunner.class).values())
 *runners.addAll(context.getBeansOfType(CommandLineRunner.class).values())
 * 执行context中,ApplicationRunner,CommandLineRunner下的对象。此处是取出来,之后通过循环执行它们的run方法,参数就是applicationArguments中的参数
 */
callRunners(context, applicationArguments);


/*记录就绪消耗的时间*/
Duration timeTakenToReady = Duration.ofNanos(System.nanoTime() - startTime);
/*
 * 告知就绪
 */
listeners.ready(context, timeTakenToReady);

2.3 refresh方法

从此处开始,类似于Spring的生成流程。

public void refresh() throws BeansException, IllegalStateException {
		synchronized (this.startupShutdownMonitor) {
			StartupStep contextRefresh = this.applicationStartup.start("spring.context.refresh");

			// Prepare this context for refreshing.
			/*
			 * 内部调用了父类的prepareRefresh()
			 * 设置启动时间
			 * 设置活跃状态
			 * 日志
			 * 初始化资源配置
			 * 获取环境对象
			 * 等操作
			 * 完成准备工作
			 */
			prepareRefresh();
			
			// Tell the subclass to refresh the internal bean factory.
			/*
			 * 获取一个Bean工程(也即是在prepareContext方法中,我们的beanFactory)
			 * 在记录中,其名为DefaultListableBeanFactory
			 */
			ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

			// Prepare the bean factory for use in this context.
			/*
			 * 设置工厂的属性值等
			 */
			prepareBeanFactory(beanFactory);

			try {
				// Allows post-processing of the bean factory in context subclasses.
				/*
				 * 向工厂中添加和设置额外的东西
				 * 为何不一次性增加完?
				 * post一般就是作为扩展预留,方便在以前的基础上增添新的东西
				 * 或作为在基础功能上,作为功能增强(额外)部分,使用时并不一定需要用到
				 * 此外将功能分为多块,方便维护
				 */
				/*
				 *如果只有Spring,此处应该是空方法。
				 */
				postProcessBeanFactory(beanFactory);
				/*
	 			 * Create a new step and marks its beginning.
	 			 * <p>A step name describes the current action or phase. This technical
				 * name should be "." namespaced and can be reused to describe other instances of
				 * the same step during application startup.
	 			 * @param name the step name
	 			 */
	 			 /*
   				  * 创建一个新的步骤,并标记它的开始
   				  */
				StartupStep beanPostProcess = this.applicationStartup.start("spring.context.beans.post-process");
				// Invoke factory processors registered as beans in the context.
				/*
				 * Instantiate and invoke all registered BeanFactoryPostProcessor beans,
				 * 核心,其中处理了注释
				 * 实例化并执行所有注册在beanfactory的BeanFactoryPostProcessor beans
				 */
				invokeBeanFactoryPostProcessors(beanFactory);

				// Register bean processors that intercept bean creation.
				/*
				 * Instantiate and register all BeanPostProcessor beans,
				 * 先向注册beanfactory后实例化所有的BeanPostProcessor bean
				 */
				registerBeanPostProcessors(beanFactory);
				beanPostProcess.end();

				// Initialize message source for this context.
				/*
				 *  国际化操作,i18n
				 */
				initMessageSource();

				// Initialize event multicaster for this context.
				/*
				 * 初始化事件多波器,监听事件的广播
				 */
				initApplicationEventMulticaster();

				// Initialize other special beans in specific context subclasses.
				/*
				 *Spring中应该是空方法
				 * 调用父类的onRefresh,注册主宾信息
				 * 随后 createWebServer(),服务器源码在里面
				 */
				onRefresh();

				// Check for listener beans and register them.
				/*
				 * 注册监听器信息
				 */
				registerListeners();

				// Instantiate all remaining (non-lazy-init) singletons.
				/*
				 * 完成bean对象的实例化
				 * 其中可以找到getBean,createBean,createBeanInstance等方法
				 * 与Spring创建Bean的流程大致一致
				 */
				finishBeanFactoryInitialization(beanFactory);

				// Last step: publish corresponding event.
				finishRefresh();
			}

			catch (BeansException ex) {
				if (logger.isWarnEnabled()) {
					logger.warn("Exception encountered during context initialization - " +
							"cancelling refresh attempt: " + ex);
				}

				// Destroy already created singletons to avoid dangling resources.
				destroyBeans();

				// Reset 'active' flag.
				cancelRefresh(ex);

				// Propagate exception to caller.
				throw ex;
			}

			finally {
				// Reset common introspection caches in Spring's core, since we
				// might not ever need metadata for singleton beans anymore...
				resetCommonCaches();
				contextRefresh.end();
			}
		}

上述为大流程。与Spring类似。

invokeBeanFactoryPostProcessors

protected void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory) {
/*
 *于此处的invokeBeanFactoryPostProcessors在往下翻阅则是实际处理流程
 *篇幅太长,不列出
 */
/*
 * 它要执行所有的BeanFactoryPostProcessors
 * 
 * 虽然很长,但他很大一部分,都是如下流程:
 * ------------------------------
 * 类型X = beanFactory.getBeanNamesForType(XXXXX)|寻找
 * ->for循环遍历类型X中的所有,对每一个判断,为true则添加进currentRegistryProcessors(Bean),processedBeans(名)
 * ->sortPostProcessors|排序currentRegistryProcessors和beanFactory
 * ->registryProcessors|注册currentRegistryProcessors
 * ->invokeBeanDefinitionRegistryPostProcessors|执行它们
 * -- 实际由ConfigurationClassPostProcessor类的postProcessBeanDefinitionRegistry方法处理
 * -- 在postProcessBeanDefinitionRegistry中会执行processConfigBeanDefinitions
 * -- 来扫描,识别注解后,处理相应的注解(尤其是@import注解)
 * ->currentRegistryProcessors.clear();|清空
 * -------------------------
 * 类型有三类,代表着执行顺序
 * 先识别子类接口
 * 再识别父类接口
 * 最后识别无父子类接口
 * 执行
 */

	PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors());

	// Detect a LoadTimeWeaver and prepare for weaving, if found in the meantime
	// (e.g. through an @Bean method registered by ConfigurationClassPostProcessor)
	if (!NativeDetector.inNativeImage() && beanFactory.getTempClassLoader() == null && beanFactory.containsBean(LOAD_TIME_WEAVER_BEAN_NAME)) {
			beanFactory.addBeanPostProcessor(new LoadTimeWeaverAwareProcessor(beanFactory));
			beanFactory.setTempClassLoader(new ContextTypeMatchClassLoader(beanFactory.getBeanClassLoader()));
	}
}

processConfigBeanDefinitions对注解的扫描

此处笔者仅截取部分代码
configCandidates此处是我们的程序的启动类(主类)

	...
	// Return immediately if no @Configuration classes were found
	//没有找到@Configuration就返回
		if (configCandidates.isEmpty()) {
			return;
		}
	// Sort by previously determined @Order value, if applicable
	//如果有@Order,就按照其值排序
		configCandidates.sort((bd1, bd2) -> {
			int i1 = ConfigurationClassUtils.getOrder(bd1.getBeanDefinition());
			int i2 = ConfigurationClassUtils.getOrder(bd2.getBeanDefinition());
			return Integer.compare(i1, i2);
		});
		// Detect any custom bean name generation strategy supplied through the enclosing application context
		//检测通过外围应用程序上下文提供的任何定制bean名称生成策略
		SingletonBeanRegistry sbr = null;
		if (registry instanceof SingletonBeanRegistry) {
			sbr = (SingletonBeanRegistry) registry;
			if (!this.localBeanNameGeneratorSet) {
				BeanNameGenerator generator = (BeanNameGenerator) sbr.getSingleton(
						AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR);
				if (generator != null) {
					this.componentScanBeanNameGenerator = generator;
					this.importBeanNameGenerator = generator;
				}
			}
		}
		//环境为空,创建新的
		if (this.environment == null) {
			this.environment = new StandardEnvironment();
		}
		// Parse each @Configuration class
		//解析每一个有@Configuration的类
		ConfigurationClassParser parser = new ConfigurationClassParser(
				this.metadataReaderFactory, this.problemReporter, this.environment,
				this.resourceLoader, this.componentScanBeanNameGenerator, registry);
		//存储相应的分类
		Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates);
		Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size());
		do {
		//同之前StartupStep的作用
			StartupStep processConfig = this.applicationStartup.start("spring.context.config-classes.parse");
			//这里开始进一步解析
			parser.parse(candidates);
			....
		}
	...

步入parse

public void parse(Set<BeanDefinitionHolder> configCandidates) {
		for (BeanDefinitionHolder holder : configCandidates) {
			BeanDefinition bd = holder.getBeanDefinition();
			try {
				if (bd instanceof AnnotatedBeanDefinition) {
					parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName());
				}
				else if (bd instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) bd).hasBeanClass()) {
					parse(((AbstractBeanDefinition) bd).getBeanClass(), holder.getBeanName());
				}
				else {
					parse(bd.getBeanClassName(), holder.getBeanName());
				}
			}
			catch (BeanDefinitionStoreException ex) {
				throw ex;
			}
			catch (Throwable ex) {
				throw new BeanDefinitionStoreException(
						"Failed to parse configuration class [" + bd.getBeanClassName() + "]", ex);
			}
		}

		this.deferredImportSelectorHandler.process();
	}

再步入parse

protected final void parse(AnnotationMetadata metadata, String beanName) throws IOException {
	/*实际处理扫描
	 *判断是否用了一下注解,有就处理,无就跳过
	 *@PropertySource,@ComponentScan,@Import
	 *处理时,如果是内含有多层的话,就用递归扫描内部
	 *比如@Import,就需要识别里面的包、类
	 */
		processConfigurationClass(new ConfigurationClass(metadata, beanName), DEFAULT_EXCLUSION_FILTER);
	}

三、自动装配流程

自动装配就是让应用程序上下文为你找出依赖项的过程。

标识装配位置:SpringMVC注释
装配过程:Spring
其中的主要类有:

  • BFPP:BeanFactoryProcessor(接口)
  • BPP:BeanPostProcessor
  • BDRPP:BeanDefinitionRegistryPostProcessor(接口,实现了BFPP)

大致流程

  • 当启动 SpringBoot 应用程序的时候,会先创建 SpringApplicationt 的的对象, 在对象的构造方法中进行参数初始化工作。
    • 其中最主要的是判断应用程序的类型以及初始化器和监听器,在整个过程中,会加载 Spring.factories 文件,将文件内容放在缓存对象中,方便获取。
  • SpringApplicationt 对象创建完成后,开始执行 run 方法。其中核心的方法为: prepareContextrefreshContext 。它们完成了自动装配的核心功能。同时也会创建上下文对象,banner的打印,异常报告的准备等其他准备工作,方便以后调用。
    • prepareContext 完成了上下对象的初始化操作(属性值的设置,环境对象)。
      • 其中的 load 方法将当前启动类作为一个 beanDefinition 注册到了 registry ,方便后续在进行 BeanFactoryProcessor调用执行的时候,找到主类,完成对注解的解析工作。(@SpringBootApplication
    • refreshContext 方法中会进行整个容器的刷新过程,完成装配。会调用 Spring 中的 refresh 方法,refresh 中有13个非常关键的方法,来完成整个Spring 应用程序的启动以及刷新。
      • 其中会调用invokeBeanFactoryPostProcessor 方法。主要调用ConfigurationClassPostProcessor 类进行处理和执行(该类实现了BDRPP)。调用时,先调用它中的postProcessBeanDefinitionRegistry,随后调用postProcessBeanFactory。在执行postProcessBeanDefinitionRegistry时,会调用processConfigBeanDefinitions解析各种注解。比如:@PropertySource, @ComponentScan, @ComponentScans, @Bean, @Import(最重要)
      • 在解析 @Import 时,会有getImports的方法。从主类,开始递归解析注解,把所有包含@Import的注解都解析到。随后在processImport方法中,对Import进行分类(主要是在识别时,将AutoConfigurationImportSelect(在其中将指定的一个包下的所有组件进行批量导入) 归属于ImportSelect的子类)后续过程中调用deferredImportSelectorHandler中的process方法,来完成EnableAutoConfiguration的加载。
      • finishBeanFactoryInitialization中完成bean对象的实例化

四、容器功能

Spring 容器看作 beans

4.1 Spring自动装配中的autowire

使用autowire,减少或者消除属性或构造器参数的设置。
propertyref标签过多的问题。

beans中使用default-autowire,设置全局bean
bean中使用autowire,设置当前bean

分为byNamebyType

4.1.1 byName

XML配置文件中beanautowire属性设置为byName

然后,装配时,会寻找与我们类的成员类名称相同(id)的bean中的一个进行匹配和连线。。如果找到匹配项,它将注入这些其中,否则,它将抛出异常。

autowire

<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
    "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
	<bean id="user_1" class="boot.entity.User">
		<property name="name" value="张三"/>
		<property name="age" value="18" />
		<property name="cat" ref="cat" />
	</bean>
	<bean id="cat" class="boot.entity.Pet">
		<property name="name" value="tomcat"/>
	</bean>
</beans>   

如下配置,在其他类中如果有,就会通过名称自动装配。

<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
    "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
	<bean id="user_1" class="boot.entity.User" autowire="byName">
		<property name="name" value="张三"/>
		<property name="age" value="18" />
	</bean>
	<bean id="cat" class="boot.entity.Pet">
		<property name="name" value="tomcat"/>
	</bean>
</beans>   

4.1.2 byType

XML配置文件中beanautowire 属性设置为 byType

然后,装配时,会寻找成员类类型相同(class)的bean中的一个进行匹配和连线。如果找到匹配项,它将注入成员变量,否则,它将抛出异常。

autowire

<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
    "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
	<bean id="user_1" class="boot.entity.User">
		<property name="name" value="张三"/>
		<property name="age" value="18" />
		<property name="cat" ref="cat" />
	</bean>
	<bean id="cat" class="boot.entity.Pet">
		<property name="name" value="tomcat"/>
	</bean>
</beans>   

如下配置,在其他类中如果有,就会通过类型自动装配。

<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
    "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
	<bean id="user_1" class="boot.entity.User" autowire="byType">
		<property name="name" value="张三"/>
		<property name="age" value="18" />
	</bean>
	<bean id="cat" class="boot.entity.Pet">
		<property name="name" value="tomcat"/>
	</bean>
</beans>   

4.1.3 constructor

XML配置文件中beanautowire 属性设置为 constructor

然后,装配时,会寻找与我们构造函数的参数名称相同(id)的bean中的一个进行匹配和连线。如果找到匹配项,则注入,否则,它会抛出异常。

注意,需要在当前类里准备一个构造方法(类似User(Cat cat,String name,int age){this.cat = cat;this.name = name;this.age = age;})

autowire

<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
    "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
	<bean id="user_1" class="boot.entity.User">
		<constructor-arg  ref="cat" />
		<constructor-arg  value="张三" />
		<constructor-arg  value="18" />
	</bean>
	<bean id="cat" class="boot.entity.Pet">
		<property name="name" value="tomcat"/>
	</bean>
</beans>   

如下配置,在其他类中如果有,就会通过类型自动装配。

<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
    "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
	<bean id="user_1" class="boot.entity.User" autowire="byType">
		<constructor-arg  value="张三" />
		<constructor-arg  value="18" />
	</bean>
	<bean id="cat" class="boot.entity.Pet">
		<property name="name" value="tomcat"/>
	</bean>
</beans>   

4.2 组件添加

我们在 boot 包下,创建一个新包 entity ,在准备两个类 UserPet

package boot.entity;

public class User {
	private String name;
	private Integer age;
	
	/*
	 * 构造
	 */
	public User() {
		
	}
	public User(String name,Integer age) {
		this.name = name;
		this.age  = age;
	}
	
	/*
	 * 方法
	 */
	@Override
	public String toString() {
		return "User{name='" + name + "',age=" + age.intValue() + '}';
	}
	
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	
	public Integer getAge() {
		return age;
	}
	public void setAge(Integer age) {
		this.age = age;
	}
}
package boot.entity;

public class Pet {
	private String name;
	
	
	/*
	 * 构造
	 * */
	
	public Pet(){
		
	}
	
	public Pet(String name){
		this.name = name;
	}
	
	
	
	/*
	*方法
	* */
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	@Override
	public String toString() {
		return "Pet{name = '" + name +"'}";
	}
}

4.2.1 使用Spring的装配方式(旧)

也即是使用 xml
如果我们想要将这两个类自动装配,用之前的IOC的方式。我们需要配置 .xml
创建如下位置的文件 bean.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
    "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
	<bean id="user_1" class="boot.entity.User">
		<property name="name" value="张三"/>
		<property name="age" value="18" />
	</bean>
	<bean id="cat" class="boot.entity.Pet">
		<property name="name" value="tomcat"/>
	</bean>
</beans>   
 

创建如下位置的test.java文件

package boot;

import org.springframework.beans.factory.BeanFactory;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import boot.entity.Pet;
import boot.entity.User;

public class test {
	public static void main(String[] args) {
		BeanFactory beanFactory = new ClassPathXmlApplicationContext("/beans.xml"); 
		User user = (User)beanFactory.getBean("user_1");
		System.out.println(user);
		Pet pet = (Pet)beanFactory.getBean("cat");
		System.out.println(pet);
	}
}

如今更多的使用Config 方式。即通过注释与设置类的方式。

4.2.2 @Configuration

boot 包下,创建 config

package boot.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import boot.entity.Pet;
import boot.entity.User;

/*
 * @Configuration
 * 告知SpringBoot这是一个配置类 == 配置文件
 * */
@Configuration
public class MyConfig {
	/*
	 * 给容器添加组件,以方法名作为组件id,返回值为组件类型
	 * */
	@Bean//此处组件名为User01
	public User User01() {
		return new User("张三 ",18);
	}
	@Bean("Tom")//此处组件名为Tom
	public Pet tomcat() {
		return new Pet("tomcat");
	}
}
package boot;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;

import boot.config.MyConfig;
import boot.entity.Pet;
import boot.entity.User;

/*
 *
 *@SpringBootApplication:告知这是一个springboot应用
   *主程序类
 */
@SpringBootApplication
public class MainApplication {
	public static void main(String[] args) {
		//1.SpringApplication.run会返回我们的IOC容器
		ConfigurableApplicationContext run = SpringApplication.run(MainApplication.class, args);
		
		//2.查看容器里面的组件
		String[] names = run.getBeanDefinitionNames();
		for(String name : names) {
			System.out.println(name);
		}
		
		
		//3.从容器中获取组件
		//此处你会发现,它默认时单例
		Pet tom_01 = run.getBean("Tom",Pet.class);
		Pet tom_02 = run.getBean("Tom",Pet.class);
		System.out.println("组件 tom_01 == tom_02?" + (tom_01==tom_02));
		
		User user_01 = run.getBean("User01",User.class);
		User user_02 = run.getBean("User01",User.class);
		System.out.println("组件 user_01 == user_02?" + (user_01==user_02));
		
		//获取配置类
		//配置类同时也是组件
		MyConfig bean = run.getBean(MyConfig.class);
		System.out.println(bean);
		/*
		*获取组件方法可写为
		*User user_01 = bean.User01();
		*Pet tom_01 = bean.tomcat();
		**/
	}
}
与SpringBoot1 不同

在SpringBoot2,多出了
@Configuration(proxyBeanMethods = true)
proxyBeanMethods :代理bean的方法

默认为true:
它使得,外部无论对配置类中的这个组件注册方法调用多少次,都是单实例对象。
即使用代理对象调用方法,有该代理对象则直接调用,无则创建。以此保持组件单实例。

改为false后,不再相等。

因此此多出了
Full (proxyBeanMethods = true):全模式
Lite (proxyBeanMethods = false):轻量级模式

与此相关的就有组件依赖问题。
Spring 三级缓存解决单例下的循环依赖)

4.2.3 @Component

在类路径扫描期间, SpringBoot 找到装饰有 @Component 的Java 类,并在上下文中注册为 Bean。我们不在设置类中通过 @Bean 去添加它们。

也就是说被标识后,还需要被扫描后,才能作为一个组件被管理。

同时默认也是单实例。可以用 @Scope修改

package boot.entity;


import org.springframework.stereotype.Component;

@Component
/*
 * 设置作用域,默认也是单实例
 * @Scope("prototype"),为原型,即每次会创建新的对象
 */
@Scope("singleton")//单实例
public class Pet {

	private String name;
	

	
	/*
	 * 构造
	 * */
	
	public Pet(){
		
	}

	public Pet(String name){
		this.name = name;
	}
	
	
	
	/*
	*方法
	* */
	
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	@Override
	public String toString() {
		return "Pet{name = '" + name +"'}";
	}
}

package boot.entity;


import org.springframework.stereotype.Component;

@Component
public class User {
	private String name;
	private Integer age;
	
	public User() {
		
	}
	public User(String name,Integer age) {
		this.name = name;
		this.age  = age;
	}
	/*
	 * 方法
	 */
	@Override
	public String toString() {
		return "User{name='" + name + "',age=" + age.intValue() + '}';
	}
	
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	
	public Integer getAge() {
		return age;
	}
	public void setAge(Integer age) {
		this.age = age;
	}
}

4.2.4 @Autowired

@Autowired可以用于单独注释setter,构造函数,成员变量、有任意名称和多个参数的方法等。或者混合注释成员变量和构造函数。

当将 @Autowired 注解添加到需要该类型数组的字段或方法,则会从 (Configuration)ApplicationContext中搜寻符合指定类型的所有bean

有四个模式:

  • byName
  • byType
  • constructor
  • autodectect:自省机制,判断用constructor还是byType

@Autowired(required=true)

内部有一个required,默认为true。表示解析被标记的字段或方法,一定有对应的bean存在。为false时,没有对应的bean存在不会报错。

优先用byType,但需要多个相同类的实例时,再用byName细分。

推断构造方法
  • 在有多个构造方法时,且没有用**@Autowired**标注用哪一个,会优先用无参构造。
  • 在有多个构造方法时,且没有用**@Autowired**标注用哪一个时,且无无参构造时,会报错。
  • 在只有一个构造函数时,且没有用**@Autowired**标注用哪一个时,使用唯一的构造函数
  • 在有**@Autowired**标注时,但标注了多个构造方法,也会报错。

4.2.5 @Controller

用于标注控制层组件。

SpringMVC虽然有前端控制器对请求统一处理了,但是需要创建处理具体请求的类请求控制器

请求控制器中的每一个处理请求的方法,成为了控制器方法。

因为 SpringMVC 的控制器由一个POJO担任,因此需要通过 @Controller 注解将其标识为一个控制层组件,交由IoC容器管理

被标识且被扫描后,才能作为一个组件管理。

4.2.6 @Service

@Service(value = “”),不填value则是用默认命名方式。标注在类上,用于标注业务层组件。

并将创建好的对象直接注入给Action(Controller)使用。
通过从容器中获取的方式使用。默认命名,需要XX.class方式。

getBean("XX");
getBean(XX.class)

被标识且被扫描后,才能作为一个组件管理。

4.2.7 @Repository

@Repository(value=“”),不填value则是用默认命名方式。注释在类上,用于标注数据访问组件,即DAO组件。

Service层使用@Resource注解(标注在成员类上)告诉SpringBoot,然后它把创建好的Dao注入给Service即可。
也可以@Resource(name=“”)用于指定具体的实例。

使用方式都是

被标识且被扫描后,才能作为一个组件管理。

4.2.8 @Required

注解应用于 bean 属性的 setter 方法,它表明受影响的bean属性在配置时必须放在 XML配置文件中,否则容器就会抛出一个BeanInitializationException异常。

4.2.9 @Qualifier

当创建多个具有相同类型的bean时,我们想要指定当前的一个成员变量用哪一个实例时,我们 同时使用 @Autowired@Qualifier 来指定实例名。
因此,某种意义上,@Qualifier(“id”),就是byName

4.2.10 @Import

@Import(User.class,Cat.class)

通过上述方式,给容器中自动创建出这两个类型的组件。名字默认为全类名。

4.2.11 @Conditional

满足指定条件则装配。

@Conditional

(value = { null })

比如

package boot.config;

import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import boot.entity.Pet;
import boot.entity.User;


@Configuration(proxyBeanMethods = false)
public class MyConfig {
	@Bean
	public Pet cat() {
		return new Pet("tomcat");
	}
	@ConditionalOnBean(name = "cat")//有叫cat的Bean时,我们才装配user1
	@Bean
	public User user1() {
		
		return new User("zhangsan",18);
	}
	

}

package boot;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.web.filter.FormContentFilter;

import boot.entity.Pet;
import boot.entity.User;

@SpringBootApplication
public class MainApplication {
	public static void main(String[] args) {
		ConfigurableApplicationContext run = SpringApplication.run(MainApplication.class, args);
		boolean user = run.containsBean("user1");
		boolean cat = run.containsBean("cat");
		
		System.out.println(user);
		System.out.println(cat);
	}
}

4.3 原生文件导入

用于引入xml文件。
@ImportResource(“classpath:beans.xml”)//在resources下

4.4 配置绑定

Properties代码绑定到JavaBean中。

4.4.1 @Component+@ConfigurationProperties

applicatio.properties里,添加如下配置

user2.name=xiaoming
user2.age=20

User类

package boot.entity;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@Component
@ConfigurationProperties(prefix="user2")//查找前缀为user2的配置
public class User {
	private String name;
	private Integer age;
	
	public User() {
		
	}
	public User(String name,Integer age) {
		this.name = name;
		this.age  = age;
	}
	/*
	 * 方法
	 */
	@Override
	public String toString() {
		return "User{name='" + name + "',age=" + age.intValue() + '}';
	}
	
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	
	public Integer getAge() {
		return age;
	}
	public void setAge(Integer age) {
		this.age = age;
	}
}


再用一个Controller

package boot.controller;




import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import boot.entity.User;


@Controller
public class test {
	@Autowired
	User user;
	
	@ResponseBody
	@RequestMapping("/")
	public String testFwd(){
		return user.toString();		
	}

}

4.4.2@EnableConfigurationProperties+@ConfigurationProperties

package boot.config;


import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Configuration;

import boot.entity.User;



@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(User.class)//开启User的组件配置功能
public class MyConfig {

	

}


package boot.entity;

import org.springframework.boot.context.properties.ConfigurationProperties;

@ConfigurationProperties(prefix="user2")//查找前缀为user2的配置
public class User {
	private String name;
	private Integer age;
	
	public User() {
		
	}
	public User(String name,Integer age) {
		this.name = name;
		this.age  = age;
	}
	/*
	 * 方法
	 */
	@Override
	public String toString() {
		return "User{name='" + name + "',age=" + age.intValue() + '}';
	}
	
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	
	public Integer getAge() {
		return age;
	}
	public void setAge(Integer age) {
		this.age = age;
	}
}

package boot.controller;




import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import boot.entity.User;


@Controller
public class test {
	@Autowired
	User user;
	
	@ResponseBody
	@RequestMapping("/")
	public String testFwd(){
		return user.toString();		
	}

}

参考资源

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值