SpringBoot 基础学习

22 篇文章 0 订阅

SpringBoot 基础学习


参考 狂神说 springboot
更多资料
尚硅谷雷神springboot2
尚硅谷雷神对应语雀笔记

SpringBoot简介

回顾什么是Spring

轻量级的Java 开发框架,为了解决企业级应用开发的复杂性而创建的,简化开发

Spring是如何简化Java开发的

为了降低Java开发的复杂性,Spring采用了以下4种关键策略:

  1. 基于POJO的轻量级和最小侵入性编程,所有东西都是bean
  2. 通过IOC,依赖注入(DI)面向接口实现松耦合
  3. 基于切面(AOP)和惯例进行声明式编程
  4. 通过切面和模版减少样式代码RedisTemplate,xxxTemplate

什么是SpringBoot

学过javaweb的同学就知道,开发一个web应用,从最初开始接触Servlet结合Tomcat, 跑出一个Hello Wolrld程序,是要经历特别多的步骤;后来就用了框架Struts,再后来是SpringMVC,到了现在的SpringBoot,过一两年又会有其他web框架出现;你们有经历过框架不断的演进,然后自己开发项目所有的技术也在不断的变化、改造吗?建议都可以去经历一遍;

言归正传,什么是SpringBoot呢,就是一个javaweb的开发框架,和SpringMVC类似,对比其他javaweb框架的好处,官方说是简化开发,约定大于配置, you can “just run”,能迅速的开发web应用,几行代码开发一个http接口。

所有的技术框架的发展似乎都遵循了一条主线规律:从一个复杂应用场景 衍生 一种规范框架,人们只需要进行各种配置而不需要自己去实现它,这时候强大的配置功能成了优点;发展到一定程度之后,人们根据实际生产应用情况,选取其中实用功能和设计精华,重构出一些轻量级的框架;之后为了提高开发效率,嫌弃原先的各类配置过于麻烦,于是开始提倡“约定大于配置”,进而衍生出一些一站式的解决方案。

是的这就是Java企业级应用->J2EE->spring->springboot的过程。

随着 Spring 不断的发展,涉及的领域越来越多,项目整合开发需要配合各种各样的文件,慢慢变得不那么易用简单,违背了最初的理念,甚至人称配置地狱。Spring Boot 正是在这样的一个背景下被抽象出来的开发框架,目的为了让大家更容易的使用 Spring 、更容易的集成各种常用的中间件、开源软件

Spring Boot 基于 Spring 开发,Spirng Boot 本身并不提供 Spring 框架的核心特性以及扩展功能,只是用于快速、敏捷地开发新一代基于 Spring 框架的应用程序。也就是说,它并不是用来替代 Spring 的解决方案,而是和 Spring 框架紧密结合用于提升 Spring 开发者体验的工具。Spring Boot 以约定大于配置的核心思想,默认帮我们进行了很多设置,多数 Spring Boot 应用只需要很少的 Spring 配置。同时它集成了大量常用的第三方库配置(例如 Redis、MongoDB、Jpa、RabbitMQ、Quartz 等等),Spring Boot 应用中这些第三方库几乎可以零配置的开箱即用。

简单来说就是SpringBoot其实不是什么新的框架,它默认配置了很多框架的使用方式,就像maven整合了所有的jar包spring boot整合了所有的框架

Spring Boot 出生名门,从一开始就站在一个比较高的起点,又经过这几年的发展,生态足够完善,Spring Boot 已经当之无愧成为 Java 领域最热门的技术。

Spring Boot的主要优点:

  • 为所有Spring开发者更快的入门
  • 开箱即用,提供各种默认配置来简化项目配置
  • 内嵌式容器简化Web项目
  • 没有冗余代码生成和XML配置的要求

架构模式

单体应用架构

all in one 一个应用的所有应用服务封装在一个应用中,数据库访问,web访问等各个功能放到一个war中

  • 好处:方便开发测试,方便部署,需要扩展时,只需要把war复制多份放到不同的服务器即可
  • 缺点:改动一个小地方也要停掉整个服务重新打包部署

微服务架构

打破 all in one 模式,把每个功能元素独立出来,是对功能元素进行复制,而不是对整个应用进行复制需要的功能动态组合

  • 好处:节省调用资源每个功能元素都是一个可替换的可独立升级的部分

在这里插入图片描述

创建项目初步

准备工作

  • java version “1.8.0_181”
  • Maven-3.6.1
  • SpringBoot 2.x 最新版

创建项目

Spring官方提供了非常方便的工具让我们快速构建应用
Spring Initializr:https://start.spring.io/

方式一:使用Spring Initializr 的 Web页面创建项目

	1. 打开 https://start.spring.io/
	2. 填写项目信息
	3. 点击”Generate Project“按钮生成项目;下载此项目
	4. 解压项目包,并用IDEA以Maven项目导入,一路下一步即可,直到项目导入完毕。
	5. 如果是第一次使用,可能速度会比较慢,包比较多、需要耐心等待一切就绪。

方式二:使用 IDEA 直接创建项目

	1. 创建一个新项目
	2. 选择spring initalizr , 可以看到默认就是去官网的快速构建工具那里实现
	3. 填写项目信息
	4. 选择初始化的组件(初学勾选 Web 即可)
	5. 填写项目路径
	6. 等待项目构建成功

通过上面步骤完成了基础项目的创建。就会自动生成以下文件。

	程序的主启动类 XXXXAppication 程序主入口
	一个 application.properties 核心配置文件
	一个 测试类 
	一个 pom.xml

pom.xml

<!-- 父依赖,控制版本和打包-->
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.2.5.RELEASE</version>
    <relativePath/>
</parent>

<dependencies>
    <!-- web依赖,tomcat作为默认嵌入式容器,dispatcherservlet ,xml.... 实现http接口,包含了spring mvc->
    <!-- 是一个web启动器 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <!-- springboot单元测试 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
		<!-- springboot 所有的依赖都是 spring-boot-starter 开头的 -->
        <!-- 剔除依赖 -->
        <exclusions>
            <exclusion>
                <groupId>org.junit.vintage</groupId>
                <artifactId>junit-vintage-engine</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
</dependencies>

构建配置,打成jar包
<build>
    <plugins>
        <!-- 打包插件 -->
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
        </plugin>
    </plugins>
</build>

不加打包插件spring-boot-maven-plugin,直接在侧边栏maven工具打包,会导致打的包中没有一些jar可以单独运行的依赖jar,比如没有tomcat的jar,命令行运行时会导致以下错误

:target didi$ java -jar helloworld-1.0-SNAPSHOT.jar 
helloworld-1.0-SNAPSHOT.jar中没有主清单属性
:target didi$ 

而添加打包插件后,右侧工具栏plugins中会显示此插件jar,没添加或添加失败不显示

在这里插入图片描述

打包后的jar包在 unzip xxx 解压后,会包含很多运行需要的jar包,通过 java -jar xxx 可以直接运行项目

在这里插入图片描述

第一个接口

  1. 在主程序的同级目录下,新建一个controller包,一定要在同级目录下,否则识别不到
  2. 在包中新建一个HelloController类
	@RestController
	public class HelloController {
	    @RequestMapping("/hello")
	    public String hello() {
	        return "Hello World";
	    }
	}
  1. 编写完毕后,从主程序启动项目,浏览器发起请求,看页面返回;控制台输出了 Tomcat 访问的端口号!
    在这里插入图片描述
  2. 可以通过idea侧边栏的maven菜单栏中package打包,命令行中直接输入以下代码,即可运行项目
	java -jar jar包拖过来

彩蛋

  • 更改访问端口号
	application.properties文件中设置
	sever.port=8081
  • 更改启动时显示的字符拼成的字母,到项目下的 resources 目录下新建一个banner.txt 即可,图案可以到:https://www.bootschool.net/ascii 这个网站生成,然后拷贝到文件中
    在这里插入图片描述

原理

父依赖

主要是依赖一个父项目,主要是管理项目的资源过滤及插件

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.2.5.RELEASE</version>
    <relativePath/> <!-- lookup parent from repository -->
</parent>

点进去,发现还有一个父依赖,这里才是真正管理SpringBoot应用里面所有依赖版本的地方,SpringBoot的版本控制中心
以后我们导入依赖默认是不需要写版本;但是如果导入的包没有在依赖中管理着就需要手动配置版本

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-dependencies</artifactId>
    <version>2.2.5.RELEASE</version>
    <relativePath>../../spring-boot-dependencies</relativePath>
</parent>

自定义jar版本号

引入依赖默认都可以不写版本,springboot自动配置版本号,必要时自己设置

1、查看spring-boot-dependencies里面规定当前依赖的版本 用的 key。
2、在当前项目里面重写配置
    <properties>
        <mysql.version>5.1.43</mysql.version>
    </properties>

启动器 spring-boot-starter

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

springboot-boot-starter-xxx就是spring-boot的场景启动器

spring-boot-starter-web:帮我们导入了web模块 正常运行所依赖的组件

SpringBoot将所有的功能场景都抽取出来,做成一个个的starter (启动器)只需要在项目中引入这些starter即可所有相关的依赖都会导入进来 , 我们要用什么功能就导入什么样的场景启动器即可 ;我们未来也可以自己自定义 starter;

启动类

//@SpringBootApplication 来标注一个主程序类
//说明这是一个Spring Boot应用
@SpringBootApplication
public class SpringbootApplication {

   public static void main(String[] args) {
     //将springboot应用启动
      SpringApplication.run(SpringbootApplication.class, args);
   }
}

重要的注解

@Configuration

  • 配置类组件之间无依赖关系,用Lite模式加速容器启动过程,减少判断,运行时不用生成CGLIB子类,提高运行性能,降低启动时间,可以作为普通类使用。但是不能声明@Bean之间的依赖
  • 配置类组件之间有依赖关系,方法会被调用得到之前单实例组件,用Full模式,单例模式能有效避免Lite模式下的错误。性能没有Lite模式好

Full模式和Lite模式是针对spring配置而言的,和xml配置无关。

Spring的@Configuration配置类-Full和Lite模式

何时为Lite 模式:

  1. 配置类上有@Component、@ComponentScan、@Import、@ImportResource注解之一
  2. 配置类上没有任何注解,但是类中存在@Bean方法
  3. 配置类上有@Configuration(proxyBeanMethods = false)注解

何时为Full模式:单例默认是Full模式

  • 标注有@Configuration或者@Configuration(proxyBeanMethods = true)的配置类被称为Full模式的配置类
@Configuration(proxyBeanMethods = true)  生成一个 proxy 做单例代理
#############################Configuration使用示例######################################################
/**
 * 1、配置类里面使用@Bean标注在方法上给容器注册组件,默认也是单实例的
 * 2、配置类本身也是组件
 * 3、proxyBeanMethods:代理bean的方法
 *      Full模式:@Configuration(proxyBeanMethods = true)【保证每个@Bean方法被调用多少次返回的组件都是单实例的】
 *      Lite模式:@Configuration(proxyBeanMethods = false)【每个@Bean方法被调用多少次返回的组件都是新创建的】
 *      组件依赖必须使用Full模式默认。其他默认是否Lite模式
 *
 *
 *
 */
@Configuration(proxyBeanMethods = false) //告诉SpringBoot这是一个配置类 == 配置文件
public class MyConfig {

    /**
     * Full:外部无论对配置类中的这个组件注册方法调用多少次获取的都是之前注册容器中的单实例对象
     * @return
     */
    @Bean //给容器中添加组件。以方法名作为组件的id。返回类型就是组件类型。返回的值,就是组件在容器中的实例
    public User user01(){
        User zhangsan = new User("zhangsan", 18);
        //user组件依赖了Pet组件
        zhangsan.setPet(tomcatPet());
        return zhangsan;
    }

    @Bean("tom")
    public Pet tomcatPet(){
        return new Pet("tomcat");
    }
}

################################@Configuration测试代码如下########################################
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan("com.atguigu.boot")
public class MainApplication {

    public static void main(String[] args) {
        //1、返回我们IOC容器
        ConfigurableApplicationContext run = SpringApplication.run(MainApplication.class, args);

        //2、查看容器里面的组件
        String[] names = run.getBeanDefinitionNames();
        for (String name : names) {
            System.out.println(name);
        }

        //3、从容器中获取组件

        Pet tom01 = run.getBean("tom", Pet.class);

        Pet tom02 = run.getBean("tom", Pet.class);
        
		//Lite 和 Full 模式模式都是true
        System.out.println("组件:"+(tom01 == tom02));
		

        //4、com.atguigu.boot.config.MyConfig$$EnhancerBySpringCGLIB$$51f1e1ca@1654a892
        MyConfig bean = run.getBean(MyConfig.class);
        System.out.println(bean);

        //如果@Configuration(proxyBeanMethods = true)代理对象调用方法。SpringBoot总会检查这个组件是否在容器中有。
        //保持组件单实例
        User user = bean.user01();
        User user1 = bean.user01();
        System.out.println(user == user1);


        User user01 = run.getBean("user01", User.class);
        Pet tom = run.getBean("tom", Pet.class);

        System.out.println("用户的宠物:"+(user01.getPet() == tom));
    }
}

@Bean、@Component、@Controller、@Service、@Repository

根据它们的源码可以看到,Controller、Service、Repository其本质就是Component,都是Bean组件,都会在项目启动时间,加载到容器中

它存在的本质只是给开发者看的,对Spring而言它们就都是Component。

@Controller 控制层类,@Service 业务层类,@Repository 持久层类@Component 无法归类到前3种时就称为组件。

@ComponentScan、@Import

给容器注入组件:
	1. 包扫描+组件标注注解 @Controller/@Service/@Repository/@Component [把自己的包导入]
	2. @Bean[导入第三方包里的组件]
	3. @Import[快速导入组件到容器],将组件创建出来导入容器
AnnotationMetadata:当前标注@Import注解的类的所有注解信息
Import使用方式:
在MyConfig等添加@Configuration注解的自定义配置文件中添加@Import注解

	1. //给容器中 自动创建 出这两个类型的组件、默认组件的名字就是全类名
	   @Import({User.class, DBHelper.class})
	2. //创建一个MyImportSelector对象实现ImportSelector接口,重写selectImports方法,return 要加载到容器中的对象class
    	public class MyImportSelector implements ImportSelector{
    		//返回值就是导入到容器中的组件全类名
    		//AnnotationMetadata:当前标注@Import注解的类的所有注解信息
    		@Override
    		public String[] selectImports(AnnotationMetadata importingClassMetadata){
    			return new String[]{"com.demo.bean.User","com.demo.bean.Pets"};
    		}
    	}
 	  MyConfig@Import(MyImportSelector.class)
	3.2中类似
		public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar{
    		//BeanDefinitionRegistry:bean注册类,调用registry.registerBeanDefinition()手动注册
    		//AnnotationMetadata:当前标注@Import注解的类的所有注解信息
    		@Override
    		public void registerBeanDefinitions()(AnnotationMetadata importingClassMetadata,BeanDefinitionRegistry registry){
    		//指定bean,注册一个bean	
    			BeanDefinition beanDefinition = new RootBeanDefinition(RainBow.class);
    			registry.registerBeanDefinition("rainBow",beanDefinition)
    		}
    	}
     
     MyConfig@Import(MyImportBeanDefinitionRegistrar.class)

@Conditional

放在配置类上表示,当容器中满足条件时,配置类中的组件才生效
放在配置方法上的时候,表示的意思是当满足条件的时候配置方法才生效

在这里插入图片描述
在这里插入图片描述

那么多的自动配置类,必须在一定的条件下才能生效;也就是说,我们加载了这么多的配置类,但不是所有的都生效了

可以通过启用 debug=true属性;来让控制台打印自动配置报告,这样我们就可以很方便的知道哪些自动配置类生效

#开启springboot的调试类
debug=true

Positive matches:(自动配置类启用的:正匹配)
Negative matches:(没有启动,没有匹配成功的自动配置类:负匹配)
Unconditional classes: (没有条件的类)

或者在 启动时 通过 ConfigurableApplicationContext 查看

在这里插入图片描述


=====================测试条件装配==========================
@Configuration(proxyBeanMethods = false) //告诉SpringBoot这是一个配置类 == 配置文件
//@ConditionalOnBean(name = "tom")
@ConditionalOnMissingBean(name = "tom")  //当容器中没有tom组件时,MyConfig配置才生效,才会有user01和tom22组件
public class MyConfig {

    @Bean //给容器中添加组件。以方法名作为组件的id。返回类型就是组件类型。返回的值,就是组件在容器中的实例
    public User user01(){
        User zhangsan = new User("zhangsan", 18);
        //user组件依赖了Pet组件
        zhangsan.setPet(tomcatPet());
        return zhangsan;
    }

