springBoot

1.SpringBoot简介

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

  • SpringBoot优点:

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

2.微服务架构

(1)微服务就是一种架构风格
(2)微服务就是把一个项目拆分成独立的多个服务,并且多个服务是可以独立运行的,而每个服务都会占用线程

2.1、微服务架构的由来

  • 微服务架构风格是一种方法 将单个应用程序开发为一套小型服务, 每个都在自己的进程中运行并与轻量级通信 机制,通常是 HTTP 资源 API。这些服务是构建的 围绕业务功能,可完全独立部署 自动化部署机制。有最低限度 集中管理这些服务,可以写在 不同的编程语言和使用不同的数据存储 技术

2.2、微服务架构与单一架构的区别

(1)单体架构所有的模块全都耦合在一块,代码量大,维护困难。

微服务每个模块就相当于一个单独的项目,代码量明显减少,遇到问题也相对来说比较好解决。

(2)单体架构所有的模块都共用一个数据库,存储方式比较单一。

微服务每个模块都可以使用不同的存储方式(比如有的用redis,有的用mysql等),数据库也是单个模块对应自己的数据库。(单体架构也可以实现,但是比较麻烦)

(3)单体架构所有的模块开发所使用的技术一样。

微服务每个模块都可以使用不同的开发技术,开发模式更灵活。

2.3、微服务本质

  • 微服务,关键其实不仅仅是微服务本身,而是系统要提供一套基础的架构,这种架构使得微服务可以独立的部署、运行、升级,不仅如此,这个系统架构还让微服务与微服务之间在结构上“松耦合”,而在功能上则表现为一个统一的整体。这种所谓的“统一的整体”表现出来的是统一风格的界面,统一的权限管理,统一的安全策略,统一的上线过程,统一的日志和审计方法,统一的调度方式,统一的访问入口等等。
  • 微服务的目的是有效的拆分应用,实现敏捷开发和部署 。
  • 微服务提倡的理念团队间应该是 inter-operate, not integrate 。inter-operate是定义好系统的边界和接口,在一个团队内全栈,让团队自治,原因就是因为如果团队按照这样的方式组建,将沟通的成本维持在系统内部,每个子系统就会更加内聚,彼此的依赖耦合能变弱,跨系统的沟通成本也就能降低。

2.4、微服务架构图

在这里插入图片描述

3.第一个SpringBoot程序

3.1、在官网创建SpingBoot程序

  • 创建地址:https://start.spring.io/

在这里插入图片描述

  • 下载好jar包,用idea打开,使用import project方式打开

3.2、在idea中创建SpringBoot程序

1、创建一个新项目

2、选择spring initalizr , 可以看到默认就是去官网的快速构建工具那里实现

3、填写项目信息

4、选择初始化的组件(初学勾选 Web 即可)

5、填写项目路径

6、等待项目构建成功

  • idea创建时可以在resources下添加一个banner.txt文件可以修改启动spring boot终端开始画面

  • 添加新的包时需要与主程序在同级目录下

在这里插入图片描述

4.SpringBoot原理初解析

pom.xml

父依赖

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

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

点进去,发现还有一个父依赖

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-dependencies</artifactId>
    <version>3.1.2</version>
</parent>

这里才是真正管理SpringBoot应用里面所有依赖版本的地方,SpringBoot的版本控制中心;

以后我们导入依赖默认是不需要写版本;但是如果导入的包没有在依赖中管理着就需要手动配置版本了;

启动器 spring-boot-starter

<!--以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;

主启动类

  • 默认主启动类
package com.mixcus;

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

@SpringBootApplication
public class SpringBoot01Application {

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

}

1.@SpringBootApplication

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

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

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration 
@EnableAutoConfiguration
@ComponentScan(
    excludeFilters = {@Filter(
    type = FilterType.CUSTOM,
    classes = {TypeExcludeFilter.class}
), @Filter(
    type = FilterType.CUSTOM,
    classes = {AutoConfigurationExcludeFilter.class}
)}
)
public @interface SpringBootApplication {
    //..
}

@ComponentScan

这个注解在Spring中很重要 ,它对应XML配置中的元素。

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

@SpringBootConfiguration

作用:SpringBoot的配置类 ,标注在某个类上 , 表示这是一个SpringBoot的配置类;

我们继续进去这个注解查看

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
@Indexed
public @interface SpringBootConfiguration {
    @AliasFor(
        annotation = Configuration.class
    )
    boolean proxyBeanMethods() default true;
}

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

@Configuration

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

    boolean proxyBeanMethods() default true;

    boolean enforceUniqueMethods() default true;
}

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

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

@EnableAutoConfiguration

@EnableAutoConfiguration :开启自动配置功能

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

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

    Class<?>[] exclude() default {};

    String[] excludeName() default {};
}

点进注解接续查看:

@AutoConfigurationPackage :自动配置包

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import({AutoConfigurationPackages.Registrar.class})
public @interface AutoConfigurationPackage {
    String[] basePackages() default {};

    Class<?>[] basePackageClasses() default {};
}

@importSpring底层注解@import , 给容器中导入一个组件

Registrar.class 作用:将主启动类的所在包及包下面所有子包里面的所有组件扫描到Spring容器 ;

@Import({AutoConfigurationImportSelector.class}) :给容器导入组件 ;

AutoConfigurationImportSelector :自动配置导入选择器,那么它会导入哪些组件的选择器呢?我们点击去这个类看源码:

1、这个类中有一个这样的方法

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
    List<String> configurations = ImportCandidates.load(AutoConfiguration.class, this.getBeanClassLoader()).getCandidates();
    Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports. If you are using a custom packaging, make sure that file is correct.");
    return configurations;
}

2、这个方法又调用了 ImportCandidates 类的静态方法!我们进入ImportCandidates类load()方法

public static ImportCandidates load(Class<?> annotation, ClassLoader classLoader) {
    Assert.notNull(annotation, "'annotation' must not be null");
    ClassLoader classLoaderToUse = decideClassloader(classLoader);
    String location = String.format("META-INF/spring/%s.imports", annotation.getName());
    Enumeration<URL> urls = findUrlsInClasspath(classLoaderToUse, location);
    List<String> importCandidates = new ArrayList();

    while(urls.hasMoreElements()) {
        URL url = (URL)urls.nextElement();
        importCandidates.addAll(readCandidateConfigurations(url));
    }

    return new ImportCandidates(importCandidates);
}

3、发现一个多次出现的文件:org.springframework.boot.autoconfigure.AutoConfiguration.imports,全局搜索它

在这里插入图片描述

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

结论:

  1. SpringBoot在启动的时候从类路径下的META-INF/spring.factories中获取EnableAutoConfiguration指定的值
  2. 将这些值作为自动配置类导入容器 , 自动配置类就生效 , 帮我们进行自动配置工作;
  3. 整个J2EE的整体解决方案和自动配置都在springboot-autoconfigure的jar包中;
  4. 它会给容器中导入非常多的自动配置类 (xxxAutoConfiguration), 就是给容器中导入这个场景需要的所有组件 , 并配置好这些组件 ;
  5. 有了自动配置类 , 免去了我们手动编写配置注入功能组件等的工作;

2.SpringApplication.run方法解析

分析该方法主要分两部分,一部分是SpringApplication的实例化,二是run方法的执行;

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();
}

流程图:

图片

5.yaml、yml

配置文件

SpringBoot使用一个全局的配置文件 , 配置文件名称是固定的

  • application.properties

    • 语法结构 :key=value
  • application.yml

    • 语法结构 :key:空格 value

**配置文件的作用 :**修改SpringBoot自动配置的默认值,因为SpringBoot在底层都给我们自动配置好了;

5.1、yaml简介

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

这种语言以数据作为中心,而不是以标记语言为重点

以前的配置文件,大多数都是使用xml来配置;比如一个简单的端口配置,我们来对比下yaml和xml

传统xml配置:

<server>
	<!--将端口号修改为8081-->
	<port>8081<port>
</server>

yaml配置:

server:  
	prot: 8080

5.2、yaml基础语法

说明:语法要求严格!

1、空格不能省略

2、以缩进来控制层级关系,只要是左边对齐的一列数据都是同一个层级的。

3、属性和值的大小写都是十分敏感的。

字面量:普通的值 [ 数字,布尔值,字符串 ]

字面量直接写在后面就可以 , 字符串默认不用加上双引号或者单引号;

k: v

注意:

  • “ ” 双引号,不会转义字符串里面的特殊字符 , 特殊字符会作为本身想表示的意思;

    比如 :name: “hello \n world” 输出 :hello 换行 world

  • ‘’ 单引号,会转义特殊字符 , 特殊字符最终会变成和普通字符一样输出

    比如 :name: “hello \n world” 输出 :hello \n world

对象、Map(键值对)

#对象、Map格式
k:     
	v1:    
	v2:

在下一行来写对象的属性和值得关系,注意缩进;比如:

student:    
	name: 张三
    age: 3

行内写法

student: {name: qinjiang,age: 3}

数组( List、set )

用 - 值表示数组中的一个元素,比如:

pets:
	- cat
    - dog
    - pig

行内写法

pets: [cat,dog,pig]

修改SpringBoot的默认端口号

配置文件中添加,端口号的参数,就可以切换端口;

server:  
	port: 8082

5.3、注入配置文件

@Value()注解

dog.class


@Component
public class Dog {
	//@Value() 通过反射进行注入
    @Value("大黄")
    private String name;

    @Value("10")
    private Integer age;

    //....
}

yaml注入

application.yaml

person:
  name: "张三"
  dog:
    name: "旺财"
    age: 10
  map: {k1: v1,k2: v2}
  list:
    - code
    - game

dog:
  name: "旺财"
  age: 10

person.class

package com.mixcus.pojo;

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

import java.util.List;
import java.util.Map;

@Component
@ConfigurationProperties(prefix = "person")//该注解是绑定配置文件前缀名:person 与yaml中对象对应
public class Person {

    private String name;

    private Dog dog;

    private Map<String,Object> map;

    private List<String> list;

	//....
}


dog.class

@Component

public class Dog {
  
    private String name;

    private Integer age;
	//...
}

运行结果:

Person{

​ name=‘张三’,

​ dog=Dog{

​ name=‘旺财’, age=10

​ },

​ map={

​ k1=v1,

​ k2=v2},

​ list=[code, game]}
Dog{

​ name=‘旺财’,

​ age=10

}

5.4、加载指定的配置文件

**@PropertySource :**加载指定的配置文件;

@configurationProperties:默认从全局配置文件中获取值;

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

name="张三"

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

使用@PropertySource(value=“classpath:文件路径”)注解

package com.mixcus.pojo;

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

@Component
@PropertySource(value = "classpath:person.properties")
public class Dog {

    @Value("${name}")//使用EL表达式取值
    private String firstName;

    private Integer age;

	//...
}

3.处理properties乱码

在这里插入图片描述

5.5、yaml在不用路径的优先级

在这里插入图片描述

file 项目路径

classpath 资源路径

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

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

  1. file/config/application.yaml

在这里插入图片描述

  1. file/application.yaml

在这里插入图片描述

  1. classpath:/application

在这里插入图片描述

  1. classpath:/config/application
    在这里插入图片描述

运行结果:

在这里插入图片描述

5.6、配置多种环境

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

例如:

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

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

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

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

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

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

server:  
	port: 8081
#选择要激活那个环境块
spring:  
	profiles:    
		active: prod
