SpringBoot的应用(一)

1.写在前面

前面笔者介绍完了Spring的源码,最近笔者也在看设计模式,由于休息了太久了,这个时候笔者打算继续看SpringBoot的源码,在看SpringBoot的源码的之前,笔者先带大家了解SpringBoot的应用。

2.本篇博客概述

在这里插入图片描述

3.从零开始构建SpringBoot项目

笔者这儿要介绍两种构建SpringBoot项目的方式,第一种通过官网的方式构建SpringBoot项目,第二种是通过idea工具构建SpringBoot项目

3.1通过官网方式构建SpringBoot项目

首先我们可以通过官网来构建SpringBoot项目,具体的地址,打开后会提示你怎么构建SpringBoot项目,读者可以直接去官网查看具体的过程,笔者在这儿就不做过多的赘述了,直接看如下的图吧

在这里插入图片描述

可以看到SpringBoot的官网给我一些常用的配置,同时右边可以让我们选一些依赖,笔者由于这儿只做测试,所以这儿就加了一个Web的依赖,于是我们直接复制对应的pom.xml然后直接复制到对应的工具中,就可以生成对应的SpringBoot的项目

在这里插入图片描述

测试的代码如下:

package com.ys;

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

@SpringBootApplication
public class SpringBootTestApplication {

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

}
package com.ys.controller;

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

@RestController
@RequestMapping("/test")
public class TestController {

    @RequestMapping("/index")
    public String test(){
        return "helloSpringBoot";
    }
}

运行的结果如下:

在这里插入图片描述

可以看到我们一个简单的SpringBoot的项目就搭建完成了。

3.2通过Idea创建SpringBoot项目

利用idea创建SpringBoot的项目就比较简单了,直接创建项目的时候,选择创建SpringBoot项目即可,具体的如下图:

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

其实创建的过程和官网的创建的方式差不多,笔者在这就不做过多的赘述了。

3.3拓展的知识点

有心细的读者,可以发现,笔者SpringBoot的启动类中没有加@ComponentScan注解,但是还是可以扫描到对应的Controller,这是因为SpringBoot在启动的过程中会自动的扫描启动类所在目录下的所有包,其中包括子包。

笔者再带大家看下对应的SpringBoot的pom文件,具体的pom文件如下:

<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.7.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.example</groupId>
    <artifactId>demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>demo</name>
    <description>Demo project for Spring Boot</description>
    <properties>
        <java.version>11</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

可以发现这个项目依赖一个父pom文件,具体的内容如下:

<dependencyManagement>
    <dependencies>
      <dependency>
        <groupId>org.apache.activemq</groupId>
        <artifactId>activemq-amqp</artifactId>
        <version>${activemq.version}</version>
      </dependency>
      <dependency>
        <groupId>org.apache.activemq</groupId>
        <artifactId>activemq-blueprint</artifactId>
        <version>${activemq.version}</version>
      </dependency>
  </dependencies>
</dependencyManagement>

由于篇幅的原因,笔者只在这儿展示了一小部分,笔者只要说明一下dependencyManagement和dependencies的区别,具体的如下:

dependencyManagement里只是声明依赖,并不实现引入,因此子项目需要显式的声明需要用的依赖。如果不在子项目中声明依赖,是不会从父项目中继承下来的;只有在子项目中写了该依赖项,并且没有指定具体版本,才会从父项目中继承该项,并且version和scope都读取自父pom;另外如果子项目中指定了版本号,那么会使用子项目中指定的jar版本。
dependencies即使在子模块中不写该依赖项,那么子模块仍然会从父项目中继承该依赖项(全部继承)。

4.简述SpringBoot自动配置的原理

整个项目中SpringBoot项目启动的时候加了一个注解@SpringBootApplication,所以自动配置的原理肯定在其中,具体的代码如下:

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.beans.factory.support.BeanNameGenerator;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.boot.context.TypeExcludeFilter;
import org.springframework.context.annotation.AnnotationBeanNameGenerator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.ComponentScan.Filter;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import org.springframework.core.annotation.AliasFor;
import org.springframework.data.repository.Repository;