    @Bean("tom22")
    public Pet tomcatPet(){
        return new Pet("tomcat");
    }
}

========测试类=========

public static void main(String[] args) {
        //1、返回我们IOC容器
        ConfigurableApplicationContext run = SpringApplication.run(MainApplication.class, args);

        //2、查看容器里面的组件
        String[] names = run.getBeanDefinitionNames();
        for (String name : names) {
            System.out.println(name);
        }

        boolean tom = run.containsBean("tom");
        System.out.println("容器中Tom组件:"+tom);

        boolean user01 = run.containsBean("user01");
        System.out.println("容器中user01组件:"+user01);

        boolean tom22 = run.containsBean("tom22");
        System.out.println("容器中tom22组件:"+tom22);
    }

原生配置文件引入@ImportResource

指以.xml结尾的配置文件,通过@ImportResource导入后SpringBoot进行解析,完成对应的组件注册

======================beans.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:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">

    <bean id="haha" class="com.atguigu.boot.bean.User">
        <property name="name" value="zhangsan"></property>
        <property name="age" value="18"></property>
    </bean>

    <bean id="hehe" class="com.atguigu.boot.bean.Pet">
        <property name="name" value="tomcat"></property>
    </bean>
</beans>
@ImportResource("classpath:beans.xml")
public class MyConfig {}

======================测试=================
        boolean haha = run.containsBean("haha");
        boolean hehe = run.containsBean("hehe");
        System.out.println("haha:"+haha);//true
        System.out.println("hehe:"+hehe);//true

配置绑定

如何使用Java读取到properties文件中的内容,并且把它封装到JavaBean中,以供随时使用

ConfigurationProperties 注解主要的目的是设置前缀,相当于初始化参数,之后我们想要用到,需要容器中找,所以肯定是需要放到容器中,通过Component,想要用通过autowired,或者不用component用另一个具有这功能的注解

application.properties中

mycar.name=yadi
mycar.age=21

方式一:@Component 和 @ConfigurationProperties

@Component 和 @ConfigurationProperties(prefix = "mycar")声明在要绑定的类的上方

/**
 * 只有在容器中的组件,才会拥有SpringBoot提供的强大功能
 */
@Component
@ConfigurationProperties(prefix = "mycar")
public class Car {
}

方式二:@EnableConfigurationProperties + @ConfigurationProperties

  1. @ConfigurationProperties(prefix = "mycar")声明在要绑定的类的上方;
  2. 在配置类的上方声明@EnableConfigurationProperties(Car.class),开启对应类的配置绑定功能,把Car这个组件自动注入到容器中@EnableconfigurationProperties必须在配置类中
@EnableConfigurationProperties(Car.class) // 开启 Car 的属性配置并自动注入到容器中
public class MyConfiguration {}

@ConfigurationProperties(prefix = "mycar")
public class Car {}

实例二

task:
  init: false
  namespace:
    name: userTask
    enable: false
  zone:
    uuid: user task
    displayName: user zone
    description: user zone 
zookeeper.endpoints: 127.0.0.1:2181
@Component
@Data
@AllArgsConstructor
@NoArgsConstructor
@ConfigurationProperties("task")
public class TaskProperties {

    private boolean init = true;
    private Zone zone = new Zone();
    private Namespace namespace = new Namespace();

    @Data
    public static class Zone {
        private String uuid;
        private String displayName;
        private String description; 
    }

    @Data
    public static class Namespace {
        private String name;
        private boolean enable;
    }
}

或者 new 对象时 绑定配置

@Data
@AllArgsConstructor
@NoArgsConstructor
public class TaskProperties {
   ...
}
@Bean
@ConfigurationProperties("task")
public TaskProperties taskProperties () {
   return new TaskProperties();
}

自动配置原理

必看 ! SpringBoot 自动装配

自动配置真正实现是从 classpath 中搜寻所有的META-INF/spring.factories配置文件
并将其中对应的 org.springframework.boot.autoconfigure… 作为 key, 将其下的配置项
通过反射实例化为对应标注了 @ConfigurationJavaConfig形式的 IOC 容器配置类
然后将这些都汇总成为一个实例并加载到IOC容器中。

xxxxAutoConfiguration

我们以HttpEncodingAutoConfiguration(Http编码自动配置)为例解释自动配置原理

//表示这是一个配置类,和以前编写的配置文件一样,也可以给容器中添加组件;
@Configuration 

//启动指定类的ConfigurationProperties功能;
  //进入这个HttpProperties查看,将配置文件中对应的值和HttpProperties绑定起来;
  //并把HttpProperties加入到ioc容器中
@EnableConfigurationProperties({HttpProperties.class}) 

//Spring底层@Conditional注解
  //根据不同的条件判断,如果满足指定的条件,整个配置类里面的配置就会生效;
  //这里的意思就是判断当前应用是否是web应用,如果是,当前配置类生效
@ConditionalOnWebApplication(
    type = Type.SERVLET
)

//判断当前项目有没有这个类CharacterEncodingFilter;SpringMVC中进行乱码解决的过滤器;
@ConditionalOnClass({CharacterEncodingFilter.class})

//判断配置文件中是否存在某个配置:spring.http.encoding.enabled;
  //如果不存在,判断也是成立的
  //即使我们配置文件中不配置spring.http.encoding.enabled=true,也是默认生效的;
@ConditionalOnProperty(
    prefix = "spring.http.encoding",
    value = {"enabled"},
    matchIfMissing = true
)

public class HttpEncodingAutoConfiguration {
    //他已经和SpringBoot的配置文件映射了
    private final Encoding properties;
    //只有一个有参构造器的情况下,参数的值就会从容器中拿
    public HttpEncodingAutoConfiguration(HttpProperties properties) {
        this.properties = properties.getEncoding();
    }
    
    //给容器中添加一个组件,这个组件的某些值需要从properties中获取
    @Bean
    @ConditionalOnMissingBean //判断容器没有这个组件
    public CharacterEncodingFilter characterEncodingFilter() {
        CharacterEncodingFilter filter = new OrderedCharacterEncodingFilter();
        filter.setEncoding(this.properties.getCharset().name());
        filter.setForceRequestEncoding(this.properties.shouldForce(org.springframework.boot.autoconfigure.http.HttpProperties.Encoding.Type.REQUEST));
        filter.setForceResponseEncoding(this.properties.shouldForce(org.springframework.boot.autoconfigure.http.HttpProperties.Encoding.Type.RESPONSE));
        return filter;
    }
    //。。。。。。。
}

一句话总结 :根据当前不同的条件判断,决定这个配置类是否生效

  • 一但这个配置类生效;这个配置类就会给容器中添加各种组件
  • 这些组件的属性是从对应的properties类中获取的,这些类里面的每一个属性又是和配置文件绑定的
  • 所有在配置文件中能配置的属性都是在xxxxProperties类中封装着
  • 配置文件能配置什么就可以参照某个功能对应的这个属性类
//从配置文件中获取指定的值和bean的属性进行绑定
@ConfigurationProperties(prefix = "spring.http") 
public class HttpProperties {
    // .....
}

配置文件里面试试前缀
在这里插入图片描述)

总结:

  1. SpringBoot启动会加载大量的自动配置类
  2. 看我们需要的功能有没有在SpringBoot默认写好的自动配置类当中
  3. 再来看这个自动配置类中到底配置了哪些组件;(只要我们要用的组件存在在其中,我们就不需要再手动配置了
  4. 给容器中自动配置类添加组件的时候,会从properties类中获取某些属性。我们只需要在配置文件中指定这些属性的值即可

springboot到底帮我配置了什么?

  • xxxAutoConfiguration:自动配置类,向容器中自动配置组件
  • xxxProperties:自动配置类,装配配位文件中自定义内容,封装配置文件中相关属性

查看配置了什么

在这里插入图片描述
在这里插入图片描述

相关注解

@SpringBootApplication

作用:标注在某个类上说明这个类是SpringBoot的主配置类 , SpringBoot就应该运行这个类的main方法来启动SpringBoot应用

@SpringBootApplication
public class DemoApplication {

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

}

进入这个注解:可以看到上面还有很多其他注解!

//这三个注解等于SpringBootApplication
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(
    excludeFilters = {@Filter(
    type = FilterType.CUSTOM,
    classes = {TypeExcludeFilter.class}
), @Filter(
    type = FilterType.CUSTOM,
    classes = {AutoConfigurationExcludeFilter.class}
)}
)
public @interface SpringBootApplication {
    // ......
}
@ComponentScan

这个注解在Spring中很重要 ,它对应XML配置中的元素,决定扫描哪些包

用法 @ComponentScan("com.atguigu.boot")

作用:自动扫描并加载符合条件的组件或者bean , 将这些bean定义加载到IOC容器中

@SpringBootConfiguration

作用:SpringBoot的配置类 ,标注在某个类上 , 表示这是一个SpringBoot的配置类
我们继续进去这个注解查看

// 点@Configuration进去得到下面的 @Component
@Configuration
public @interface SpringBootConfiguration {}

@Component
public @interface Configuration {}

这里的 @Configuration,说明这是一个配置类 配置类就是对应Spring的xml 配置文件

里面的@Component这就说明,启动类本身也是Spring中的一个组件而已,负责启动应用!

我们回到 SpringBootApplication 注解中继续看。

@EnableAutoConfiguration

开启自动配置功能,以前我们需要自己配置的东西,而现在SpringBoot可以自动帮我们配置
@EnableAutoConfiguration告诉SpringBoot开启自动配置功能,这样自动配置才能生效

点进注解接续查看:

@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {}
@AutoConfigurationPackage

自动配置包

@Import({Registrar.class})
public @interface AutoConfigurationPackage {
}

@importSpring底层注解@import , 给容器中导入一个组件
Registrar.class 作用:将主启动类MainApplication的所在包及包下面所有子包里面的所有组件扫描到Spring容器 ;

Registrarstatic class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
	@Override
	public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
		register(registry, new PackageImports(metadata).getPackageNames().toArray(new String[0]));
	}
}

registerBeanDefinitons方法的metadate参数指的是注解源,new PackaImports(metadata)导入包中的组件,getPackageNames()获得包名,toArray封装到数组中。
最终注册进去

@Import({AutoConfigurationImportSelector.class})
@Import  给容器导入组件,自动导入包的核心!!!
AutoConfigurationImportSelector.class   自动导入选择器,选择了啥

给容器导入组件,AutoConfigurationImportSelector :自动配置导入选择器

AutoConfigurationImportSelector.class

// 获取到所有需要导入到容器中的候选配置类  
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
    //这里的getSpringFactoriesLoaderFactoryClass()方法
    //返回的就是我们最开始看的启动自动导入配置文件的注解类;EnableAutoConfiguration
    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;
}

在这里插入图片描述

调用的方法 SpringFactoriesLoader.loadFactoryNames() 得到所有的组件
SpringFactoriesLoader 在 spring-core 包中
public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
    String factoryClassName = factoryClass.getName();
    //这里它又调用了 loadSpringFactories 方法
    return (List)loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
}
调用的方法loadSpringFactories(classLoader).getOrDefault()

private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
    //获得classLoader , 我们返回可以看到这里得到的就是EnableAutoConfiguration标注的类本身
    MultiValueMap<String, String> result = (MultiValueMap)cache.get(classLoader);
    if (result != null) {
        return result;
    } else {
        try {
            //去获取一个资源 "META-INF/spring.factories"
            Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");
            LinkedMultiValueMap result = new LinkedMultiValueMap();

            //将读取到的资源遍历,封装成为一个Properties
            while(urls.hasMoreElements()) {
                URL url = (URL)urls.nextElement();
                UrlResource resource = new UrlResource(url);
                Properties properties = PropertiesLoaderUtils.loadProperties(resource);
                Iterator var6 = properties.entrySet().iterator();

                while(var6.hasNext()) {
                    Entry<?, ?> entry = (Entry)var6.next();
                    String factoryClassName = ((String)entry.getKey()).trim();
                    String[] var9 = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());
                    int var10 = var9.length;

                    for(int var11 = 0; var11 < var10; ++var11) {
                        String factoryName = var9[var11];
                        result.add(factoryClassName, factoryName.trim());
                    }
                }
            }

            cache.put(classLoader, result);
            return result;
        } catch (IOException var13) {
            throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var13);
        }
    }
}
spring.factories

自动配置根源所在,在org.springframework.boot.autoconfigure. 包
文件里面写死了spring-boot一启动就要给容器中加载的所有配置类

在这里插入图片描述
我们在上面的自动配置类随便找一个打开看看,比如 :WebMvcAutoConfiguration
在这里插入图片描述
可以看到这些一个个的都是JavaConfig配置类,而且都注入了一些Bean

自动配置真正实现是从classpath中搜寻所有的META-INF/spring.factories配置文件 ,并将其中对应的 org.springframework.boot: spring-boot-autoconfigure包下的配置项,通过反射实例化为对应标注了 @Configuration的JavaConfig形式的IOC容器配置类 , 然后将这些都汇总成为一个实例加载到IOC容器中

按需开启自动配置项

虽然我们127个场景的所有自动配置启动的时候默认全部加载xxxxAutoConfiguration
按照条件装配规则(@Conditional),最终会按需配置

修改默认配置

给容器中加入了文件上传解析器;
@Bean
@ConditionalOnBean(MultipartResolver.class)  //容器中有这个类型组件
@ConditionalOnMissingBean(name = DispatcherServlet.MULTIPART_RESOLVER_BEAN_NAME) //容器中没有这个名字 multipartResolver 的组件
public MultipartResolver multipartResolver(MultipartResolver resolver) {
    //给@Bean标注的方法传入了对象参数,这个参数的值就会从容器中找。
    //	防止有些用户配置的文件上传解析器不符合规范
	// Detect if the user has created a MultipartResolver but named it incorrectly
	return resolver;
}

SpringBoot默认会在底层配好所有的组件。但是如果用户自己配置了以用户的优先

@Bean
@ConditionalOnMissingBean
public CharacterEncodingFilter characterEncodingFilter() {}

结论

  • SpringBoot在启动的时候从类路径下的 META-INF/spring.factories 中获取 EnableAutoConfiguration 指定的类
  • 每个自动配置类按照条件进行生效默认都会绑定配置文件指定的值xxxxProperties里面拿。xxxProperties和配置文件进行了绑定
  • 将这些生效的类作为自动配置类导入容器,帮我们进行自动配置工作
  • 整个J2EE的整体解决方案和自动配置都在org.springframework.boot: spring-boot-autoconfigurejar包中
  • 它会给容器中导入非常多的自动配置类 (xxxAutoConfiguration), 就是给容器中导入这个场景需要的所有组件 , 并配置好这些组件
  • 有了自动配置类 , 免去了我们手动编写配置注入功能组件等的工作
  • 定制化配置
    • 用户直接自己@Bean替换底层的组件
    • 常用:用户去看这个组件是获取的配置文件什么值就去修改。
      xxxxxAutoConfiguration —> 组件 —> xxxxProperties里面拿值 ----> application.properties中配置,例如server.port=8081

最佳实践

  • 引入场景依赖
    https://docs.spring.io/spring-boot/docs/current/reference/html/using-spring-boot.html#using-boot-starter
  • 查看自动配置了哪些(选做)
    • 自己分析,引入场景对应的自动配置一般都生效了
    • 配置文件中debug=true开启自动配置报告。Negative(不生效)\Positive(生效)
  • 是否需要修改
    • 参照文档修改配置项
      https://docs.spring.io/spring-boot/docs/current/reference/html/appendix-application-properties.html#common-application-properties
      自己分析。xxxxProperties绑定了配置文件的哪些。
    • 自定义加入或者替换组件
      @Bean、@Component。。。
  • 自定义器 XXXXXCustomizer;