---
server:
	port: 8083
spring:  
	profiles: dev #配置环境的名称
---
server:  
	port: 8084
spring:  
	profiles: prod  #配置环境的名称

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

对比

在这里插入图片描述

松散绑定

例:

实体类中有一个属性 String userName;

此时yaml中:user-name: “jkasd”

此时user-name 会转换为 userName

-后面跟着的字母默认是大写的。

这就是松散绑定

JSR303数据校验

这个就是我们可以在字段是增加一层过滤器验证 , 可以保证数据的合法性

在实体类中使用@Validated注解

需要导入其依赖

<!--validation启动器-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-validation</artifactId>
</dependency>

类:

@Component
@ConfigurationProperties(prefix = "person")
@Validated //验证注解
public class Person {

    @Email(message = "请输入邮箱!")//判断是否为email格式,message不符合时显示的信息
    private String name;

    private Integer age;
	//...
}

在这里插入图片描述

复杂类型封装

  • yml中可以封装对象 , 使用value就不支持

6.自动装配原理

配置文件到底能写什么?怎么写?

SpringBoot官方文档中有大量的配置,我们无法全部记住

图片

6.1、分析自动配置原理

我们以**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;
  //如果不存在,判断也是成立的
  //即使我们配置文件中不配置pring.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类中获取某些属性。我们只需要在配置文件中指定这些属性的值即可;

**xxxxAutoConfigurartion:自动配置类;**给容器中添加组件

xxxxProperties:封装配置文件中相关属性;

6.2、了解:@Conditional

了解完自动装配的原理后,我们来关注一个细节问题,自动配置类必须在一定的条件下才能生效;

@Conditional派生注解(Spring注解版原生的@Conditional作用)

作用:必须是@Conditional指定的条件成立,才给容器中添加组件,配置配里面的所有内容才生效;

图片

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

我们怎么知道哪些自动配置类生效?

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

#开启springboot的调试类
debug=true

Positive matches:(自动配置类启用的:正匹配)

Negative matches:(没有启动,没有匹配成功的自动配置类:负匹配)

Unconditional classes: (没有条件的类)

7.MVC自动配置原理

7.1、源码解析

拓展MVC配置官方文档:MvcAutoConfiguration


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

进入ContentNegotiatingViewResolve类,找到一个解析视图名字的方法:resolveViewName

@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 var8 = requestedMediaTypes.iterator();

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

在该类中还有个initServletContext方法

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());
    }
    // ...............
}

发现它是在容器中去找视图解析器,因此我们可以自己给容器中去添加一个视图解析器,这个类就会帮我们自动的将它组合进来

7.2、创建一个视图解析器

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方法 加个断点进行调试一下

在这里插入图片描述

  1. 启动项目后访问一个页面查看debugger界面

在这里插入图片描述

7.3、修改SpringBoot的默认配置

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

我们要做的就是编写一个@Configuration注解类,并且类型要为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");
    }
}

原理分析

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

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

@Import({EnableWebMvcConfiguration.class})
@EnableConfigurationProperties({WebMvcProperties.class, WebProperties.class})
@Order(0)
public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer, ServletContextAware {
 	//....   
}

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

这个父类中有这样一段代码:

public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {
    private final WebMvcConfigurerComposite configurers = new WebMvcConfigurerComposite();

    public DelegatingWebMvcConfiguration() {
    }
    
	// 从容器中获取所有的webmvcConfigurer
    @Autowired(
        required = false
    )
    public void setConfigurers(List<WebMvcConfigurer> configurers) {
        if (!CollectionUtils.isEmpty(configurers)) {
            this.configurers.addWebMvcConfigurers(configurers);
        }

    }
}

4、我们可以在这个类中去寻找一个我们刚才设置的viewController当做参考,发现它调用了一个

protected void addViewControllers(ViewControllerRegistry registry) {
    this.configurers.addViewControllers(registry);
}

5、进入addViewControllers方法

public void addViewControllers(ViewControllerRegistry registry) {
    Iterator var2 = this.delegates.iterator();

    while(var2.hasNext()) {
        WebMvcConfigurer delegate = (WebMvcConfigurer)var2.next();
        delegate.addViewControllers(registry);
    }

}

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

全面接管SpringMVC

全面接管即:SpringBoot对SpringMVC的自动配置不需要了,所有都是我们自己去配置!

只需在我们的配置类中要加一个@EnableWebMvc。可以使得springMvc自动配置全部失效

  • 分析源码

对于

@AutoConfiguration(
    after = {DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class, ValidationAutoConfiguration.class}
)
@ConditionalOnWebApplication(
    type = Type.SERVLET
)
@ConditionalOnClass({Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class})
@ConditionalOnMissingBean({WebMvcConfigurationSupport.class})
@AutoConfigureOrder(-2147483638)
@ImportRuntimeHints({WebResourcesRuntimeHints.class})
public class WebMvcAutoConfiguration {
    //....
}
  • 其中有个**@ConditionalOnMissingBean({WebMvcConfigurationSupport.class})**

意思:没有WebMvcConfigurationSupport这个类时WebMvcAutoConfiguration类生效

  • 现在查看**@EnableWebMvc**注解
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
@Import({DelegatingWebMvcConfiguration.class})
public @interface EnableWebMvc {
}
  • 发现有个**@Import({DelegatingWebMvcConfiguration.class})**注解

查看DelegatingWebMvcConfiguration

public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {
    //....
}

发现该类继承了WebMvcConfigurationSupport类

结论:

  • 使用@EnableWebMvc的类标明这个类也是WebMvcConfigurationSupport类,会使得WebMvcAutoConfiguration类失效

8.编写一个自定义启动器

微信笔记https://mp.weixin.qq.com/s/2eB2uT088BvzaqRULezdsw

9.Web开发

9**.1、静态资源映射规则**

SpringBoot中,SpringMVC的web配置都在 WebMvcAutoConfiguration 这个配置类里面;

我们可以去看看 WebMvcAutoConfigurationAdapter 中有很多配置方法;

有一个方法:addResourceHandlers 添加资源处理

public void addResourceHandlers(ResourceHandlerRegistry registry) {
    if (!this.resourceProperties.isAddMappings()) {
        //如果添加了资源文件映射
        //就禁用默认资源处理
        logger.debug("Default resource handling disabled");
    } else {
        //首先判断是不是资源文件是不是classpath:/META-INF/resources/webjars/ 下的,是就添加进来
        this.addResourceHandler(registry, this.mvcProperties.getWebjarsPathPattern(), "classpath:/META-INF/resources/webjars/");
        this.addResourceHandler(registry, this.mvcProperties.getStaticPathPattern(), (registration) -> {
            registration.addResourceLocations(this.resourceProperties.getStaticLocations());
            if (this.servletContext != null) {
                // 静态资源配置
                ServletContextResource resource = new ServletContextResource(this.servletContext, "/");
                registration.addResourceLocations(new Resource[]{resource});
            }

        });
    }
}

读一下源代码:比如所有的 /webjars/ , 都需要去 classpath:/META-INF/resources/webjars/ 找对应的资源;

webjars介绍

Webjars本质就是以jar包的方式引入我们的静态资源 , 我们以前要导入一个静态资源文件,直接导入即可。

使用SpringBoot需要使用Webjars

网站:https://www.webjars.org

例如:

要使用jQuery,我们只要要引入jQuery对应版本的pom依赖即可!

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

查看jquery所在路径:

在这里插入图片描述

测试进行访问,结果成功

在这里插入图片描述

9.2、第二种静态资源映射规则

  • 资源添加源码
public void addResourceHandlers(ResourceHandlerRegistry registry) {
    if (!this.resourceProperties.isAddMappings()) {
        logger.debug("Default resource handling disabled");
    } else {
        this.addResourceHandler(registry, this.mvcProperties.getWebjarsPathPattern(), "classpath:/META-INF/resources/webjars/");
        this.addResourceHandler(registry, this.mvcProperties.getStaticPathPattern(), (registration) -> {
            registration.addResourceLocations(this.resourceProperties.getStaticLocations());
            if (this.servletContext != null) {
                ServletContextResource resource = new ServletContextResource(this.servletContext, "/");
                registration.addResourceLocations(new Resource[]{resource});
            }

        });
    }
}

在addResourceHandlers方法中有个registration.addResourceLocations(this.resourceProperties.getStaticLocations());

进入**getStaticLocations()**方法,在Resource类中

public static class Resources {
    private static final String[] CLASSPATH_RESOURCE_LOCATIONS = new String[]{"classpath:/META-INF/resources/", "classpath:/resources/", "classpath:/static/", "classpath:/public/"};
    private String[] staticLocations;
    private boolean addMappings;
    private boolean customized;
    private final Chain chain;
    private final Cache cache;

    public Resources() {
        //将静态数组赋值给staticLocations数组
        this.staticLocations = CLASSPATH_RESOURCE_LOCATIONS;
        this.addMappings = true;
        this.customized = false;
        this.chain = new Chain();
        this.cache = new Cache();
    }

