Spring 基础知识、执行流程、源码分析和设计思想

目录

Spring 基础

Spring 体系结构

Core (IoC & DI)

Bean

AOP

Spring Boot  原理解析和 SpringApplication 执行流程

spring-boot-starter 原理

手写一个 starter

手写 Spring 核心代码


官方文档:https://docs.spring.io/spring-framework/docs/current/reference/html/

Spring 基础

Spring 体系结构

Core Container核心容器包括

  • Core核心模块(IoC和DI功能)
  • Beans模块(提供BeanFactory,Spring把管理对象称为Bean)
  • Context上下文模块(访问定义和配置的任务对象的媒介ApplicationContext)
  • SpEL模块(运行时查询和操作对象的强大的表达式语言)

AOP:运行定义方法拦截器和切入点。

AspectJ 是一个功能强大且成熟的面向切面编程(AOP)框架。

ORM:对象关系映射(Object Relational Mapping,简称ORM)。

Core (IoC & DI)

从获取对象的方式来进行比较:

传统的方式: 

通过new 关键字主动创建一个对象

IOC方式

对象的生命周期由Spring来管理,直接从Spring那里去获取一个对象。 IOC是控制反转 (Inversion Of Control)的缩写,控制反转是把控制权交出去,交给Spring,通过IOC方式松耦合。

传统方式导致了硬编码耦合。

入门实例:

1.新建一个Java project,新建一个com.cxf.pojo包,Category 类

package com.cxf.pojo;

public class Category {
    private int id;
    private String name;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;

    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

2.新建一个配置文件applicationContext.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:aop="http://www.springframework.org/schema/aop"
    xmlns:tx="http://www.springframework.org/schema/tx"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="
   http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
   http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
   http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
   http://www.springframework.org/schema/context/spring-context-3.0.xsd">

  <bean name="c" class="com.cxf.pojo.Category">
      <property name="name" value="category 1"/>
  </bean>    

</beans>

对Category的name属性注入了"category 1"字符串。

3.新建一个测试类TestSpring

package com.cxf.test;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import com.cxf.pojo.Category;

public class TestSpring {

    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext(new String[]{"applicationContext.xml"});
        Category c = (Category)context.getBean("c");
        System.out.println(c.getName());
    }

}

DI (Dependency Injection)

IoC是一种设计思想,主要是为了解耦,DI是这种思想的一种实现方式。依赖注入是指在启动Spring容器加载 bean 配置的时候,完成对变量的赋值行为。

依赖注入通常有两种方式:

  • 设值注入:property相当于bean的成员变量,ref自动调用 setter(set方法)对属性进行赋值。
  • 构造器注入

eg:

(1)第一步在XML里分别用两种方式写:

(2)第二步在Java里分别用两种方式写:

Bean

Bean的作用域

(这个模块后续再整理)

AOP

aspect-oriented programming

(这个模块后续再整理)

Spring Boot  原理解析和 SpringApplication 执行流程

将普通类交给 Spring 容器管理通常有2种方法:

(1)使用 @Configuration,@Bean,@Controller,@Service,@Repository,@Component注解,然后启用 @ComponentScan 自动扫描。

(2)使用 @Import 。

Spring Boot 入口类 @SpringBootApplication 和 main 方法中 SpringApplication.run方法的解析:

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(
    excludeFilters = {@Filter(
    type = FilterType.CUSTOM,
    classes = {TypeExcludeFilter.class}
), @Filter(
    type = FilterType.CUSTOM,
    classes = {AutoConfigurationExcludeFilter.class}
)}
)
public @interface SpringBootApplication {
    // 省略代码
}

@SpringBootConfiguration 是对 @Configuration 的简单“包装”,它就是Java Config形式的Spring IoC 容器的配置类:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {

}

@EnableAutoConfiguration:

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {

}

@AutoConfigurationPackage:

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import({Registrar.class})
public @interface AutoConfigurationPackage {
    String[] basePackages() default {};

    Class<?>[] basePackageClasses() default {};
}

Registrar:

@Order(Ordered.HIGHEST_PRECEDENCE)
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {

    @Override
    public void registerBeanDefinitions(AnnotationMetadata metadata,
            BeanDefinitionRegistry registry) {
        register(registry, new PackageImport(metadata).getPackageName());
    }

    @Override
    public Set<Object> determineImports(AnnotationMetadata metadata) {
        return Collections.<Object>singleton(new PackageImport(metadata));
    }

}

 @Import:

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Import {
    Class<?>[] value();
}

AutoConfigurationImportSelector:

    protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
        List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader());
        Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you are using a custom packaging, make sure that file is correct.");
        return configurations;
    }

从 spring.factories 中可以看到: 

@Import 注解的主要作用就是借助 AutoConfigurationImportSelector 将 Spring Boot 应用所有符合条件的 @Configuration 配置都加载到当前 Spring Boot 创建并使用的 IoC 容器中,IoC 容器就是Spring 应用程序上下文 ApplicationContext。

Spring 框架提供了很多 @Enable 开头的注解定义,比如 @EnableScheduling,@EnableCaching,@EnableAsync 等,这些注解的共同点是借助 @Import 收集和注册特定场景相关的 bean定义。

