SpringBoot入门篇2 -- 简单工程hello-world深度解析

智慧源于勤奋,伟大出自平凡

谨以上句献给每一个勤奋的平凡的人。
什么是平凡,在软件开发中,从不缺乏平凡之人,但是你真的甘心做一个平凡的人吗?在目前平凡的生活中,如何拒绝平庸,如何拥有智慧呢?那就需要不断的学习和善于总结了~今天,我们来从平凡的事物(hello-world程序)入手,来看看在平凡中我们如何学的透彻~

启动类代码深度解析

上一篇文章https://www.jianshu.com/p/c261ba6a4bd4简单的工程hello-world搭建,我并没有详细的介绍程序中的代码,那么今天让我们来揭开SpringBoot简单的工程下神秘的面纱吧。首先,上我们启动类的代码:

package top.flygrk.ishare.helloworld;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class HelloWorldApplication {

    public static void main(String[] args) {
        SpringApplication.run(HelloWorldApplication.class, args);
    }
}

在上面的代码,我们可以看出这段程序代码非常的简洁明了,只有一个main方法,并且在类上方有注解@SpringBootApplication修饰。我们首先看一下@SpringBootApplication注解的含义,我们查看下该注解的源码如下:

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package org.springframework.boot.autoconfigure;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.boot.context.TypeExcludeFilter;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.FilterType;
import org.springframework.context.annotation.ComponentScan.Filter;
import org.springframework.core.annotation.AliasFor;

@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 {
    @AliasFor(
        annotation = EnableAutoConfiguration.class
    )
    Class[] exclude() default {};

    @AliasFor(
        annotation = EnableAutoConfiguration.class
    )
    String[] excludeName() default {};

    @AliasFor(
        annotation = ComponentScan.class,
        attribute = "basePackages"
    )
    String[] scanBasePackages() default {};

    @AliasFor(
        annotation = ComponentScan.class,
        attribute = "basePackageClasses"
    )
    Class[] scanBasePackageClasses() default {};
}

从@SpringBootApplication注解的源码可以看出,@SpringBootApplication是一个复合注解,包括@ComponentScan、@SpringBootConfiguration、@EnableAutoConfiguration等注解。那么我们先不关心这几个注解的作用,我们将SpringBoot的hello-world程序启动类HelloWorldApplcation的@SpringBootApplication注解替换成以上三个,程序能不能够运行呢?我们来尝试一下:

package top.flygrk.ishare.helloworld;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;

//@SpringBootApplication
@ComponentScan
@EnableAutoConfiguration
@SpringBootConfiguration
public class HelloWorldApplication {

    public static void main(String[] args) {
        SpringApplication.run(HelloWorldApplication.class, args);
    }
}

我们启动工程,发现启动过程中并没有报错,打开浏览器,输入http://localhost:8080/sayHello访问,能够成功打印出我们想要得到的结果,所以,用上面的三个注解替换@SpringBootApplication是可以的。

SpringBoot入门篇2 -- 简单工程hello-world深度解析_第1张图片

@SringBootConfiguration

好了,我们来仔细看看这几个注解及@SpringBootApplication的方法。首先,我们来看下@SringBootConfiguration注解(关于前面四个java注解这里不再细述,可查阅其他资料)

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package org.springframework.boot;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.context.annotation.Configuration;

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

从@SpringBootConfiguration注解的源码可以看出其继承自@Configuration,并且没有自己独有的方法,即我们认为它们之间是is-a关系,也就是替代原则,表示的功能也是一致的,标识当前类是配置类,将会将当前类内声明的一个或者多个以@Bean注解标识的方法的实例注入到Spring容器中,且实例名为方法名。我们继续延伸一下,看看@Configuration注解的源码:

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package org.springframework.context.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.core.annotation.AliasFor;
import org.springframework.stereotype.Component;

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Configuration {
    @AliasFor(
        annotation = Component.class
    )
    String value() default "";
}

这里我们可以看到其继承于Spring的@Component注解,哈哈,@Component注解的作用不就是定义Spring管理Bean的吗~~~(@Component注解的源码大家可以自行去阅读啦~后续也会进行分享)

@EnableAutoConfiguration