自定义banner图

  1. 在官方文档(https://docs.spring.io/spring-boot/docs/current/reference/html/application-properties.html#application-properties.core)中找到banner的配置
    在这里插入图片描述

  2. 配置application.properties配置文件中的自定义属性

    #不配置的话,默认为resources路径下的banner.gif,找不到这个图片就使用原生的spring文字
    #spring.banner.image.location=classpath:banner.gif
    #spring.banner.image.location=classpath:test.png
    

    在这里插入图片描述
    设置spring.banner.location 在这里插入图片描述 可以commend点击spring.banner.image.location在打开的配置文件中找到默认配置 在这里插入图片描述

SpringBoot的main方法

实际上不仅仅是运行了一个main方法,同时开启了一个服务

@SpringBootApplication
public class SpringbootApplication {
    public static void main(String[] args) {
        //run方法实际上是springApplication的实例化,二是run方法的执行
        SpringApplication.run(SpringbootApplication.class, args);
    }
}

SpringApplication

这个类主要做了以下四件事情:

  1. 推断应用的类型是普通的项目还是Web项目
  2. 查找并加载所有可用初始化器 , 设置到initializers属性中
  3. 找出所有的应用程序监听器,设置到listeners属性中
  4. 推断并设置main方法的定义类,找到运行的主类
public SpringApplication(ResourceLoader resourceLoader, Class... primarySources) {
    // ......
    this.webApplicationType = WebApplicationType.deduceFromClasspath();
    this.setInitializers(this.getSpringFactoriesInstances();
    this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class));
    this.mainApplicationClass = this.deduceMainApplicationClass();
}

在这里插入图片描述

yaml语法配置

YAML是 “YAML Ain’t a Markup Language” (YAML不是一种标记语言)的递归缩写。在开发的这种语言时,YAML 的意思其实是:“Yet Another Markup Language”(仍是一种标记语言)

官方不推荐使用application.properties文件,推荐使用 application.yml 配置方式

  • application.properties
    语法结构 :key=value
  • application.yml
    语法结构 :key:空格 value
传统xml配置
	<server>
	    <port>8081<port>
	</server>
	
yaml配置
	server:
	  prot: 8080

yaml基础语法

  1. key: value;kv之间有空格,空格不能省略
  2. 以缩进来控制层级关系,只要是左边对齐的一列数据都是同一个层级的
  3. 缩进不允许使用tab,只允许空格
  4. 属性和值的大小写都是十分敏感的
  5. 字面量:普通的值 [ 数字,布尔值,字符串 ]
  6. 字面量直接写在后面就可以 ,字符串默认不用加上双引号或者单引号,如果要加,''与""表示字符串内容 会被 转义/不转义
  7. '#'表示注释
  8. 如果属性是 userName 大写,在yml配置文件中为 user-name ,- 表示后面的 n 是大写

注意:

  • “ ” 双引号,不会转义字符串里面的特殊字符 , 特殊字符会作为本身想表示的意思
    比如 :name: “kuang \n shen” 输出 :kuang 换行 shen
  • ‘’ 单引号,会转义特殊字符 , 特殊字符最终会变成和普通字符一样输出
    比如 :name: ‘kuang \n shen’ 输出 :kuang \n shen
 # 对象、Map键值对格式
	 k: 
	 	v1:
	 	v2:
 
 # 对象
	 student:
	 	name: zhangsan
	 	age: 3

	student: {name: zhangsan,age: 3}	行内写法

#数组
	pets:
		- cat
		- dog
	
	pets: [cat,dog]		行内写法
修改springboot的默认端口号
	server:
		port: 8082

传统注入配置文件

yaml文件更强大的地方在于,他可以给我们的实体类直接注入匹配值!

  1. 在springboot项目中的resources目录下新建一个文件 application.yml

  2. 编写一个实体类 Dog,并像原来一样使用@Value给bean注入属性值

    package com.kuang.springboot.pojo;
    
    @Component  //注册bean到容器中
    public class Dog {
     	@Value("阿黄")
        private String name;
        @Value("11")
        private Integer age;
        
        //有参无参构造、get、set方法、toString()方法  
    }
    
  3. 在SpringBoot的测试类下注入狗狗输出一下;

    @SpringBootTest
    class DemoApplicationTests {
    
        @Autowired //将狗狗自动注入进来
        Dog dog;
    
        @Test
        public void contextLoads() {
            System.out.println(dog); //打印看下狗狗对象
        }
    }
    

    在这里插入图片描述

yaml注入配置文件

  1. 编写一个复杂一点的实体类:Person 类,注意没有get、set方法是注入不上的

    @Component //注册bean到容器中
    public class Person {
        private String name;
        private Integer age;
        private Boolean happy;
        private Date birth;
        private Map<String,Object> maps;
        private List<Object> lists;
        private Dog dog;
        
        //有参无参构造、get、set方法、toString()方法  
    }
    
  2. 使用yaml配置的方式进行注入,大家写的时候注意区别和优势,我们编写一个yaml配置

    # yaml中的字符串可以不加双引号,自动解析
    
    person:
      name: qinjiang
      age: 3
      happy: false
      birth: 2000/01/01
      maps: {k1: v1,k2: v2}
      lists:
       - code
       - girl
       - music
      dog:
        name: 旺财
        age: 1
    
     # allPets:
     #   sick:
     #     - {name: tom}
     #     - {name: jerry,weight: 47}
     #     - name: merry
     #       width: 30
     #   health: [{name: mario,weight: 47},{name: tom,weight: 12}]
    
  3. 注入到我们的类中

    /*
    @ConfigurationProperties作用:
    将配置文件中配置的每一个属性的值,映射到这个组件中;
    告诉SpringBoot将本类中的所有属性和配置文件中相关的配置进行绑定
    参数 prefix = “person” : 将配置文件中的person下面的所有属性一一对应
    */
    @Component //注册bean
    @ConfigurationProperties(prefix = "person")
    public class Person {
        private String name;
        private Integer age;
        private Boolean happy;
        private Date birth;
        private Map<String,Object> maps;
        private List<Object> lists;
        private Dog dog;
    }
    
  4. IDEA 提示,springboot配置注解处理器没有找到,在配置文件中输入person. 不会弹出要输入的属性提示,让我们看文档,我们可以查看文档,找到一个依赖

    在这里插入图片描述在这里插入图片描述

    <!-- 导入配置文件处理器,配置文件进行属性配置比如person.就会有person类属性的提示,需要重启 -->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-configuration-processor</artifactId>
      <optional>true</optional>
    </dependency>
    
  5. 确认以上配置都OK之后,我们去测试类中测试一下

    @SpringBootTest
    class DemoApplicationTests {
    
        @Autowired
        Person person; //将person自动注入进来
    
        @Test
        public void contextLoads() {
            System.out.println(person); //打印person信息
        }
    }
    

    在这里插入图片描述

自定义配置文件文件名

  1. 我们去在resources目录下新建一个person.properties文件

  2. 然后在我们的代码中指定加载person.properties文件

    @PropertySource :加载指定的配置文件;
    @configurationProperties:默认从全局配置文件中获取值;
    
    @PropertySource(value = "classpath:person.properties")
    @Component //注册bean
    public class Person {
    
        @Value("${name}")
        private String name;
        ......  
    }
    

yaml配置文件使用占位符

person:
    name: qinjiang${random.uuid} # 随机uuid
    age: ${random.int}  # 随机int
    happy: false
    birth: 2000/01/01
    maps: {k1: v1,k2: v2}
    lists: [code,girl,music]
    dog:
      name: ${person.hello:other}_旺财	如果person的hello属性为空,则取other,输出other_旺财,否则输出hello对应的属性值_旺财
      age: 1

传统的properties注入配置文件

properties配置文件在写中文的时候,会有乱码 , 我们需要去IDEA中设置settings–>FileEncodings 中配置编码格式为UTF-8
在这里插入图片描述

  1. 新建一个实体类User

    @Component //注册bean
    public class User {
        private String name;
        private int age;
        private String sex;
    }
    
  2. 编辑配置文件 user.properties

    user1.name=kuangshen
    user1.age=18
    user1.sex=
  3. 在User类上使用@Value来进行注入

    @Component //注册bean
    @PropertySource(value = "classpath:user.properties")
    public class User {
        //直接使用@value
        @Value("${user.name}") //从配置文件中取值
        private String name;
        @Value("#{9*2}")  // #{SPEL} Spring表达式
        private int age;
        @Value("男")  // 字面量
        private String sex;
    }
    

    在这里插入图片描述

yaml和properties对比

在这里插入图片描述

  1. @ConfigurationProperties只需要写一次即可 , @Value则需要每个字段都添加
  2. 松散绑定:这个什么意思呢? 比如我的yml中写的last-name,这个和lastName是一样的, - 后面跟着的字母默认是大写的。这就是松散绑定
  3. JSR303数据校验 , 这个就是我们可以在字段是增加一层过滤器验证 , 可以保证数据的合法性
  4. 复杂类型封装,yml中可以封装对象 , 使用value就不支持

总结:

  • 如果我们在某个业务中,只需要获取配置文件中的某个值,可以使用一下 @value;
  • 如果说,我们专门编写了一个JavaBean来和配置文件进行一一映射,就直接@configurationProperties,不要犹豫

JSR303数据校验及多环境切换

JSR303数据校验

Springboot中可以用@validated来校验数据,如果数据异常则会统一抛出异常,方便异常中心统一处理。我们这里来写个注解让我们的name只能支持Email格式

@Component //注册bean
@ConfigurationProperties(prefix = "person")
@Validated  //数据校验
public class Person {

    @Email(message="邮箱格式错误") //name必须是邮箱格式
    private String name;
}
常用参数:注意和 @Validate 使用

@NotNull(message="名字不能为空")
private String userName;
@Max(value=120,message="年龄最大不能查过120")
private int age;
@Email(message="邮箱格式错误")
private String email;

空检查
@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=) 字符串长度 is between min and max included.

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

.......等等
除此以外,我们还可以自定义一些数据校验规则

多配置文件

properties多文件配置

我们在主配置文件编写的时候,文件名可以是 application-{profile}.properties/yml , 用来指定多个环境版本

application-test.properties 代表测试环境配置
application-dev.properties 代表开发环境配置

但是Springboot并不会直接启动这些配置文件,它默认使用application.properties主配置文件;

我们需要通过一个配置来选择需要激活的环境

#比如在配置文件中指定使用dev环境,我们可以通过设置不同的端口号进行测试;
#我们启动SpringBoot,就可以看到已经切换到dev下的配置了;
spring.profiles.active=dev

yaml多文档块配置

和properties配置文件中一样,但是使用yml去实现不需要创建多个配置文件,更加方便了

如果yml和properties同时都配置了端口,并且没有激活其他环境 , 默认会使用properties配置文件的

server:
  port: 8081
#选择要激活那个环境块
spring:
  profiles:
    active: prod

---
server:
  port: 8083
spring:
  profiles: dev #配置环境的名称


---

server:
  port: 8084
spring:
  profiles: prod  #配置环境的名称

配置文件的加载位置

springboot 启动会扫描以下位置的application.properties或者application.yml文件作为Spring boot的默认配置文件

优先级由高到底,高优先级的配置会覆盖低优先级的配置

优先级1:项目路径下的config文件夹配置文件
优先级2:项目路径下配置文件
优先级3:资源路径 resources 下的 config文件夹 配置文件
优先级4:资源路径 resources 下配置文件

SpringMVC自动配置概览

  • Spring Boot provides auto-configuration for Spring MVC that works well with most applications.(大多场景我们都无需自定义配置)The auto-configuration adds the following features on top of Spring’s defaults:

  • Inclusion of ContentNegotiatingViewResolver and BeanNameViewResolver beans.
    内容协商视图解析器和BeanName视图解析器

  • Support for serving static resources, including support for WebJars (covered later in this document)).
    静态资源(包括webjars)

  • Automatic registration of Converter, GenericConverter, and Formatter beans.
    自动注册 Converter,GenericConverter,Formatter

  • Support for HttpMessageConverters (covered later in this document).
    支持 HttpMessageConverters (后来我们配合内容协商理解原理)

  • Automatic registration of MessageCodesResolver (covered later in this document).
    自动注册 MessageCodesResolver (国际化用)

  • Static index.html support.
    静态index.html 页支持

  • Custom Favicon support (covered later in this document).
    自定义 Favicon

  • Automatic use of a ConfigurableWebBindingInitializer bean (covered later in this document).
    自动使用 ConfigurableWebBindingInitializer ,(DataBinder负责将请求数据绑定到JavaBean上)

  • If you want to keep those Spring Boot MVC customizations and make more MVC customizations (interceptors, formatters, view controllers, and other features), you can add your own @Configuration class of type WebMvcConfigurer but without @EnableWebMvc.
    不用@EnableWebMvc注解。使用 @Configuration + WebMvcConfigurer 自定义规则

  • If you want to provide custom instances of RequestMappingHandlerMapping, RequestMappingHandlerAdapter, or ExceptionHandlerExceptionResolver, and still keep the Spring Boot MVC customizations, you can declare a bean of type WebMvcRegistrations and use it to provide custom instances of those components.
    声明 WebMvcRegistrations 改变默认底层组件

  • If you want to take complete control of Spring MVC, you can add your own @Configuration annotated with @EnableWebMvc, or alternatively add your own @Configuration-annotated DelegatingWebMvcConfiguration as described in the Javadoc of @EnableWebMvc.
    使用 @EnableWebMvc+@Configuration+DelegatingWebMvcConfiguration 全面接管SpringMVC

简单功能分析

静态资源访问

静态资源目录

