SpringBoot 入门及两大特性分析

SpringBoot 入门及两大特性分析

0 前言

Spring从诞生之初,基于最核心的依赖注入(DI)和面向切面编程(AOP),实现了重量级EJB的功能,虽然说Spring是一个轻量级的框架(和EJB相比),但是配置却“重量级”,Spring 从最初版本的XML配置 -> Spring2.5 的基于注解的组件扫描(component-scan) -> Spring3.0 的基于 Java 的配置【1】,目的都是为了减少应用程序内的显示XML配置。

尽管如此,我们仍需要显示配置。例如,我们需要使用SpringMVC的时候,仍需要配置DispatcherServlet。

除此之外,项目的依赖管理也很头疼,我们需要考虑不同版本之间依赖是否会发生冲突的问题。

当使用了Spring Boot之后,这一切都成为了过去。

本文通过简单的入门案例,分析SpringBoot的两大特性:起步依赖和自动配置,以及最后SpringBoot如何创建一个生产环境可用的"胖"jar包。

1 一个简单的Spring Boot 应用

在开始SpringBoot之前,我们简单想想之前是怎么开发一个简单的Web 应用?

需要准备如下:

  • 导入 SpringMVC 和 Servlet API 的依赖
  • 一个 web.xml 文件
  • 一个启用Spring MVC 的Spring 配置
  • 一个 Controller 控制器,处理http请求
  • 一个web容器,如:Tomcat

作为比较,我们下面我们写一个能实现相同功能的Spring Boot应用

PS:

自动化构建SpringBoot项目的方式很多,如使用IDEA的Spring Initializr, https://start.spring.io/, CLI的方式等,大家自行 google,这里我手动进行构建。

1.1 构建Maven项目

构建一个普通的maven项目,这里省略

1.2 起步依赖

pom.xml 文件中,加入父起步依赖和web起步依赖

<?xml version="1.0" encoding="UTF-8"?>
<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的配置是从spring-boot-starter-parent继承版本号-->
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.0.RELEASE</version>
    </parent>

    <groupId>me.fishsx</groupId>
    <artifactId>DemoApplication</artifactId>
    <version>1.0-SNAPSHOT</version>
    <!--导入 web 的起步依赖-->
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    </dependencies>
</project>

可以看到起步依赖比较之前普通的依赖相比,有2个特点:

  1. artifactId都是以 spring-boot-starter开头
  2. 不需要写version版本号

下面第二节会详细分析起步依赖

1.3 构建启动类

src/main/java下构建包名+启动类名启动类,需要在类上加入**@SpringBootApplication**注解,并且类中写入一个main方法,代码是固定的 SpringApplication.run(DemoApplication.class, args);

package me.fishsx;

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

/**
 * SpringBoot启动类
 */
@SpringBootApplication
public class DemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}

PS: 注意启动类的路径位置

启动类的位置必须在其他类/包的 同级或父级目录中,这是因为默认情况下,Spring扫描的注解会根据启动类的位置开始向子目录递归扫描

1.4 构建控制器类

me.fishsx包下,新增controller包并新建UserController类,代码如下:

@RestController
@RequestMapping("/user")
public class UserController {
    @RequestMapping(value = "/hello", method = RequestMethod.GET)
    public String hello() {
        return "hello springboot!";
    }
}	

1.5 运行启动类测试

启动Spring Boot应用有两种方式

  • 使用IDE工具启动
  • 使用Maven命令:mvn spring-boot:run

使用IDE工具运行启动类,发现日志打印如下

发现Tomcat已启动在8080端口,我们访问 http://localhost:8080/user/hello

输出结果:

同时,我们发现控制台日志也发生了变化,出现了DispatcherServlet的初始化信息

总结:

上面我们简单的写了一个简单的SpringBoot Demo,但是我们并没有配置Tomcat和DispatcherServlet,但是日志中却出现了相关的信息,这就是Spring Boot带来的自动化配置,下面第三节会详细进行剖析

2 起步依赖分析

使用了起步依赖之后,我们并不需要指定版本号,这是因为在Spring Boot的版本号中已经指定了版本号,如下,在此案例中我们使用了 2.0.0.RELEASE版本,而parent标签类似于java中的extends,继承指定的pom文件,达到复用的效果。当然,还有一种方式能达到同样的效果而不必使用parent标签【2】,由于并不是很常用,这里主要使用继承的方式。

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

我们接着找spring-boot-starter-parent.pom文件