@ComponentScan:启动组件扫描,组件或bean定义能被自动发现并注入Spring应用程序上下文,@Controller,@Service,@Component,@Repository等都可以被扫描到。

SpringApplication.run 的run底层代码是new SpringApplication后再调用run:

	public ConfigurableApplicationContext run(String... args) {
		long startTime = System.nanoTime();
		DefaultBootstrapContext bootstrapContext = createBootstrapContext();
        // 注意是ConfigurableApplicationContext
		ConfigurableApplicationContext context = null;
		configureHeadlessProperty();
        // 开启SpringApplicationRunListeners监听器
		SpringApplicationRunListeners listeners = getRunListeners(args);
		listeners.starting(bootstrapContext, this.mainApplicationClass);
		try {
			ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
			ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
			configureIgnoreBeanInfo(environment);
			Banner printedBanner = printBanner(environment);
            // 创建应用上下文
			context = createApplicationContext();
			context.setApplicationStartup(this.applicationStartup);
            // 准备上下文
			prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
            // 刷新上下文
			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);
			callRunners(context, applicationArguments);
		}
		catch (Throwable ex) {
			handleRunFailure(context, ex, listeners);
			throw new IllegalStateException(ex);
		}
		try {
			Duration timeTakenToReady = Duration.ofNanos(System.nanoTime() - startTime);
			listeners.ready(context, timeTakenToReady);
		}
		catch (Throwable ex) {
			handleRunFailure(context, ex, null);
			throw new IllegalStateException(ex);
		}
		return context;
	}

创建、准备、刷新 ConfigurableApplicationContext,通过上下文加载应用所需的类和各种环境配置等,最后启动应用实例。

查询和加载所有的 SpringApplicationRunListener 监听器(通过 SpringFactoriesLoader 加载 META-INF/spring.factories 文件)。

SpringApplicationRunListener 规定了 Spring Boot 的生命周期,在各个生命周期广播响应的事件(ApplicationEvent),实际调用的是 ApplicationListener 类:

 

ApplicationListener 是 Spring 框架的监听器模式的一种实现。

@FunctionalInterface
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {
    void onApplicationEvent(E event);

    static <T> ApplicationListener<PayloadApplicationEvent<T>> forPayload(Consumer<T> consumer) {
        return (event) -> {
            consumer.accept(event.getPayload());
        };
    }
}

prepareContext时:

创建和准备应用要使用的 Environment,包括配置要使用的 PropertySource 和 Profile。

ApplicationContextInitializer 类的 initialize方法对应用程序上下文进行设置和处理。

@FunctionalInterface
public interface ApplicationContextInitializer<C extends ConfigurableApplicationContext> {
    void initialize(C applicationContext);
}

在调用 ApplicationContext 的 refresh 方法使 IoC容器达到可用状态后,查找当前上下文是否注册有 ApplicationRunner 与 CommandLineRunner,从 SpringApplicationRunListener 类的 started 方法的注释中可以看出: 

void run(ApplicationArguments args) throws Exception; // ApplicationRunner
void run(String... args) throws Exception; // CommandLineRunner
ApplicationRunner 和 CommandLineRunner 都是run方法,区别就是入参不一样。

如果想在 Spring Boot 启动时运行一些特定代码,可以实现 implements ApplicationRunner 或 CommandLineRunner 接口。

以上描述的完整流程如下:

另外可借用网上的其他图辅助理解: 

spring-boot-starter 原理

起步依赖本质上是一个Maven项目对象模型,定义了对其他库的传递依赖。

举个例子,mybatis-spring-boot-starter的pom.xml是:

<?xml version="1.0" encoding="UTF-8"?>
<!--

       Copyright 2015-2017 the original author or authors.

       Licensed under the Apache License, Version 2.0 (the "License");
       you may not use this file except in compliance with the License.
       You may obtain a copy of the License at

          http://www.apache.org/licenses/LICENSE-2.0

       Unless required by applicable law or agreed to in writing, software
       distributed under the License is distributed on an "AS IS" BASIS,
       WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
       See the License for the specific language governing permissions and
       limitations under the License.

-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <parent>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot</artifactId>
    <version>1.3.1</version>
  </parent>
  <artifactId>mybatis-spring-boot-starter</artifactId>
  <name>mybatis-spring-boot-starter</name>
  <dependencies>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter</artifactId>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-jdbc</artifactId>
    </dependency>
    <dependency>
      <groupId>org.mybatis.spring.boot</groupId>
      <artifactId>mybatis-spring-boot-autoconfigure</artifactId>
    </dependency>
    <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis</artifactId>
    </dependency>
    <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis-spring</artifactId>
    </dependency>
  </dependencies>
</project>

MybatisAutoConfiguration的源代码:

