Spring Boot(01)——初体验

Spring Boot初体验

Spring Boot的理念是抛弃XML配置(当然,如果你想采用XML配置也是可以的,但是Spring Boot推荐使用基于Java的配置),采用纯Java配置和properties或yml文件配置,通过提供一系列的Starter可以使开发者快速的搭建起一套开发环境。Starter将某一工具相关的依赖整合到了一起,通过依赖一个Starter会间接的依赖该Starter相关的所有依赖。使用的Starter在启用了自动配置时是可以自识别的,即只要把它们加入到Classpath中,在程序启动的时候可以进行自动的识别和启动。比如想使用Spring Data MongoDB,只需加入spring-boot-starter-data-mongodb依赖,然后通过配置文件配置MongoDB对应的配置信息即可。使用Spring Boot时,通常会使用Maven进行项目管理,然后指定spring-boot-starter-parent为父项目。spring-boot-starter-parent中间接的提供了一些<dependencyManagement/>,将它作为父项目后在项目需要使用依赖时可以忽略版本号的配置,非常的方便。

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.0.0.RELEASE</version>
</parent>

定义好的<dependencyManagement/>可以从artifactId为spring-boot-dependencies的pom.xml中查看,它属于spring-boot-starter-parent的父项目。如果需要改变依赖项的版本号,可以在子项目中重写对应的properties中的版本号。

比如想要开发一个Web项目,在项目的pom.xml文件中加入spring-boot-starter-web的依赖,在项目启动时就会自动的按照SpringMVC的配置为我们配置好。这是由Spring boot的autoconfigure模块来实现的,autoconfigure模块中定义了一系列的自动配置类,它们的自动配置规则基本都差不多。比如在classpath下拥有某Class的时候进行自动配置,或者在配置文件中配置了某个属性后进行自动配置,等等。每个自动配置类都使用了@Configuration标注。需要启用自动配置,需要在Spring Boot的入口类上加上@EnableAutoConfiguration注解。

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
</dependencies>

查看spring-boot-starter-web的pom.xml文件可以看到它打包了以下依赖项:

 <dependencies>
   <dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter</artifactId>
     <version>2.0.3.RELEASE</version>
     <scope>compile</scope>
   </dependency>
   <dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-json</artifactId>
     <version>2.0.3.RELEASE</version>
     <scope>compile</scope>
   </dependency>
   <dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-tomcat</artifactId>
     <version>2.0.3.RELEASE</version>
     <scope>compile</scope>
   </dependency>
   <dependency>
     <groupId>org.hibernate.validator</groupId>
     <artifactId>hibernate-validator</artifactId>
     <version>6.0.10.Final</version>
     <scope>compile</scope>
   </dependency>
   <dependency>
     <groupId>org.springframework</groupId>
     <artifactId>spring-web</artifactId>
     <version>5.0.7.RELEASE</version>
     <scope>compile</scope>
   </dependency>
   <dependency>
     <groupId>org.springframework</groupId>
     <artifactId>spring-webmvc</artifactId>
     <version>5.0.7.RELEASE</version>
     <scope>compile</scope>
   </dependency>
 </dependencies>

其它的Starter也是类似的,打包了相关的依赖项。

SpringMVC的自动配置类由DispatcherServletAutoConfiguration定义,其主要源码如下所示。我们可以看到类上面拥有各种各样的Conditional打头的注解,它们都是用来定义对应的配置生效的条件。

@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@Configuration
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass(DispatcherServlet.class)
@AutoConfigureAfter(ServletWebServerFactoryAutoConfiguration.class)
@EnableConfigurationProperties(ServerProperties.class)
public class DispatcherServletAutoConfiguration {

    /*
     * The bean name for a DispatcherServlet that will be mapped to the root URL "/"
     */
    public static final String DEFAULT_DISPATCHER_SERVLET_BEAN_NAME = "dispatcherServlet";

    /*
     * The bean name for a ServletRegistrationBean for the DispatcherServlet "/"
     */
    public static final String DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME = "dispatcherServletRegistration";

