ApplicationContext接口相当于负责bean的初始化、配置和组装的IoC容器.
Spring为ApplicationContext提供了一些开箱即用的实现, 独立的应用可以使用
ClassPathXmlApplicationContext或者FileSystemXmlApplicationContext,web应用在web.xml配置监
听,提供xml位置和org.springframework.web.context.ContextLoaderListener即可初始化
WebApplicationContextIoC容器.
配置元数据配置了应用中实例的实例化、配置以及组装的规则,SpringIoC容器通过此配置进行管理
Bean. 配置元数据有以下几种方式:
基于XML配置: 清晰明了,简单易用
基于Java代码配置:无xml,通过 @Configuration 来声明配置、对象实例化与依赖关系
基于Java注解配置:少量的XML( context:annotation-config/ ),通过注解声明实例化类与依赖关系
后续的分析基于XML配置, 与Java代码和注解大体上的机制是一样
实例化容器非常简单,只需要提供本地配置路径或者根据 ApplicationContext 的构造器提供相应的资
源(Spring的另一个重要抽象)即可.
ApplicationContext context = new
ClassPathXmlApplicationContext("application.xml");
![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/b6362593aff34496b26923832bf635f1.png)
refresh()方法的实现代码如下
public void refresh() throws BeansException, IllegalStateException {
synchronized(this.startupShutdownMonitor) {
this.prepareRefresh(); // 准备工作
ConfigurableListableBeanFactory beanFactory =
this.obtainFreshBeanFactory(); // 获取ConfigurableListableBeanFactory最终的目的是
DefaultListableBeanFactory
this.prepareBeanFactory(beanFactory); // 准备bean工厂
try {
this.postProcessBeanFactory(beanFactory); // 一个空的实现,注意这里的
spring版本号为:5.3x
this.invokeBeanFactoryPostProcessors(beanFactory); // 注册bean的工厂
this.registerBeanPostProcessors(beanFactory);
this.initMessageSource(); // Spring 从所有的 @Bean 定义中抽取出来了
BeanPostProcessor,然后都注册进 beanPostProcessors,等待后面的的顺序调用 注册
BeanPostProcessor
this.initApplicationEventMulticaster(); // 初始化事件监听多路广播器
this.onRefresh(); // 一个空的实现
this.registerListeners(); // 注册监听器
this.finishBeanFactoryInitialization(beanFactory); // 到了spring加载流
程最复杂的一步,开始实例化所有的bd
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();
}
}
}
loadBeanDefinitions:
ConfigurableListableBeanFactory beanFactory = this.obtainFreshBeanFactory();
this.refreshBeanFactory();
this.loadBeanDefinitions(beanFactory);
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory)
throws BeansException, IOException {
XmlBeanDefinitionReader beanDefinitionReader = new
XmlBeanDefinitionReader(beanFactory);
beanDefinitionReader.setEnvironment(this.getEnvironment());
beanDefinitionReader.setResourceLoader(this);
beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));
this.initBeanDefinitionReader(beanDefinitionReader);
this.loadBeanDefinitions(beanDefinitionReader);
}
1、传统的构建方式
我们沟通SpringMVC+SpringFramework来构建一个web项目,过程如下
创建一个maven-webapp项目
添加jar包依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
spring-context
spring-context-support
spring-core
spring-expression
spring-web
spring-webmvc
修改web.xml文件
<context-param><!--配置上下文配置路径-->
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml</param-value>
</context-param>
<!--配置监听器-->
<listener>
<listenerclass>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<listener>
<listener-class>org.springframework.web.util.IntrospectorCleanupListener</listener-class>
</listener>
<!--配置Spring MVC的请求拦截-->
<servlet>
<servlet-name>springmvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:dispatcher-servlet.xml</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>springmvc</servlet-name>
<url-pattern>/</url-patter>
</servlet-mapping>
在resources目录下添加dispatcher-servlet.xml文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 扫描 controller -->
<context:component-scan base-package="com.mashibingedu.controller" />
<!--开启注解驱动-->
<mvc:annotation-driven/>
<!-- 定义视图解析器 -->
<bean
class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/"/>
<property name="suffix" value=".jsp"/>
</bean>
创建一个Controller
@Controller
public class HelloController {
@RequestMapping(method = RequestMethod.GET,path = "/index")
public String index(Model model){
model.addAttribute("key","Hello mashibing");
return "index";
}
}
修改默认的index.jsp,设置el表达式的解析
<%@ page language="java" contentType="text/html; charset=utf-8"
pageEncoding="utf-8" isELIgnored="false" %>
${key}
运行项目
2、理解SpringBoot
Spring Boot被官方定位为“BUILD ANYTHING”,Spring Boot官方的概述是这么描述Spring Boot的。
Spring Boot makes it easy to create stand-alone, production-grade Spring based
Applications that you can "just run".
// 通过Spring Boot可以轻松的创建独立的、生产级别的基于Spring 生态下的应用,你只需要运行即可。
We take an opinionated view of the Spring platform and third-party libraries so
you can get started with minimum fuss. Most Spring Boot applications need
minimal Spring configuration.
//对于Spring平台和第三方库,我们提供了一个固化的视图,这个视图可以让我们在构建应用时减少很多麻
烦。大部分spring boot应用只需要最小的Spring 配置即可
3、理解约定优于配置
约定优于配置是一种软件设计的范式,主要是为了减少软件开发人员需做决定的数量,获得简单的好
处,而又不失灵活性。
简单来说,就是你所使用的工具默认会提供一种约定,如果这个约定和你的期待相符合,就可以省略那些基础的配置,否则,你就需要通过相关配置来达到你所期待的方式。
约定优于配置有很多地方体现,举个例子,比如交通信号灯,红灯停、绿灯行,这个是一个交通规范。你可以在红灯的时候不停,因为此时没有一个障碍物阻碍你。但是如果大家都按照这个约定来执行,那么不管是交通的顺畅度还是安全性都比较好。
而相对于技术层面来说,约定有很多地方体现,比如一个公司,会有专门的文档格式、代码提交规范、接口命名规范、数据库规范等等。这些规定的意义都是让整个项目的可读性和可维护性更强。
4、Spring Boot Web应用中约定优于配置的体现
那么在前面的案例中,我们可以思考一下,Spring Boot为什么能够把原本繁琐又麻烦的工作省略掉呢?实际上这些工作并不是真正意义上省略了,只是Spring Boot帮我们默认实现了。
而这个时候我们反过来思考一下,Spring Boot Web应用中,相对Spring MVC框架的构建而言,它的约定优于配置体现在哪些方面呢?
Spring Boot的项目结构约定,Spring Boot默认采用Maven的目录结构,其中
src.main.java 存放源代码文件
src.main.resource 存放资源文件
src.test.java 测试代码
src.test.resource 测试资源文件
target 编译后的class文件和jar文件
内置了嵌入式的Web容器,在Spring 2.2.6版本的官方文档中3.9章节中,有说明Spring Boot支持
四种嵌入式的Web容器
Tomcat
Jetty
Undertow
Reactor
Spring Boot默认提供了两种配置文件,一种是application.properties、另一种是
application.yml。Spring Boot默认会从该配置文件中去解析配置进行加载。
Spring Boot通过starter依赖,来减少第三方jar的依赖。
这些就是Spring Boot能够方便快捷的构建一个Web应用的秘密。当然Spring Boot的约定优于配置还不
仅体现在这些地方,在后续的分析中还会看到Spring Boot中约定优于配置的体现。
5、Import注解
import注解是什么意思呢? 联想到xml形式下有一个 形式的注解,就明白它的作
用了。import就是把多个分来的容器配置合并在一个配置中。在JavaConfig中所表达的意义是一样的。
创建一个包,并在里面添加一个单独的configuration
public class DefaultBean {
}
@Configuration
public class SpringConfig {
@Bean
public DefaultBean defaultBean(){
return new DefaultBean();
}
}
此时运行测试方法,
public class MainDemo {
public static void main(String[] args) {
ApplicationContext ac=new
AnnotationConfigApplicationContext(SpringConfig.class);
String[] defNames=ac.getBeanDefinitionNames();
for(String name:defNames){
System.out.println(name);
}
}
}
在另外一个包路径下在创建一个配置类。此时再次运行前面的测试方法,打印OtherBean实例时,
这个时候会报错,提示没有该实例
public class OtherBean {
}
@Configuration
public class OtherConfig {
@Bean
public OtherBean otherBean(){
return new OtherBean();
}
}
修改springConfig,把另外一个配置导入过来
@Import(OtherConfig.class)
@Configuration
public class SpringConfig {
@Bean
public DefaultBean defaultBean(){
return new DefaultBean();
}
}
再次运行测试方法,即可看到对象实例的输出。
6、深入分析EnableAutoConfiguration
EnableAutoConfiguration的主要作用其实就是帮助springboot应用把所有符合条件的@Configuration
配置都加载到当前SpringBoot创建并使用的IoC容器中。
再回到EnableAutoConfiguration这个注解中,我们发现它的import是这样
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
但是从EnableAutoCOnfiguration上面的import注解来看,这里面并不是引入另外一个Configuration。
而是一个ImportSelector。这个是什么东西呢?
7、AutoConfigurationImportSelecto
Enable注解不仅仅可以像前面演示的案例一样很简单的实现多个Configuration的整合,还可以实现一些
复杂的场景,比如可以根据上下文来激活不同类型的bean,@Import注解可以配置三种不同的class
public class CacheService {
}
LoggerService
public class LoggerService {
}
EnableDefineService
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited --允许被继承
@Import({MyDefineImportSelector.class})
public @interface EnableDefineService {
String[] packages() default "";
}
MyDefineImportSelector
public class MyDefineImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
//获得指定注解的详细信息。我们可以根据注解中配置的属性来返回不同的class,
//从而可以达到动态开启不同功能的目的
annotationMetadata.getAllAnnotationAttributes(EnableDefineService.class.getName(
),true)
.forEach((k,v) -> {
log.info(annotationMetadata.getClassName());
log.info("k:{},v:{}",k,String.valueOf(v));
});
return new String[]{CacheService.class.getName()};
}
}
EnableDemoTest
@SpringBootApplication
@EnableDefineService(name = "mashibing",value = "mashibing")
public class EnableDemoTest {
public static void main(String[] args) {
ConfigurableApplicationContext
ca=SpringApplication.run(EnableDemoTest.class,args);
System.out.println(ca.getBean(CacheService.class));
System.out.println(ca.getBean(LoggerService.class));
}
}
了解了selector的基本原理之后,后续再去分析AutoConfigurationImportSelector的原理就很简单了,
它本质上也是对于bean的动态加载。