    public String[] getStaticLocations() {
        return this.staticLocations;
    }
    //....
  • 这个静态字符串的值代表可以放入资源文件的位置
String[] CLASSPATH_RESOURCE_LOCATIONS = 
    //优先级从上到下依次递减
    new String[]{
    "classpath:/META-INF/resources/",
    "classpath:/resources/",
    "classpath:/static/",
    "classpath:/public/"
};

比如:

​ 我们访问 http://localhost:8080/1.js , 它就会去这些文件夹中寻找对应的静态资源文件(优先级从上到下依次递减);

文件位置:

在这里插入图片描述

自定义静态资源路径

在applicaton.properties中配置属性

# 额外增加一个自定义资源路径
spring.web.resources.static-locations=classpath:/test,classpath:/hello

测试:

在这里插入图片描述

访问1.js,结果成功

在这里插入图片描述

9.3、首页处理

查看源码

在WebMvcAutoConfiguration中有个函数接口

@FunctionalInterface
interface WelcomePageHandlerMappingFactory<T extends AbstractUrlHandlerMapping> {
    T create(TemplateAvailabilityProviders templateAvailabilityProviders, ApplicationContext applicationContext, Resource indexHtmlResource, String staticPathPattern);
}

在WelcomePageHandlerMappingFactoryr有个方法getWelcomePage

private Resource getWelcomePage(ResourceLoader resourceLoader, String[] staticLocations) {
    return (Resource)Arrays.stream(staticLocations).map((location) -> {
        return this.getIndexHtml(resourceLoader, location);
    }).filter(this::isReadable).findFirst().orElse((Object)null);
}

发现staticLocations(资源文件位置),其中还调用了getIndexHtml方法

private Resource getIndexHtml(ResourceLoader resourceLoader, String location) {
    return resourceLoader.getResource(location + "index.html");
}

总结:

静态资源文件夹下的所有 index.html 页面;被 /** 映射。

springboot会将资源文件下名字为index.html的文件作为首页

比如我访问 http://localhost:8080/ ,就会找静态资源文件夹下的 index.html

新建一个 index.html ,在我们上面的3个目录中任意一个;然后访问测试 http://localhost:8080/ 看结果!

在这里插入图片描述

注:

  • 如果在资源文件中有多个index.html以资源文件访问顺序决定welcomePage

10.Thymeleaf

10.1、thymeleaf简介

模板引擎,其实jsp就是一个模板引擎,还有用的比较多的freemarker,包括SpringBoot给我们推荐的Thymeleaf,模板引擎有非常多,但再多的模板引擎,他们的思想都是一样的,什么样一个思想呢我们来看一下这张图:

将模板和数据结合后重新渲染成html

在这里插入图片描述

模板引擎的作用就是我们来写一个页面模板,比如有些值呢,是动态的,我们写一些表达式。而这些值,从哪来呢,就是我们在后台封装一些数据。然后把这个模板和这个数据交给我们模板引擎,模板引擎按照我们这个数据帮你把这表达式解析、填充到我们指定的位置,然后把这个数据最终生成一个我们想要的内容给我们写出去,这就是我们这个模板引擎,不管是jsp还是其他模板引擎,都是这个思想。只不过呢,就是说不同模板引擎之间,他们可能这个语法有点不一样。其他的我就不介绍了,我主要来介绍一下SpringBoot给我们推荐的Thymeleaf模板引擎,这模板引擎呢,是一个高级语言的模板引擎,他的这个语法更简单。而且呢,功能更强大。

10.2、thymeleaf使用

导入thymeleaf依赖
<!--thymeleaf-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

org.springframework.boot.autoconfigure.thymeleaf包下有个ThymeleafProperties配置类

@ConfigurationProperties(
    prefix = "spring.thymeleaf"
)
public class ThymeleafProperties {
    private static final Charset DEFAULT_ENCODING;
    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;
    //....
}

发现类似视图解析器前缀和后缀

private String prefix = "classpath:/templates/";
private String suffix = ".html";

发现thymeleaf会在资源目录的templates/目录下寻找html文件,会自动渲染

public static final String DEFAULT_PREFIX = "classpath:/templates/";
public static final String DEFAULT_SUFFIX = ".html";
测试thymeleaf
  1. 编写一个controller

    @Controller
    public class TestController {
        
        @RequestMapping("/hello")
        public String test(){
            
            
            return "test";
        }
    }
    
  2. 在templates目录下创建一个test.html文件

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <body>
      <h1>hello,world</h1>
    
    </body>
    </html>
    
  3. 测试

在这里插入图片描述

10.3、thymeleaf接收数据

接收普通数据
  1. 修改controller

    @Controller
    public class TestController {
    
        @RequestMapping("/hello")
        public String test(Model model){
    
            model.addAttribute("message","测试数据");
            return "test";
        }
    }
    
  2. 我们要使用thymeleaf,需要在html文件中导入命名空间的约束,方便提示。

     xmlns:th="http://www.thymeleaf.org"
    
  3. 修改test.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
  <!--th:text绑定文本-->  
  <div th:text="${message}"></div>

</body>
</html>
  1. 测试

在这里插入图片描述

接收集合或数组
  1. controller

    @Controller
    public class TestController {
    
        @RequestMapping("/hello")
        public String test(Model model){
            //传递一个数组
            model.addAttribute("users", Arrays.asList("java","spring","web"));
            return "test";
        }
    }
    
  2. test.html

    <!DOCTYPE html>
    <html lang="en" xmlns:th="http://www.thymeleaf.org">
        <head>
            <meta charset="UTF-8">
            <title>Title</title>
        </head>
        <body>
    		<!--行内写法-->
            <!--从users数组中遍历-->
            <!--th:each每次遍历都会生成当前这个标签-->
            <div th:each="user:${users}">[[${user}]]</div>
            
            <!--
    		<div th:each="user:${users}" th:text="${user}">
    		</div>
    		-->
    
        </body>
    </html>
    
  3. 测试

在这里插入图片描述

10.4、thymeleaf基本语法

  1. 我们可以使用任意的 th:attr 来替换Html中原生属性的值!

图片

  1. 常用取值

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


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.
==================================================================================


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: _

11.国际化页面设置

配置文件编写

1、我们在resources资源文件下新建一个i18n目录,存放国际化配置文件

2、建立一个login.properties文件,还有一个login_zh_CN.properties;发现IDEA自动识别
在这里插入图片描述

  • login.propertier
login.password=密码
login.remember=记住我
login.signin=登录
login.tip=请登录
login.username=用户名
  • login_en_US.propertier
login.password=password
login.remember=Remember me
login.signin=Sign in
login.tip=Please sign in
login.username=username
  • login_zh_CN.properties
login.password=密码
login.remember=记住我
login.signin=登录
login.tip=请登录
login.username=用户名

页面

<form class="form-signin" th:action="@{/user/login}">
    <img alt="" class="mb-4" height="72" th:src="@{/img/bootstrap-solid.svg}" width="72">
    <!--请登入-->
    <h1 class="h3 mb-3 font-weight-normal" th:text="#{login.tip}"></h1>
    <p style="color:red;" th:text="${msg}" th:if="${! #strings.isEmpty(msg)}"></p>
 	<!--用户名-->
    <label class="sr-only" for="inputEmail" th:text="#{login.username}"></label>

    <input autofocus="" class="form-control" name="userName" id="inputEmail" th:placeholder="#{login.username}" required="" type="text">
	<!--密码-->
    <label class="sr-only" for="inputPassword" th:text="#{login.password}"></label>
    <input class="form-control" id="inputPassword"  name="password" th:placeholder="#{login.password}" required="" type="password">
    <div class="checkbox mb-3">
        <label>
			<!--记住我-->
            <input type="checkbox" value="remember-me"  th:text="#{login.remember}">
        </label>
    </div>
    <!--登录-->
    <button class="btn btn-lg btn-primary btn-block" type="submit">[[#{login.signin}]]</button>
    <a class="btn btn-sm" th:href="@{/(l='zh_CN')}">中文</a>
    <a class="btn btn-sm" th:href="@{/(l='en_US')}">英文</a>
</form>

配置国际化解析

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

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

在WebMvcAutoConfiguration类中

@Bean
@ConditionalOnMissingBean(
    name = {"localeResolver"}
)
public LocaleResolver localeResolver() {
    // 容器中没有就自己配,有的话就用用户配置的
    if (this.webProperties.getLocaleResolver() == org.springframework.boot.autoconfigure.web.WebProperties.LocaleResolver.FIXED) {
        return new FixedLocaleResolver(this.webProperties.getLocale());
    } else if (this.mvcProperties.getLocaleResolver() == org.springframework.boot.autoconfigure.web.servlet.WebMvcProperties.LocaleResolver.FIXED) {
        return new FixedLocaleResolver(this.mvcProperties.getLocale());
    } else {
        // 接收头国际化分解
        AcceptHeaderLocaleResolver localeResolver = new AcceptHeaderLocaleResolver();
        Locale locale = this.webProperties.getLocale() != null ? this.webProperties.getLocale() : this.mvcProperties.getLocale();
        localeResolver.setDefaultLocale(locale);
        return localeResolver;
    }
}

进入AcceptHeaderLocaleResolver类中有个resolveLocale方法

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>

编写一个自己定义的LocaleResolver类

package com.mixcus.config;

import org.springframework.web.servlet.LocaleResolver;
import org.thymeleaf.util.StringUtils;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Locale;

public class MyLocaleResolver implements LocaleResolver {
    @Override
    public Locale resolveLocale(HttpServletRequest httpServletRequest) {

        String language= httpServletRequest.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) {

    }
}

在将其在自定义Mvc配置类中添加bean

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

测试成功,直接将语言转换

12.整合JDBC

12.1、SpringData介绍

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

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

官方文档:Spring Data Commons - Reference Documentation

12.2、整合JDBC

  1. 导入JDBC

添加依赖,可以创建SpringBoot程序时添加,也可以添加maven依赖
在这里插入图片描述

  1. 项目建好之后,发现自动帮我们导入了如下的启动器
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

<dependency>
    <groupId>com.mysql</groupId>
    <artifactId>mysql-connector-j</artifactId>
    <scope>runtime</scope>
</dependency>
  1. 编写yaml配置文件连接数据库
spring:
  datasource:
    username: root
    password: 123456
    # mysql 8.0使用com.mysql.cj.jdbc.Driver 
    driver-class-name: com.mysql.jdbc.Driver
    # mysql 8.0以上需要加上serverTimezone=UTC解决时区的报错
    url: jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=utf-8
  1. 测试连接是否成功
@Autowired
DataSource dataSource;

@Test
void contextLoads() throws SQLException {

    Connection connection = dataSource.getConnection();

    //数据源
    System.out.println(dataSource.getClass());

    System.out.println(connection);

    connection.close();
}
  1. 结果:

class com.zaxxer.hikari.HikariDataSource
HikariProxyConnection@1871084300 wrapping com.mysql.cj.jdbc.ConnectionImpl@21a6a494

12.3、JDBCTemplate

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

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

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

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

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

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

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

测试JDBCTemplate

  1. 创建一个controller

    package com.mixcus.controller;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.jdbc.core.JdbcTemplate;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    import java.util.List;
    import java.util.Map;
    
    
    
    @RestController//让返回结果为json字符串
    public class TestJDBCTemplate {
    
        /**
         * Spring Boot 默认提供了数据源,默认提供了 org.springframework.jdbc.core.JdbcTemplate
         * JdbcTemplate 中会自己注入数据源,用于简化 JDBC操作
         * 还能避免一些常见的错误,使用起来也不用再自己来关闭数据库连接
         */
        @Autowired
        JdbcTemplate jdbcTemplate;
    
        @GetMapping("/userList")
        public List<Map<String,Object>> getUserList(){
    
            String sql = "select * from mybatis.user";
            //查询user表中所有数据
            //List 中的1个 Map 对应数据库的 1行数据
            //Map 中的 key 对应数据库的字段名,value 对应数据库的字段值
    
            List<Map<String, Object>> maps = jdbcTemplate.queryForList(sql);
    
            return maps;
        }
        //新增一个用户
        @GetMapping("/add")
        public String addUser(){
            //插入语句,注意时间问题
            String sql = "insert into user(id, name,password,age)" +
                " values ('10','张三','123456','20')";
            jdbcTemplate.update(sql);
            //查询
            return "addOk";
        }
    
        //修改用户信息
        @GetMapping("/update/{id}")
        public String updateUser(@PathVariable("id") int id){
            //插入语句
            String sql = "update user set name=?,password=? where id="+id;
            //数据
            Object[] objects = new Object[2];
            objects[0] = "tom";
            objects[1] = "321654";
            jdbcTemplate.update(sql,objects);
            //查询
            return "updateOk";
        }
    
        //删除用户
        @GetMapping("/delete/{id}")
        public String delUser(@PathVariable("id") int id){
            //插入语句
            String sql = "delete from user where id=?";
            jdbcTemplate.update(sql,id);
            //查询
            return "deleteOk";
        }
    }
    
  2. 运行测试结果:

在这里插入图片描述

13.整合Druid数据源

13.1、Druid简介

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

Druid 是阿里巴巴开源平台上一个数据库连接池实现,结合了 C3P0、DBCP 等 DB 池的优点,同时加入了日志监控。

Druid 可以很好的监控 DB 池连接和 SQL 的执行情况,天生就是针对监控而生的 DB 连接池。

Druid已经在阿里巴巴部署了超过600个应用,经过一年多生产环境大规模部署的严苛考验。

Spring Boot 2.0 以上默认使用 Hikari 数据源,可以说 Hikari 与 Driud 都是当前 Java Web 上最优秀的数据源,我们来重点介绍 Spring Boot 如何集成 Druid 数据源,如何实现数据库监控。

Github地址:https://github.com/alibaba/druid/