<!--由于内容过多,省略其他内容-->
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-dependencies</artifactId>
    <version>2.0.0.RELEASE</version>
    <relativePath>../../spring-boot-dependencies</relativePath>
</parent>

发现此pom文件继承自spring-boot-dependencies.pom文件

<!--pom文件的坐标信息-->
<modelVersion>4.0.0</modelVersion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.0.0.RELEASE</version>
<packaging>pom</packaging>
<name>Spring Boot Dependencies</name>
...
<!--自定义属性 
	e.g.   <key>value</key> 进行声明 
			${key} 进行获取 value
	注意:将自定义的属性定义在<properties>标签中
	-->
<properties>
    <activemq.version>5.15.3</activemq.version>
    <aspectj.version>1.8.13</aspectj.version>
    <dom4j.version>1.6.1</dom4j.version>
    <jackson.version>2.9.4</jackson.version>
	<mysql.version>5.1.45</mysql.version>
    <servlet-api.version>3.1.0</servlet-api.version>
    <spring.version>5.0.4.RELEASE</spring.version>
    ...
</properties>
<!--依赖管理-->
<dependencyManagement>
    <dependencies>
        <!--这里指定了 spring-boot-starter-web的版本号--->
        <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
                <version>2.0.0.RELEASE</version>
            </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>${mysql.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>${spring.version}</version>
        </dependency>
            ...
    </dependencies>
</dependencyManagement>            

看到这里大家应该都明白了,我们不需要指定版本号,是因为Maven的父POM文件中已经指定。

对于依赖间的兼容问题,Spring Boot团队经过了足够的测试,确保了引入的全部依赖之间的兼容性,这点我们并不需要担心。

对于我们开发者来说,这是一种解脱,我们并不用再担心需要维护哪些库,也不必担心他们的版本。

当然有时候

**如何覆盖起步依赖引入的传递依赖 **?

如果我们不想在项目中使用Spring Boot推荐的某个依赖的版本号时,我们如何指定自己的依赖版本呢?很简单,我们只需要在pom文件中加入自己版本号即可,与我们之前引入maven坐标的方式并无差别,这是因为Maven总是会用最近的依赖(就近原则)

e.g. 我们需要使用使用 5.2.4版本的mysql来取代原来默认的5.1.45版本,只需要引入带版本的坐标即可

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.2.4</version>
</dependency>

3 启动类分析

示例中我们编写了一个最为常见的启动类,其中包括**@SpringBootApplication**注解和一个固定的main方法。

@SpringBootApplication
public class DemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}
  • @SpringBootApplication:将3个有用的注解组合在一起
    • Spring的**@Configuration** 注解:标明该类是一个配置类
    • Spring的**@ComponentScan注解:启动组件扫描,这样我们写的@Component**注解的类会被自动发现并自动注册为Spring上下文的bean
    • SpringBoot的**@EnableAutoConfiguration**注解:这个配置开启了Spring Boot的自动配置能力
  • main方法:调用了SpringApplicationrun静态方法
    • 参数1:当前启动类的字节码
    • 参数2:可选,表示我们可以传一些命令行启动的参数

3.1 自动配置 @EnableAutoConfiguration

自动配置指的是让Spring Boot根据导入的依赖来确定如何配置Spring

  • 例如示我们导入了spring-boot-starter-web后,Spring Boot会自动配置默认的Tomcat端口号8080以及DispatcherServlet的配置
  • 再例如我们导入了spring-boot-starter-jdbc并配置了数据源后,Spring Boot会根据数据源自动配置JdbcTemplate

Spring Boot的自动配置本质是利用了Spring4.0引入的新特性:条件化配置

注意:starter和自动配置

starter相关的起步依赖和自动配置并没有必然的联系,换句话说,除了starter前缀的依赖外,我们导入其他普通依赖,只要满足spring的自动化配置条件,同样也会自动配置。

3.2 条件化配置

条件化配置:当满足一定的条件时,配置自动生效

3.2.1 SpringBoot提供的自动配置类

我们可以通过查看spring-boot-autoconfigure源码中的 META-INF/spring.factories文件,能查看到当前版本的所有的自动配置类,如下图

就拿其中的JdbcTemplateAutoConfiguration自动配置类来说,下面是源码一部反

@Configuration
@ConditionalOnClass({ DataSource.class, JdbcTemplate.class })
@ConditionalOnSingleCandidate(DataSource.class)
@AutoConfigureAfter(DataSourceAutoConfiguration.class)
@EnableConfigurationProperties(JdbcProperties.class)
public class JdbcTemplateAutoConfiguration {