@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 {};

	
	@AliasFor(annotation = ComponentScan.class, attribute = "nameGenerator")
	Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;

	
	@AliasFor(annotation = Configuration.class)
	boolean proxyBeanMethods() default true;

}

这个时候笔者和读者一样看到了@EnableAutoConfiguration注解,于是好奇心又趋势这笔者打开了这个注解中的代码,具体的代码如下:

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.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.boot.web.servlet.server.ServletWebServerFactory;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.core.io.support.SpringFactoriesLoader;

@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 {};
  
}

看过笔者写的Spring的源码的专题的话,应该对这个类中的@Import的注解很了解了,这个时候笔者就只需要打开AutoConfigurationImportSelector类中实现DeferredImportSelector接口的方法,看对应的方法就可以了,具体的方法如下:

@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
	if (!isEnabled(annotationMetadata)) {
		return NO_IMPORTS;
	}
	AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
	return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}

会调用getAutoConfigurationEntry(annotationMetadata);方法,具体的代码如下:

protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
		if (!isEnabled(annotationMetadata)) {
			return EMPTY_ENTRY;
		}
		AnnotationAttributes attributes = getAttributes(annotationMetadata);
		List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
		configurations = removeDuplicates(configurations);
		Set<String> exclusions = getExclusions(annotationMetadata, attributes);
		checkExcludedClasses(configurations, exclusions);
		configurations.removeAll(exclusions);
		configurations = getConfigurationClassFilter().filter(configurations);
		fireAutoConfigurationImportEvents(configurations, exclusions);
		return new AutoConfigurationEntry(configurations, exclusions);
	}

这个时候我们需要看getCandidateConfigurations(annotationMetadata, attributes);方法,具体代码如下:

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
		List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
				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;
}

从注释可以看出这儿是获取候选的配置,主要是META-INF/spring.factories文件,笔者就带着读者看下,具体的如下:

在这里插入图片描述

可以这些事SpringBoot提供的自动配置的类,当加了EnableXXX注解的时候,SpringBoot会通过@Import机制来这个文件中找到对应的自动配置的类进行初始化,这就是SpringBoot自动配置的原理,这儿笔者只简单的介绍了下,笔者后面的博客会详细的介绍的。

5.SpringBoot的配置文件的详解

在讲SpringBoot的配置文件的时候,我们要先知道SpringBoot的两个注解@ConfigurationProperties()@Value注解,我们先看@ConfigurationProperties()注解,于是笔者写出了如下的代码,具体的代码如下:首先笔者先创建了一个application.yml文件,具体的内容如下:

server:
  port: 80

student:
  name: zhangsan
  age: 18
  gender: male

然后笔者要做的事就是将student中内容封装到Student类中,于是笔者写出了如下的代码,具体的代码如下,其中最重要的注解就是@ConfigurationProperties(prefix = "student")注解中的prefix = "student"是为了匹配student的配置文件,如果没有加这个配置,就会将配置文件中所有内容都读取到加了这个注解的类中去。

package com.ys.controller;

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

@Component
@ConfigurationProperties(prefix = "student")
public class Student {
    private String name;
    private Integer age;
    private String gender;

    public String getName() {
        return name;
    }

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

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public String getGender() {
        return gender;
    }

    public void setGender(String gender) {
        this.gender = gender;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", gender='" + gender + '\'' +
                '}';
    }
}

最后写出如下的测试类,具体的代码如下:

package com.ys;

import com.ys.controller.Student;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
public class SpringBootDemoTest {

    @Autowired
    Student student;

    @Test
    public void test() {
        System.out.println(student);
    }
}

运行的结果如下:

在这里插入图片描述

看完了@ConfigurationProperties(prefix = "student")笔者再带大家看下@Value注解,我们修改刚才的配置类,修改过后的内容如下:

server:
  port: 80


student:
#  name: zhangsan
  age: 18
  gender: male

name: lisi

然后笔者又修改了Student的类的代码,具体的代码如下:

package com.ys.controller;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

@Component
@ConfigurationProperties(prefix = "student")
public class Student {

    @Value("${name}")
    private String name;
    private Integer age;
    private String gender;

    public String getName() {
        return name;
    }

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

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public String getGender() {
        return gender;
    }