接下来,我们来看下@EnableAutoConfiguration的作用及其实现。从其字面意思上来看,其作用是实现能够自动加载配置,确实,@EnableAutoConfiguration的作用是从classpath中搜索所有的META-INF/spring.factoriies配置文件,然后将其中的org.springframework.boot.autoconfigure.EnableAutoConfiguration的 key对应的配置项加载到Spring容器。并且只有当spring.boot.enableautoconfiguration为true时(默认为true)才启动该自动配置。

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package org.springframework.boot.autoconfigure;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.context.annotation.Import;

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
    String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

    Class[] exclude() default {};

    String[] excludeName() default {};
}

@Import注解:
@Import注解通过导入的方式实现把实例加入到SpringIOC容器中,其只注解在类上,并且其唯一的参数value上可以配置3种类型的值:Configuration、ImportSelector、ImportBeanDefinitionRegistrar

我们可以看到@Import将AutoConfigurationImportSelector实例加入到SpringIOC容器中,我们来看下该类的具体实现的部分代码:


    ……
    private static final AutoConfigurationImportSelector.AutoConfigurationEntry EMPTY_ENTRY = new AutoConfigurationImportSelector.AutoConfigurationEntry();
    private static final String[] NO_IMPORTS = new String[0];
    private static final Log logger = LogFactory.getLog(AutoConfigurationImportSelector.class);
    private static final String PROPERTY_NAME_AUTOCONFIGURE_EXCLUDE = "spring.autoconfigure.exclude";
    private ConfigurableListableBeanFactory beanFactory;
    private Environment environment;
    private ClassLoader beanClassLoader;
    private ResourceLoader resourceLoader;

    ……
    public String[] selectImports(AnnotationMetadata annotationMetadata) {
        if (!this.isEnabled(annotationMetadata)) {
            return NO_IMPORTS;
        } else {
            AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
            AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(autoConfigurationMetadata, annotationMetadata);
            return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
        }
    }
    protected AutoConfigurationImportSelector.AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata, AnnotationMetadata annotationMetadata) {
        if (!this.isEnabled(annotationMetadata)) {
            return EMPTY_ENTRY;
        } else {
            AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
            List configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
            configurations = this.removeDuplicates(configurations);
            Set exclusions = this.getExclusions(annotationMetadata, attributes);
            this.checkExcludedClasses(configurations, exclusions);
            configurations.removeAll(exclusions);
            configurations = this.filter(configurations, autoConfigurationMetadata);
            this.fireAutoConfigurationImportEvents(configurations, exclusions);
            return new AutoConfigurationImportSelector.AutoConfigurationEntry(configurations, exclusions);
        }
    }

其中List configurations = this.getCandidateConfigurations(annotationMetadata, attributes);表示去获取类路径下spring.factories下key为EnableAutoConfiguration全限定名对应值。

    protected List getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
        List 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;
    }

    protected Class getSpringFactoriesLoaderFactoryClass() {
        return EnableAutoConfiguration.class;
    }

从上面代码getCandidateConfigurations会到classpath下读取META-INF/spring.factories文件的配置,返回一个字符串数组。spring.factories的内容为:(位于spring-boot-autoconfigure-2.1.0.RELEASE.jar包下)

# Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\
org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener

# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.boot.autoconfigure.BackgroundPreinitializer

# Auto Configuration Import Listeners
org.springframework.boot.autoconfigure.AutoConfigurationImportListener=\
org.springframework.boot.autoconfigure.condition.ConditionEvaluationReportAutoConfigurationImportListener

# Auto Configuration Import Filters
org.springframework.boot.autoconfigure.AutoConfigurationImportFilter=\
org.springframework.boot.autoconfigure.condition.OnBeanCondition,\
org.springframework.boot.autoconfigure.condition.OnClassCondition,\
org.springframework.boot.autoconfigure.condition.OnWebApplicationCondition

# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\

…………(省略)

org.springframework.boot.autoconfigure.websocket.servlet.WebSocketMessagingAutoConfiguration,\
org.springframework.boot.autoconfigure.webservices.WebServicesAutoConfiguration,\
org.springframework.boot.autoconfigure.webservices.client.WebServiceTemplateAutoConfiguration