只要静态资源放在类路径下: called /static (or /public or /resources or /META-INF/resources
访问 : 当前项目根路径/ + 静态资源名

原理: 静态映射/**。双*表示拦截所有,所有 / 下层级的路径

请求进来,先去找Controller看能不能处理。不能处理的所有请求又都交给静态资源处理器。静态资源也找不到则响应404页面

public,static,/**,resources(项目路径下):localhost:8080/love.html

在这里插入图片描述
在这里插入图片描述

改变默认的静态资源路径

我们也可以自己通过配置文件来指定一下,哪些文件夹是需要我们放静态资源文件的,在application.properties中配置

一旦自己定义了静态文件夹的路径,原来的自动配置就都会失效了

spring:
  mvc:
    static-path-pattern: /res/**

  resources:
    static-locations: [classpath:/haha/]

静态资源访问前缀
当前项目 + static-path-pattern + 静态资源名 = 静态资源文件夹下找

spring:
  mvc:
    static-path-pattern: /res/**

webjar
自动映射 /webjars/**
Webjars本质就是以jar包的方式引入我们的静态资源 , 我们以前要导入一个静态资源文件,直接导入即可
webjars网站查找依赖注入pom文件(在依赖包中找):https://www.webjars.org/
访问地址:http://localhost:8080/webjars/jquery/3.5.1/jquery.js 后面地址要按照依赖里面的包路径

 <dependency>
     <groupId>org.webjars</groupId>
     <artifactId>jquery</artifactId>
     <version>3.5.1</version>
 </dependency>

在这里插入图片描述

欢迎页支持

静态资源路径下放 index.html

  • 可以配置静态资源路径
  • 但是不可以配置静态资源的访问前缀。否则导致 index.html不能被默认访问
spring:
#  mvc:
#    static-path-pattern: /res/**   这个会导致welcome page功能失效

  resources:
    static-locations: [classpath:/haha/]

自定义 Favicon

favicon.ico 放在静态资源目录下即可

spring:
#  mvc:
#    static-path-pattern: /res/**   这个会导致 Favicon 功能失效

静态资源访问原理

  • SpringBoot启动默认加载 xxxAutoConfiguration 类(自动配置类)
  • SpringMVC功能的自动配置类 WebMvcAutoConfiguration,生效
@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
@AutoConfigureAfter({ DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class,
    ValidationAutoConfiguration.class })
public class WebMvcAutoConfiguration {}
  • 给容器中配了什么
@Configuration(proxyBeanMethods = false)
@Import(EnableWebMvcConfiguration.class)
@EnableConfigurationProperties({ WebMvcProperties.class, ResourceProperties.class })
@Order(0)
public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer {}
  • 配置文件的相关属性和xxx进行了绑定。WebMvcPropertiesspring.mvc、ResourcePropertiesspring.resources

配置类只有一个有参构造器

//有参构造器所有参数的值都会从容器中确定
//ResourceProperties resourceProperties;获取和spring.resources绑定的所有的值的对象
//WebMvcProperties mvcProperties 获取和spring.mvc绑定的所有的值的对象
//ListableBeanFactory beanFactory Spring的beanFactory
//HttpMessageConverters 找到所有的HttpMessageConverters
//ResourceHandlerRegistrationCustomizer 找到 资源处理器的自定义器。=========
//DispatcherServletPath  
//ServletRegistrationBean   给应用注册Servlet、Filter....
	public WebMvcAutoConfigurationAdapter(ResourceProperties resourceProperties, WebMvcProperties mvcProperties,
				ListableBeanFactory beanFactory, ObjectProvider<HttpMessageConverters> messageConvertersProvider,
				ObjectProvider<ResourceHandlerRegistrationCustomizer> resourceHandlerRegistrationCustomizerProvider,
				ObjectProvider<DispatcherServletPath> dispatcherServletPath,
				ObjectProvider<ServletRegistrationBean<?>> servletRegistrations) {
			this.resourceProperties = resourceProperties;
			this.mvcProperties = mvcProperties;
			this.beanFactory = beanFactory;
			this.messageConvertersProvider = messageConvertersProvider;
			this.resourceHandlerRegistrationCustomizer = resourceHandlerRegistrationCustomizerProvider.getIfAvailable();
			this.dispatcherServletPath = dispatcherServletPath;
			this.servletRegistrations = servletRegistrations;
		}

WebMvcAutoConfiguration.class 文件中源码分析

资源处理的默认规则

public void addResourceHandlers(ResourceHandlerRegistry registry) {
//如果配置文件中有自己的配置,静态资源存储在自定义路径中,默认配置失效
	if (!this.resourceProperties.isAddMappings()) {
	       logger.debug("Default resource handling disabled");
	       return;
	     }
	     //可以通过resources.cache.period配置缓存时间
	     Duration cachePeriod = this.resourceProperties.getCache().getPeriod();
	     CacheControl cacheControl = this.resourceProperties.getCache().getCachecontrol().toHttpCacheControl();
	     //webjars的规则
	     //添加静态资源到 /META-INF/resources/webjars/** 文件夹中
	           if (!registry.hasMappingForPattern("/webjars/**")) {
	       customizeResourceHandlerRegistration(registry.addResourceHandler("/webjars/**")
	           .addResourceLocations("classpath:/META-INF/resources/webjars/")
	           .setCachePeriod(getSeconds(cachePeriod)).setCacheControl(cacheControl));
	     }

	     String staticPathPattern = this.mvcProperties.getStaticPathPattern();
	     if (!registry.hasMappingForPattern(staticPathPattern)) {
	       customizeResourceHandlerRegistration(registry.addResourceHandler(staticPathPattern)
	           //getStaticPathPattern 获取静态资源路径,点进resourceProperties中
	           .addResourceLocations(getResourceLocations(this.resourceProperties.getStaticLocations()))
	           .setCachePeriod(getSeconds(cachePeriod)).setCacheControl(cacheControl));
	     }
	   }

可以通过spring.resources.add-mappings 禁用静态资源的访问

spring:
#  mvc:
#    static-path-pattern: /res/**

  resources:
    add-mappings: false   禁用所有静态资源规则
点进入resourceProperties,它是WebMvcAutoConfiguration类的WebMvcAutoConfigurationAdapter()方法的一个静态属性,继续点进去,是WebProperties的一个静态内部类
public static class Resources {
		//下面代码就是指定的 静态资源默认存储路径
		//优先级:resources > static(默认) > public
        private static final String[] CLASSPATH_RESOURCE_LOCATIONS = new String[]{"classpath:/META-INF/resources/", "classpath:/resources/", "classpath:/static/", "classpath:/public/"};
       ......

        public Resources() {
            this.staticLocations = CLASSPATH_RESOURCE_LOCATIONS;
            ......
        }

总结:以下四个目录存放的静态资源可以被我们识别,比如我们访问 http://localhost:8080/1.js , 他就会去这些文件夹中寻找对应的静态资源文件

"classpath:/META-INF/resources/"
"classpath:/resources/"
"classpath:/static/"
"classpath:/public/"

欢迎页的处理规则

HandlerMapping:处理器映射。保存了每一个Handler能处理哪些请求。	

@Bean
public WelcomePageHandlerMapping welcomePageHandlerMapping(ApplicationContext applicationContext,
		FormattingConversionService mvcConversionService, ResourceUrlProvider mvcResourceUrlProvider) {
	WelcomePageHandlerMapping welcomePageHandlerMapping = new WelcomePageHandlerMapping(
			new TemplateAvailabilityProviders(applicationContext), applicationContext, getWelcomePage(),
			this.mvcProperties.getStaticPathPattern());
	welcomePageHandlerMapping.setInterceptors(getInterceptors(mvcConversionService, mvcResourceUrlProvider));
	welcomePageHandlerMapping.setCorsConfigurations(getCorsConfigurations());
	return welcomePageHandlerMapping;
}

WelcomePageHandlerMapping(TemplateAvailabilityProviders templateAvailabilityProviders,
	ApplicationContext applicationContext, Optional<Resource> welcomePage, String staticPathPattern) {
if (welcomePage.isPresent() && "/**".equals(staticPathPattern)) {
          //要用欢迎页功能,必须是/**
	logger.info("Adding welcome page: " + welcomePage.get());
	setRootViewName("forward:index.html");
}
else if (welcomeTemplateExists(templateAvailabilityProviders, applicationContext)) {
          // 调用Controller  /index
	logger.info("Adding welcome page template: index");
	setRootViewName("index");
}
}

请求参数处理

请求映射

rest使用与原理
  • @GetMapping() @PostMapping() @PutMapping() @DeleteMapping()
  • Rest风格支持(使用HTTP请求方式动词来表示对资源的操作)
    以前:/getUser 获取用户 /deleteUser 删除用户 /editUser 修改用户 /saveUser 保存用户
    现在: /user GET-获取用户 DELETE-删除用户 PUT-修改用户 POST-保存用户
    核心Filte:HiddenHttpMethodFilter
    用法: 表单method=post,隐藏域type=“hidden” name=“_method” value=想用的方法
    在这里插入图片描述
 @RequestMapping(value = "/user",method = RequestMethod.GET)
  public String getUser(){
      return "GET-张三";
  }

  @RequestMapping(value = "/user",method = RequestMethod.POST)
  public String saveUser(){
      return "POST-张三";
  }


  @RequestMapping(value = "/user",method = RequestMethod.PUT)
  public String putUser(){
      return "PUT-张三";
  }

  @RequestMapping(value = "/user",method = RequestMethod.DELETE)
  public String deleteUser(){
      return "DELETE-张三";
  }


@Bean
@ConditionalOnMissingBean(HiddenHttpMethodFilter.class)
// matchIfMissing = false 表示如果没有自定义,就不配置不开启,默认false
@ConditionalOnProperty(prefix = "spring.mvc.hiddenmethod.filter", name = "enabled", matchIfMissing = false)
public OrderedHiddenHttpMethodFilter hiddenHttpMethodFilter() {
return new OrderedHiddenHttpMethodFilter();
}

SpringBoot中手动开启
扩展:如何把_method 这个名字换成我们自己喜欢的

//自定义filter
//@ConditionalOnProperty(prefix = "spring.mvc.hiddenmethod.filter", name = "enabled", matchIfMissing = false)
//默认没有HiddenHttpMethodFilter配置,此注解下方的配置new OrderedHiddenHttpMethodFilter()不生效,所以要自己配置一个HiddenHttpMethodFilter
  @Bean
  public HiddenHttpMethodFilter hiddenHttpMethodFilter(){
      HiddenHttpMethodFilter methodFilter = new HiddenHttpMethodFilter();
      methodFilter.setMethodParam("_m");
      return methodFilter;
  }
Rest原理(表单提交要使用REST的时候)
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        HttpServletRequest requestToUse = request;
        if ("POST".equals(request.getMethod()) && request.getAttribute("javax.servlet.error.exception") == null) {
            String paramValue = request.getParameter(this.methodParam);
            if (StringUtils.hasLength(paramValue)) {
                String method = paramValue.toUpperCase(Locale.ENGLISH);
                if (ALLOWED_METHODS.contains(method)) {
                    requestToUse = new HiddenHttpMethodFilter.HttpMethodRequestWrapper(request, method);
                }
            }
        }

        filterChain.doFilter((ServletRequest)requestToUse, response);
    }
  • 表单提交会带上_method=PUT
  • 请求过来被HiddenHttpMethodFilter拦截
    • 请求是否正常,并且是POST
      • 获取到_method的值。
      • 兼容以下请求;PUT.DELETE.PATCH
      • 原生request(post),包装模式requesWrapper重写了getMethod方法,返回的是传入的值。
      • 过滤器链放行的时候用wrapper。以后的方法调用getMethod是调用requesWrapper的。
spring:
  mvc:
    hiddenmethod:
      filter:
        enabled: true   #开启页面表单的Rest功能

Rest使用客户端工具,如PostMan直接发送Put、delete等方式请求,无需Filter。

请求映射原理

在这里插入图片描述

SpringMVC功能分析都从 org.springframework.web.servlet.DispatcherServlet--------》doDispatch

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
		HttpServletRequest processedRequest = request;
		HandlerExecutionChain mappedHandler = null;
		boolean multipartRequestParsed = false;

		WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

		try {
			ModelAndView mv = null;
			Exception dispatchException = null;

			try {
				processedRequest = checkMultipart(request);
				multipartRequestParsed = (processedRequest != request);

				// 找到当前请求使用哪个Handler(Controller的方法)处理
				mappedHandler = getHandler(processedRequest);
                
                //HandlerMapping:处理器映射。/xxx->>xxxx

在这里插入图片描述
RequestMappingHandlerMapping:保存了所有@RequestMapping 和handler的映射规则
在这里插入图片描述
所有的请求映射都在HandlerMapping中。

  • SpringBoot自动配置欢迎页的 WelcomePageHandlerMapping 。访问 /能访问到index.html;
  • SpringBoot自动配置了默认 的 RequestMappingHandlerMapping
  • 请求进来,挨个尝试所有的HandlerMapping看是否有请求信息。
    • 如果有就找到这个请求对应的handler
    • 如果没有就看是不是下一个 HandlerMapping
  • 我们需要一些自定义的映射处理,我们也可以自己给容器中放HandlerMapping。自定义 HandlerMapping
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
	if (this.handlerMappings != null) {
		for (HandlerMapping mapping : this.handlerMappings) {
			HandlerExecutionChain handler = mapping.getHandler(request);
			if (handler != null) {
				return handler;
			}
		}
	}
	return null;
}

普通参数与基本注解

注解

@PathVariable、@RequestHeader、@ModelAttribute、@RequestParam、@MatrixVariable、@CookieValue、@RequestBody

@RestController
public class ParameterTestController {
	/**
    *  car/2/owner/zhangsan
    * 
    * @PathVariable		获取请求链接中的路径变量,可以Map获取
    * @RequestHeader	获取请求头,可以Map获取
    * @RequestParam		获取请求参数,可以List,Map获取
    * @CookieValue		获取cookie,可以Cookie类型获取
    * @RequestBody		获取原始的表单提交信息 username=zhangsan&age=22
    * @RequestAttribute("msg") 		获取请求作用域中的value
    */
    
    @GetMapping("/car/{id}/owner/{username}")
    public Map<String,Object> getCar(@PathVariable("id") Integer id,
                                     @PathVariable("username") String name,
                                     @PathVariable Map<String,String> pv,
                                     @RequestHeader("User-Agent") String userAgent,
                                     @RequestHeader Map<String,String> header,
                                     @RequestParam("age") Integer age,
                                     @RequestParam("inters") List<String> inters,
                                     @RequestParam Map<String,String> params,
                                     @CookieValue("_ga") String _ga,
                                     @CookieValue("_ga") Cookie cookie){


        Map<String,Object> map = new HashMap<>();

//        map.put("id",id);
//        map.put("name",name);
//        map.put("pv",pv);
//        map.put("userAgent",userAgent);
//        map.put("headers",header);
        map.put("age",age);
        map.put("inters",inters);
        map.put("params",params);
        map.put("_ga",_ga);
        System.out.println(cookie.getName()+"===>"+cookie.getValue());
        return map;
    }


    @PostMapping("/save")
    public Map postMethod(@RequestBody String content){
        Map<String,Object> map = new HashMap<>();
        map.put("content",content);
        return map;
    }
@Controller
public class RequestController {
    @GetMapping("/goto")
    public String goToPage(HttpServletRequest request){
        request.setAttribute("msg","msg--1");
        request.setAttribute("code",200);
//        请求转发,attribute还是那个attribute
        return "forward:/success";
    }

    @GetMapping("/success")
    @ResponseBody
    public Map success(@RequestAttribute("msg") String msg,
                       @RequestAttribute("code") Integer code,
                       HttpServletRequest request){
        //通过请求转发后的作用域取到
        Object msg1 = request.getAttribute("msg");
        System.out.println(msg1);
        
        //通过annotation注解取到
        Map<Object,Object> map = new HashMap<>();
        map.put("msg",msg);
        map.put("code",code);
        return map;
    }
}
/cars/{path}?xxx=xxx&aaa=aaa  称作queryString 查询字符串。使用@RequestParam获得
/cars/sell;low=34;brand=byd,audi,yd  称作矩阵变量

涉及到一个面试题:
	页面开发中,假如cookie被禁用了,session中的内容怎么获取
		正常情况下,是通过session.set(a,b),每个人每一个session,都有一个sessionid,这个sessionid称作jsessionid,jsessionid会保存在cookie中,每次发送请求都会携带cookie
		cookie被禁用,jsessionid无法传递,进而session中的内容无法传递
	正解: 通过url重写/cars/sell;jsessionid=xxx 把cookie的值通过矩阵变量的方式传递

开启矩阵变量功能

自定义配置就重写WebMvcAutoConfiguration类中的对应方法,或者WebMvcAutoConfiguration中对应方法所在类实现的接口,一般是WebMvcConfigurer,@Bean在容器中放自己定义的WebMvcConfigurer,在其中重写对应方法

自定义配置

@Configuration(proxyBeanMethods = false)
public class MyConfig{
//方式一
//    @Override
//    public void configurePathMatch(PathMatchConfigurer configurer) {
//        UrlPathHelper urlPathHelper = new UrlPathHelper();
//        urlPathHelper.setRemoveSemicolonContent(false);
//        configurer.setUrlPathHelper(urlPathHelper);
//    }
	
	//方式二
    @Bean
    public WebMvcConfigurer webMvcConfigurer(){
        WebMvcConfigurer webMvcConfigurer = new WebMvcConfigurer() {
            @Override
            public void configurePathMatch(PathMatchConfigurer configurer) {
                UrlPathHelper urlPathHelper = new UrlPathHelper();
                urlPathHelper.setRemoveSemicolonContent(false);
                configurer.setUrlPathHelper(urlPathHelper);
            }
        };
        return webMvcConfigurer;
    }
}
  //1、语法: / 前面是请求路径,后面是请求变量,多个变量用;分割
  //	请求路径:/cars/sell;low=34;brand=byd,audi,yd
  //2、SpringBoot默认是禁用了矩阵变量的功能
  //      手动开启:原理。对于路径的处理。都是用UrlPathHelper进行解析。
  //              属性removeSemicolonContent(移除分号内容)用来支持矩阵变量的,需要自定义配置,设置removeSemicolonContent=false
  //3、矩阵变量必须有url路径变量才能被解析


  ///cars/sell;low=34;brand=byd,audi,yd
  @GetMapping("/cars/{path}")
  public Map carsSell(@MatrixVariable("low") Integer low,
                      @MatrixVariable("brand") List<String> brand,
                      @PathVariable("path") String path){
      Map<String,Object> map = new HashMap<>();

      map.put("low",low);
      map.put("brand",brand);
      map.put("path",path);
      return map;
    }
    
 // /boss/1;age=20/2;age=10
 @GetMapping("/boss/{bossId}/{empId}")
 public Map boss(@MatrixVariable(value = "age",pathVar = "bossId") Integer bossAge,
                 @MatrixVariable(value = "age",pathVar = "empId") Integer empAge){
     Map<String,Object> map = new HashMap<>();

     map.put("bossAge",bossAge);
     map.put("empAge",empAge);
     return map;

 }
Servlet API

WebRequest、ServletRequest、MultipartRequest、 HttpSession、javax.servlet.http.PushBuilder、Principal、InputStream、Reader、HttpMethod、Locale、TimeZone、ZoneId

核心代码

@Override
public boolean supportsParameter(MethodParameter parameter) {
	Class<?> paramType = parameter.getParameterType();
	return (WebRequest.class.isAssignableFrom(paramType) ||
			ServletRequest.class.isAssignableFrom(paramType) ||
			MultipartRequest.class.isAssignableFrom(paramType) ||
			HttpSession.class.isAssignableFrom(paramType) ||
			(pushBuilder != null && pushBuilder.isAssignableFrom(paramType)) ||
			Principal.class.isAssignableFrom(paramType) ||
			InputStream.class.isAssignableFrom(paramType) ||
			Reader.class.isAssignableFrom(paramType) ||
			HttpMethod.class == paramType ||
			Locale.class == paramType ||
			TimeZone.class == paramType ||
			ZoneId.class == paramType);
}
复杂参数

Map、Model(map、model里面的数据会被放在request的请求域 request.setAttribute)、Errors/BindingResult、RedirectAttributes( 重定向携带数据)、ServletResponse(response)、SessionStatus、UriComponentsBuilder、ServletUriComponentsBuilder

Map<String,Object> map,  Model model, HttpServletRequest request 都是可以给request域中放数据,
request.getAttribute();

Map、Model类型的参数,会返回 mavContainer.getModel();—> BindingAwareModelMap 是Model 也是Map
Map、Model底层都是调用mavContainer.getModel(); 获取到值的

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

参数处理原理

  • HandlerMapping中找到能处理请求的Handler(Controller.method())
  • 为当前Handler 找一个适配器 HandlerAdapter; RequestMappingHandlerAdapter
  • 适配器执行目标方法并确定方法参数的每一个值
HandlerAdapter

在这里插入图片描述
0 - 支持方法上标注@RequestMapping
1 - 支持函数式编程的
3 - 访问静态资源

执行目标方法
// Actually invoke the handler.
//DispatcherServlet -- doDispatch
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

mav = invokeHandlerMethod(request, response, handlerMethod); //执行目标方法

//ServletInvocableHandlerMethod
Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
//获取方法的参数值
Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
参数解析器-HandlerMethodArgumentResolver

确定将要执行的目标方法的每一个参数的值是什么;
SpringMVC目标方法能写多少种参数类型。取决于参数解析器。
在这里插入图片描述
在这里插入图片描述

  • 当前解析器是否支持解析这种参数
  • 支持就调用 resolveArgument
返回值处理器

在这里插入图片描述

如何确定目标方法每一个参数的值
============InvocableHandlerMethod==========================
protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
			Object... providedArgs) throws Exception {

	MethodParameter[] parameters = getMethodParameters();
	if (ObjectUtils.isEmpty(parameters)) {
		return EMPTY_ARGS;
	}
	
	Object[] args = new Object[parameters.length];
	for (int i = 0; i < parameters.length; i++) {
		MethodParameter parameter = parameters[i];
		parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
		args[i] = findProvidedArgument(parameter, providedArgs);
		if (args[i] != null) {
			continue;
		}
		if (!this.resolvers.supportsParameter(parameter)) {
			throw new IllegalStateException(formatArgumentError(parameter, "No suitable resolver"));
		}
		try {
			args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);
		}
		catch (Exception ex) {
			// Leave stack trace for later, exception may actually be resolved and handled...
			if (logger.isDebugEnabled()) {
				String exMsg = ex.getMessage();
				if (exMsg != null && !exMsg.contains(parameter.getExecutable().toGenericString())) {
					logger.debug(formatArgumentError(parameter, exMsg));
				}
			}
			throw ex;
		}
	}
	return args;
}
挨个判断所有参数解析器那个支持解析这个参数
@Nullable
private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {
	HandlerMethodArgumentResolver result = this.argumentResolverCache.get(parameter);
	if (result == null) {
		for (HandlerMethodArgumentResolver resolver : this.argumentResolvers) {
			if (resolver.supportsParameter(parameter)) {
				result = resolver;
				this.argumentResolverCache.put(parameter, result);
				break;
			}
		}
	}
	return result;
}
解析这个参数的值
调用各自 HandlerMethodArgumentResolver 的 resolveArgument 方法即可
自定义类型参数 封装POJO