com.alibaba.druid.pool.DruidDataSource 基本配置参数如下:

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

13.2、配置数据源

  1. 添加上 Druid 数据源依赖
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.2.18</version>
</dependency>
  1. 切换数据源;之前已经说过 Spring Boot 2.0 以上默认使用 com.zaxxer.hikari.HikariDataSource 数据源,但可以 通过 spring.datasource.type 指定数据源。
spring:
  datasource:
    username: root
    password: 123456
    driver-class-name: com.mysql.jdbc.Driver
    # ?serverTimezone=UTC解决时区的报错
    url: jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=utf-8&useSSL=true
    # 自定义数据源
    type: com.alibaba.druid.pool.DruidDataSource
  1. 进行测试查看是否切换成功
@Autowired
DataSource dataSource;

@Test
void contextLoads() throws SQLException {

    Connection connection = dataSource.getConnection();

    //数据源
    System.out.println(dataSource.getClass());

    System.out.println(connection);

    connection.close();
}
  1. 结果:

class com.alibaba.druid.pool.DruidDataSource 切换成功
com.mysql.cj.jdbc.ConnectionImpl@1934339

13.3、配置Druid数据源监控

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

所以第一步需要设置 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<>();
    initParams.put("loginUsername", "admin"); //后台管理界面的登录账号
    initParams.put("loginPassword", "123456"); //后台管理界面的登录密码

    //后台允许谁可以访问
    //initParams.put("allow", "localhost"):表示只有本机可以访问
    //initParams.put("allow", ""):为空或者为null时,表示允许所有访问
    initParams.put("allow", "");
    //deny:Druid 后台拒绝谁访问
    //initParams.put("user", "IP地址");表示禁止此ip访问

    //设置初始化参数
    bean.setInitParameters(initParams);
    return bean;
}

访问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;
}

完整:

package com.mixcus.config;

import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.support.jakarta.StatViewServlet;
import com.alibaba.druid.support.jakarta.WebStatFilter;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.sql.DataSource;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

@Configuration
public class MyConfig {

    @ConfigurationProperties(prefix ="spring.datasource")
    @Bean
    public DataSource dataSource(){
        return new DruidDataSource();
    }

    @Bean
    public ServletRegistrationBean statViewServlet(){
        ServletRegistrationBean<StatViewServlet> bean = new ServletRegistrationBean<>(new StatViewServlet(),"/druid/*");

        // 这些参数可以在 com.alibaba.druid.support.http.StatViewServlet的父类
        // com.alibaba.druid.support.http.ResourceServlet 中找到
        Map<String, String> initParams = new HashMap<>();
        initParams.put("loginUsername", "admin"); //后台管理界面的登录账号
        initParams.put("loginPassword", "123456"); //后台管理界面的登录密码

        //后台允许谁可以访问
        //initParams.put("allow", "localhost"):表示只有本机可以访问
        //initParams.put("allow", ""):为空或者为null时,表示允许所有访问
        initParams.put("allow", "");
        //deny:Druid 后台拒绝谁访问
        //initParams.put("用户名", "IP地址");表示禁止此ip访问

        //设置初始化参数
        bean.setInitParameters(initParams);
        return bean;
    }


    //配置 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;
    }

}


14.整合Mybatis

官方文档:http://mybatis.org/spring-boot-starter/mybatis-spring-boot-autoconfigure/

Maven仓库地址:https://mvnrepository.com/artifact/org.mybatis.spring.boot/mybatis-spring-boot-starter/2.1.1

  1. 导入依赖
<!--mybatis-->
<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>3.0.2</version>
</dependency>
  1. 组合其他依赖
<!--druid-->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.2.18</version>
</dependency>

<!--mybatis-->
<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>3.0.2</version>
</dependency>

<!--log4j-->
<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-core</artifactId>
    <version>2.20.0</version>
</dependency>

<!--jdbc-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<!--web-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--mysql-->
<dependency>
    <groupId>com.mysql</groupId>
    <artifactId>mysql-connector-j</artifactId>
    <scope>runtime</scope>
</dependency>

<!--lombok-->
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
</dependency>


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

</dependencies>

<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
        </plugin>
    </plugins>
    <!--资源过滤-->
    <resources>
        <resource>
            <directory>src/main/java</directory>
            <includes>
                <include>**/*.xml</include>
            </includes>
            <filtering>true</filtering>
        </resource>
    </resources>
</build>
  1. 在build标签中加入,解决资源过滤
<!--资源过滤-->
<resources>
    <resource>
        <directory>src/main/java</directory>
        <includes>
            <include>**/*.xml</include>
        </includes>
        <filtering>true</filtering>
    </resource>
</resources>
  1. 创建配置文件application.yml
spring:
  datasource:
    username: root
    password: 123456
    driver-class-name: com.mysql.jdbc.Driver
    # ?serverTimezone=UTC解决时区的报错
    url: jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=utf-8&useSSL=true
    # 自定义数据源
    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-core
    maxPoolPreparedStatementPerConnectionSize: 20
    useGlobalDataSourceStat: true
    connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
  1. 创建pojo
package com.mixcus.pojo;


import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
    private Integer id;
    private String name;
    private String password;
    private Integer age;

}
  1. 创建mapper
package com.mixcus.mapper;

import com.mixcus.pojo.User;
import org.apache.ibatis.annotations.Mapper;

import java.util.List;

//表示本类是一个 MyBatis 的 Mapper
@Mapper
public interface UserMapper {

    //获取用户列表
    List<User> getUserList();

    //添加一个用户
    int addUser(User user);

    //删除一个用户
    int deleteUser(Integer id);

    //修改一个用户
    int updateUser(User user);

}
  1. 创建controller
package com.mixcus.controller;

import com.mixcus.mapper.UserMapper;
import com.mixcus.pojo.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

/**
 * &#064;@BelongsProject: SpringBoot-07-mybatis
 * &#064;@BelongsPackage: com.mixcus.controller
 * &#064;@FileName: UserController
 * &#064;@Author: 86151
 * &#064;@Date: 2023/7/31-20:57
 * &#064;@Version: 1.0
 * &#064;@Description:
 */

@RestController//使用该注解使返回值变成json字符串
public class UserController {

    @Autowired
    private UserMapper userMapper;

    @RequestMapping("/userList")
    public List<User> getUserList(){
		//调用service层处理业务
        List<User> userList = userMapper.getUserList();

        return userList;
    }
}
  1. 在resources目录下创建mybatis/mapper/*.xml

在这里插入图片描述

<?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.mixcus.mapper.UserMapper">

    <select id="getUserList" resultType="user">
        select * from mybatis.user
    </select>

    <update id="updateUser" parameterType="user">
        update mybatis.user
        set name = #{name},password = #{password},age = #{age}
        where id = #{id}
    </update>

</mapper>
  1. 在application.yml配置文件中添加配置mybatis属性
#配置mybatis属性
mybatis:
  # 包取别名
  type-aliases-package: com.mixcus.pojo
  # mapper配置文件位置
  mapper-locations: classpath:mybatis/mapper/*.xml
  1. 进行测试:

在这里插入图片描述

15.SpringSecurity

安全简介

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

15.1、SpringSecurity简介

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

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

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

对于上面提到的两种应用情景,Spring Security 框架都有很好的支持。在用户认证方面,Spring Security 框架支持主流的认证方式,包括 HTTP 基本认证、HTTP 表单验证、HTTP 摘要认证、OpenID 和 LDAP 等。在用户授权方面,Spring Security 提供了基于角色的访问控制和访问控制列表(Access Control List,ACL),可以对应用中的领域对象进行细粒度的控制。

认识SpringSecurity

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

记住几个类:

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

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

“认证”(Authentication)

身份验证是关于验证您的凭据,如用户名/用户ID和密码,以验证您的身份。

身份验证通常通过用户名和密码完成,有时与身份验证因素结合使用。

“授权” (Authorization)

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

这个概念是通用的,而不是只在Spring Security 中存在。

用户认证流程

在这里插入图片描述

Authentication接口: 它的实现类,表示当前访问系统的用户,封装了用户相关信息。

AuthenticationManager接口:定义了认证Authentication的方法

UserDetailsService接口:加载用户特定数据的核心接口。里面定义了一个根据用户名查询用户信息的方法。

UserDetails接口:提供核心用户信息。通过UserDetailsService根据用户名获取处理的用户信息要封装成UserDetails对象返回。然后将这些信息封装到Authentication对象中。****

15.2、SpringSecurity测试

搭建环境

  1. 导入依赖
<!--thymeleaf-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

<!--thymeleaf-security-->
<dependency>
    <groupId>org.thymeleaf.extras</groupId>
    <artifactId>thymeleaf-extras-springsecurity6</artifactId>
</dependency>

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

  1. 导入静态资源,css、html、js
  2. 创建controller
package com.mixcus.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;


@Controller
public class TestSecurity {

    //跳转至主页
    @RequestMapping({"/","/index.html"})
    public String toIndex(){

        return "index";
    }

    //跳转至登入页面
    @RequestMapping("/toLogin")

    public String toLogin(){

        return "/views/login";
    }
}
  1. 运行测试成功

认证和授权

  1. 导入security依赖
<!--security-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>

2、编写 Spring Security 配置类

参考官网:https://spring.io/projects/spring-security

官网文档:Spring Security Kerberos :: Spring Security Kerberos


16.shiro框架

16.1、shiro简介

Shiro是一个强大的简单易用的Java安全框架,主要用来更便捷的认证,授权,加密,会话管理。Shiro首要的和最重要的目标就是容易使用并且容易理解。

Shiro是一个有许多特性的全面的安全框架,下面这幅图可以了解Shiro的特性:

img

16.2、shiro架构

从大的角度来看,Shiro有三个主要的概念:SubjectSecurityManagerRealms,下面这幅图可以看到这些原件之间的交互

img

  • Subject:翻译为主角(当前用户),当前参与应用安全部分的主角。可以是用户,可以试第三方服务,可以是cron 任务,或者任何东西。主要指一个正在与当前软件交互的东西。
    所有Subject都需要SecurityManager,当你与Subject进行交互,这些交互行为实际上被转换为与SecurityManager的交互

  • SecurityManager:安全管理员,Shiro架构的核心,它就像Shiro内部所有原件的保护伞。然而一旦配置了SecurityManager,SecurityManager就用到的比较少,开发者大部分时间都花在Subject上面。
    请记得,当你与Subject进行交互的时候,实际上是SecurityManager在背后帮你举起Subject来做一些安全操作。

  • Realms:Realms作为Shiro和你的应用的连接桥,当需要与安全数据交互的时候,像用户账户,或者访问控制,Shiro就从一个或多个Realms中查找。
    Shiro提供了一些可以直接使用的Realms,如果默认的Realms不能满足你的需求,你也可以定制自己的Realms

16.3、shiro入门

官方快速上手10 Minute Tutorial on Apache Shiro | Apache Shiro

  1. 创建一个maven项目,导入依赖
<!-- https://mvnrepository.com/artifact/org.apache.shiro/shiro-core -->
<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-core</artifactId>
    <version>1.12.0</version>
    </dependency>

    <!-- https://mvnrepository.com/artifact/org.slf4j/slf4j-simple -->

<!-- configure logging -->
    <dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>jcl-over-slf4j</artifactId>
    <version>2.0.7</version>
    <scope>runtime</scope>
    </dependency>
    <dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-slf4j2-impl</artifactId>
    <version>2.20.0</version>
    <scope>runtime</scope>
    </dependency>
    <!-- https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-core -->
<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-core</artifactId>
    <version>2.20.0</version>
    </dependency>
  1. copy官方quickstat类

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.config.IniSecurityManagerFactory;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.Factory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


/**
 * Simple Quickstart application showing how to use Shiro's API.
 *
 * @since 0.9 RC2
 */