# Failure analyzers
org.springframework.boot.diagnostics.FailureAnalyzer=\
org.springframework.boot.autoconfigure.diagnostics.analyzer.NoSuchBeanDefinitionFailureAnalyzer,\
org.springframework.boot.autoconfigure.jdbc.DataSourceBeanCreationFailureAnalyzer,\
org.springframework.boot.autoconfigure.jdbc.HikariDriverConfigurationFailureAnalyzer,\
org.springframework.boot.autoconfigure.session.NonUniqueSessionRepositoryFailureAnalyzer

# Template availability providers
org.springframework.boot.autoconfigure.template.TemplateAvailabilityProvider=\
org.springframework.boot.autoconfigure.freemarker.FreeMarkerTemplateAvailabilityProvider,\
org.springframework.boot.autoconfigure.mustache.MustacheTemplateAvailabilityProvider,\
org.springframework.boot.autoconfigure.groovy.template.GroovyTemplateAvailabilityProvider,\
org.springframework.boot.autoconfigure.thymeleaf.ThymeleafTemplateAvailabilityProvider,\
org.springframework.boot.autoconfigure.web.servlet.JspTemplateAvailabilityProvider

此外,需要注意的还有@EnableAutoConfiguration可以进行排除,其有2种排除方式:

  • 根据class来排除(exclude)
  • 根据class name 来排除(excludeName)来排除
    读者可以阅读其源码深度了解其含义
@ComponentScan

接下来,我们来看看第三个注解@ComponentScan注解的含义。@ComponentScan主要是从定义的扫描路径中找出标识了需要装配的类,将其自动装配到Spring的Bean容器中。@ComponentScan注解默认会装配标识了@Controller、@Service、@Repository、@Component等注解类到Spring容器中。我们看下其源码:

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package org.springframework.context.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.beans.factory.support.BeanNameGenerator;
import org.springframework.core.annotation.AliasFor;

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
@Repeatable(ComponentScans.class)
public @interface ComponentScan {
    @AliasFor("basePackages")
    String[] value() default {};

    @AliasFor("value")
    String[] basePackages() default {};

    Class[] basePackageClasses() default {};

    Class nameGenerator() default BeanNameGenerator.class;

    Class scopeResolver() default AnnotationScopeMetadataResolver.class;

    ScopedProxyMode scopedProxy() default ScopedProxyMode.DEFAULT;

    String resourcePattern() default "**/ *.class";

    boolean useDefaultFilters() default true;
    
    ComponentScan.Filter[] includeFilters() default {};
    
    //指定不适合组件扫描的类型
    ComponentScan.Filter[] excludeFilters() default {};
    
    //是否启用懒加载,默认不启动
    boolean lazyInit() default false;

    @Retention(RetentionPolicy.RUNTIME)
    @Target({})
    public @interface Filter {
        FilterType type() default FilterType.ANNOTATION;

        @AliasFor("classes")
        Class[] value() default {};

        @AliasFor("value")
        Class[] classes() default {};

        String[] pattern() default {};
    }
}

其可以通过includeFilters加入扫描路径下没有以上注解的类到Spring容器;并且可以通过excludeFilters过滤出不用加入Spring容器的类。其默认扫描地址为其所在的包及其所有的子包。