ServletModelAttributeMethodProcessor 这个参数处理器支持是否为简单类型。

public static boolean isSimpleValueType(Class<?> type) {
		return (Void.class != type && void.class != type &&
				(ClassUtils.isPrimitiveOrWrapper(type) ||
				Enum.class.isAssignableFrom(type) ||
				CharSequence.class.isAssignableFrom(type) ||
				Number.class.isAssignableFrom(type) ||
				Date.class.isAssignableFrom(type) ||
				Temporal.class.isAssignableFrom(type) ||
				URI.class == type ||
				URL.class == type ||
				Locale.class == type ||
				Class.class == type));
	}
@Override
@Nullable
public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
		NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
...
  • WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name);
    WebDataBinder :web数据绑定器,将请求参数的值绑定到指定的JavaBean里面
    WebDataBinder 利用它里面的 Converters 将请求数据转成指定的数据类型。再次封装到JavaBean中

  • GenericConversionService:在设置每一个值的时候,找它里面的所有converter那个可以将这个数据类型(request带来参数的字符串)转换到指定的类型(JavaBean – Integer)
    byte – > file

在这里插入图片描述
在这里插入图片描述

目标方法执行完成

将所有的数据都放在 ModelAndViewContainer;包含要去的页面地址View。还包含Model数据。
在这里插入图片描述

数据响应与内容协商

响应JSON

jackson.jar+@ResponseBody
 <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
  </dependency>
web场景自动引入了json场景
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-json</artifactId>
    <version>2.3.4.RELEASE</version>
    <scope>compile</scope>
  </dependency>

原理
更多内容见尚硅谷-SpringBoot2核心技术与响应式编程-05、Web开发

内容协商

根据客户端接收能力不同,返回不同媒体类型的数据。若客户端无法解析服务端返回的内容,即媒体类型未匹配,那么响应406

  1. 引入xml依赖
 <dependency>
   <groupId>com.fasterxml.jackson.dataformat</groupId>
   <artifactId>jackson-dataformat-xml</artifactId>
</dependency>
  1. postman分别测试返回json和xml,只需要改变请求头中Accept字段。Http协议中规定的,告诉服务器本客户端可以接收的数据类型。
    Accept字段改为 application/json 或者 application/xml
    在这里插入图片描述
    为了方便内容协商,开启基于请求参数的内容协商功能
spring:
    contentnegotiation:
      favor-parameter: true  #开启请求参数内容协商模式

发请求: http://localhost:8080/test/person?format=json
http://localhost:8080/test/person?format=xml

在这里插入图片描述
确定客户端接收什么样的内容类型;

  1. Parameter策略优先确定是要返回json数据(获取请求头中的format的值)
    在这里插入图片描述

  2. 最终进行内容协商返回给客户端json即可。

Thymeleaf模板引擎

更多见尚硅谷-SpringBoot2核心技术与响应式编程-05、Web开发

模板引擎

前端交给我们的页面,是html页面。如果是我们以前开发,我们需要把他们转成jsp页面,jsp好处就是当我们查出一些数据转发到JSP页面以后,我们可以用jsp轻松实现数据的显示,及交互等。

jsp支持非常强大的功能,包括能写Java代码,但是呢,我们现在的这种情况,SpringBoot这个项目首先是以jar的方式,不是war,像第二,我们用的还是嵌入式的Tomcat,所以呢,他现在默认是不支持jsp的。

SpringBoot默认不支持 JSP,需要引入第三方模板引擎技术实现页面渲染

SpringBoot推荐你可以来使用模板引擎:

模板引擎,我们其实大家听到很多,其实jsp就是一个模板引擎,还有用的比较多的freemarker,包括SpringBoot给我们推荐的Thymeleaf,模板引擎有非常多,但再多的模板引擎,他们的思想都是一样的,什么样一个思想呢我们来看一下这张图:
在这里插入图片描述
模板引擎的作用就是我们来写一个页面模板,比如有些值呢,是动态的,我们写一些表达式。而这些值,从哪来呢,就是我们在后台封装一些数据。然后把这个模板和这个数据交给我们模板引擎,模板引擎按照我们这个数据帮你把这表达式解析、填充到我们指定的位置,然后把这个数据最终生成一个我们想要的内容给我们写出去,这就是我们这个模板引擎,不管是jsp还是其他模板引擎,都是这个思想。

SpringBoot给我们推荐的Thymeleaf模板引擎,这模板引擎呢,是一个高级语言的模板引擎,他的这个语法更简单。而且呢,功能更强大。

引入Thymeleaf

Thymeleaf 官网:https://www.thymeleaf.org/

Thymeleaf 在Github 的主页:https://github.com/thymeleaf/thymeleaf

Spring官方文档:找到我们对应的版本

https://docs.spring.io/spring-boot/docs/2.2.5.RELEASE/reference/htmlsingle/#using-boot-starter

到对应的pom依赖:可以适当点进源码看下本来的包!

<!--thymeleaf-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

Thymeleaf分析

@ConfigurationProperties(
    prefix = "spring.thymeleaf"
)
public class ThymeleafProperties {
    private static final Charset DEFAULT_ENCODING;
    //只需要把我们的html页面放在类路径下的templates下,thymeleaf就可以帮我们自动渲染了
    //静态资源文件前缀
    public static final String DEFAULT_PREFIX = "classpath:/templates/";
    //静态资源文件后缀
    public static final String DEFAULT_SUFFIX = ".html";
    private boolean checkTemplate = true;
    private boolean checkTemplateLocation = true;
    private String prefix = "classpath:/templates/";
    private String suffix = ".html";
    private String mode = "HTML";
    private Charset encoding;
}

测试

  1. 编写一个TestController
@Controller
public class TestController {
    
    @RequestMapping("/t1")
    public String test1(){
        //classpath:/templates/test.html
        return "test";
    }
}
  1. 编写一个测试页面 test.html 放在 templates 目录下
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>测试页面</h1>

</body>
</html>

Thymeleaf 语法学习

Thymeleaf 官网:https://www.thymeleaf.org/

  1. 修改测试请求,增加数据传输
@RequestMapping("/t1")
public String test1(Model model){
    //存入数据
    model.addAttribute("msg","Hello,Thymeleaf");
    //classpath:/templates/test.html
    return "test";
}
  1. 编写下前端页面
<!DOCTYPE html>
// 导入命名空间的约束,方便提示
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>狂神说</title>
</head>
<body>
<h1>测试页面</h1>

<!--th:text就是将div中的内容设置为它指定的值,和之前学习的Vue一样-->
<div th:text="${msg}"></div>
</body>
</html>
  1. 启动测试

常用语法:
在这里插入图片描述

表达式:

Simple expressions:(表达式语法)
Variable Expressions: ${...}:获取变量值;OGNL;
    1)、获取对象的属性、调用方法
    2)、使用内置的基本对象:#18
         #ctx : the context object.
         #vars: the context variables.
         #locale : the context locale.
         #request : (only in Web Contexts) the HttpServletRequest object.
         #response : (only in Web Contexts) the HttpServletResponse object.
         #session : (only in Web Contexts) the HttpSession object.
         #servletContext : (only in Web Contexts) the ServletContext object.

    3)、内置的一些工具对象:
      #execInfo : information about the template being processed.
      #uris : methods for escaping parts of URLs/URIs
      #conversions : methods for executing the configured conversion service (if any).
      #dates : methods for java.util.Date objects: formatting, component extraction, etc.
      #calendars : analogous to #dates , but for java.util.Calendar objects.
      #numbers : methods for formatting numeric objects.
      #strings : methods for String objects: contains, startsWith, prepending/appending, etc.
      #objects : methods for objects in general.
      #bools : methods for boolean evaluation.
      #arrays : methods for arrays.
      #lists : methods for lists.
      #sets : methods for sets.
      #maps : methods for maps.
      #aggregates : methods for creating aggregates on arrays or collections.
==================================================================================

  Selection Variable Expressions: *{...}:选择表达式:和${}在功能上是一样;
  Message Expressions: #{...}:获取国际化内容
  Link URL Expressions: @{...}:定义URL;
  Fragment Expressions: ~{...}:片段引用表达式

Literals(字面量)
      Text literals: 'one text' , 'Another one!' ,Number literals: 0 , 34 , 3.0 , 12.3 ,Boolean literals: true , false
      Null literal: null
      Literal tokens: one , sometext , main ,Text operations:(文本操作)
    String concatenation: +
    Literal substitutions: |The name is ${name}|
    
Arithmetic operations:(数学运算)
    Binary operators: + , - , * , / , %
    Minus sign (unary operator): -
    
Boolean operations:(布尔运算)
    Binary operators: and , or
    Boolean negation (unary operator): ! , not
    
Comparisons and equality:(比较运算)
    Comparators: > , < , >= , <= ( gt , lt , ge , le )
    Equality operators: == , != ( eq , ne )
    
Conditional operators:条件运算(三元运算符)
    If-then: (if) ? (then)
    If-then-else: (if) ? (then) : (else)
    Default: (value) ?: (defaultvalue)
    
Special tokens:
    No-Operation: _

测试:

  1. 编写一个Controller,放一些数据
@RequestMapping("/t2")
public String test2(Map<String,Object> map){
    //存入数据
    map.put("msg","<h1>Hello</h1>");
    map.put("users", Arrays.asList("qinjiang","kuangshen"));
    //classpath:/templates/test.html
    return "test";
}
  1. html页面取出数据
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>狂神说</title>
</head>
<body>
<h1>测试页面</h1>

<div th:text="${msg}"></div>
<!--不转义-->
<div th:utext="${msg}"></div>

<!--遍历数据-->
<!--th:each每次遍历都会生成当前这个标签:官网#9-->
<h4 th:each="user :${users}" th:text="${user}"></h4>

<h4>
    <!--行内写法:官网#12-->
    <span th:each="user:${users}">[[${user}]]</span>
</h4>

</body>
</html>

SpringBoot:MVC自动配置原理

我们还需要知道一个东西,就是SpringBoot对我们的SpringMVC还做了哪些配置,包括如何扩展,如何定制

官方文档 :https://docs.spring.io/spring-boot/docs/2.2.5.RELEASE/reference/htmlsingle/#boot-features-spring-mvc-auto-configuration

Spring MVC Auto-configuration

// Spring Boot为Spring MVC提供了自动配置,它可以很好地与大多数应用程序一起工作。
Spring Boot provides auto-configuration for Spring MVC that works well with most applications.
// 自动配置在Spring默认设置的基础上添加了以下功能:
The auto-configuration adds the following features on top of Spring’s defaults:
// 包含视图解析器
Inclusion of ContentNegotiatingViewResolver and BeanNameViewResolver beans.
// 支持静态资源文件夹的路径,以及webjars
Support for serving static resources, including support for WebJars 
// 自动注册了Converter:
// 转换器,这就是我们网页提交数据到后台自动封装成为对象的东西,比如把"1"字符串自动转换为int类型
// Formatter:【格式化器,比如页面给我们了一个2019-8-10,它会给我们自动格式化为Date对象】
Automatic registration of Converter, GenericConverter, and Formatter beans.
// HttpMessageConverters
// SpringMVC用来转换Http请求和响应的的,比如我们要把一个User对象转换为JSON字符串,可以去看官网文档解释;
Support for HttpMessageConverters (covered later in this document).
// 定义错误代码生成规则的
Automatic registration of MessageCodesResolver (covered later in this document).
// 首页定制
Static index.html support.
// 图标定制
Custom Favicon support (covered later in this document).
// 初始化数据绑定器:帮我们把请求数据绑定到JavaBean中!
Automatic use of a ConfigurableWebBindingInitializer bean (covered later in this document).

/*
如果您希望保留Spring Boot MVC功能,并且希望添加其他MVC配置(拦截器、格式化程序、视图控制器和其他功能),则可以添加自己
的@configuration类,类型为webmvcconfiguer,但不添加@EnableWebMvc。如果希望提供
RequestMappingHandlerMapping、RequestMappingHandlerAdapter或ExceptionHandlerExceptionResolver的自定义
实例,则可以声明WebMVCregistrationAdapter实例来提供此类组件。
*/
If you want to keep Spring Boot MVC features and you want to add additional MVC configuration 
(interceptors, formatters, view controllers, and other features), you can add your own 
@Configuration class of type WebMvcConfigurer but without @EnableWebMvc. If you wish to provide 
custom instances of RequestMappingHandlerMapping, RequestMappingHandlerAdapter, or 
ExceptionHandlerExceptionResolver, you can declare a WebMvcRegistrationsAdapter instance to provide such components.

// 如果您想完全控制Spring MVC,可以添加自己的@Configuration,并用@EnableWebMvc进行注释。
If you want to take complete control of Spring MVC, you can add your own @Configuration annotated with @EnableWebMvc

ContentNegotiatingViewResolver 内容协商视图解析器

自动配置了ViewResolver,就是我们之前学习的SpringMVC的视图解析器;

即根据方法的返回值取得视图对象(View),然后由视图对象决定如何渲染(转发,重定向)。

我们去看看这里的源码:我们找到 WebMvcAutoConfiguration , 然后搜索ContentNegotiatingViewResolver。找到如下方法