    public void setGender(String gender) {
        this.gender = gender;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", gender='" + gender + '\'' +
                '}';
    }
}

然后运行对应的测试类,具体的运行的结果如下:

在这里插入图片描述

如果笔者将上面的配置文件的注释给去掉,运行的结果如下:

在这里插入图片描述

从这儿可以看到两个注解的优先级了。最后笔者带着读者看下两者的区别,具体的如下表:

@ConfigurationProperties@Value
功能批量注入配置文件中的属性单个支持
松散绑定(松散语法)支持不支持
SpEL不支持支持
JSR303数据校验支持不支持
复杂类型封装支持不支持

看着上面的表格,笔者有必要讲下SpEL表达式和JSR303数据校验。

5.1SpEL表达式

SpEL(Spring Expression Language),即Spring表达式语言,是比JSP的EL更强大的一种表达式语言。为什么要 总结SpEL,因为它可以在运行时查询和操作数据,尤其是数组列表型数据,因此可以缩减代码量,优化代码结构。 个人认为很有用。

算数运算符:+,-,*,/,%,^

<!-- 3 -->
<property name="num" value="#{2+1}" />
<!-- 1 -->
<property name="num" value="#{2-1}" />
<!-- 4 -->
<property name="num" value="#{2*2}" />
<!-- 3 -->
<property name="num" value="#{9/3}" />
<!-- 1 -->
<property name="num" value="#{10%3}" />
<!-- 1000 -->
<property name="num" value="#{10^3}" />

字符串连接符:+

<!-- 10年3个月 -->
<property name="numStr" value="#{10+''+3+'个月'}" />

比较运算符:<(<),>(>),==,<=,>=,lt,gt,eq,le,ge

<!-- false -->
<property name="numBool" value="#{10&lt;0}" />
<!-- false -->
<property name="numBool" value="#{10 lt 0}" />
<!-- true -->
<property name="numBool" value="#{10&gt;0}" />
<!-- true -->
<property name="numBool" value="#{10 gt 0}" />
<!-- true -->
<property name="numBool" value="#{10==10}" />
<!-- true -->
<property name="numBool" value="#{10 eq 10}" />
<!-- false -->
<property name="numBool" value="#{10&lt;=0}" />
<!-- false -->
<property name="numBool" value="#{10 le 0}" />
<!-- true -->
<property name="numBool" value="#{10&gt;=0}" />
<!-- true -->
<property name="numBool" value="#{10 ge 0}" />

逻辑运算符:and,or,not,&&(&&),||,!

<!-- false -->
<property name="numBool" value="#{true and false}" />
<!-- false -->
<property name="numBool" value="#{true&amp;&amp;false}" />
<!-- true -->
<property name="numBool" value="#{true or false}" />
<!-- true -->
<property name="numBool" value="#{true||false}" />
<!-- false -->
<property name="numBool" value="#{not true}" />
<!-- false -->
<property name="numBool" value="#{!true}" />

三元表达式: ?true:false

<!-- 真 -->
<property name="numStr" value="#{(10>3)?'':''}" />

正则表达式:matches

<!-- true -->
<property name="numBool" value="#{user.email matches '[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+.
[a-zA-Z]{2,4}'}" />

调用静态资源:

<!-- 3.141592653589793 -->
<property name="PI" value="#{T(java.lang.Math).PI}" />

5.2JSR303校验参数规范

JSR是Java Specification Requests的缩写,意思是Java 规范提案。是指向JCP(Java Community Process)提出新增 一个标准化技术规范的正式请求。任何人都可以提交JSR,以向Java平台增添新的API和服务。JSR已成为Java界的一 个重要标准。

由于篇幅的原因,笔者在这只介绍JSR303中的常用的注解,至于怎么使用,读者可以自行查找。常用的注解如下:

空检查
@Null     验证对象是否为null
@NotNull  验证对象是否不为null, 无法查检长度为0的字符串
@NotBlank 检查约束字符串是不是Null还有被Trim的长度是否大于0,只对字符串,且会去掉前后空格.
@NotEmpty 检查约束元素是否为NULL或者是EMPTY.

Booelan检查
@AssertTrue  验证 Boolean 对象是否为 true
@AssertFalse 验证 Boolean 对象是否为 false