    @Configuration
    @Conditional(DefaultDispatcherServletCondition.class)
    @ConditionalOnClass(ServletRegistration.class)
    @EnableConfigurationProperties(WebMvcProperties.class)
    protected static class DispatcherServletConfiguration {

        private final WebMvcProperties webMvcProperties;

        public DispatcherServletConfiguration(WebMvcProperties webMvcProperties) {
            this.webMvcProperties = webMvcProperties;
        }

        @Bean(name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
        public DispatcherServlet dispatcherServlet() {
            DispatcherServlet dispatcherServlet = new DispatcherServlet();
            dispatcherServlet.setDispatchOptionsRequest(
                    this.webMvcProperties.isDispatchOptionsRequest());
            dispatcherServlet.setDispatchTraceRequest(
                    this.webMvcProperties.isDispatchTraceRequest());
            dispatcherServlet.setThrowExceptionIfNoHandlerFound(
                    this.webMvcProperties.isThrowExceptionIfNoHandlerFound());
            return dispatcherServlet;
        }

        @Bean
        @ConditionalOnBean(MultipartResolver.class)
        @ConditionalOnMissingBean(name = DispatcherServlet.MULTIPART_RESOLVER_BEAN_NAME)
        public MultipartResolver multipartResolver(MultipartResolver resolver) {
            // Detect if the user has created a MultipartResolver but named it incorrectly
            return resolver;
        }

    }

    @Configuration
    @Conditional(DispatcherServletRegistrationCondition.class)
    @ConditionalOnClass(ServletRegistration.class)
    @EnableConfigurationProperties(WebMvcProperties.class)
    @Import(DispatcherServletConfiguration.class)
    protected static class DispatcherServletRegistrationConfiguration {

        private final ServerProperties serverProperties;

        private final WebMvcProperties webMvcProperties;

        private final MultipartConfigElement multipartConfig;

        public DispatcherServletRegistrationConfiguration(
                ServerProperties serverProperties, WebMvcProperties webMvcProperties,
                ObjectProvider<MultipartConfigElement> multipartConfigProvider) {
            this.serverProperties = serverProperties;
            this.webMvcProperties = webMvcProperties;
            this.multipartConfig = multipartConfigProvider.getIfAvailable();
        }

        @Bean(name = DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME)
        @ConditionalOnBean(value = DispatcherServlet.class, name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
        public ServletRegistrationBean<DispatcherServlet> dispatcherServletRegistration(
                DispatcherServlet dispatcherServlet) {
            ServletRegistrationBean<DispatcherServlet> registration = new ServletRegistrationBean<>(
                    dispatcherServlet,
                    this.serverProperties.getServlet().getServletMapping());
            registration.setName(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME);
            registration.setLoadOnStartup(
                    this.webMvcProperties.getServlet().getLoadOnStartup());
            if (this.multipartConfig != null) {
                registration.setMultipartConfig(this.multipartConfig);
            }
            return registration;
        }

    }

    //...忽略部分代码,完整的代码请参考对应的源码

}

@EnableConfigurationProperties用来定义该自动配置内部可以使用的配置参数对应的配置类,配置参数对应的配置类上都会使用@ConfigurationProperties指定配置属性对应的前缀,然后配置类上的每一个属性加上对应的配置属性前缀组成一个完整的配置文件中的属性名。比如下面的WebMvcProperties指定的属性前缀是spring.mvc,其属性dateFormat将匹配配置文件中的spring.mvc.dateFormat属性值。如果配置类的属性仍然是一个对象,则可以进行多级嵌套,多个层级之间通过英文的句号进行连接。

@ConfigurationProperties(prefix = "spring.mvc")
public class WebMvcProperties {

    /**
     * Formatting strategy for message codes. For instance, `PREFIX_ERROR_CODE`.
     */
    private DefaultMessageCodesResolver.Format messageCodesResolverFormat;

    /**
     * Locale to use. By default, this locale is overridden by the "Accept-Language"
     * header.
     */
    private Locale locale;

    /**
     * Define how the locale should be resolved.
     */
    private LocaleResolver localeResolver = LocaleResolver.ACCEPT_HEADER;

    /**
     * Date format to use. For instance, `dd/MM/yyyy`.
     */
    private String dateFormat;