   @Configuration
   static class JdbcTemplateConfiguration {

      private final DataSource dataSource;

      private final JdbcProperties properties;

      JdbcTemplateConfiguration(DataSource dataSource, JdbcProperties properties) {
         this.dataSource = dataSource;
         this.properties = properties;
      }

      @Bean
      @Primary
      @ConditionalOnMissingBean(JdbcOperations.class)
      public JdbcTemplate jdbcTemplate() {
         JdbcTemplate jdbcTemplate = new JdbcTemplate(this.dataSource);
         JdbcProperties.Template template = this.properties.getTemplate();
         jdbcTemplate.setFetchSize(template.getFetchSize());
         jdbcTemplate.setMaxRows(template.getMaxRows());
         if (template.getQueryTimeout() != null) {
            jdbcTemplate
                  .setQueryTimeout((int) template.getQueryTimeout().getSeconds());
         }
         return jdbcTemplate;
      }

   }
 ...     

其中我们最为关心的是像@ConditionalOnClass@ConditionalOnMissingBean这样的注解,这类注解都是以Conditional作为前缀,它们被称作是条件化注解,作用也很明显,例如:@ConditionalOnClass表示Classpath中存在DataSourceJdbcTemplate这两个类时,这个JdbcTemplateAutoConfiguration配置类自动生效;而此配置类中的JdbcTempalte在满足BeanFactory中没有注册JdbcOperations这个bean的时候,自动配置一个JdbcTemplate加入BeanFactory

JdbcOperations 接口

jdbcTemplate的创建条件是@ConditionalOnMissingBean(JdbcOperations.class)条件成立。

JdbcOperations这个接口大家可能不是很熟悉,其实jdbcTemplate就是JdbcOperations的实现类,我们可以自定义一个实现了JdbcOperations的实现类然后注入到beanfactory中,这样的话,jdbcTemplate就没必要自动创建了。

3.2.2 SpringBoot 提供的条件化注解

在SpringBoot中提供了十几种的条件化注解,整理如下:

条件化注解配置生效条件
ConditionalOnClassClasspath中有指定的类
ConditionalOnMissingClassClasspath中缺少指定的类
ConditionalOnBean配置了某个特定的bean
ConditionalOnMissingBean没有配置特定的bean
ConditionalOnProperty指定的配置属性有明确的值
ConditionalOnResourceClasspath里有指定的资源
ConditionalOnJndi参数中给定的JNDI位置必须要存在一个,如果没有给定参数,则要有JNDI InitialContext
ConditionalOnCloudPlatform指定的云平台处于活动状态时
ConditionalOnRepositoryType启用特定类型的Spring Data存储库时
ConditionalOnJavaJava 的版本匹配特定值或一个范围
ConditionalOnSingleCandidate只有在BeanFactory中已经包含指定类的bean并且可以确定单个候选者时才匹配
ConditionalOnExpression给定的SpEL表达式结果为true
ConditionalOnWebApplication这是一个Web应用程序
ConditionalOnNotWebApplication这不是一个Web应用程序
ConditionalOnEnabledResourceChain检查是否启用了Spring资源处理链 ResourceProperties.Chain.getEnabled() 返回true 或者classpath存在 webjars-locator-core

所有的这些条件化注解都是基于Condition接口实现的,通过覆盖重写接口中的matches()方法,返回boolean值

package org.springframework.context.annotation;

@FunctionalInterface
public interface Condition {