长度检查
@Size(min=, max=) 验证对象(Array,Collection,Map,String)长度是否在给定的范围之内
@Length(min=, max=) Validates that the annotated string is between min and max included.

日期检查
@Past     验证 Date 和 Calendar 对象是否在当前时间之前
@Future   验证 Date 和 Calendar 对象是否在当前时间之后
@Pattern  验证 String 对象是否符合正则表达式的规则

数值检查,建议使用在Stirng,Integer类型,不建议使用在int类型上,因为表单值为“”时无法转换为int,但 可以转换为Stirng为"",Integer为null
@Min        验证 Number 和 String 对象是否大等于指定的值
@Max        验证 Number 和 String 对象是否小等于指定的值
@DecimalMax 被标注的值必须不大于约束中指定的最大值. 这个约束的参数是一个通过BigDecimal定义的最大 值的字符串表示.小数存在精度
@DecimalMin 被标注的值必须不小于约束中指定的最小值. 这个约束的参数是一个通过BigDecimal定义的最小 值的字符串表示.小数存在精度
@Digits     验证 Number 和 String 的构成是否合法
@Digits(integer=,fraction=) 验证字符串是否是符合指定格式的数字,interger指定整数精度, fraction指定小数精度。

@Range(min=, max=) 检查数字是否介于min和max之间.
@Range(min=10000,max=50000,message="range.bean.wage")

@Valid 递归的对关联对象进行校验, 如果关联对象是个集合或者数组,那么对其中的元素进行递归校验,如果是一 个map,则对其中的值部分进行校验.(是否进行递归验证)
@CreditCardNumber 信用卡验证
@Email 验证是否是邮件地址,如果为null,不进行验证,算通过验证。
@ScriptAssert(lang= ,script=, alias=)
@URL(protocol=,host=, port=,regexp=, flags=)

6.自动配置

有的时候我们在自动配置的时候,需要对条件进行判断,判断这个类是否需要自动配置,这个时候就需要我们使用@Conditional注解,常用的@Conditional扩展的注解如下,主要的作用就是判断是否满足当前指定的条件

@ConditionalOnJava系统的Java版本是否符合要求
@ConditionalOnBean容器中存在指定Bean
@ConditionalOnMissingBean容器中不存在指定的Bean
@ConditionalOnExpression满足SpEL表达式指定
@ConditionalOnClass系统中有指定的类
@ConditionalOnMissingClass系统中没有指定的类
@ConditionalOnSingleCandidate容器中只有一个指定的Bean,或者这个Bean是首选Bean
@ConditionalOnProperty系统中指定的属性是否有指定的值
@ConditionalOnResource类路径下是否存在指定的资源文件
@ConditionalOnWebApplication当前是web环境
@ConditionalOnNotWebApplication当前不是Web环境

笔者就简单的举个例子,废话不多说,直接上代码,具体的代码如下,笔者先创建了一个A类

package com.ys.controller;

import org.springframework.stereotype.Component;

@Component
public class A {
}

还是原来的Student类,只不过给对应的Student类中添加了一个注解,具体的代码如下:

package com.ys.controller;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

@Component
@ConditionalOnBean(value = A.class)
@ConfigurationProperties(prefix = "student")
public class Student {

    @Value("${name}")
    private String name;
    private Integer age;
    private String gender;

    public String getName() {
        return name;
    }

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

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public String getGender() {
        return gender;
    }

    public void setGender(String gender) {
        this.gender = gender;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", gender='" + gender + '\'' +
                '}';
    }
}

然后运行测试类,具体的结果如下:

在这里插入图片描述

当我们将A类从Spring容器中去除,然后再运行,具体的结果如下:

在这里插入图片描述

直接创建失败,因为依赖的student的属性没有在Spring容器中,由于A不在Spring的容器中,所以Student这个类也不会添加到Spring的容器中,所以这儿直接报错。

7.写在最后

笔者这篇博客大概的介绍了下SpringBoot的一些常用的应用,后面的博客笔者会继续介绍SpringBoot的一些应用,等一些常用的应用介绍完了,就会介绍SpringBoot的源码,这篇博客只是简单的介绍SpringBoot自动配置原理。后面的博客会详细的介绍。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值