@Bean
@ConditionalOnBean(ViewResolver.class)
@ConditionalOnMissingBean(name = "viewResolver", value = ContentNegotiatingViewResolver.class)
public ContentNegotiatingViewResolver viewResolver(BeanFactory beanFactory) {
    ContentNegotiatingViewResolver resolver = new ContentNegotiatingViewResolver();
    resolver.setContentNegotiationManager(beanFactory.getBean(ContentNegotiationManager.class));
    // ContentNegotiatingViewResolver使用所有其他视图解析器来定位视图,因此它应该具有较高的优先级
    resolver.setOrder(Ordered.HIGHEST_PRECEDENCE);
    return resolver;
}
@Nullable // 注解说明:@Nullable 即参数可为null
public View resolveViewName(String viewName, Locale locale) throws Exception {
    RequestAttributes attrs = RequestContextHolder.getRequestAttributes();
    Assert.state(attrs instanceof ServletRequestAttributes, "No current ServletRequestAttributes");
    List<MediaType> requestedMediaTypes = this.getMediaTypes(((ServletRequestAttributes)attrs).getRequest());
    if (requestedMediaTypes != null) {
        // 获取候选的视图对象
        List<View> candidateViews = this.getCandidateViews(viewName, locale, requestedMediaTypes);
        // 选择一个最适合的视图对象,然后把这个对象返回
        View bestView = this.getBestView(candidateViews, requestedMediaTypes, attrs);
        if (bestView != null) {
            return bestView;
        }
    }
    // .....
}

点进getCandidateViews中看到他是把所有的视图解析器拿来,进行while循环,挨个解析

Iterator var5 = this.viewResolvers.iterator();

结论:ContentNegotiatingViewResolver 这个视图解析器就是用来组合所有的视图解析器的

研究下他的组合逻辑,看到有个属性viewResolvers,看看它是在哪里进行赋值的

protected void initServletContext(ServletContext servletContext) {
    // 这里它是从beanFactory工具中获取容器中的所有视图解析器
    // ViewRescolver.class 把所有的视图解析器来组合的
    Collection<ViewResolver> matchingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(this.obtainApplicationContext(), ViewResolver.class).values();
    ViewResolver viewResolver;
    if (this.viewResolvers == null) {
        this.viewResolvers = new ArrayList(matchingBeans.size());
    }
    // ...............
}

自定义一个视图解析器

  1. 自定义视图解析器
@Bean //放到bean中
public ViewResolver myViewResolver(){
    return new MyViewResolver();
}

//我们写一个静态内部类,视图解析器就需要实现ViewResolver接口
private static class MyViewResolver implements ViewResolver{
    @Override
    public View resolveViewName(String s, Locale locale) throws Exception {
        return null;
    }
}
  1. 视图解析器有没有起作用
    给DispatcherServlet 中的 doDispatch方法 加个断点进行调试一下,因为所有的请求都会走到这个方法中
    在这里插入图片描述
  2. 我们启动我们的项目,然后随便访问一个页面,看一下Debug信息,找到视图解析器,我们看到我们自己定义的就在这里
    在这里插入图片描述
    在这里插入图片描述

格式化转换器

格式化转换器

@Bean
@Override
public FormattingConversionService mvcConversionService() {
    // 拿到配置文件中的格式化规则
    WebConversionService conversionService = 
        new WebConversionService(this.mvcProperties.getDateFormat());
    addFormatters(conversionService);
    return conversionService;
}

点进getDateFormat方法

public String getDateFormat() {
    return this.dateFormat;
}

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

可以看到在我们的Properties文件中,我们可以进行自动配置它!

如果配置了自己的格式化方式,就会注册到Bean中生效,我们可以在配置文件中配置日期格式化的规则
在这里插入图片描述

SpringBoot扩展

SpringBoot在自动配置很多组件的时候,先看容器中有没有用户自己配置的(如果用户自己配置@bean),如果有就用用户配置的,如果没有就用自动配置的

如果有些组件可以存在多个,比如我们的视图解析器,就将用户配置的和自己默认的组合起来

springboot 扩展官方文档

If you want to keep Spring Boot MVC features and you want to add additional MVC configuration 
(interceptors, formatters, view controllers, and other features), you can add your
 own @Configuration class of type WebMvcConfigurer but without @EnableWebMvc. If you wish to provide custom instances of 
 RequestMappingHandlerMapping, RequestMappingHandlerAdapter, or ExceptionHandlerExceptionResolver, 
 you can declare a WebMvcRegistrationsAdapter instance to provide such components.

我们要做的就是编写一个@Configuration注解类,并且类型要为WebMvcConfigurer(实现WebMvcConfigurer),还不能标注@EnableWebMvc注解

我们去自己写一个;我们新建一个包叫config,写一个类MyMvcConfig

//应为类型要求为WebMvcConfigurer,所以我们实现其接口
//可以使用自定义类扩展MVC的功能
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {

    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        // 浏览器发送/test , 就会跳转到test页面;
        registry.addViewController("/test").setViewName("test");
    }
}

扩展原理

  • WebMvcAutoConfiguration 是 SpringMVC的自动配置类,里面有一个类WebMvcAutoConfigurationAdapter

  • 这个类上有一个注解,在做其他自动配置时会导入:@Import(EnableWebMvcConfiguration.class)

  • 我们点进EnableWebMvcConfiguration这个类看一下,它继承了一个父类:DelegatingWebMvcConfiguration

    public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {
        private final WebMvcConfigurerComposite configurers = new WebMvcConfigurerComposite();
        
      // 从容器中获取所有的webmvcConfigurer
        @Autowired(required = false)
        public void setConfigurers(List<WebMvcConfigurer> configurers) {
            if (!CollectionUtils.isEmpty(configurers)) {
                this.configurers.addWebMvcConfigurers(configurers);
            }
        }
    }
    
    我们可以在这个类中去寻找一个我们刚才设置的viewController当做参考,发现它调用了一个
    protected void addViewControllers(ViewControllerRegistry registry) {
        this.configurers.addViewControllers(registry);
    }
    
    点进addViewControllers
    public void addViewControllers(ViewControllerRegistry registry) {
        Iterator var2 = this.delegates.iterator();
    
        while(var2.hasNext()) {
            // 将所有的WebMvcConfigurer相关配置来一起调用!包括我们自己配置的和Spring给我们配置的
            WebMvcConfigurer delegate = (WebMvcConfigurer)var2.next();
            delegate.addViewControllers(registry);
        }
    
    }
    

结论:所有的WebMvcConfiguration都会被作用,不止Spring自己的配置类,我们自己的配置类当然也会被调用

全面接管

即SpringBoot对SpringMVC的自动配置不需要了,所有都是我们自己去配置

If you want to take complete control of Spring MVC
you can add your own @Configuration annotated with @EnableWebMvc.


全面接管 只需在我们的配置类中要加一个@EnableWebMvc
也就是说,只要加上@EnableWebMvc注解,自动配置失效

分析:

这里发现它是导入了一个类,我们可以继续进去看
@Import({DelegatingWebMvcConfiguration.class})public @interface EnableWebMvc {}

它继承了一个父类 WebMvcConfigurationSupport
public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {  // ......}

回顾一下Webmvc自动配置类
@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })
// 这个注解的意思就是:容器中没有这个组件的时候,这个自动配置类才生效
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
@AutoConfigureAfter({ DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class,
    ValidationAutoConfiguration.class })
public class WebMvcAutoConfiguration {
    
}

总结:@EnableWebMvc将WebMvcConfigurationSupport组件导入进来了;而导入的WebMvcConfigurationSupport只是SpringMVC最基本的功能

页面国际化

有的时候,我们的网站会去涉及中英文甚至多语言的切换,这时候我们就需要学习国际化

准备

先在IDEA中统一设置properties的编码问题
在这里插入图片描述

配置文件编写

编写国际化配置文件,抽取页面需要显示的国际化页面消息。我们可以去登录页面查看一下,哪些内容我们需要编写国际化的配置

  1. 我们在resources资源文件下新建一个i18n目录,存放国际化配置文件
  2. 建立一个login.properties文件,还有一个login_zh_CN.properties;发现IDEA自动识别了我们要做国际化操作;文件夹变了在这里插入图片描述
  3. 可以在这上面去新建一个文件,添加一个英文配置文件在这里插入图片描述在这里插入图片描述
  4. 接下来,我们就来编写配置,我们可以看到idea下面有另外一个视图,点击 + 号就可以直接添加属性了;我们新建一个login.tip,可以看到边上有三个文件框可以输入

在这里插入图片描述
5.
在这里插入图片描述
在这里插入图片描述

login.properties 默认
	login.btn=登录
	login.password=密码
	login.remember=记住我
	login.tip=请登录
	login.username=用户名

英文
	login.btn=Sign in
	login.password=Password
	login.remember=Remember me
	login.tip=Please sign in
	login.username=Username

中文
	login.btn=登录
	login.password=密码
	login.remember=记住我
	login.tip=请登录
	login.username=用户名

原理

我们去看一下SpringBoot对国际化的自动配置!这里又涉及到一个类:MessageSourceAutoConfiguration

里面有一个方法,这里发现SpringBoot已经自动配置好了管理我们国际化资源文件的组件 ResourceBundleMessageSource

// 获取 properties 传递过来的值进行判断
@Bean
public MessageSource messageSource(MessageSourceProperties properties) {
    ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
    if (StringUtils.hasText(properties.getBasename())) {
        // 设置国际化文件的基础名(去掉语言国家代码的)
        messageSource.setBasenames(
            StringUtils.commaDelimitedListToStringArray(
                                       StringUtils.trimAllWhitespace(properties.getBasename())));
    }
    if (properties.getEncoding() != null) {
        messageSource.setDefaultEncoding(properties.getEncoding().name());
    }
    messageSource.setFallbackToSystemLocale(properties.isFallbackToSystemLocale());
    Duration cacheDuration = properties.getCacheDuration();
    if (cacheDuration != null) {
        messageSource.setCacheMillis(cacheDuration.toMillis());
    }
    messageSource.setAlwaysUseMessageFormat(properties.isAlwaysUseMessageFormat());
    messageSource.setUseCodeAsDefaultMessage(properties.isUseCodeAsDefaultMessage());
    return messageSource;
}

我们真实 的情况是放在了i18n目录下,所以我们要去配置这个messages的路径

spring.messages.basename=i18n.login

配置页面国际化值

去页面获取国际化的值,查看Thymeleaf的文档,找到message取值操作为:#{…}。我们去页面测试
在这里插入图片描述

配置国际化解析

在Spring中有一个国际化的Locale (区域信息对象);里面有一个叫做LocaleResolver (获取区域信息对象)的解析器!

我们去我们webmvc自动配置文件,寻找一下!看到SpringBoot默认配置

@Bean
@ConditionalOnMissingBean
@ConditionalOnProperty(prefix = "spring.mvc", name = "locale")
public LocaleResolver localeResolver() {
    // 容器中没有就自己配,有的话就用用户配置的
    if (this.mvcProperties.getLocaleResolver() == WebMvcProperties.LocaleResolver.FIXED) {
        return new FixedLocaleResolver(this.mvcProperties.getLocale());
    }
    // 接收头国际化分解
    AcceptHeaderLocaleResolver localeResolver = new AcceptHeaderLocaleResolver();
    localeResolver.setDefaultLocale(this.mvcProperties.getLocale());
    return localeResolver;
}

AcceptHeaderLocaleResolver 这个类中有一个方法

public Locale resolveLocale(HttpServletRequest request) {
    Locale defaultLocale = this.getDefaultLocale();
    // 默认的就是根据请求头带来的区域信息获取Locale进行国际化
    if (defaultLocale != null && request.getHeader("Accept-Language") == null) {
        return defaultLocale;
    } else {
        Locale requestLocale = request.getLocale();
        List<Locale> supportedLocales = this.getSupportedLocales();
        if (!supportedLocales.isEmpty() && !supportedLocales.contains(requestLocale)) {
            Locale supportedLocale = this.findSupportedLocale(request, supportedLocales);
            if (supportedLocale != null) {
                return supportedLocale;
            } else {
                return defaultLocale != null ? defaultLocale : requestLocale;
            }
        } else {
            return requestLocale;
        }
    }
}

那假如我们现在想点击链接让我们的国际化资源生效,就需要让我们自己的Locale生效!

我们去自己写一个自己的LocaleResolver,可以在链接上携带区域信息!

修改一下前端页面的跳转连接:

<!-- 这里传入参数不需要使用 ?使用 (key=value)-->
<a class="btn btn-sm" th:href="@{/index.html(l='zh_CN')}">中文</a>
<a class="btn btn-sm" th:href="@{/index.html(l='en_US')}">English</a>

写一个处理的组件

//可以在链接上携带区域信息
public class MyLocaleResolver implements LocaleResolver {

    //解析请求
    @Override
    public Locale resolveLocale(HttpServletRequest request) {

        String language = request.getParameter("l");
        Locale locale = Locale.getDefault(); // 如果没有获取到就使用系统默认的
        //如果请求链接不为空
        if (!StringUtils.isEmpty(language)){
            //分割请求参数
            String[] split = language.split("_");
            //国家,地区
            locale = new Locale(split[0],split[1]);
        }
        return locale;
    }

    @Override
    public void setLocale(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Locale locale) {

    }
}

为了让我们的区域化信息能够生效,我们需要再配置一下这个组件!在我们自己的MvcConofig下添加bean

@Bean
public LocaleResolver localeResolver(){    return new MyLocaleResolver();}

SpringBoot整合JDBC

SpringData简介

对于数据访问层,无论是 SQL(关系型数据库) 还是 NOSQL(非关系型数据库),Spring Boot 底层都是采用 Spring Data 的方式进行统一处理。

Spring Boot 底层都是采用 Spring Data 的方式进行统一处理各种数据库,Spring Data 也是 Spring 中与 Spring Boot、Spring Cloud 等齐名的知名项目。

Sping Data 官网:https://spring.io/projects/spring-data

数据库相关的启动器 :可以参考官方文档:https://docs.spring.io/spring-boot/docs/2.2.5.RELEASE/reference/htmlsingle/#using-boot-starter

创建测试项目测试数据源

新建一个项目测试:springboot-data-jdbc ; 引入相应的模块!基础模块
在这里插入图片描述项目建好之后,发现自动帮我们导入了如下的启动器

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

编写yaml配置文件连接数据库

spring:
  datasource:
    username: root
    password: 123456
    #?serverTimezone=UTC解决时区的报错
    url: jdbc:mysql://localhost:3306/springboot?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8
    driver-class-name: com.mysql.cj.jdbc.Driver

配置完这一些东西后,我们就可以直接去使用了,因为SpringBoot已经默认帮我们进行了自动配置;去测试类测试一下

@SpringBootTest
class SpringbootDataJdbcApplicationTests {

    //DI注入数据源
    @Autowired
    DataSource dataSource;

    @Test
    public void contextLoads() throws SQLException {
        //看一下默认数据源
        System.out.println(dataSource.getClass());
        //获得连接
        Connection connection =   dataSource.getConnection();
        System.out.println(connection);
        //关闭连接
        connection.close();
    }
}

我们可以看到他默认给我们配置的数据源为 : class com.zaxxer.hikari.HikariDataSource , 我们并没有手动配置

我们来全局搜索一下,找到数据源的所有自动配置都在 :DataSourceAutoConfiguration文件

@Import(
    {Hikari.class, Tomcat.class, Dbcp2.class, Generic.class, DataSourceJmxConfiguration.class}
)
protected static class PooledDataSourceConfiguration {
    protected PooledDataSourceConfiguration() {
    }
}

这里导入的类都在 DataSourceConfiguration 配置类下,可以看出 Spring Boot 2.2.5 默认使用HikariDataSource 数据源,而以前版本,如 Spring Boot 1.5 默认使用 org.apache.tomcat.jdbc.pool.DataSource 作为数据源;

HikariDataSource 号称 Java WEB 当前速度最快的数据源,相比于传统的 C3P0 、DBCP、Tomcat jdbc 等连接池更加优秀;

可以使用 spring.datasource.type 指定自定义的数据源类型,值为 要使用的连接池实现的完全限定名

JDBCTemplate

有了数据源(com.zaxxer.hikari.HikariDataSource),然后可以拿到数据库连接(java.sql.Connection),有了连接,就可以使用原生的 JDBC 语句来操作数据库;

即使不使用第三方第数据库操作框架,如 MyBatis等,Spring 本身也对原生的JDBC 做了轻量级的封装,即JdbcTemplate。

数据库操作的所有 CRUD 方法都在 JdbcTemplate 中。

Spring Boot 不仅提供了默认的数据源,同时默认已经配置好了 JdbcTemplate 放在了容器中,程序员只需自己注入即可使用

JdbcTemplate 的自动配置是依赖 org.springframework.boot.autoconfigure.jdbc 包下的 JdbcTemplateConfiguration 类