   /**
    * 确定条件是否匹配
    * @param context: the condition context
    * @param metadata: metadata of the {@link org.springframework.core.type.AnnotationMetadata class} or {@link org.springframework.core.type.MethodMetadata method} being checked
    * @return true  --> 条件匹配,组件可以被注册
    * 		  false --> 不匹配,组件不能被注册
    */
   boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
}

这样,只要我们可以通过实现Condition接口来编写自定义的条件类【3】,然后在声明bean的时候,通过Conditional注解来使用

4 部署:创建可执行jar包 (胖jar包)

一个传统的java web项目部署在服务器端,通常我们需要生成一个war包,并且部署在诸如Tomcat这样的web容器中。而Spring Boot提供了一个新的方式:我们只需要打一个jar包,只需要在有jre的服务器环境下就可以运行,不需要再依赖外部的web容器,这个jar包我们通常称之为可执行jar包(execuable jar),有时也称为胖jar包(fat jar)

PS:

想法听上去很简单,但是实现起来有一个问题那就是:java并没有提供嵌套的jar文件的标准方法,换句话说就是,就是jar包中嵌套jar包可能会出现问题。因此Spring团队自己实现了一种嵌套jar的方法,感兴趣的话可以参考官网链接 springboot官方提供的可执行jar格式的背景知识

4.1 操作步骤

step1 添加maven 插件

我们需要在POM文件中加入一段maven插件代码,如下:

<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
        </plugin>
    </plugins>
</build>
step2 执行maven打包命令

在控制台输入(或者使用IDE的maven插件按钮)

mvn package

若没有错误,则在target目录下会生成一个jar包:DemoApplication-1.0-SNAPSHOT.jar

step3 执行jar文件

生成的jar文件我们可以移动到任何一个有jre环境的服务器上,然后通过java -jar命令执行此文件即可,这样就可以简单的启动一个web项目

java -jar xxxx.jar

附录

附1 基于java的配置(纯注解的方式)

Spring基于java的配置是通过@Configuration和@Bean注解来实现的,例如:我们需要向Spring的IoC容器中配置一个Bean,两种不同的方式如下:

a. 基于Java的配置

import org.springframework.context.annotation.*;
@Configuration
public class HelloWorldConfig {
   @Bean 
   public HelloWorld helloWorld(){
      return new HelloWorld();
   }
}

上面的代码等同于下面的XML配置

b .传统xml的配置

<beans>
   <bean id="helloWorld" class="com.tutorialspoint.HelloWorld" />
</beans>

附2 使用SpringBoot启动依赖的第二种方式

并不是任何情况下都能够使用继承spring-boot-starter-parentpom文件的,有时可能我们需要继承公司内部的pom文件,而Maven是不支持多继承的,因此我们可就将原来的POM文件中的parent相关配置替换为如下配置,仍能达到同样的效果

<dependencyManagement>
    <dependencies>
        <dependency>
            <!-- Import dependency management from Spring Boot -->
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-dependencies</artifactId>
            <version>2.0.0.RELEASE</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

附3 自定义条件化配置

假设一种场景,我们需要根据某个条件自定义bean的注册

例如:当Classpath中存在spring-jdbc的JdbcTemplate类时,自动将MyService注册在BeanFactory中。

step1 自定义一个 JdbcTemplateCondition条件类

其实这个自定义条件类模拟的就是Spring提供的ConditionalOnClass

//自定义的条件类
public class JdbcTemplateCondition implements Condition {
   
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        try {
            //类加载器能加载到目标类,则认为条件成立
            context.getClassLoader().loadClass("org.springframework.jdbc.core.JdbcTemplate");
            return true;
        } catch (Exception e) {
            return false;
        }
    }
}
step2 自定义一个服务类
public class MyService {
    //do something
}
step3 自定义自动配置类

JdbcTemplateCondition的matches()方法返回true的时候,将MyService注册到BeanFactory中(这里的成立条件就是Classpath中存在JdbcTemplate这个类)

@Configuration
public class MyAutoConfiguration {
    @Bean
    @Conditional(JdbcTemplateCondition.class)
    public MyService myService() {
        return new MyService();
    }
}
step4 编写测试类
@RunWith(SpringJUnit4ClassRunner.class) //使用Junit启动器
@SpringBootTest(classes = DemoApplication.class) //启动类字节码
public class ServiceTest {
    @Autowired(required = false) 
    //required属性:默认true,启动时检测到Bean工厂没有注册myservice时会抛异常,改为false,不会抛异常
    private MyService myService;

    @Test
    public void test1() {
        System.out.println(myService);
    }
}

需要额外加入2个依赖,jdbc的起步依赖和数据库驱动,另外需要在配置文件中加入数据源信息

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
</dependency>

resources下新建application.yml(SpringBoot的配置文件通常是在resources下,并且以application作为前缀命名),加入数据源信息

spring:
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://localhost:3306/***
    username: ****
    password: ****
测试

由于JdbcTemplatespring-boot-starter-jdbc中,测试方式就是将POM文件中``spring-boot-starter-jdbc`的坐标注释,查看注释前后的结果

Classpath中存在JdbcTemplate(注释前):

执行mvn test,结果是

me.fishsx.conditional.MyService@4bb003e9

Classpath中不存在JdbcTemplate(注释后):

执行mvn test,结果是

null

这样我们的目的就达到了

参考

[1].《Spring Boot in Action》

[2]. Spring Boot 官方文档

转载于:https://my.oschina.net/u/3606160/blog/3017118

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值