public class Quickstart {

    private static final transient Logger log = LoggerFactory.getLogger(Quickstart.class);


    public static void main(String[] args) {
        //工厂模式
        //返回一个工厂
        Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
        //返回一个securityManager实例
        SecurityManager securityManager = factory.getInstance();

        SecurityUtils.setSecurityManager(securityManager);

        // Now that a simple Shiro environment is set up, let's see what you can do:

        //通过工具类获取当前用户
        Subject currentUser = SecurityUtils.getSubject();

        //获得用户的session
        Session session = currentUser.getSession();
        //可以给session设置属性
        session.setAttribute("someKey", "aValue");
        String value = (String) session.getAttribute("someKey");
        if (value.equals("aValue")) {
            log.info("Retrieved the correct value! [" + value + "]");
        }



        //判断用户是否被认证
        if (!currentUser.isAuthenticated()) {

            //没有认证,会将用户信息存入一个token令牌中
            UsernamePasswordToken token = new UsernamePasswordToken("lonestarr", "vespa");

            //开启记住我功能
            token.setRememberMe(true);
            try {
                //登入该用户
                currentUser.login(token);
            } catch (UnknownAccountException uae) {
                //UnknownAccountException 未知账号
                log.info("There is no user with username of " + token.getPrincipal());
            } catch (IncorrectCredentialsException ice) {
                //IncorrectCredentialsException 密码错误
                log.info("Password for account " + token.getPrincipal() + " was incorrect!");
            } catch (LockedAccountException lae) {
                //LockedAccountException 用户被锁定了
                log.info("The account for username " + token.getPrincipal() + " is locked.  " +
                        "Please contact your administrator to unlock it.");
            }
            // ... catch more exceptions here (maybe custom ones specific to your application?
            catch (AuthenticationException ae) {
                //unexpected condition?  error?
            }
        }

        //say who they are:
        //print their identifying principal (in this case, a username):
        log.info("User [" + currentUser.getPrincipal() + "] logged in successfully.");

        //测试用户拥有指定的角色(权限)
        if (currentUser.hasRole("schwartz")) {
            log.info("May the Schwartz be with you!");
        } else {
            log.info("Hello, mere mortal.");
        }

        //测试用户是否拥有某个权限
        if (currentUser.isPermitted("lightsaber:wield")) {
            log.info("You may use a lightsaber ring.  Use it wisely.");
        } else {
            log.info("Sorry, lightsaber rings are for schwartz masters only.");
        }

        //a (very powerful) Instance Level permission:
        if (currentUser.isPermitted("winnebago:drive:eagle5")) {
            log.info("You are permitted to 'drive' the winnebago with license plate (id) 'eagle5'.  " +
                    "Here are the keys - have fun!");
        } else {
            log.info("Sorry, you aren't allowed to drive the 'eagle5' winnebago!");
        }

        //退出
        currentUser.logout();

        System.exit(0);
    }
}

16.4、shiro整合SpringBoot

springBoot版本选择2.7.2

在之前项目中创建一个springboot项目

  1. 导入初步依赖
<!--thymeleaf-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!--web-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--shiro-spring-->
<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-spring</artifactId>
    <version>1.12.0</version>
</dependency>

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
</dependency>
  1. 编写页面

index.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
  <meta charset="UTF-8">
  <title>首页</title>
</head>
<body>
  <div>
    <h1>首页</h1>
    <p th:text="${msg}"></p>

    <hr>
    <a th:href="@{/user/add}">add</a>   | <a th:href="@{/user/update}">update</a>
  </div>
</body>
</html>

add.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <h1>add</h1>
</body>
</html>

update.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <h1>update</h1>
</body>
</html>
  1. 编写一个config类
package com.mixcus.config;


import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.LinkedHashMap;
import java.util.Map;


@Configuration
public class ShiroConfig {


    //3. shiroFilterFactoryBean
    @Bean
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("getDefaultWebSecurityManager") DefaultWebSecurityManager defaultWebSecurityManager) {
        ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
        // 设置安全管理器
        bean.setSecurityManager(defaultWebSecurityManager);

        /*
        anon: 无需认证就可以访问
        authc: 必须认证了才能访问
        user: 必须拥有记住我功能才能用
        perms: 拥有对某个资源的权限才能访问
        role: 拥有某个角色权限
        */

        Map<String, String> filterMap = new LinkedHashMap<>();

        //必须认证才能访问
        filterMap.put("/user/add","authc");

        filterMap.put("/user/update","authc");

        bean.setFilterChainDefinitionMap(filterMap);

        return bean;
    }

    //2. DefaultWebSecurityManager
    @Bean
    public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm) {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();

        // 关联userRealm
        securityManager.setRealm(userRealm);
        return securityManager;
    }
    
    //1. 创建realm对象,需要自定义类
    @Bean
    public UserRealm userRealm() {
        return new UserRealm();
    }

}
  1. 创建Realm对象时需要创建一个自定义的AuthorizingRealm类
package com.mixcus.config;

import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;

public class UserRealm extends AuthorizingRealm {

    //授权
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        System.out.println("debugger:执行了授权===>");

        return null;
    }

    //认证
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        System.out.println("debugger:执行了认证===>");

        return null;
    }
}
  1. 此时进行测试

在这里插入图片描述

点击add或update出现

在这里插入图片描述

说明该请求已被拦截

  1. 添加被拦截后跳转的login.html
  • 创建一个login.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <form action="">
        <div>
            <span>账号:</span>
            <input type="text" name="username">
        </div>
        <div>
            <span>密码:</span>
            <input type="password" name="password">
        </div>
        <div>
            <input type="submit" value="提交">
        </div>
    </form>
</body>
</html>
  • 在UserController中添加
@RequestMapping("/login")
public String toLogin(){

    return "login";
}
  • ShiroConfig中的getShiroFilterFactoryBean方法中添加如下配置
//设置登入请求
bean.setLoginUrl("/login");
  1. 此时再次运行程序,点击add或update出现登入页面,表明设置成功

在这里插入图片描述

16.5、shiro实现用户认证

  1. 修改login.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <!--提示信息-->
    <p th:text="${msg}" style="color: red;"></p>
    <form th:action="@{/login}">

        <div>
            <span>账号:</span>
            <input type="text" name="username">
        </div>
        <div>
            <span>密码:</span>
            <input type="password" name="password">
        </div>
        <div>
            <input type="submit" value="提交">
        </div>

    </form>

</body>
</html>
  1. 在UserController中添加表单提交的处理
//添加登入表单验证
@RequestMapping("/login")
public String login(String username,String password,Model model){

    //获取当前用户
    Subject subject = SecurityUtils.getSubject();
    //封装当前用户的数据,使用token令牌
    UsernamePasswordToken token = new UsernamePasswordToken(username,password);
    
    //处理登入
    try{
        //登入该用户
        subject.login(token);
        //账号和密码正确
        return "index";

    }catch (UnknownAccountException uae){
        //UnknownAccountException 用户名不存在
        model.addAttribute("msg","账号错误!");
        return "login";
    }catch (IncorrectCredentialsException ice){
        model.addAttribute("msg","密码错误!");
        return "login";
    }
}
  1. 运行程序点击登入按钮,填写表单后

在这里插入图片描述

  1. 编写UserRealm中的认证(doGetAuthenticationInfo)实现用户认证
//认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken)throws AuthenticationException {
    System.out.println("执行了=>认证doGetAuthorizationInfo");
    String username = "root";
    String password = "123456";

    //将封装好的认证token转换为用户密码token
    UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;

    //将认证token的信息与账号进行判断
    if(!token.getUsername().equals(username)){
        //抛出异常 UnknownAccountException
        return null;
    }

    //密码验证shiro去执行
    return new SimpleAuthenticationInfo("",password,"");
}
  1. 进行测试

16.6、shiro整合mybatis

  1. 导入依赖
<dependencies>
    <!--thymeleaf-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-thymeleaf</artifactId>
    </dependency>
    <!--web-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <!--shiro-spring-->
    <dependency>
        <groupId>org.apache.shiro</groupId>
        <artifactId>shiro-spring</artifactId>
        <version>1.12.0</version>
    </dependency>
    <!--mybatis官网而非spring的-->
    <dependency>
        <groupId>org.mybatis.spring.boot</groupId>
        <artifactId>mybatis-spring-boot-starter</artifactId>
        <version>2.3.1</version>
    </dependency>
    <!--druid-->
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid</artifactId>
        <version>1.2.18</version>
    </dependency>
    <!--lombok-->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
    </dependency>
    <!--log4j-->
    <dependency>
        <groupId>org.apache.logging.log4j</groupId>
        <artifactId>log4j-core</artifactId>
        <version>2.20.0</version>
    </dependency>

    <!--mysql-->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>
  1. 编写配置文件

application.yml

# druid 数据源
spring:
  datasource:
  	# 连接数据库
    username: root
    password: admin
    #?serverTimezone=UTC解决时区的报错
    url: jdbc:mysql://localhost:3306/mybatis?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-core
    maxPoolPreparedStatementPerConnectionSize: 20
    useGlobalDataSourceStat: true
    connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500

  1. 创建实体类
package com.mixcus.pojo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;


@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {

    private Integer id;
    private String name;
    private String password;
}
  1. 创建UserMapper和在resources下创建mapper包在创建UserMapper.xml
package com.mixcus.mapper;

import com.mixcus.pojo.User;
import org.apache.ibatis.annotations.Mapper;
import org.springframework.stereotype.Repository;

@Mapper
@Repository
public interface UserMapper {

    public User getUserByName(String username);
}

UserMapper.xml

<?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">
<!--namespace=绑定一个对应的Dao/Mapper接口-->
<mapper namespace="com.mixcus.mapper.UserMapper">

    <select id="getUserByName" parameterType="String" resultType="user">
        select * from mybatis.user
        where name = #{username}
    </select>

</mapper>

  1. 创建UserService和UserServiceImpl

UserService

package com.mixcus.service;

import com.mixcus.pojo.User;


public interface UserService {
    public User getUserByName(String username);
}

UserServiceImpl

package com.mixcus.service;

import com.mixcus.mapper.UserMapper;
import com.mixcus.pojo.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class UserServiceImpl implements UserService{

    @Autowired
    private UserMapper userMapper;

    @Override
    public User getUserByName(String username) {
        return userMapper.getUserByName(username);
    }
}
  1. 测试连接数据库成功

在这里插入图片描述

  1. 修改认证的代码,使用真实的数据库的数据
//认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken)throws AuthenticationException {
    System.out.println("执行了=>认证doGetAuthorizationInfo");

    //将封装好的认证token转换为用户密码token
    UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;

    //通过用户名获取用户
    User user = userService.getUserByName(token.getUsername());

    if(user==null){
        //用户不存在
        return null;
    }
    //密码验证shiro去执行

    return new SimpleAuthenticationInfo("",user.getPassword(),"");
}
  1. 在进行用户登入测试

数据库:

在这里插入图片描述

符合条件登入成功:

在这里插入图片描述

16.7、shiro实现用户授权

  1. 在ShiroFilterFactoryBean getShiroFilterFactoryBean方法中