@Configuration
@ConditionalOnClass({SqlSessionFactory.class, SqlSessionFactoryBean.class})
@ConditionalOnBean({DataSource.class})
@EnableConfigurationProperties({MybatisProperties.class})
@AutoConfigureAfter({DataSourceAutoConfiguration.class})
public class MybatisAutoConfiguration {
    private static final Logger logger = LoggerFactory.getLogger(MybatisAutoConfiguration.class);
    private final MybatisProperties properties;
    private final Interceptor[] interceptors;
    private final ResourceLoader resourceLoader;
    private final DatabaseIdProvider databaseIdProvider;
    private final List<ConfigurationCustomizer> configurationCustomizers;

    public MybatisAutoConfiguration(MybatisProperties properties, ObjectProvider<Interceptor[]> interceptorsProvider, ResourceLoader resourceLoader, ObjectProvider<DatabaseIdProvider> databaseIdProvider, ObjectProvider<List<ConfigurationCustomizer>> configurationCustomizersProvider) {
        this.properties = properties;
        this.interceptors = (Interceptor[])interceptorsProvider.getIfAvailable();
        this.resourceLoader = resourceLoader;
        this.databaseIdProvider = (DatabaseIdProvider)databaseIdProvider.getIfAvailable();
        this.configurationCustomizers = (List)configurationCustomizersProvider.getIfAvailable();
    }

    @PostConstruct
    public void checkConfigFileExists() {
        if (this.properties.isCheckConfigLocation() && StringUtils.hasText(this.properties.getConfigLocation())) {
            Resource resource = this.resourceLoader.getResource(this.properties.getConfigLocation());
            Assert.state(resource.exists(), "Cannot find config location: " + resource + " (please add config file or check your Mybatis configuration)");
        }

    }

    @Bean
    @ConditionalOnMissingBean
    public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
        // 省略其他代码
    }
    
    // 省略其他代码
}

 DataSourceProperties 定义了数据源的各个属性,且使用 @ConfigurationProperties 指定了配置文件的前缀:

 @ConfigurationProperties 的作用是把yml或properties文件转化为Bean。

手写一个 starter

Spring Boot 提供的 starter 都是以 spring-boot-starter-xxx 的方式命名的,自定义的 starter 官方建议以 xxx-spring-boot-starter 命名。

New Project => Maven => Create from archetype (maven-archetype-quickstart) =>

 pom.xml添加:

   <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-autoconfigure</artifactId>
    </dependency>
  <dependencyManagement>
    <dependencies>
      <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-dependencies</artifactId>
        <version>2.1.6.RELEASE</version>
        <type>pom</type>
        <scope>import</scope>
      </dependency>
    </dependencies>
  </dependencyManagement>

创建 main/resources/META-INF/spring.factories: 

# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  com.cxf.config.CxfStarterEnableAutoConfiguration

com.cxf.config.CxfStarterEnableAutoConfiguration:

package com.cxf.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
@ConditionalOnClass(HelloService.class)
@EnableConfigurationProperties(HelloServiceProperties.class)
public class CxfStarterEnableAutoConfiguration {
    private final HelloServiceProperties helloServiceProperties;

    @Autowired
    public CxfStarterEnableAutoConfiguration(HelloServiceProperties helloServiceProperties) {
        this.helloServiceProperties = helloServiceProperties;
    }

    @Bean
    @ConditionalOnProperty(prefix = "hello.service", name = "enable", havingValue = "true")
    HelloService helloService() {
        return new HelloService(helloServiceProperties.getPrefix(), helloServiceProperties.getSuffix());
    }

}

HelloServiceProperties:

package com.cxf.config;

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

@ConfigurationProperties("hello.service")
public class HelloServiceProperties {
    private String prefix;
    private String suffix;

    public String getPrefix() {
        return prefix;
    }

    public void setPrefix(String prefix) {
        this.prefix = prefix;
    }

    public String getSuffix() {
        return suffix;
    }

    public void setSuffix(String suffix) {
        this.suffix = suffix;
    }
}

HelloService:

package com.cxf.config;

public class HelloService {
    private String prefix;
    private String suffix;

    public HelloService(String prefix, String suffix) {
        this.prefix = prefix;
        this.suffix = suffix;
    }

    public String say(String text) {
        return String.format("%s, hi, %s, %s", prefix, text, suffix);
    }
}

当项目依赖该 starter 且配置文件中包含 hello.service前缀且hello.service.enable为true时,自动生成HelloService的Bean。

在别的 Spring Boot 项目中的pom.xml引入:

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>cxf-spring-boot-starter</artifactId>
  <version>1.0-SNAPSHOT</version>
</dependency>

在application.properties(或对应的application.yml)中配置:

hello.service.enable=true
hello.service.prefix=pre
hello.service.suffix=suf


手写 Spring 核心代码

Spring 核心代码可参考阅读《SPRING技术内幕:深入解析SPRING架构与设计原理 第2版》(计文柯著)、《Spring源码深度解析 第2版》(郝佳著)、《Spring 5核心原理与30个类手写实战》(谭勇德著)等书籍或者B站的Spring源码分析视频。

用300行代码手写提炼Spring核心原理和设计思想,并保证功能可用的例子:GitHub - gupaoedu-tom/spring5-samples: 《Spring5核心原理与30个类手写实战》随书代码示例工程

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值