    /**
     * Whether to dispatch TRACE requests to the FrameworkServlet doService method.
     */
    private boolean dispatchTraceRequest = false;

    /**
     * Whether to dispatch OPTIONS requests to the FrameworkServlet doService method.
     */
    private boolean dispatchOptionsRequest = true;

    /**
     * Whether the content of the "default" model should be ignored during redirect
     * scenarios.
     */
    private boolean ignoreDefaultModelOnRedirect = true;

    /**
     * Whether a "NoHandlerFoundException" should be thrown if no Handler was found to
     * process a request.
     */
    private boolean throwExceptionIfNoHandlerFound = false;

    /**
     * Whether to enable warn logging of exceptions resolved by a
     * "HandlerExceptionResolver".
     */
    private boolean logResolvedException = false;

    /**
     * Path pattern used for static resources.
     */
    private String staticPathPattern = "/**";

    //...忽略部分代码

}

这样我们就可以直接在项目中定义Controller,并定义对应的处理器方法。

@Controller
public class SampleController {

    @RequestMapping("sample/helloworld")
    public void sample(Writer writer) throws IOException {
        writer.append("hello world!");
        writer.flush();
    }

}

接下来需要启动项目,项目可以按照纯Java项目进行启动,也可以打包为war包丢到Tomcat中。以下是直接按照纯Java项目进行启动的方式。通常建议在项目的根包路径下创建一个Class用于启动项目,而且在根包下面通常只有这样一个启动类。比如下面我们的Application如果是定义在com.elim.springboot包下面的,则项目在启动后Spring将自动将com.elim.springboot包作为根包进行bean定义扫描,这是由于类上加了@SpringBootApplication。启动类需要使用@SprintBootApplication注解进行标注,然后在main方法体中调用SpringApplication的run方法,然后传递需要作为配置类的Class作为第一个参数,如果有多个这样的Class,也可以传递一个数组,第二个参数为启动时传递的参数,通常就直接取main方法传递的参数,这样Spring Boot就启动了。@SprintBootApplication上是使用了@EnableAutoConfiguration注解的,所以下面的代码将启用Spring Boot的自动配置机制。另外@SpringBootApplication上使用了@Configuration@ComponentScan,这就相当于使用@SpringBootApplication标注的类等价于标注了@Configuration@ComponentScan。这允许我们在@SpringBootApplication类中定义Spring Bean、进行bean扫描等。

@SpringBootApplication
public class Application {

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

Spring Boot的核心是基于Java配置的Spring的进一步封装,所以我们在它的配置类上可以像在使用Java配置类时一样使用,比如加上@EnableAsync@ImportResource@Import等。当有多个配置类时,Spring Boot也是建议在主配置类上进行引入。

启动完成后就可以打开浏览器,通过/sample/helloworld访问上面定义的SampleController的sample方法了。

Spring Boot的核心配置文件是类根路径下的application.properties文件或application.yml文件。Spring Boot中需要的外部配置信息基本都从这里来,包括前面提到的自动配置的ConfigurationProperties。看下面代码中,类SampleController有一个appId属性,其值由test.appId这么一个占位符指定。

@Controller
public class SampleController {

    @Value("${test.appId}")
    private String appId;
    
    @RequestMapping("sample/helloworld")
    public void sample(Writer writer) throws IOException {
        writer.append("hello world!");
        writer.append(this.appId);
        writer.flush();
    }

}

那么在我们的application.properties文件中就可以指定这么一个占位符并进行赋值。程序运行时appId的值就将被替换为下面配置的Spring Boot

test.appId=Spring Boot

Spring Boot的配置文件其实除了application.properties或application.yml外,根据启动的profile的不同,还可以读取带profile后缀的配置文件。比如启动的profile是dev,则可以读取application-dev.properties或application-dev.yml文件。

(注:本文是基于Spring Boot 2.0.3所写)

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,406评论 5 475
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,976评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,302评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,366评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,372评论 5 363
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,457评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,872评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,521评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,717评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,523评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,590评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,299评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,859评论 3 306
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,883评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,127评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,760评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,290评论 2 342

推荐阅读更多精彩内容