在设置权限拦截时加上自定义权限

// 只有有"user:add"这个权限的用户才能访问/user/add该路径
filterMap.put("/user/add","perms[user:add]");
filterMap.put("/user/update","perms[user:update");
  1. 此时启动程序点击add或update显示:

在这里插入图片描述

  1. 解决未认证出现的错误页面,可以自定义跳转至某个页面
  • 在ShiroConfig的ShiroFilterFactoryBean getShiroFilterFactoryBean()方法中添加
//设置未授权跳转的未授权显示界面
bean.setUnauthorizedUrl("/unAuthorize");//请求路径

//将对象封装到
return new SimpleAuthenticationInfo(user,user.getPassword(),"");
  • 同时在controller中添加请求跳转
//设置未授权跳转界面
@RequestMapping("/unAuthorize")
@ResponseBody
public String unAuthorize(){
    return "没有权限进入该界面!";
}
  1. 进行测试

    在这里插入图片描述

发现同时在控制台出现下面信息,表示进入了授权方法

执行了=>授权doGetAuthorizationInfo


principalCollection.getPrimaryPrincipal() 是 Shiro 框架中的一个方法,用于获取主要的身份认证对象。

在 Shiro 中,PrincipalCollection 是一个身份信息的集合,可以包含多个身份认证对象。每个身份认证对象都被称为 “Principal”(主要身份)。而 getPrimaryPrincipal() 方法则返回这个集合中的主要身份,即第一个身份认证对象。

通常情况下,PrincipalCollection 中的主要身份是通过用户名、邮箱或其他唯一标识来表示用户身份。当应用程序需要获取当前登录用户的身份信息时,可以使用 principalCollection.getPrimaryPrincipal() 方法来获取主要身份对象,并进一步对其进行操作或获取相关信息。

  1. 此时对AuthorizationInfo doGetAuthorizationInfo()方法添加授权处理
  • 在数据库添加一个权限字段authorize
//授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {

    System.out.println("执行了=>授权doGetAuthorizationInfo");

    SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();

    /*
    *	//AuthenticationInfo doGetAuthenticationInfo()方法中
	*	return new SimpleAuthenticationInfo(user,user.getPassword(),"");
    *	通过getPrimaryPrincipal()获取认证中传递过来的用户对象
    *	
    */
    User currentUser = (User) principalCollection.getPrimaryPrincipal();

    //设置当前用户的权限
    info.addStringPermission(currentUser.getAuthority());

    return info;
}
  1. 此时在进行测试

数据库:
在这里插入图片描述

登入root,测试点击add和update

add成功访问:

在这里插入图片描述

update跳转至未授权:

在这里插入图片描述

16.8、shiro整合Thymeleaf

  1. 导入依赖:
<dependency>
    <groupId>com.github.theborakompanioni</groupId>
    <artifactId>thymeleaf-extras-shiro</artifactId>
    <version>2.1.0</version>
</dependency>
  1. 需要使用ShrioDialect整合shiro,在ShirotConfig中添加
// 整合ShiroDialect: 用来整合 Shiro thymeleaf
@Bean
public ShiroDialect getShiroDialect(){
    return new ShiroDialect();
}
  1. 修改index.html
  • 添加shiro约束
xmlns:shiro="http://www.pollix.at/thymeleaf/shiro"

index.html

<!DOCTYPE html>
<html lang="en"
      xmlns:th="http://www.thymeleaf.org"
      xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">
<head>
  <meta charset="UTF-8">
  <title>首页</title>
</head>
<body>
  <div>
    <h1>首页</h1>
    <p th:text="${msg}"></p>
    <hr>
    <!--没有认证时显示登入-->
    <div shiro:notAuthenticated>
      <a th:href="@{/toLogin}">登入</a>
    </div>

    <!--有add权限时显示add链接-->
    <div shiro:hasPermission="user:add">
      <a th:href="@{/user/add}">add</a>
    </div>

    <!--有update权限时显示update链接-->
    <div shiro:hasPermission="user:update">
      <a th:href="@{/user/update}">update</a>

    </div>

  </div>
</body>
</html>
  1. 测试:
  • 未登入时:

    在这里插入图片描述

  • 登入root(有add权限)用户后:

在这里插入图片描述

  • 登入mixcus(有update权限)用户

在这里插入图片描述

  • 登入一个没有任何权限的用户

在这里插入图片描述

17.Swagger

17.1、Swagger简介

前后端分离

  • 前端 -> 前端控制层、视图层
  • 后端 -> 后端控制层、服务层、数据访问层
  • 前后端通过API进行交互
  • 前后端相对独立且松耦合

产生的问题

  • 前后端集成,前端或者后端无法做到“及时协商,尽早解决”,最终导致问题集中爆发

解决方案

  • 首先定义schema [ 计划的提纲 ],并实时跟踪最新的API,降低集成风险

Swagger

  • 号称世界上最流行的API框架
  • Restful Api 文档在线自动生成器 => API 文档 与API 定义同步更新
  • 直接运行,在线测试API
  • 支持多种语言 (如:Java,PHP等)
  • 官网:https://swagger.io/

17.2、SpringBoot集成Swagger

SpringBoot集成Swagger => springfox,两个jar包

  • Springfox-swagger2
  • swagger-springmvc
  1. 导入依赖

    <!--springfox-swagger2 -->
    <dependency>
        <groupId>io.springfox</groupId>
        <artifactId>springfox-swagger2</artifactId>
        <version>3.0.0</version>
    </dependency>
    
    <!-- springfox-swagger-ui -->
    <dependency>
        <groupId>io.springfox</groupId>
        <artifactId>springfox-swagger-ui</artifactId>
        <version>3.0.0</version>
    </dependency>
    ----------------------------------------------------------------------------------
    
    <!--swagger3整合springboot-->
    <dependency>
        <groupId>io.springfox</groupId>
        <artifactId>springfox-boot-starter</artifactId>
        <version>3.0.0</version>
    </dependency>
    
  2. 编写一个controller确保环境搭建成功

  3. 编写SwaggerConfig‘

@Configuration//配置类
@EnableSwagger2//开启swagger2
public class SwaggerConfig {

    
}

swagger3.0在application.properties中添加下面内容解决报错问题

spring.mvc.pathmatch.matching-strategy=ant_path_matcher
  1. 访问http://localhost:8080/swagger-ui/index.html可以看到swagger界面

在这里插入图片描述

17.3、配置Swagger

  1. 由于不是springboot官方的,要自己创建一个配置类
package com.mixcus.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Contact;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

import java.util.ArrayList;

@Configuration
@EnableSwagger2//开启swagger2功能
public class SwaggerConfig {

    //Docket是一个用于配置Swagger的构建器
    @Bean
    public Docket docket(){
        return new Docket(DocumentationType.SWAGGER_2)
            //关联apiInfo()
            .apiInfo(apiInfo());
    }

    //创建一个apiInfo
    public ApiInfo apiInfo(){
        Contact contact = new Contact("百度", "http://baidu.com", "12346549@qq.com");
        return new ApiInfoBuilder()
            .title("learn swagger")//标题
            .description("这是一个swagger文档")//描述
            .version("v1.0")//版本
            .license("Apache 2.0")//许可
            .licenseUrl("")//许可链接
            .contact(contact)
            .extensions(new ArrayList<>())
            .build();
    }
}
  1. 访问http://localhost:8080/swagger-ui/index.html进行测试

在这里插入图片描述

17.4、配置扫描接口

  1. 让Swagger-ui界面显示我们指定的接口

修改Docket

@Bean
public Docket docket(){
    return new Docket(DocumentationType.SWAGGER_2)
        .apiInfo(apiInfo())
        .select()
        // 通过.select()方法,去配置扫描接口,RequestHandlerSelectors配置扫描指定包
        .apis(RequestHandlerSelectors.basePackage("com.mixcus.controller"))
        .build();
}
  1. 重启访问http://localhost:8080/swagger-ui/index.html进行测试

.apis(RequestHandlerSelectors.basePackage(“com.mixcus.controller”))

其扫描了com.mixcus.controller,该包(接口)下只有一个controller,页面只显示一个controller
在这里插入图片描述

  1. 除了通过包路径配置扫描接口外,还可以通过配置其他方式扫描接口,这里注释一下所有的配置方式

在这里插入图片描述

any() // 任何请求都扫描
none() // 任何请求都不扫描
regex(final String pathRegex) // 通过正则表达式控制
ant(final String antPattern) // 通过ant()控制
  • 实现接口下扫描指定的路径
@Bean
public Docket docket(){
    return new Docket(DocumentationType.SWAGGER_2)
        .apiInfo(apiInfo())
        .select()
        // 通过.select()方法,去配置扫描接口,RequestHandlerSelectors配置扫描指定包
        .apis(RequestHandlerSelectors.basePackage("com.mixcus.controller"))
        //指定扫描/user/**下的所有接口
        .path(PathSelectors.ant("/user/**"))
        .build();
}

17.5、配置Swagger开关

  1. 通过enable()方法配置是否启用swagger,如果是false,swagger将不能在浏览器中访问了
@Bean
public Docket docket(){
    return new Docket(DocumentationType.SWAGGER_2)
        .apiInfo(apiInfo())
        .select()
        //配置是否启用Swagger,如果是false,在浏览器将无法访问
        .enabled(false)
        // 通过.select()方法,去配置扫描接口,RequestHandlerSelectors配置扫描指定包
        .apis(RequestHandlerSelectors.basePackage("com.mixcus.controller"))
        //指定扫描/user/**下的所有接口
        .path(PathSelectors.ant("/user/**"))
        .build();
}
  1. 动态配置当项目处于test、dev环境时显示swagger,处于prod时不显示

在主配置文件中添加

spring.profiles.active=dev

或则在配置类上添加==@Profile(“dev”)注解==

将其应用于整个类。这意味着只有当激活了指定的配置文件时,该类中的所有组件才会被注册和使用

17.6、配置API分组

在这里插入图片描述

  1. 如果没有配置分组,默认是default。在docket中通过groupName()方法即可配置分组:
@Bean
public Docket docket(Environment environment){

    //设置要显示swagger的环境
    Profiles of = Profiles.of("dev");

    // 判断当前是否处于该环境
    // 通过 enable() 接收此参数判断是否要显示
    boolean flag = environment.acceptsProfiles(of);

    return new Docket(DocumentationType.SWAGGER_2)
        .apiInfo(apiInfo())
        .enable(flag)//通过环境判断swagger是否显示
        .select()
        // 通过.select()方法,去配置扫描接口,RequestHandlerSelectors配置扫描指定包
        .apis(RequestHandlerSelectors.basePackage("com.mixcus.controller"))
        .paths(PathSelectors.ant("/hello"))
        .build()
        .groupName("hello");
}
  1. 测试查看:

在这里插入图片描述

创建多个分组,即创建多个Docket,不同的docket显示不用的文档内容,对于一个ApiInfo

  1. 创建多个controller

TestController

@RestController
public class TestController {

    @GetMapping(value = "/test")
    public String test(){
        return "test页面";
    }

}

UserController

@RestController
public class UserController {

    @GetMapping(value = "/user/test")
    public String test(){
        return "UserController页面";
    }
}
  1. 创建docket