JdbcTemplate主要提供以下几类方法:

  • execute方法:可以用于执行任何SQL语句,一般用于执行DDL语句
  • update方法及batchUpdate方法:update方法用于执行新增、修改、删除等语句;batchUpdate方法用于执行批处理相关语句
  • query方法及queryForXXX方法:用于执行查询相关语句
  • call方法:用于执行存储过程、函数相关语句

测试

编写一个Controller,注入 jdbcTemplate,编写测试方法进行访问测试

@RestController
@RequestMapping("/jdbc")
public class JdbcController {

    /**
     * Spring Boot 默认提供了数据源,默认提供了 org.springframework.jdbc.core.JdbcTemplate
     * JdbcTemplate 中会自己注入数据源,用于简化 JDBC操作
     * 还能避免一些常见的错误,使用起来也不用再自己来关闭数据库连接
     */
    @Autowired
    JdbcTemplate jdbcTemplate;

    //查询employee表中所有数据
    //List 中的1个 Map 对应数据库的 1行数据
    //Map 中的 key 对应数据库的字段名,value 对应数据库的字段值
    @GetMapping("/list")
    public List<Map<String, Object>> userList(){
        String sql = "select * from employee";
        List<Map<String, Object>> maps = jdbcTemplate.queryForList(sql);
        return maps;
    }
    
    //新增一个用户
    @GetMapping("/add")
    public String addUser(){
        //插入语句,注意时间问题
        String sql = "insert into employee(last_name, email,gender,department,birth)" +
                " values ('狂神说','24736743@qq.com',1,101,'"+ new Date().toLocaleString() +"')";
        jdbcTemplate.update(sql);
        //查询
        return "addOk";
    }

    //修改用户信息
    @GetMapping("/update/{id}")
    public String updateUser(@PathVariable("id") int id){
        //插入语句
        String sql = "update employee set last_name=?,email=? where id="+id;
        //数据
        Object[] objects = new Object[2];
        objects[0] = "秦疆";
        objects[1] = "24736743@sina.com";
        jdbcTemplate.update(sql,objects);
        //查询
        return "updateOk";
    }

    //删除用户
    @GetMapping("/delete/{id}")
    public String delUser(@PathVariable("id") int id){
        //插入语句
        String sql = "delete from employee where id=?";
        jdbcTemplate.update(sql,id);
        //查询
        return "deleteOk";
    }
    
}

原理分析

org.springframework.boot.autoconfigure.jdbc.DataSourceConfiguration 数据源配置类作用 :根据逻辑判断之后,添加数据源

SpringBoot默认支持以下数据源:

  • com.zaxxer.hikari.HikariDataSource (Spring Boot 2.0 以上,默认使用此数据源)
  • org.apache.tomcat.jdbc.pool.DataSource
  • org.apache.commons.dbcp2.BasicDataSource

可以使用 spring.datasource.type 指定自定义的数据源类型,值为要使用的连接池实现的完全限定名。默认情况下,它是从类路径自动检测的。

    @Configuration
    @ConditionalOnMissingBean({DataSource.class})
    @ConditionalOnProperty(
        name = {"spring.datasource.type"}
    )
    static class Generic {
        Generic() {
        }

        @Bean
        public DataSource dataSource(DataSourceProperties properties) {
            return properties.initializeDataSourceBuilder().build();
        }
    }

SpringBoot整合Druid

Druid简介

Java程序很大一部分要操作数据库,为了提高性能操作数据库的时候,又不得不使用数据库连接池

Druid结合了 C3P0、DBCP 等 DB 池的优点,同时加入了日志监控,可以很好的监控 DB 池连接和 SQL 的执行情况,天生就是针对监控而生的 DB 连接池

com.alibaba.druid.pool.DruidDataSource 常用配置参数

配置					  缺省值				说明
name								配置这个属性的意义在于,如果存在多个数据源,监控的时候可以通过名字来区分开来。 如果没有配置,将会生成一个名字,格式是:"DataSource-" + System.identityHashCode(this). 另外配置此属性至少在1.0.5版本中是不起作用的,强行设置name会出错
url									连接数据库的url,不同数据库不一样。例如: mysql : jdbc:mysql://10.20.153.104:3306/druid2 oracle : jdbc:oracle:thin:@10.20.149.85:1521:ocnauto
username							连接数据库的用户名
password							连接数据库的密码。如果你不希望密码直接写在配置文件中,可以使用ConfigFilter。
driverClassName	 	根据url自动识别	这一项可配可不配,如果不配置druid会根据url自动识别dbType,然后选择相应的driverClassName
initialSize		   		0			初始化时建立物理连接的个数。初始化发生在显示调用init方法,或者第一次getConnection时
maxActive				8			最大连接池数量
minIdle								最小连接池数量
maxWait								获取连接时最大等待时间,单位毫秒。配置了maxWait之后,缺省启用公平锁,并发效率会有所下降,如果需要可以通过配置useUnfairLock属性为true使用非公平锁。
poolPreparedStatements	false		是否缓存preparedStatement,也就是PSCachePSCache对支持游标的数据库性能提升巨大,比如说oracle。在mysql下建议关闭。

配置数据源

  1. 添加上 Druid 数据源依赖

    <!-- https://mvnrepository.com/artifact/com.alibaba/druid -->
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid</artifactId>
        <version>1.1.12</version>
    </dependency>
    
  2. 切换数据源;之前已经说过 Spring Boot 2.0 以上默认使用 com.zaxxer.hikari.HikariDataSource 数据源,但可以 通过 spring.datasource.type 指定数据源

    spring:
      datasource:
        username: root
        password: 123456
        url: jdbc:mysql://localhost:3306/springboot?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8
        driver-class-name: com.mysql.cj.jdbc.Driver
         # 自定义数据源
        type: com.alibaba.druid.pool.DruidDataSource
    
  3. 数据源切换之后,在测试类中注入 DataSource,然后获取到它,输出一看便知是否成功切换
    在这里插入图片描述

  4. 切换成功!既然切换成功,就可以设置数据源连接初始化大小、最大连接数、等待时间、最小连接数 等设置项;可以查看源码

    spring:
      datasource:
        username: root
        password: 123456
        #?serverTimezone=UTC解决时区的报错
        url: jdbc:mysql://localhost:3306/springboot?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8
        driver-class-name: com.mysql.cj.jdbc.Driver
        type: com.alibaba.druid.pool.DruidDataSource
    
        #Spring Boot 默认是不注入这些属性值的,需要自己绑定
        #druid 数据源专有配置
        initialSize: 5
        minIdle: 5
        maxActive: 20
        maxWait: 60000
        timeBetweenEvictionRunsMillis: 60000
        minEvictableIdleTimeMillis: 300000
        validationQuery: SELECT 1 FROM DUAL
        testWhileIdle: true
        testOnBorrow: false
        testOnReturn: false
        poolPreparedStatements: true
    
        #配置监控统计拦截的filters,stat:监控统计、log4j:日志记录、wall:防御sql注入
        #如果允许时报错  java.lang.ClassNotFoundException: org.apache.log4j.Priority
        #则导入 log4j 依赖即可,Maven 地址:https://mvnrepository.com/artifact/log4j/log4j
        filters: stat,wall,log4j
        maxPoolPreparedStatementPerConnectionSize: 20
        useGlobalDataSourceStat: true
        connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
    
  5. 导入Log4j 的依赖

    	 <!-- https://mvnrepository.com/artifact/log4j/log4j -->
    	 <dependency>
    	     <groupId>log4j</groupId>
    	     <artifactId>log4j</artifactId>
    	     <version>1.2.17</version>
    	 </dependency>
    
  6. 现在需要程序员自己为 DruidDataSource 绑定全局配置文件中的参数,再添加到容器中,而不再使用 Spring Boot 的自动生成了;我们需要 自己添加 DruidDataSource 组件到容器中,并绑定属性

    @Configuration
    public class DruidConfig {
    
        /*
           @Configuration 将自定义的 Druid数据源添加到容器中,不再让 Spring Boot 自动创建
           绑定全局配置文件中的 druid 数据源属性到 com.alibaba.druid.pool.DruidDataSource从而让它们生效
           @ConfigurationProperties(prefix = "spring.datasource"):作用就是将 全局配置文件中
           		前缀为 spring.datasource的属性值注入到 com.alibaba.druid.pool.DruidDataSource 的同名参数中
         */
        @ConfigurationProperties(prefix = "spring.datasource")
        @Bean
        public DataSource druidDataSource() {
            return new DruidDataSource();
        }
    
    }
    
  7. 去测试类中测试一下;看是否成功

    @SpringBootTest
    class SpringbootDataJdbcApplicationTests {
    
        //DI注入数据源
        @Autowired
        DataSource dataSource;
    
        @Test
        public void contextLoads() throws SQLException {
            //看一下默认数据源
            System.out.println(dataSource.getClass());
            //获得连接
            Connection connection =   dataSource.getConnection();
            System.out.println(connection);
    
            DruidDataSource druidDataSource = (DruidDataSource) dataSource;
            System.out.println("druidDataSource 数据源最大连接数:" + druidDataSource.getMaxActive());
            System.out.println("druidDataSource 数据源初始化连接数:" + druidDataSource.getInitialSize());
    
            //关闭连接
            connection.close();
        }
    }
    

    在这里插入图片描述

配置Druid数据源监控

Druid 数据源具有监控的功能,并提供了一个 web 界面方便用户查看,类似安装 路由器 时,人家也提供了一个默认的 web 页面

  1. 设置 Druid 的后台管理页面,比如 登录账号、密码等;配置后台管理

    //配置 Druid 监控管理后台的Servlet;
    //内置 Servlet 容器时没有web.xml文件,所以使用 Spring Boot 的注册 Servlet 方式
    @Bean
    public ServletRegistrationBean statViewServlet() {
        ServletRegistrationBean bean = new ServletRegistrationBean(new StatViewServlet(), "/druid/*");
    
        // 这些参数可以在 com.alibaba.druid.support.http.StatViewServlet 
        // 的父类 com.alibaba.druid.support.http.ResourceServlet 中找到
        Map<String, String> initParams = new HashMap<>();
        //loginUsername等参数是固定的,不能改
        initParams.put("loginUsername", "admin"); //后台管理界面的登录账号
        initParams.put("loginPassword", "123456"); //后台管理界面的登录密码
    
        //后台允许谁可以访问
        //initParams.put("allow", "localhost"):表示只有本机可以访问
        //initParams.put("allow", ""):为空或者为null时,表示允许所有访问
        initParams.put("allow", "");
        //deny:Druid 后台拒绝谁访问
        //initParams.put("deny", "192.168.1.20");表示禁止此ip访问
    
        //设置初始化参数
        bean.setInitParameters(initParams);
        return bean;
    }
    
  2. 配置完毕后,我们可以选择访问 :http://localhost:8080/druid/login.html
    在这里插入图片描述
    在这里插入图片描述

配置 Druid web 监控 filter 过滤器

平时在工作中,按需求进行配置即可,主要用作监控

//配置 Druid 监控 之  web 监控的 filter
//WebStatFilter:用于配置Web和Druid数据源之间的管理关联监控统计
@Bean
public FilterRegistrationBean webStatFilter() {
    FilterRegistrationBean bean = new FilterRegistrationBean();
    bean.setFilter(new WebStatFilter());

    //exclusions:设置哪些请求进行过滤排除掉,从而不进行统计
    Map<String, String> initParams = new HashMap<>();
    initParams.put("exclusions", "*.js,*.css,/druid/*,/jdbc/*");
    bean.setInitParameters(initParams);

    //"/*" 表示过滤所有请求
    bean.setUrlPatterns(Arrays.asList("/*"));
    return bean;
}