好啦,我们将@SpringBootApplication的注解简单的看了下,那么接下来,我们看下该main方法的作用吧~看下SpringApplication的代码:

    public static ConfigurableApplicationContext run(Class primarySource, String... args) {
        return run(new Class[]{primarySource}, args);
    }

    public static ConfigurableApplicationContext run(Class[] primarySources, String[] args) {
        return (new SpringApplication(primarySources)).run(args);
    }

    …………

    public ConfigurableApplicationContext run(String... args) {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        ConfigurableApplicationContext context = null;
        Collection exceptionReporters = new ArrayList();
        this.configureHeadlessProperty();
        SpringApplicationRunListeners listeners = this.getRunListeners(args);
        listeners.starting();

        Collection exceptionReporters;
        try {
            ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
            ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments);
            this.configureIgnoreBeanInfo(environment);
            Banner printedBanner = this.printBanner(environment);
            context = this.createApplicationContext();
            exceptionReporters = this.getSpringFactoriesInstances(SpringBootExceptionReporter.class, new Class[]{ConfigurableApplicationContext.class}, context);
            this.prepareContext(context, environment, listeners, applicationArguments, printedBanner);
            this.refreshContext(context);
            this.afterRefresh(context, applicationArguments);
            stopWatch.stop();
            if (this.logStartupInfo) {
                (new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), stopWatch);
            }

            listeners.started(context);
            this.callRunners(context, applicationArguments);
        } catch (Throwable var10) {
            this.handleRunFailure(context, var10, exceptionReporters, listeners);
            throw new IllegalStateException(var10);
        }

        try {
            listeners.running(context);
            return context;
        } catch (Throwable var9) {
            this.handleRunFailure(context, var9, exceptionReporters, (SpringApplicationRunListeners)null);
            throw new IllegalStateException(var9);
        }
    }

@SpringBootApplication方法释义

我们可以看到@SpringBootApplication的源码中有以下方法:

    //根据class 来排除特定的类加入Spring 容器,传入的value类型是class类型
    @AliasFor(
        annotation = EnableAutoConfiguration.class
    )
    Class[] exclude() default {};

    //根据class name来排除特定的类加入Spring容器,传入参数value类型是class的全类名字符串数组
    @AliasFor(
        annotation = EnableAutoConfiguration.class
    )
    String[] excludeName() default {};
  
    //指定扫描包,参数名是包名的字符串数组
    @AliasFor(
        annotation = ComponentScan.class,
        attribute = "basePackages"
    )
    String[] scanBasePackages() default {};

    //扫描特定的包,参数是Class类型的数组
    @AliasFor(
        annotation = ComponentScan.class,
        attribute = "basePackageClasses"
    )
    Class[] scanBasePackageClasses() default {};

我们可以在@SpringBootApplication后配置其指定扫描的包等信息,例如:

package top.flygrk.ishare.helloworld;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;

//设置扫描的包路径为top.flygrk及其所有的子包
@SpringBootApplication(scanBasePackages = "top.flygrk")
//@ComponentScan
//@EnableAutoConfiguration
//@SpringBootConfiguration
public class HelloWorldApplication {

    public static void main(String[] args) {
        SpringApplication.run(HelloWorldApplication.class, args);
    }
}

控制器controller层代码深度解析

了解了启动类的相关原理,那么我们来看下控制器层做了什么?看一下@RestController的作用是什么?它和@Controller究竟有什么区别?
我们先看下HelloController的代码:

package top.flygrk.ishare.helloworld.controller;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @Author: flygrk
 * @Date: Created in 2018/11/12 22:26
 * @Description: HelloWorld 控制器类
 */
@RestController
public class HelloController {

    @RequestMapping("sayHello")
    public String sayHello() {
        return "Hello World!";
    }
}

在类上方的@RestController究竟有什么作用呢?先来看下@RestController的源代码:

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package org.springframework.web.bind.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.core.annotation.AliasFor;
import org.springframework.stereotype.Controller;

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Controller
@ResponseBody
public @interface RestController {
    @AliasFor(
        annotation = Controller.class
    )
    String value() default "";
}

呀哈,我们看到了修饰它的注解竟然是@Controller和@ResponseBody,这个时候我们在@SpringBootApplication的源码中看到的@ComponentScan就可以扫描到该类,并将其注入Spring容器中了。另外@RestController具有它们两个注解语义,所以在注解处理时@RestController要比@Controller多具有一个注解@ResponseBody语义(@ResponseBody返回值是json格式的),这也就造成了@RestController的返回值为什么都是经过转换的json的原因啦~
但是,注意了,当我们在controller返回一个html(或者其他模板语言),我们这个时候一般都是采用@Controller注解;如果同一个Controller中,即包含接口开发,又包含返回页面的配置,那么这个时候我们可以在接口开发(此处指json报文)的方法上使用@ResponseBody注解即可(该注解值得细细学习,后期分享~)

平凡~但不可平庸~欢迎大佬指点指点

你可能感兴趣的:(SpringBoot入门篇2 -- 简单工程hello-world深度解析)