//Docket是一个用于配置Swagger的构建器
@Bean
public Docket helloDocket(){

    return new Docket(DocumentationType.SWAGGER_2)
        .apiInfo(helloApiInfo())
        .select()
        // 通过.select()方法,去配置扫描接口,RequestHandlerSelectors配置扫描指定包
        .apis(RequestHandlerSelectors.basePackage("com.mixcus.controller"))
        .build()
        .groupName("hello");
}
@Bean
public Docket TestDocket(){

    return new Docket(DocumentationType.SWAGGER_2)
        .apiInfo(testApiInfo())
        .select()
        // 通过.select()方法,去配置扫描接口,RequestHandlerSelectors配置扫描指定包
        .apis(RequestHandlerSelectors.basePackage("com.mixcus.controller.TestController"))
        .build()
        .groupName("test");
}

@Bean
public Docket UserDocket(){

    return new Docket(DocumentationType.SWAGGER_2)
        .apiInfo(userApiInfo())
        .select()
        // 通过.select()方法,去配置扫描接口,RequestHandlerSelectors配置扫描指定包
        .apis(RequestHandlerSelectors.basePackage("com.mixcus.controller.UserController"))
        .build()
        .groupName("user");
}
  1. 编写对于的apiInfo
//创建一个apiInfo
public ApiInfo helloApiInfo(){
    Contact contact = new Contact("百度", "http://baidu.com", "12346549@qq.com");
    return new ApiInfoBuilder()
        .title("hello swagger")//标题
        .description("这是一个swagger文档")//描述
        .version("v1.0")//版本
        .license("Apache 2.0")//许可
        .licenseUrl("")//许可链接
        .contact(contact)
        .extensions(new ArrayList<>())
        .build();
}

public ApiInfo testApiInfo(){
    Contact contact = new Contact("百度", "http://baidu.com", "12346549@qq.com");
    return new ApiInfoBuilder()
        .title("test swagger")//标题
        .description("这是一个swagger文档")//描述
        .version("v1.0")//版本
        .license("Apache 2.0")//许可
        .licenseUrl("")//许可链接
        .contact(contact)
        .extensions(new ArrayList<>())
        .build();
}

public ApiInfo userApiInfo(){
    Contact contact = new Contact("百度", "http://baidu.com", "12346549@qq.com");
    return new ApiInfoBuilder()
        .title("user swagger")//标题
        .description("这是一个swagger文档")//描述
        .version("v1.0")//版本
        .license("Apache 2.0")//许可
        .licenseUrl("")//许可链接
        .contact(contact)
        .extensions(new ArrayList<>())
        .build();
}
  1. 测试

在这里插入图片描述

选择不用的分组跳转至不用的页面

17.7、model配置

  1. 编写一个实体类
@Data
@AllArgsConstructor
@NoArgsConstructor
@ApiModel("用户")// swagger文档上model注释
public class User {

    @ApiModelProperty("密码")// swagger文档上model注释
    private String name;
    
    @ApiModelProperty("年龄")// swagger文档上model注释
    private Integer age;

}
  1. 在controller中添加请求
@GetMapping(value = "/getUser")
public User getUser(){
    return new User("张三",10);
}
  1. 测试

在这里插入图片描述

注:并不是因为@ApiModel这个注解让实体显示在这里了,而是只要出现在接口方法的返回值上的实体都会显示在这里,而@ApiModel和@ApiModelProperty这两个注解只是为实体添加注释的。

@ApiModel为类添加注释

@ApiModelProperty为类属性添加注释

17.8、常用注解

Swagger的所有注解定义在io.swagger.annotations包下

下面列一些经常用到的,未列举出来的可以另行查阅说明:

Swagger注解简单说明
@Api(tags = “xxx模块说明”)作用在模块类上
@ApiOperation(“xxx接口说明”)作用在接口方法上
@ApiModel(“xxxPOJO说明”)作用在模型类上:如VO、BO
@ApiModelProperty(value = “xxx属性说明”,hidden = true)作用在类方法和属性上,hidden设置为true可以隐藏该属性
@ApiParam(“xxx参数说明”)作用在参数、方法和字段上,类似@ApiModelProperty

我们也可以给请求的接口配置一些注释

@ApiOperation("接口名")
@PostMapping("/test")
@ResponseBody
public String test(@ApiParam("这个名字会被返回")String username){
   return username;
}

这样的话,可以给一些比较难理解的属性或者接口,增加一些配置信息,让人更容易阅读!

相较于传统的Postman或Curl方式测试接口,使用swagger简直就是傻瓜式操作,不需要额外说明文档(写得好本身就是文档)而且更不容易出错,只需要录入数据然后点击Execute,如果再配合自动化框架,可以说基本就不需要人为操作了。

Swagger是个优秀的工具,现在国内已经有很多的中小型互联网公司都在使用它,相较于传统的要先出Word接口文档再测试的方式,显然这样也更符合现在的快速迭代开发行情。当然了,提醒下大家在正式环境要记得关闭Swagger,一来出于安全考虑二来也可以节省运行时内存。

18.任务

18.1、异步任务

  1. 创建一个service包

  2. 创建一个类Service

异步处理还是非常常用的,比如我们在网站上发送邮件,后台会去发送邮件,此时前台会造成响应不动,直到邮件发送完毕,响应才会成功,所以我们一般会采用多线程的方式去处理这些任务。

编写方法,假装正在处理数据,使用线程设置一些延时,模拟同步等待的情况;

@Service
public class HelloService {
    
    public void hello(){

        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }

        System.out.println("hello,world");
    }
}
  1. 编写controller
@Controller
public class TestController {

    @Autowired
    HelloService helloService;

    @ResponseBody
    @RequestMapping("/test")
    public String test(){

        helloService.hello();

        return "发送中~";
    }
}
  1. 进行测试,发现经过3秒后控制台才输出 hello,world

问题:我们如果想让用户直接得到消息,就在后台使用多线程的方式进行处理即可,但是每次都需要自己手动去编写多线程的实现的话,太麻烦了,我们只需要用一个简单的办法,在我们的方法上加一个简单的注解即可,如下:‘

  1. 给方法添加注解
@Service
public class HelloService {

    @Async//告诉spring这是一个异步方法
    public void hello(){

        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }

        System.out.println("hello,world");
    }
}
  1. 要让这个注解生效,我们还需要在主程序上添加一个注解@EnableAsync ,开启异步注解功能
package com.mixcus;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableAsync;

@EnableAsync//开启异步注解功能
@SpringBootApplication
public class SpringBoot10TaskApplication {

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

}
  1. 再次进行测试,发现页面内容先出现,过三秒后控制台才输出hello,wrold

异步完成了任务

18.2、定时任务

项目开发中经常需要执行一些定时任务,比如需要在每天凌晨的时候,分析一次前一天的日志信息,Spring为我们提供了异步执行任务调度的方式,提供了两个接口。

  • TaskExecutor接口
  • TaskScheduler接口

两个注解:

  • @EnableScheduling
  • @Scheduled

定时需要使用cron表达式

  • CRON表达式格式

CRON表达式的格式是一个字符串,共有六个字段,分别代表秒、分、小时、日期、月份、星期。每个字段都可以使用通配符、范围、逗号分隔的列表以及"/"取模符号等来表示时间规则。CRON表达式包含了六个部分,分别表示任务执行的时间点。这些部分被空格分隔开,依次表示:

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

  • 测试定时任务
  1. 创建一个ScheduledService
@Service
public class ScheduledService {
    
    //秒   分   时     日   月   周几
    //0 * * * * MON-FRI
    //注意cron表达式的用法;
    @Scheduled(cron = "0 * * * * 0-7")//每个月的每一周的每一天每隔一秒执行
    public void hello(){
        System.out.println("hello,world");
    }
}
  1. 在主程序上加上@EnableScheduling 开启基于注解的定时任务
@EnableAsync//开启异步注解功能
@EnableScheduling//开启基于注解的定时任务
@SpringBootApplication
public class SpringBoot10TaskApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringBoot10TaskApplication.class, args);
    }
}
  1. 启动程序测试,发现控制台会每隔一秒打印一个hello,world

  2. cron表达式生成器:

http://www.bejson.com/othertools/cron/

  1. 常用的表达式
(1)0/2 * * * * ?   表示每2秒 执行任务
(1)0 0/2 * * * ?   表示每2分钟 执行任务
(1)0 0 2 1 * ?   表示在每月的1日的凌晨2点调整任务
(2)0 15 10 ? * MON-FRI   表示周一到周五每天上午10:15执行作业
(3)0 15 10 ? 6L 2002-2006   表示2002-2006年的每个月的最后一个星期五上午10:15执行作
(4)0 0 10,14,16 * * ?   每天上午10点,下午2点,4点
(5)0 0/30 9-17 * * ?   朝九晚五工作时间内每半小时
(6)0 0 12 ? * WED   表示每个星期三中午12点
(7)0 0 12 * * ?   每天中午12点触发
(8)0 15 10 ? * *   每天上午10:15触发
(9)0 15 10 * * ?     每天上午10:15触发
(10)0 15 10 * * ?   每天上午10:15触发
(11)0 15 10 * * ? 2005   2005年的每天上午10:15触发
(12)0 * 14 * * ?     在每天下午2点到下午2:59期间的每1分钟触发
(13)0 0/5 14 * * ?   在每天下午2点到下午2:55期间的每5分钟触发
(14)0 0/5 14,18 * * ?     在每天下午2点到2:55期间和下午6点到6:55期间的每5分钟触发
(15)0 0-5 14 * * ?   在每天下午2点到下午2:05期间的每1分钟触发
(16)0 10,44 14 ? 3 WED   每年三月的星期三的下午2:10和2:44触发
(17)0 15 10 ? * MON-FRI   周一至周五的上午10:15触发
(18)0 15 10 15 * ?   每月15日上午10:15触发
(19)0 15 10 L * ?   每月最后一日的上午10:15触发
(20)0 15 10 ? * 6L   每月的最后一个星期五上午10:15触发
(21)0 15 10 ? * 6L 2002-2005   2002年至2005年的每月的最后一个星期五上午10:15触发
(22)0 15 10 ? * 6#3   每月的第三个星期五上午10:15触发

18.3、邮件任务

邮件发送,在我们的日常开发中,也非常的多,Springboot也帮我们做了支持

  • 邮件发送需要引入spring-boot-start-mail
  • SpringBoot 自动配置MailSenderAutoConfiguration
  • 定义MailProperties内容,配置在application.yml中
  • 自动装配JavaMailSender
  • 测试邮件发送
  1. 导入依赖
<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-mail</artifactId>
</dependency>

其引入了jakarta.mail jar包

<dependency>
   <groupId>com.sun.mail</groupId>
   <artifactId>jakarta.mail</artifactId>
   <version>1.6.4</version>
   <scope>compile</scope>
</dependency>
  1. 查看MailSenderAutoConfiguration类

在这里插入图片描述

  1. 进入该类MailSenderJndiConfiguration

在这里插入图片描述

  1. 进入MailPropertier类中

其属性为我们需要在配置文件中配置的内容

@ConfigurationProperties(
    prefix = "spring.mail"
)
public class MailProperties {
    private static final Charset DEFAULT_CHARSET;
    private String host;
    private Integer port;
    private String username;
    private String password;
    private String protocol = "smtp";
    private Charset defaultEncoding;
    private final Map<String, String> properties;
    private String jndiName;
}
  1. 需要在QQ邮箱中开启发送功能

在这里插入图片描述

  1. 配置文件