SpringBoot 整合mybatis

  1. 创建项目时勾选springweb,MySQL驱动,导入mybatis所需要的依赖

    <!-- 引入 myBatis,这是 MyBatis官方提供的适配 Spring Boot 的,而不是Spring Boot自己的-->
       <dependency>
           <groupId>org.mybatis.spring.boot</groupId>
           <artifactId>mybatis-spring-boot-starter</artifactId>
           <version>2.1.0</version>
       </dependency>
    
  2. 配置数据库连接信息

    spring.datasource.username=root
    spring.datasource.password=123456
    spring.datasource.url=jdbc:mysql://localhost:3306/mybatis?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8
    spring.datasource.driver-class-name=com.mysql.jdbc.Driver
    
  3. 我们这里就是用默认的数据源了;先去测试一下连接是否成功

    @RunWith(SpringRunner.class)
    @SpringBootTest
    public class SpringbootDemoMybatisApplicationTests {
    
        @Autowired
        DataSource dataSource;
    
        @Test
        public void contextLoads() throws SQLException {
    
            System.out.println("数据源>>>>>>" + dataSource.getClass());
            Connection connection = dataSource.getConnection();
            System.out.println("连接>>>>>>>>>" + connection);
            System.out.println("连接地址>>>>>" + connection.getMetaData().getURL());
            connection.close();
    
        }
    
    }
    
  4. 创建实体类

    package com.kuang.mybatis.pojo;
    
    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public class User {
    
        private int id;
        private String name;
        private String pwd;
    
  5. 对应Mapper映射文件 UserMapper,放在resources/mybatis/mapper中

    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE mapper
            PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
            "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <mapper namespace="com.kuang.mybatis.pojo.mapper.UserMapper">
    
        <select id="selectUser" resultType="User">
      		  select * from user
        </select>
    
        <select id="selectUserById" resultType="User">
       		   select * from user where id = #{id}
    	</select>
    
        <insert id="addUser" parameterType="User">
    		   insert into user (id,name,pwd) values (#{id},#{name},#{pwd})
    	</insert>
    
        <update id="updateUser" parameterType="User">
      		  update user set name=#{name},pwd=#{pwd} where id = #{id}
    	</update>
    
        <delete id="deleteUser" parameterType="int">
    		    delete from user where id = #{id}
    	</delete>
    </mapper>
    
  6. 以前 MyBatis 未与 spring 整合时,配置数据源、事务、连接数据库的账号、密码等都是在 myBatis 核心配置文件中进行的myBatis 与 spring 整合后,配置数据源、事务、连接数据库的账号、密码等就交由 spring 管理。因此,在这里我们即使不使用mybatis配置文件也完全ok!

既然已经提供了 myBatis 的映射配置文件,自然要告诉 spring boot 这些文件的位置

已经说过 spring boot 官方并没有提供 myBaits 的启动器,是 myBatis 官方提供的开发包来适配的 spring boot,从 pom.xml 文件中的依赖包名也能看出来,并非是以 spring-boot 开头的;

同理上面全局配置文件中的这两行配置也是以 mybatis 开头 而非 spring 开头也充分说明这些都是 myBatis 官方提供的

spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.url=jdbc:mysql://localhost:3306/mybatis?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8
spring.datasource.driver-class-name=com.mysql.jdbc.Driver

#指定myBatis的核心配置文件与Mapper映射文件
mybatis.mapper-locations=classpath:mybatis/mapper/*.xml
	
注意:对应实体类的路径
mybatis.type-aliases-package=com.kuang.mybatis.pojo
  1. 编写controller

    @RestController
    public class UserController {
    
        @Autowired
        private UserMapper userMapper;
    
        //选择全部用户
        @GetMapping("/selectUser")
        public String selectUser(){
            List<User> users = userMapper.selectUser();
            for (User user : users) {
                System.out.println(user);
            }
    
            return "ok";
        }
        //根据id选择用户
        @GetMapping("/selectUserById")
        public String selectUserById(){
            User user = userMapper.selectUserById(1);
            System.out.println(user);
            return "ok";
        }
        //添加一个用户
        @GetMapping("/addUser")
        public String addUser(){
            userMapper.addUser(new User(5,"阿毛","456789"));
            return "ok";
        }
        //修改一个用户
        @GetMapping("/updateUser")
        public String updateUser(){
            userMapper.updateUser(new User(5,"阿毛","421319"));
            return "ok";
        }
        //根据id删除用户
        @GetMapping("/deleteUser")
        public String deleteUser(){
            userMapper.deleteUser(5);
            return "ok";
        }
    
    }
    

SpringBoot集成SpringSecurity

如果在应用开发的后期才考虑安全的问题,就可能陷入一个两难的境地:一方面,应用存在严重的安全漏洞,无法满足用户的要求,并可能造成用户的隐私数据被攻击者窃取;另一方面,应用的基本架构已经确定,要修复安全漏洞,可能需要对系统的架构做出比较重大的调整,因而需要更多的开发时间,影响应用的发布进程。因此,从应用开发的第一天就应该把安全相关的因素考虑进来,并在整个应用的开发过程中。

市面上存在比较有名的:Shiro,Spring Security

Spring Security是一个功能强大且高度可定制的身份验证和访问控制框架。它实际上是保护基于spring的应用程序的标准。

Spring Security是一个框架,侧重于为Java应用程序提供身份验证和授权。与所有Spring项目一样,Spring安全性的真正强大之处在于它可以轻松地扩展以满足定制需求

从官网的介绍中可以知道这是一个权限框架。想我们之前做项目是没有使用框架是怎么控制权限的?对于权限 一般会细分为功能权限,访问权限,和菜单权限。代码会写的非常的繁琐,冗余。

怎么解决之前写权限代码繁琐,冗余的问题,一些主流框架就应运而生而Spring Scecurity就是其中的一种

Spring Security 基于 Spring 框架,提供了一套 Web 应用安全性的完整解决方案。一般来说,Web 应用的安全性包括用户认证(Authentication)和用户授权(Authorization)两个部分。用户认证指的是验证某个用户是否为系统中的合法主体,也就是说用户能否访问该系统。用户认证一般要求用户提供用户名和密码。系统通过校验用户名和密码来完成认证过程。用户授权指的是验证某个用户是否有权限执行某个操作。在一个系统中,不同用户所具有的权限是不同的。比如对一个文件来说,有的用户只能进行读取,而有的用户可以进行修改。一般来说,系统会为不同的用户分配不同的角色,而每个角色则对应一系列的权限。

认识SpringSecurity

Spring Security 是针对Spring项目的安全框架,也是Spring Boot底层安全模块默认的技术选型,他可以实现强大的Web安全控制,对于安全控制,我们仅需要引入spring-boot-starter-security 模块,进行少量的配置,即可实现强大的安全管理

记住几个类:

  • WebSecurityConfigurerAdapter:自定义Security策略
  • AuthenticationManagerBuilder:自定义认证策略
  • @EnableWebSecurity:开启WebSecurity模式

Spring Security的两个主要目标是 “认证” 和 “授权”(访问控制)

“认证”(Authentication)

  • 身份验证是关于验证您的凭据,如用户名/用户ID和密码,以验证您的身份。
  • 身份验证通常通过用户名和密码完成,有时与身份验证因素结合使用。

“授权” (Authorization)

  • 授权发生在系统成功验证您的身份后,最终会授予您访问资源(如信息,文件,数据库,资金,位置,几乎任何内容)的完全权限

测试

实验环境搭建

  1. 新建一个初始的springboot项目web模块,thymeleaf模块

  2. 导入静态资源

    welcome.html
    |views
    	|level1
    		1.html
    		2.html
    		3.html
    	|level2
    		1.html
    		2.html
    		3.html
    	|level3
    		1.html
    		2.html
    		3.html
    Login.html
    
  3. controller跳转

    @Controller
    public class RouterController {
    
       @RequestMapping({"/","/index"})
       public String index(){
           return "index";
      }
    
       @RequestMapping("/toLogin")
       public String toLogin(){
           return "views/login";
      }
    
       @RequestMapping("/level1/{id}")
       public String level1(@PathVariable("id") int id){
           return "views/level1/"+id;
      }
    
       @RequestMapping("/level2/{id}")
       public String level2(@PathVariable("id") int id){
           return "views/level2/"+id;
      }
    
       @RequestMapping("/level3/{id}")
       public String level3(@PathVariable("id") int id){
           return "views/level3/"+id;
      }
    
    }
    

认证和授权

我们的测试环境,是谁都可以访问的,我们使用 Spring Security 增加上认证和授权的功能

  1. 引入 Spring Security 模块

    <dependency>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
    
  2. 编写 Spring Security 配置类
    参考官网:https://spring.io/projects/spring-security
    查看我们自己项目中的版本,找到对应的帮助文档:
    https://docs.spring.io/spring-security/site/docs/5.3.0.RELEASE/reference/html5 #servlet-applications 8.16.4在这里插入图片描述

  3. 编写基础配置类

    @EnableWebSecurity // 开启WebSecurity模式
    public class SecurityConfig extends WebSecurityConfigurerAdapter {
    
       @Override
       protected void configure(HttpSecurity http) throws Exception {
           
      }
    }
    
  4. 定制请求的授权规则

    @Override
    protected void configure(HttpSecurity http) throws Exception {
       // 定制请求的授权规则
       // 首页所有人可以访问
       http.authorizeRequests().antMatchers("/").permitAll()
      .antMatchers("/level1/**").hasRole("vip1")
      .antMatchers("/level2/**").hasRole("vip2")
      .antMatchers("/level3/**").hasRole("vip3");
    }
    
  5. 测试一下:发现除了首页都进不去了!因为我们目前没有登录的角色,因为请求需要登录的角色拥有对应的权限才可以!

  6. 在configure()方法中加入以下配置,开启自动配置的登录功能

    // 开启自动配置的登录功能
    // /login 请求来到登录页
    // /login?error 重定向到这里表示登录失败
    http.formLogin();
    
  7. 测试一下:发现,没有权限的时候,会跳转到登录的页面

  8. 我们可以定义认证规则,重写configure(AuthenticationManagerBuilder auth)方法

    //定义认证规则
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
       
       //在内存中定义,也可以在jdbc中去拿....
       auth.inMemoryAuthentication()
              .withUser("kuangshen").password("123456").roles("vip2","vip3")
              .and()
              .withUser("root").password("123456").roles("vip1","vip2","vip3")
              .and()
              .withUser("guest").password("123456").roles("vip1","vip2");
    }
    
  9. 测试,我们可以使用这些账号登录进行测试!发现会报错
    在这里插入图片描述

  10. 原因,我们要将前端传过来的密码进行某种方式加密,否则就无法登录,修改代码

    //定义认证规则
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
       //在内存中定义,也可以在jdbc中去拿....
       //Spring security 5.0中新增了多种加密方式,也改变了密码的格式。
       //要想我们的项目还能够正常登陆,需要修改一下configure中的代码。我们要将前端传过来的密码进行某种方式加密
       //spring security 官方推荐的是使用bcrypt加密方式。
       
       auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder())
              .withUser("kuangshen").password(new BCryptPasswordEncoder().encode("123456")).roles("vip2","vip3")
              .and()
              .withUser("root").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1","vip2","vip3")
              .and()
              .withUser("guest").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1","vip2");
    }
    
  11. 测试,发现,登录成功,并且每个角色只能访问自己认证下的规则!搞定

权限控制和注销

  1. 开启自动配置的注销的功能

    //定制请求的授权规则
    @Override
    protected void configure(HttpSecurity http) throws Exception {
       //....
       //开启自动配置的注销的功能
          // /logout 注销请求
       http.logout();
    }
    
  2. 我们在前端,增加一个注销的按钮,index.html 导航栏中

    <a class="item" th:href="@{/logout}">
       <i class="address card icon"></i> 注销
    </a>
    
  3. 我们可以去测试一下,登录成功后点击注销,发现注销完毕会跳转到登录页面!但是,我们想让他注销成功后,依旧可以跳转到首页,该怎么处理呢

    // .logoutSuccessUrl("/"); 注销成功来到首页
    http.logout().logoutSuccessUrl("/");
    
  4. 测试,注销完毕后,发现跳转到首页OK
    用户没有登录的时候,导航栏上只显示登录按钮,用户登录之后,导航栏可以显示登录的用户信息及注销按钮!还有就是,比如kuangshen这个用户,它只有 vip2,vip3功能,那么登录则只显示这两个功能,而vip1的功能菜单不显示!这个就是真实的网站情况了!该如何做呢?
    我们需要结合thymeleaf中的一些功能 sec:authorize=“isAuthenticated()”:是否认证登录!来显示不同的页面

    <!-- https://mvnrepository.com/artifact/org.thymeleaf.extras/thymeleaf-extras-springsecurity4 -->
    <dependency>
       <groupId>org.thymeleaf.extras</groupId>
       <artifactId>thymeleaf-extras-springsecurity5</artifactId>
       <version>3.0.4.RELEASE</version>
    </dependency>
    
  5. 修改我们的 前端页面

    导入命名空间
     xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity5"
    
    修改导航栏,增加认证判断
     <!--登录注销-->
     <div class="right menu">
     
        <!--如果未登录-->
        <div sec:authorize="!isAuthenticated()">
            <a class="item" th:href="@{/login}">
                <i class="address card icon"></i> 登录
            </a>
        </div>
     
        <!--如果已登录-->
        <div sec:authorize="isAuthenticated()">
            <a class="item">
                <i class="address card icon"></i>
               用户名:<span sec:authentication="principal.username"></span>
               角色:<span sec:authentication="principal.authorities"></span>
            </a>
        </div>
     
        <div sec:authorize="isAuthenticated()">
            <a class="item" th:href="@{/logout}">
                <i class="address card icon"></i> 注销
            </a>
        </div>
     </div>
    
  6. 重启测试,我们可以登录试试看,登录成功后确实,显示了我们想要的页面;如果注销404了,就是因为它默认防止csrf跨站请求伪造,因为会产生安全问题,我们可以将请求改为post表单提交,或者在spring security中关闭csrf功能;我们试试在配置中增加 http.csrf().disable()

    http.csrf().disable();//关闭csrf功能:跨站请求伪造,默认只能通过post方式提交logout请求
    http.logout().logoutSuccessUrl("/");
    
  7. 我们继续将下面的角色功能块认证完成

    <!-- sec:authorize="hasRole('vip1')" -->
    <div class="column" sec:authorize="hasRole('vip1')">
       <div class="ui raised segment">
           <div class="ui">
               <div class="content">
                   <h5 class="content">Level 1</h5>
                   <hr>
                   <div><a th:href="@{/level1/1}"><i class="bullhorn icon"></i> Level-1-1</a></div>
                   <div><a th:href="@{/level1/2}"><i class="bullhorn icon"></i> Level-1-2</a></div>
                   <div><a th:href="@{/level1/3}"><i class="bullhorn icon"></i> Level-1-3</a></div>
               </div>
           </div>
       </div>
    </div>
    
    <div class="column" sec:authorize="hasRole('vip2')">
       <div class="ui raised segment">
           <div class="ui">
               <div class="content">
                   <h5 class="content">Level 2</h5>
                   <hr>
                   <div><a th:href="@{/level2/1}"><i class="bullhorn icon"></i> Level-2-1</a></div>
                   <div><a th:href="@{/level2/2}"><i class="bullhorn icon"></i> Level-2-2</a></div>
                   <div><a th:href="@{/level2/3}"><i class="bullhorn icon"></i> Level-2-3</a></div>
               </div>
           </div>
       </div>
    </div>
    
    <div class="column" sec:authorize="hasRole('vip3')">
       <div class="ui raised segment">
           <div class="ui">
               <div class="content">
                   <h5 class="content">Level 3</h5>
                   <hr>
                   <div><a th:href="@{/level3/1}"><i class="bullhorn icon"></i> Level-3-1</a></div>
                   <div><a th:href="@{/level3/2}"><i class="bullhorn icon"></i> Level-3-2</a></div>
                   <div><a th:href="@{/level3/3}"><i class="bullhorn icon"></i> Level-3-3</a></div>
               </div>
           </div>
       </div>
    </div>
    

记住我

只要登录之后,关闭浏览器,再登录,就会让我们重新登录,但是很多网站的情况,就是有一个记住密码的功能,这个该如何实现呢?很简单

登录成功后,将cookie发送给浏览器保存,以后登录带上这个cookie,只要通过检查就可以免登录了。如果点击注销,则会删除这个cookie

  1. 开启记住我功能

    //定制请求的授权规则
    @Override
    protected void configure(HttpSecurity http) throws Exception {
    //。。。。。。。。。。。
       //记住我
       http.rememberMe();
    }
    
  2. 我们再次启动项目测试一下,发现登录页多了一个记住我功能,我们登录之后关闭 浏览器,然后重新打开浏览器访问,发现用户依旧存在!
    思考:如何实现的呢?其实非常简单
    我们可以查看浏览器的cookie
    在这里插入图片描述

  3. 我们点击注销的时候,可以发现,spring security 帮我们自动删除了这个 cookie在这里插入图片描述

定制登录页

现在这个登录页面都是spring security 默认的,怎么样可以使用我们自己写的Login界面呢

  1. 在刚才的登录页配置后面指定 loginpage

    http.formLogin().loginPage("/toLogin");
    
  2. 然后前端也需要指向我们自己定义的 login请求

    <a class="item" th:href="@{/toLogin}">
       <i class="address card icon"></i> 登录
    </a>
    
  3. 我们登录,需要将这些信息发送到哪里,我们也需要配置,login.html 配置提交请求及方式,方式必须为post

    <form th:action="@{/login}" method="post">
       <div class="field">
           <label>Username</label>
           <div class="ui left icon input">
               <input type="text" placeholder="Username" name="username">
               <i class="user icon"></i>
           </div>
       </div>
       <div class="field">
           <label>Password</label>
           <div class="ui left icon input">
               <input type="password" name="password">
               <i class="lock icon"></i>
           </div>
       </div>
       <input type="submit" class="ui blue submit button"/>
    </form>
    
  4. 这个请求提交上来,我们还需要验证处理,怎么做呢?我们可以查看formLogin()方法的源码!我们配置接收登录的用户名和密码的参数

    http.formLogin()
      .usernameParameter("username")
      .passwordParameter("password")
      .loginPage("/toLogin")
      .loginProcessingUrl("/login"); // 登陆表单提交请求
    
  5. 在登录页增加记住我的多选框

    <input type="checkbox" name="remember"> 记住我
    
  6. 后端验证处理

    //定制记住我的参数!
    http.rememberMe().rememberMeParameter("remember");
    

完整配置代码

@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

   //定制请求的授权规则
   @Override
   protected void configure(HttpSecurity http) throws Exception {

       http.authorizeRequests().antMatchers("/").permitAll()
      .antMatchers("/level1/**").hasRole("vip1")
      .antMatchers("/level2/**").hasRole("vip2")
      .antMatchers("/level3/**").hasRole("vip3");


       //开启自动配置的登录功能:如果没有权限,就会跳转到登录页面!
           // /login 请求来到登录页
           // /login?error 重定向到这里表示登录失败
       http.formLogin()
          .usernameParameter("username")
          .passwordParameter("password")
          .loginPage("/toLogin")
          .loginProcessingUrl("/login"); // 登陆表单提交请求

       //开启自动配置的注销的功能
           // /logout 注销请求
           // .logoutSuccessUrl("/"); 注销成功来到首页

       http.csrf().disable();//关闭csrf功能:跨站请求伪造,默认只能通过post方式提交logout请求
       http.logout().logoutSuccessUrl("/");

       //记住我
       http.rememberMe().rememberMeParameter("remember");
  }

   //定义认证规则
   @Override
   protected void configure(AuthenticationManagerBuilder auth) throws Exception {
       //在内存中定义,也可以在jdbc中去拿....
       //Spring security 5.0中新增了多种加密方式,也改变了密码的格式。
       //要想我们的项目还能够正常登陆,需要修改一下configure中的代码。我们要将前端传过来的密码进行某种方式加密
       //spring security 官方推荐的是使用bcrypt加密方式。

       auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder())
              .withUser("kuangshen").password(new BCryptPasswordEncoder().encode("123456")).roles("vip2","vip3")
              .and()
              .withUser("root").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1","vip2","vip3")
              .and()
              .withUser("guest").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1","vip2");
  }
}

技巧

dev-tools热部署

使用dev-tools热部署,实际上是一个假的热部署,pom文件配置依赖,项目修改代码后快捷键 commend + F9,项目就会自动加载修改的部分

不加依赖,单纯的 commend + F9 build project 并不是热部署,而是把项目重新启动了,和重新启动没有区别

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-devtools</artifactId>
    <optional>true</optional>
</dependency>

在这里插入图片描述

Spring Initailizr(项目初始化向导)

选择我们需要的开发场景
在这里插入图片描述
Spring Initailizr(项目初始化向导)可以帮我们导入自己选择功能的相关依赖
在这里插入图片描述
自动创建项目结构在这里插入图片描述
自动编写好主配置类
在这里插入图片描述

面试题

  • SpringBoot 最核心的东西?谈谈你的理解
	1. 自动装配。原理:重要
	2. 讲讲run()方法
		a. 推断应用的类型是普通的项目还是Web项目
		b. 查找并加载所有可用初始化器 , 设置到initializers属性中
		c. 找出所有的应用程序监听器,设置到listeners属性中
		d. 推断并设置main方法的定义类,找到运行的主类
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值