spring.mail.username=发送人QQ邮箱
# qq授权码
spring.mail.password=发送人的qq授权码
spring.mail.host=smtp.qq.com
# qq需要配置ssl
spring.mail.properties.mail.smtp.ssl.enable=true
  1. 编写一个发送邮件的类
@Autowired
JavaMailSenderImpl javaMailSender;

//简单发送
@Test
void contextLoads() {

    SimpleMailMessage message = new SimpleMailMessage();
    //标题
    message.setSubject("测试邮件发送");
    //内容
    message.setText("测试");

    //收件人
    message.setTo("1870638169@qq.com");
    //发件人
    message.setFrom("1870638169@qq.com");

    javaMailSender.send(message);
}

//发送带有附件的邮件
@Test
public void contextLoads2() throws MessagingException {
    //邮件设置2:一个复杂的邮件

    MimeMessage mimeMessage = javaMailSender.createMimeMessage();
    MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, true);

    helper.setSubject("test");
    helper.setText("<b style='color:red'>java!启动!</b>",true);

    //发送附件
    helper.addAttachment("1.jpg",new File("C:\\Users\\86151\\Desktop\\音乐与图片\\图片\\spring up.jpg"));
    helper.addAttachment("2.jpg",new File("C:\\Users\\86151\\Desktop\\音乐与图片\\图片\\spring up.jpg"));

    //收件人
    helper.setTo("1870638169@qq.com");
	//发件人
    helper.setFrom("1870638169@qq.com");

    javaMailSender.send(mimeMessage);
}

19.分布式

19.1、分布式系统介绍

在《分布式系统原理与范型》一书中有如下定义:“分布式系统是若干独立计算机的集合,这些计算机对于用户来说就像单个相关系统”;

分布式系统是由一组通过网络进行通信、为了完成共同的任务而协调工作的计算机节点组成的系统。分布式系统的出现是为了用廉价的、普通的机器完成单个计算机无法完成的计算、存储任务。其目的是利用更多的机器,处理更多的数据

分布式系统(distributed system)是建立在网络之上的软件系统。

首先需要明确的是,只有当单个节点的处理能力无法满足日益增长的计算、存储任务的时候,且硬件的提升(加内存、加磁盘、使用更好的CPU)高昂到得不偿失的时候,应用程序也不能进一步优化的时候,我们才需要考虑分布式系统。因为,分布式系统要解决的问题本身就是和单机系统一样的,而由于分布式系统多节点、通过网络通信的拓扑结构,会引入很多单机系统没有的问题,为了解决这些问题又会引入更多的机制、协议,带来更多的问题。。。

dubbo文档

Dubbo3 简介 | Apache Dubbo

随着互联网的发展,网站应用的规模不断扩大,常规的垂直应用架构已无法应对,分布式服务架构以及流动计算架构势在必行,急需一个治理系统确保架构有条不紊的演进

图片

单一应用架构

当网站流量很小时,只需一个应用,将所有功能都部署在一起,以减少部署节点和成本。此时,用于简化增删改查工作量的数据访问框架(ORM)是关键。

图片

适用于小型网站,小型管理系统,将所有功能都部署到一个功能里,简单易用。

缺点:

1、性能扩展比较难

2、协同开发问题

3、不利于升级维护

垂直应用架构

当访问量逐渐增大,单一应用增加机器带来的加速度越来越小,将应用拆成互不相干的几个应用,以提升效率。此时,用于加速前端页面开发的Web框架(MVC)是关键。

图片

通过切分业务来实现各个模块独立部署,降低了维护和部署的难度,团队各司其职更易管理,性能扩展也更方便,更有针对性。

缺点:公用模块无法重复利用,开发性的浪费

分布式架构

当垂直应用越来越多,应用之间交互不可避免,将核心业务抽取出来,作为独立的服务,逐渐形成稳定的服务中心,使前端应用能更快速的响应多变的市场需求。此时,用于提高业务复用及整合的**分布式服务框架(RPC)**是关键

图片

流动计算架构

当服务越来越多,容量的评估,小服务资源的浪费等问题逐渐显现,此时需增加一个调度中心基于访问压力实时管理集群容量,提高集群利用率。此时,用于提高机器利用率的资源调度和治理中心(SOA)[ Service Oriented Architecture]是关键。

图片

19.2、RPC

RPC【Remote Procedure Call】是指远程过程调用,是一种进程间通信方式,他是一种技术的思想,而不是规范。它允许程序调用另一个地址空间(通常是共享网络的另一台机器上)的过程或函数,而不用程序员显式编码这个远程调用的细节。即程序员无论是调用本地的还是远程的函数,本质上编写的调用代码基本相同。

也就是说两台服务器A,B,一个应用部署在A服务器上,想要调用B服务器上应用提供的函数/方法,由于不在一个内存空间,不能直接调用,需要通过网络来表达调用的语义和传达调用的数据。为什么要用RPC呢?就是无法在一个进程内,甚至一个计算机内通过本地调用的方式完成的需求,比如不同的系统间的通讯,甚至不同的组织间的通讯,由于计算能力需要横向扩展,需要在多台机器组成的集群上部署应用。RPC就是要像调用本地的函数一样去调远程函数;

RPC基本原理

图片

步骤解析:

图片

RPC两个核心模块:通讯,序列化。

19.3、测试环境搭建

dubbo

Apache Dubbo |ˈdʌbəʊ| 是一款高性能、轻量级的开源Java RPC框架,它提供了三大核心能力:面向接口的远程方法调用,智能容错和负载均衡,以及服务自动注册和发现

官方网站:[Dubbo 文档 | Apache Dubbo(https://cn.dubbo.apache.org/zh-cn/overview/home/)

图片

服务提供者(Provider):暴露服务的服务提供方,服务提供者在启动时,向注册中心注册自己提供的服务。

服务消费者(Consumer):调用远程服务的服务消费方,服务消费者在启动时,向注册中心订阅自己所需的服务,服务消费者,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,再选另一台调用。

注册中心(Registry):注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者

监控中心(Monitor):服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心

调用关系说明

l 服务容器负责启动,加载,运行服务提供者。

l 服务提供者在启动时,向注册中心注册自己提供的服务。

l 服务消费者在启动时,向注册中心订阅自己所需的服务。

l 注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者。

l 服务消费者,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,再选另一台调用。

l 服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心。

下载注册中心zookeeper(还有别的,nacos,redis)

下载解压好后,进入bin目录运行zkServer.cmd程序

在这里插入图片描述

发现出现闪退,对其编辑添加pause暂停可以查看错误
在这里插入图片描述

  • 解决在conf目录下复制一份zoo_example.cfg命名为zoo.cfg

在zoo.cfg文件中
在这里插入图片描述

  • 简单测试zk
  1. 运行zkServer.cmd

  2. 运行zkCli.cmd

  3. 在zkCli.cmd中输入 ls /

在这里插入图片描述

  1. 输入命令 create -e/mixcus 123

创建一个名为mixcus的节点 值为123

在这里插入图片描述

  1. 使用get /mixcus可以取出其值

在这里插入图片描述

  1. 此时再次输入 ls /

在这里插入图片描述

下载dubbo-admin

  • 作用:方便对 rpc 服务的监测、监控、以及相关配置的管理

  • 下载链接apache/dubbo-admin at master (github.com)

  • 下载后将其打包为jar包,可以在idea中打包,也可以在cmd中打包

    命令行:

    mvn clean package -Dmaven.test.skip=true
    
  • 切换到该目录下

dubbo-Admin-develop\dubbo-admin-distribution\target

在这里插入图片描述

打包成功

测试

  1. 运行zookeeper,在运行打包好的dubbo-admin jar包 dubbo-admin-0.5.0.jar
  2. 访问http://localhost:7001/
  3. application.properties中要设置一个server.port=7001,不设置的话默认使用的是8080,这就会导致cmd运行时报8080端口被占用这个错误,新版的dubbo的properties文件在dubbo-admin-server的resource下

在这里插入图片描述

19.4、进行简单服务测试

  • zookeeper对应dubbo版本的Dubbo zookeeper依赖

官方文档:Zookeeper | Apache Dubbo

在这里插入图片描述

创建提供者

此处使用的zookeeper为3.9.0 dubbo为3.2.4

<!-- dubbo -->
<dependency>
    <groupId>org.apache.dubbo</groupId>
    <artifactId>dubbo-spring-boot-starter</artifactId>
    <version>3.2.4</version>
</dependency>
<!--dubbo-dependencies-zookeeper-curator5该依赖包含了 Curator、Zookeeper Client 等依赖。-->
<dependency>
    <groupId>org.apache.dubbo</groupId>
    <artifactId>dubbo-dependencies-zookeeper-curator5</artifactId>
    <version>3.2.4</version>
    <type>pom</type>
    <exclusions>
        <exclusion>
            <artifactId>slf4j-reload4j</artifactId>
            <groupId>org.slf4j</groupId>
        </exclusion>
    </exclusions>
</dependency>

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

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
</dependency>
  1. 创建一个service包在其目录下在创建一个provider包,编写provider提供服务
    在这里插入图片描述

TicketService

package com.mixcus.service.provider;

public interface TicketService {

    public String getTicket();
}

TicketServiceImpl

package com.mixcus.service.provider;

import org.apache.dubbo.config.annotation.DubboService;
import org.springframework.stereotype.Component;

@DubboService//注册到注册中心提供服务
@Component//注册为组件
public class TicketServiceImpl implements TicketService {

    @Override
    public String getTicket() {

        return "你已经成功购买了一张票~";
    }
}
  1. 编写配置文件(也可以使用yml)
# 当前应用的名字
dubbo.application.name=dubbo_zk_provider

#注册中心地址
dubbo.registry.address=zookeeper://127.0.0.1:2181

#扫描指定包下服务
dubbo.scan.base-packages=com.mixcus.service
  1. 先启动zookeeper,在启动dubbo_zk-provider的主启动类
  2. 查看zookeeper启动终端发现连接成功

在这里插入图片描述

创建消费者

  1. 同样创建一个consumer的包

在这里插入图片描述

  1. 编写消费者服务

UserService

package com.mixcus.service.consumer;

import com.mixcus.service.provider.TicketService;
import org.apache.dubbo.config.annotation.DubboReference;
import org.apache.dubbo.config.annotation.DubboService;
import org.springframework.stereotype.Component;


@Component
public class UserService {

    @DubboReference//声明对名为 TicketService 的 Dubbo 服务的引用
    TicketService ticketService;

    public void buyTicket(){
        System.out.println(ticketService.getTicket());
    }
}
  1. 编写配置文件
# 应用名
dubbo.application.name=dubbo_zk_consumer

# 注册中心地址
dubbo.registry.address=zookeeper://127.0.0.1:2181

# 设置端口(出现端口占用的时候可以设置)
server.port=8111
  1. 需要使用远程服务需要建一个与该服务接口相同的包

服务接口:com.mixcus.service.provider
在这里插入图片描述

此时在消费者包中也要建立相同的包及接口

在这里插入图片描述

  1. 测试远程调用服务

在test的包中添加测试

package com.mixcus;

import com.mixcus.service.consumer.UserService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
class DubboZkConsumerApplicationTests {

    @Autowired
    UserService userService;

    @Test
    void contextLoads() {
        userService.buyTicket();
    }
}
  1. 启动zookeeper注册中心,在启动provider的主启动类

在这里插入图片描述

注册服务成功

  1. 启动消费者测试
    在这里插入图片描述
    服务启动成功!
  • 14
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值