❤️14万字的《微服务开发SpringBoot—从基础高级》(建议收藏)❤️

文章目录

1、什么是SpringBoot?

  1. Spring Boot是由Pivotal团队提供的全新框架
  2. 其设计目的是用来简化Spring应用的初始搭建以及开发过程。
  3. 该框架使用了特定的方式来进行配置,从而使开发人员不再需要定义样板化的配置。
  4. 通过这种方式,Spring Boot致力于在蓬勃发展的快速应用开发领域(rapid application development)成为领导者

==SpringBoot基于Spring4.0设计,不仅继承了Spring框架原有的优秀特性,而且还通过简化配置来进一步简化了Spring应用的整个搭建和开发过程。另外SpringBoot通过集成大量的框架使得依赖包的版本冲突,以及引用的不稳定性等问题得到了很好的解决。 ==

SpringBoot所具备的特征有:

(1)可以创建独立的Spring应用程序,并且基于其Maven或Gradle插件,可以创建可执行的JARs和WARs;

(2)内嵌Tomcat或Jetty等Servlet容器;

(3)提供自动配置的“starter”项目对象模型(POMS)以简化Maven配置;

(4)尽可能自动配置Spring容器;

(5)提供准备好的特性,如指标、健康检查和外部化配置;

(6)绝对没有代码生成,不需要XML配置。 [1] [2]

Spring框架回顾

  1. Spring框架是Java平台上的一种开源应用框架,提供具有控制反转特性的容器
  2. Spring框架为开发提供了一系列的解决方案,比如利用控制反转的核心特性,并通过依赖注入实现控制反转来实现管理对象生命周期容器化,利用面向切面编程进行声明式的事务管理,整合多种持久化技术管理数据访问,提供大量优秀的Web框架方便开发等等
  3. Spring框架利用容器管理对象的生命周期,容器可以通过扫描XML文件或类上特定Java注解来配置对象,开发者可以通过依赖查找或依赖注入来获得对象
  4. Spring集成多种事务模板,系统可以通过事务模板、XML或Java注解进行事务配置,并且事务框架集成了消息传递和缓存等功能。

2、什么是微服务架构?

  1. 微服务架构是一项在云中部署应用和服务的新技术
  2. 微服务可以在“自己的程序”中运行,并通过“轻量级设备与HTTP型API进行沟通”
  3. 关键在于该服务可以在自己的程序中运行。通过这一点我们就可以将服务公开与微服务架构(在现有系统中分布一个API)区分开来。
  4. 在服务公开中,许多服务都可以被内部独立进程所限制。如果其中任何一个服务需要增加某种功能,那么就必须缩小进程范围。在微服务架构中,只需要在特定的某种服务中增加所需功能,而不影响整体进程的架构。

特点

  1. 微服务的基本思想在于考虑围绕着业务领域组件来创建应用,这些应用可独立地进行开发、管理和加速。在分散的组件中使用微服务云架构和平台,使部署、管理和服务功能交付变得更加简单。
  2. 微服务是利用组织的服务投资组合,然后基于业务领域功能分解它们,在看到服务投资组合之前,它还是一个业务领域。
  3. 微服务这一概念出现于2012年,是因软件作者Martin Fowler而流行,他承认这并没有精确地定义出这一架构形式,虽然围绕业务能力、自动化部署、终端智能以及语言和数据的分散控制有一些常见的特性。

3、第一个SpringBoot程序

1、环境准备

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

开发工具:

  • IDEA

1、创建基础项目说明

Spring官方提供了非常方便的工具让我们快速构建应用

Spring Initializr:https://spring.io/

2、项目创建方式一:

使用Spring Initializr 的 Web页面创建项目

1、打开 https://start.spring.io/

2、填写项目信息

3、点击”Generate Project“按钮生成项目;下载此项目

4、解压项目包,并用IDEA以Maven项目导入即可

img

img

img

img

3、项目创建方式二:

使用 IDEA 直接创建项目

1、创建一个新项目

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

3、填写项目信息

4、选择初始化的组件

5、填写项目路径

6、等待项目构建成功

img

img

2、加载好创建的项目之后测试!编写一个http接口:HelloController

1、在主程序的同级目录下,新建一个controller包,一定要在同级目录下,否则识别不到

2、在包中新建一个HelloController类

package com.kk.springboot01.controller;

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

//自动装配
@RestController
public class HelloController {
    
    

    //接口:
    @RequestMapping("/hello")
    public String hello(){
    
    
        //调用业务,接收前端的参数
        return "hello world!";
    }
}

img

3、编写完毕后,从主程序启动项目,浏览器发起请求,看页面返回;控制台输出了 Tomcat 访问的端口号!

img

测试成功!

3、项目结构分析:

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

1、程序的主启动类

2、一个 application.properties 配置文件

3、一个 测试类

4、一个 pom.xml

1、pom.xml 分析

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.kk</groupId>
    <artifactId>SpringBoot01</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>SpringBoot01</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <spring-boot.version>2.3.7.RELEASE</spring-boot.version>
    </properties>

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

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>${spring-boot.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.1</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                    <encoding>UTF-8</encoding>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>2.3.7.RELEASE</version>
                <configuration>
                    <mainClass>com.kk.springboot01.SpringBoot01Application</mainClass>
                </configuration>
                <executions>
                    <execution>
                        <id>repackage</id>
                        <goals>
                            <goal>repackage</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

</project>

如果打包成功,则会在target目录下生成一个 jar 包

img

打包成功!

img

img

4、运行原理探究

说到SpringBoot,到底是怎么运行的呢?Maven项目,一般我们从pom.xml文件探究起

pom.xml

父依赖

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

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

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

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

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

1、启动器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;

2、主启动类

1、默认的主启动类

package com.kk.springboot01;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
//本身就是Spring的一个组件
//程序的主入口
//@SpringBootApplication 来标注一个主程序类
//说明这是一个Spring Boot应用
@SpringBootApplication
public class SpringBoot01Application {
    
    
        //启动的是一个服务
    public static void main(String[] args) {
    
    
        SpringApplication.run(SpringBoot01Application.class, args);
    }

}

2、@SpringBootApplication

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

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

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package org.springframework.boot.autoconfigure;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.beans.factory.support.BeanNameGenerator;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.boot.context.TypeExcludeFilter;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import org.springframework.context.annotation.ComponentScan.Filter;
import org.springframework.core.annotation.AliasFor;

@Target({
    
    ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(
    excludeFilters = {
    
    @Filter(
    type = FilterType.CUSTOM,
    classes = {
    
    TypeExcludeFilter.class}
), @Filter(
    type = FilterType.CUSTOM,
    classes = {
    
    AutoConfigurationExcludeFilter.class}
)}
)
public @interface SpringBootApplication {
    
    
    @AliasFor(
        annotation = EnableAutoConfiguration.class
    )
    Class<?>[] exclude() default {
    
    };

    @AliasFor(
        annotation = EnableAutoConfiguration.class
    )
    String[] excludeName() default {
    
    };

    @AliasFor(
        annotation = ComponentScan.class,
        attribute = "basePackages"
    )
    String[] scanBasePackages() default {
    
    };

    @AliasFor(
        annotation = ComponentScan.class,
        attribute = "basePackageClasses"
    )
    Class<?>[] scanBasePackageClasses() default {
    
    };

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

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

3、@ComponentScan

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

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

4、@SpringBootConfiguration

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

进入这个注解:

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package org.springframework.boot;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.AliasFor;

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

点@Configuration 进去得到下面的 @Component

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package org.springframework.context.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.core.annotation.AliasFor;
import org.springframework.stereotype.Component;

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

    boolean proxyBeanMethods() default true;
}

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

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

5、 @EnableAutoConfiguration

@EnableAutoConfiguration :开启自动配置功能

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

6、@AutoConfigurationPackage :自动配置包

@AutoConfigurationPackage
@Import({
    
    AutoConfigurationImportSelector.class})

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

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

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

AutoConfigurationImportSelector :自动配置导入选择器

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


// 获得候选的配置
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
    
    
       //这里的getSpringFactoriesLoaderFactoryClass()方法
    //返回的就是我们最开始看的启动自动导入配置文件的注解类;EnableAutoConfiguration
    List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader());
    Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you are using a custom packaging, make sure that file is correct.");
    return configurations;
}

2、这个方法又调用了 SpringFactoriesLoader 类的静态方法!进入SpringFactoriesLoader类loadFactoryNames() 方法

public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
    
    
    String factoryTypeName = factoryType.getName();
    //这里它又调用了 loadSpringFactories 方法
    return (List)loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
}

3、继续点击查看 loadSpringFactories 方法

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

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

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

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

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

7、spring.factories

我们根据源头打开spring.factories , 看到了很多自动配置的文件;这就是自动配置根源所在!

img

WebMvcAutoConfiguration

我们在上面的自动配置类随便找一个打开看看,比如 :WebMvcAutoConfiguration

img

img

可以看到这些一个个的都是JavaConfig配置类,而且都注入了一些Bean,可以找一些自己认识的类

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

3、结论

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

SpringApplication

实质是开启了一个服务;

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

SpringApplication.run分析

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

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

1、推断应用的类型是普通的项目还是Web项目;

2、查找并加载所有可用初始化器 , 设置到initializers属性中;

3、找出所有的应用程序监听器,设置到listeners属性中;

4、推断并设置main方法的定义类,找到运行的主类.

run方法流程分析

img

关于SpringBoot,谈谈你额理解

  • 自动装配

    • (1)引导类上开启@EnableAutoConfiguration
      (2)内部通过@import注解引入ImporttSelector
      (3)查找工程jar包中META-INF/spring.factories文件
      (4)装载内部的对象到容器
      
  • run()

    • 1、推断应用的类型是普通的项目还是Web项目;

      2、查找并加载所有可用初始化器 , 设置到initializers属性中;

      3、找出所有的应用程序监听器,设置到listeners属性中;

      4、推断并设置main方法的定义类,找到运行的主类.

5、yaml语法学习

1、配置文件

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

  • application.properties

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

    • 语法结构 :key:空格 value

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

比如我们可以在配置文件中修改Tomcat 默认启动的端口号!测试一下!

  • applicaion.properties
server.port=8081

2、yaml概述

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

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

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

传统xml配置:

<server>    
    <port>8081<port>
</server>

yaml配置:

server:
  port: 8080

3、yaml基础语法

说明:语法要求严格!

1、空格不能省略

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

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

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

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

k: v

注意:

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

    比如 :name: “kk\n nh” 输出 :kk换行 nh

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

    比如 :name: ‘kk\n nh’ 输出 :kk\n nh

对象、Map(键值对)

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

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

student:    name: kk    age: 17

行内写法

student: {
    
    name: kk,age: 17}

数组( List、set )

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

pets: - cat - dog - pig

行内写法

pets: [cat,dog,pig]

修改SpringBoot的默认端口号

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

server:  port: 8082

4、注入配置文件

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

1、yaml注入配置文件

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

2、编写一个实体类 Dog;

package com.kk.pojo;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component
public class Dog {
    
    


    private String name;

    private Integer age;

    public Dog() {
    
    
    }

    public Dog(String name, Integer age) {
    
    
        this.name = name;
        this.age = age;
    }

    public String getName() {
    
    
        return name;
    }

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

    public Integer getAge() {
    
    
        return age;
    }

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

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

使用传统的方式给bean注入属性值的!@Value,给dog类测试一下:

@Value("小白")    
private String name;    
@Value("2")    
private Integer age;

4、在SpringBoot的测试类下注入dog输出一下;

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

5、编写一个的实体类:Person 类

package com.kk.pojo;

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

import java.util.Date;
import java.util.List;
import java.util.Map;
@Component
@ConfigurationProperties(prefix = "person")
public class Person {
    
    
    private String name;
    private Integer age;
    private Boolean happy;
    private Date birth;
    private Map<String,Object> maps;
    private List<Object> lists;
    private Dog dog;

    public Person() {
    
    
    }

    public Person(String name, Integer age,
                  Boolean happy, Date birth, Map<String,
            Object> maps, List<Object> lists, Dog dog) {
    
    
        this.name = name;
        this.age = age;
        this.happy = happy;
        this.birth = birth;
        this.maps = maps;
        this.lists = lists;
        this.dog = dog;
    }

    public String getName() {
    
    
        return name;
    }

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

    public Integer getAge() {
    
    
        return age;
    }

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

    public Boolean getHappy() {
    
    
        return happy;
    }

    public void setHappy(Boolean happy) {
    
    
        this.happy = happy;
    }

    public Date getBirth() {
    
    
        return birth;
    }

    public void setBirth(Date birth) {
    
    
        this.birth = birth;
    }

    public Map<String, Object> getMaps() {
    
    
        return maps;
    }

    public void setMaps(Map<String, Object> maps) {
    
    
        this.maps = maps;
    }

    public List<Object> getLists() {
    
    
        return lists;
    }

    public void setLists(List<Object> lists) {
    
    
        this.lists = lists;
    }

    public Dog getDog() {
    
    
        return dog;
    }

    public void setDog(Dog dog) {
    
    
        this.dog = dog;
    }

    @Override
    public String toString() {
    
    
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", happy=" + happy +
                ", birth=" + birth +
                ", maps=" + maps +
                ", lists=" + lists +
                ", dog=" + dog +
                '}';
    }
}

6、我们来使用yaml配置的方式进行注入,我们编写一个yaml配置!

person:
  name: kk
  age: 1
  happy: false
  birth: 2000/01/01
  maps: {
    
    k1: v1,k2: v2}
  lists:
    - code
    - girl
    - music
  dog:
    name: 旺财
    age: 1

7、现在来注入到我们的类中!

/*@ConfigurationProperties作用:将配置文件中配置的每一个属性的值,映射到这个组件中;告诉SpringBoot将本类中的所有属性和配置文件中相关的配置进行绑定参数 prefix = “person” : 将配置文件中的person下面的所有属性一一对应*/
@Component //注册bean
@ConfigurationProperties(prefix = "person")
public class Person {
    
        
    private String name;    
    private Integer age;    
    private Boolean happy;    
    private Date birth;    
    private Map<String,Object> maps;    
    private List<Object> lists;    
    private Dog dog;
}

8、IDEA中提示:未配置 Spring Boot 配置注解处理器

img

可以加上该配置

<!-- 导入配置文件处理器,配置文件进行绑定就会有提示,需要重启 -->
<dependency>  
    <groupId>org.springframework.boot</groupId>  
    <artifactId>spring-boot-configuration-processor</artifactId>  
    <optional>true</optional>
</dependency>

9、测试

package com.kk;

import com.kk.pojo.Dog;
import com.kk.pojo.Person;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import java.time.Period;

@SpringBootTest
class Springboot02ConfigApplicationTests {
    
    

    @Autowired
   private Person person;

    @Test
    void contextLoads() {
    
    
        System.out.println(person);
    }

}

测试成功

img

6、JSR303校验

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

@Validated //数据校验

public class Person {
    
    

	 @Email()
    private String name;
    }

运行结果:

img

Reason: 不是一个合法的电子邮件地址

使用数据教育,可以保证数据的正确性

常见参数:


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

空检查
@Null       验证对象是否为null
@NotNull    验证对象是否不为null, 无法查检长度为0的字符串
@NotBlank   检查约束字符串是不是Null还有被Trim的长度是否大于0,只对字符串,且会去掉前后空格.
@NotEmpty   检查约束元素是否为NULL或者是EMPTY.
    
Booelan检查
@AssertTrue     验证 Boolean 对象是否为 true  
@AssertFalse    验证 Boolean 对象是否为 false  
    
长度检查
@Size(min=, max=) 验证对象(Array,Collection,Map,String)长度是否在给定的范围之内  
@Length(min=, max=) string is between min and max included.

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

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

​ 常见参数来源:狂神说,如有侵权,请联系删除!

7、多环境配置以及配置文件位置

1、多环境切换

profile是Spring对不同环境提供不同配置功能的支持,可以通过激活不同的环境版本,实现快速切换环境;

多配置文件

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

例如:

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

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

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

文件

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

application.properties

#SpringBoot的多环境配置,可以选择激活哪一个配置
spring.profiles.active=dev

application-dev.properties

server.port=8082

img

2、yaml的多文档块

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

application.yaml

  server:
    port:8081
  spring:
    profiles:
      active: dev
  ---
server:
  port:8082
spring:
  profiles: dev
---
server:
  port:8082
spring:
  profiles: test

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

3、配置文件加载位置

官方外部配置文件说明参考文档

img

springboot 启动会扫描以下位置的application.properties或者application.yaml文件作为SpringBoot的默认配置文件:

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

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

SpringBoot会从这四个位置全部加载主配置文件;互补配置;

4、拓展,运维小技巧

指定位置加载配置文件,还可以通过spring.config.location来改变默认的配置文件位置

项目打包好以后,我们可以使用命令行参数的形式,启动项目的时候来指定配置文件的新位置;相同配置,外部指定的配置文件优先级最高

java -jar spring-boot-config.jar --spring.config.location=F:/application.prop

8、自动配置原理

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

img

以上就是自动装配的原理!

分析

1、SpringBoot启动会加载大量的自动配置类

2、我们看我们需要的功能有没有在SpringBoot默认写好的自动配置类当中;

3、我们再来看这个自动配置类中到底配置了哪些组件;(只要我们要用的组件存在在其中,我们就不需要再手动配置了)

4、给容器中自动配置类添加组件的时候,会从properties类中获取某些属性。我们只需要在配置文件中指定这些属性的值即可;

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

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

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

那怎么才能知道哪些自动配置类生效?

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

#开启springboot的调试类
debug=true
  1. Positive matches:(自动配置类启用的:正匹配)
  2. Negative matches:(没有启动,没有匹配成功的自动配置类:负匹配)
  3. Unconditional classes: (没有条件的类)

9、SpringBoot Web开发

1、Web开发探究

jar:webapp!

自动装配

  • 向容器中自动配置组件 : Autoconfiguration
  • 自动配置类,封装配置文件的内容:***Properties

2、静态资源处理

建一个普通的SpringBoot项目,回顾一下HelloWorld程序!

package com.kk.controller;

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

@RestController
public class HelloController {
    
    
    @GetMapping("/hello")
    public String hello(){
    
    
        return "Hello world!";
    }
}

1、静态资源映射规则:

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

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

public void addResourceHandlers(ResourceHandlerRegistry registry) {
    
    
    if (!this.resourceProperties.isAddMappings()) {
    
    
        logger.debug("Default resource handling disabled");
    } else {
    
    
       // webjars 配置
        if (!registry.hasMappingForPattern("/webjars/**")) {
    
    
            ResourceHandlerRegistration registration = registry.addResourceHandler(new String[]{
    
    "/webjars/**"}).addResourceLocations(new String[]{
    
    "classpath:/META-INF/resources/webjars/"});
            this.configureResourceCaching(registration);
            this.customizeResourceHandlerRegistration(registration);
        }
		 // 静态资源配置
        String staticPathPattern = this.webFluxProperties.getStaticPathPattern();
        if (!registry.hasMappingForPattern(staticPathPattern)) {
    
    
            ResourceHandlerRegistration registration = registry.addResourceHandler(new String[]{
    
    staticPathPattern}).addResourceLocations(this.resourceProperties.getStaticLocations());
            this.configureResourceCaching(registration);
            this.customizeResourceHandlerRegistration(registration);
        }

    }
}

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

2、什么是webjars 呢?

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

使用SpringBoot需要使用Webjars,网站:https://www.webjars.org

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

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

导入完毕,查看webjars目录结构,并访问jquery.js文件!

img

访问:只要是静态资源,SpringBoot就会去对应的路径寻找资源,这里访问:http://localhost:8080/webjars/jquery/3.4.1/jquery.js

img

3、静态资源映射规则访问资源

找到staticPathPattern发现第二种映射规则 :/** , 访问当前的项目任意资源,它会去找 resourceProperties 这个类,点进去看一下分析:

// 进入方法
public String[] getStaticLocations() {
    
    
    return this.staticLocations;
    
}

// 找到对应的值
public ResourceProperties() {
    
    
        this.staticLocations = CLASSPATH_RESOURCE_LOCATIONS;
        this.addMappings = true;
        this.chain = new ResourceProperties.Chain();
        this.cache = new ResourceProperties.Cache();
    }

// 找到路径
    private static final String[] CLASSPATH_RESOURCE_LOCATIONS = new String[]{
    
    "classpath:/META-INF/resources/", "classpath:/resources/", "classpath:/static/", "classpath:/public/"};

ResourceProperties可以设置和我们静态资源有关的参数;这里面指向了它会去寻找资源的文件夹,即上面数组的内容。

得出结论,以下四个目录存放的静态资源可以被我们识别:

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

我们可以在resources根目录下新建对应的文件夹,都可以存放我们的静态文件;

比如我们访问 http://localhost:8080/1.js , 它会去这些文件夹中寻找对应的静态资源文件;

得出结论:优先级:resouces>static(默认)》public

img

10、首页以及图标的定制

1、首页处理

搜索welcomePageHandlerMapping,找到相关Bean

@Bean
public WelcomePageHandlerMapping welcomePageHandlerMapping(ApplicationContext applicationContext, FormattingConversionService mvcConversionService, ResourceUrlProvider mvcResourceUrlProvider) {
    
    
    
    WelcomePageHandlerMapping welcomePageHandlerMapping = new WelcomePageHandlerMapping(new TemplateAvailabilityProviders(applicationContext), applicationContext, this.getWelcomePage(), this.mvcProperties.getStaticPathPattern());
    
    welcomePageHandlerMapping.setInterceptors(this.getInterceptors(mvcConversionService, mvcResourceUrlProvider));
    
    welcomePageHandlerMapping.setCorsConfigurations(this.getCorsConfigurations());
    return welcomePageHandlerMapping;
}
private Optional<Resource> getWelcomePage() {
    
    
    String[] locations = WebMvcAutoConfiguration.getResourceLocations(this.resourceProperties.getStaticLocations());
    return Arrays.stream(locations).map(this::getIndexHtml).filter(this::isReadable).findFirst();
}
	// ::是java8 中新引入的运算符
    // Class::function的时候function是属于Class的,应该是静态方法。
    // this::function的funtion是属于这个对象的。
    // 简而言之,就是一种语法而已,是一种简写

// 欢迎页就是一个location下的的 index.html 而已
  private Resource getIndexHtml(String location) {
    
    
            return this.resourceLoader.getResource(location + "index.html");
        }

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

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

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

2、网站图标ico

1、关闭SpringBoot默认图标

application.properties

# 关闭默认图标
spring.mvc.favicon.enabled=false

2、自己放一个图标在静态资源目录下,public 目录下

img

测试成功!

img

11、Thymeleaf模板引擎

1、Thymeleaf是什么

首先,看一下官网的描述。

Thymeleaf is a modern server-side Java template engine for both web and standalone environments, capable of processing HTML, XML, JavaScript, CSS and even plain text.

意思就是Thymeleaf是适用于Web和独立环境的现代服务器端Java模板引擎,能够处理HTML,XML,JavaScript,CSS甚至纯文本。

2、Thymeleaf有何优势

再看一下官网的描述。

The main goal of Thymeleaf is to provide an elegant and highly-maintainable way of creating templates. To achieve this, it builds on the concept of Natural Templates to inject its logic into template files in a way that doesn’t affect the template from being used as a design prototype. This improves communication of design and bridges the gap between design and development teams.

Thymeleaf has also been designed from the beginning with Web Standards in mind – especially HTML5 – allowing you to create fully validating templates if that is a need for you.

简单点说,就是Thymeleaf提供一种优雅且高度可维护的模板创建方式,可缩小设计团队与开发团队之间的差距。Thymeleaf也已经从一开始就遵从Web标准,尤其是HTML5,这就允许创建一些完全验证的模板。

3、Thymeleaf 特点

来源:撸帝

  1. Thymeleaf 在有网络和无网络的环境下皆可运行,即它可以让美工在浏览器查看页面的静态效果,也可以让程序员在服务器查看带数据的动态页面效果。这是由于它支持 html 原型,然后在 html 标签里增加额外的属性来达到模板 + 数据的展示方式。浏览器解释 html 时会忽略未定义的标签属性,所以 thymeleaf 的模板可以静态地运行;当有数据返回到页面时,Thymeleaf 标签会动态地替换掉静态内容,使页面动态显示;
  2. Thymeleaf 开箱即用的特性。它提供标准和 Spring 标准两种方言,可以直接套用模板实现 JSTL、 OGNL 表达式效果,避免每天套模板、改 JSTL、改标签的困扰。同时开发人员也可以扩展和创建自定义的方言。
  3. Thymeleaf 提供 Spring 标准方言和一个与 SpringMVC 完美集成的可选模块,可以快速的实现表单绑定、属性编辑器、国际化等功能。

4、引入Thymeleaf

三方式:

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

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

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

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

img

点击Pom

thymeleaf

在pom.xml导入thymeleaf依赖

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

保证thymeleaf导入成功

img

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;
 //.....   
}
  1. 我们可以看到默认的前缀和后缀!
  2. 只需要把我们的html页面放在类路径下的templates下,thymeleaf就可以帮我们自动渲染了。
  3. 使用thymeleaf什么都不需要配置,只需要将他放在指定的文件夹下即可!

测试:controller类

package com.kk.controller;

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

@Controller
public class IndexController {
    
    

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

templates目录下新建test.html

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

测试结果:成功跳转!

img

12、Thymeleaf 语法学习

学习文档地址:https://www.yiibai.com/thymeleaf/thymeleaf3-servlet3-helloworld.html

测试:

1、Controller层

@Controller
public class IndexController {
    
    


    @RequestMapping("/test")
    public String test(Model model){
    
    
        model.addAttribute("msg","Hello SpringBoot!");
        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>
<!--所有的html元素都可以被thymeleaf替换接管,  th:元素名-->
<div th:text="${msg}"></div>
</body>
</html>

4、启动测试!

img

测试成功

测试:

1、编写Controller

@Controller
public class IndexController {
    
    

    @RequestMapping("/test")
    public String test(Model model){
    
    
        //存入数据
        model.addAttribute("msg","<h1>Hello SpringBoot!</h1>");

        model.addAttribute("users", Arrays.asList("kk","hh"));
        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>
<!--所有的html元素都可以被thymeleaf替换接管,  th:元素名-->
<div th:text="${msg}"></div>
<div th:utext="${msg}"></div>
<hr>

<h3 th:each="user:${users}" th:text="${user}"></h3>

</body>
</html>

3、启动项目测试!

大多数Thymeleaf属性允许将它们的值设置为或包含表达式,由于它们使用的方言,我们将其称为标准表达式。这些表达式可以有五种类型:

  • ${...} : 变量表达式。
  • *{...} : 选择表达式。
  • #{...} : 消息 (i18n) 表达式。
  • @{...} : 链接 (URL) 表达式。
  • ~{...} : 片段表达式。

13、SpringMVC拓展

地址 : https://docs.spring.io/spring-boot/docs/2.1.6.RELEASE/reference/html/boot-features-developing-web-applications.html

阅读一段官方文档:

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 (covered later in this document)).//自动注册了Converter:【转换器,这就是我们网页提交数据到后台自动封装成为对象的东西,比如把18字符串自动转换为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).
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.

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

SpringBoot已经帮我们自动配置好了SpringMVC,然后自动配置了哪些东西呢?

1、ContentNegotiatingViewResolver

   @Bean //我们在这里确实看到已经给容器中注册了一个bean
        @ConditionalOnBean({
    
    ViewResolver.class})
        @ConditionalOnMissingBean(
            name = {
    
    "viewResolver"},
            value = {
    
    ContentNegotiatingViewResolver.class}
        )
        public ContentNegotiatingViewResolver viewResolver(BeanFactory beanFactory) {
    
    
            ContentNegotiatingViewResolver resolver = new ContentNegotiatingViewResolver();
            resolver.setContentNegotiationManager((ContentNegotiationManager)beanFactory.getBean(ContentNegotiationManager.class));
            resolver.setOrder(-2147483648);
            return resolver;
        }

点进这类看看!找到对应的解析视图的代码

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

        String mediaTypeInfo = this.logger.isDebugEnabled() && requestedMediaTypes != null ? " given " + requestedMediaTypes.toString() : "";
        if (this.useNotAcceptableStatusCode) {
    
    
            if (this.logger.isDebugEnabled()) {
    
    
                this.logger.debug("Using 406 NOT_ACCEPTABLE" + mediaTypeInfo);
            }

            return NOT_ACCEPTABLE_VIEW;
        } else {
    
    
            this.logger.debug("View remains unresolved" + mediaTypeInfo);
            return null;
        }
    }

继续点进去看,它是怎么获得候选的视图的呢? getCandidateViews中看到它是把所有的视图解析器拿来,进行while循环,解析!

 Iterator var5 = this.viewResolvers.iterator();

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

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

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

定制自己的config

在controller同级目录下新建一个目录Config,新建一个类MyMvcConfig

package com.kk.config;

import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.View;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import java.util.Locale;    
// 如果 想要定义一些定制化的功能,只要写这个组件,然后将交给SpringBoot,SpringBoot就会帮我们自动装配!
//拓展SpringMvc  就这样去做 使用自己的config
@Configuration
public class MyMvcConfig implements WebMvcConfigurer  {
    
    

    //ViewResolver     实现了视图解析器接口的类,我们就可以把它看做视图解析器
    @Bean
    public ViewResolver myViewResolver(){
    
    
        return  new MyViewResolver();
    }

    //自定义了一个自己的视图解析器
    public static class MyViewResolver implements ViewResolver{
    
    
        @Override
        public View resolveViewName(String s, Locale locale) throws Exception {
    
    
            return null;
        }
    }


    //视图跳转
    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
    
    
        registry.addViewController("/kk").setViewName("test");
    }
}

测试自己定制的视图跳转

img

测试成功!

14、员工管理系统

1、员工管理系统:准备工作

导入我们的所有提供的资源!

pojo 及 dao 放到项目对应的路径下:

pojo实体类

img Department.java

package com.kk.pojo;



//部门表

public class Department {
    
    
    private Integer id;
    private String departmentName;

    public Department() {
    
    
    }

    public Department(Integer id, String departmentName) {
    
    
        this.id = id;
        this.departmentName = departmentName;
    }

    public Integer getId() {
    
    
        return id;
    }

    public void setId(Integer id) {
    
    
        this.id = id;
    }

    public String getDepartmentName() {
    
    
        return departmentName;
    }

    public void setDepartmentName(String departmentName) {
    
    
        this.departmentName = departmentName;
    }

    @Override
    public String toString() {
    
    
        return "Department{" +
                "id=" + id +
                ", departmentName='" + departmentName + '\'' +
                '}';
    }
}

img Employee.java

package com.kk.pojo;

import java.util.Date;

//员工表

public class Employee {
    
    
    private Integer id;
    private String lastName;
    private String email;
    private Integer gender;  //1:男  0:女
    private Department department;
    private Date birth;

    public Employee() {
    
    
    }

    public Employee(Integer id, String lastName, String email,
                    Integer gender, Department department
                  ) {
    
    
        this.id = id;
        this.lastName = lastName;
        this.email = email;
        this.gender = gender;
        this.department = department;
        //默认的创建日期
        this.birth = new Date();
    }

    public Integer getId() {
    
    
        return id;
    }

    public void setId(Integer id) {
    
    
        this.id = id;
    }

    public String getLastName() {
    
    
        return lastName;
    }

    public void setLastName(String lastName) {
    
    
        this.lastName = lastName;
    }

    public String getEmail() {
    
    
        return email;
    }

    public void setEmail(String email) {
    
    
        this.email = email;
    }

    public Integer getGender() {
    
    
        return gender;
    }

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

    public Department getDepartment() {
    
    
        return department;
    }

    public void setDepartment(Department department) {
    
    
        this.department = department;
    }

    public Date getBirth() {
    
    
        return birth;
    }

    public void setBirth(Date birth) {
    
    
        this.birth = birth;
    }

    @Override
    public String toString() {
    
    
        return "Employee{" +
                "id=" + id +
                ", lastName='" + lastName + '\'' +
                ", email='" + email + '\'' +
                ", gender=" + gender +
                ", department=" + department +
                ", birth=" + birth +
                '}';
    }
}

dao层

img DepartmentDao

package com.kk.dao;

import com.kk.pojo.Department;
import org.springframework.stereotype.Repository;

import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

//部门表dao
@Repository  //由Spring托管
public class DepartmentDao {
    
    
    //模拟数据库中的数据
    private static Map<Integer, Department> departments=null;
    static {
    
    
        departments=new HashMap<Integer,Department>();// 创建一个部门表
        departments.put(101,new Department(101,"教学部"));
        departments.put(102,new Department(102,"市场部"));
        departments.put(103,new Department(103,"调研部"));
        departments.put(104,new Department(104,"运营部"));
        departments.put(105,new Department(105,"后勤部"));

    }
    //获得所有部门信息
    public Collection<Department> getDepartments(){
    
    
        return departments.values();
    }

    //通过id得到部门信息
    public Department getDepartmentById(Integer id){
    
    
        return departments.get(id);
    }
}

img EmployeeDao

package com.kk.dao;

import com.kk.pojo.Department;
import com.kk.pojo.Employee;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;

import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

//员工dao
@Repository  //由Spring托管
public class EmployeeDao {
    
    
    //模拟数据库中的数据
    private static Map<Integer, Employee> employees=null;
    //员工有所属的部门
    @Autowired
    private DepartmentDao departmentDao;
    static {
    
    
        employees=new HashMap<Integer,Employee>();// 创建一个部门表
        employees.put(1001,new Employee(1001,"AA","[email protected]",1,new Department(101,"教学部")));
        employees.put(1002,new Employee(1002,"BB","[email protected]",0,new Department(102,"市场部")));
        employees.put(1003,new Employee(1003,"CC","[email protected]",1,new Department(103,"调研部")));
        employees.put(1004,new Employee(1004,"DD","[email protected]",0,new Department(104,"运营部")));
        employees.put(1005,new Employee(1005,"EE","[email protected]",1,new Department(105,"后勤部")));
    }

    //主键自增
    private static Integer initId=1006;
    //增加一个员工
    public void save(Employee employee){
    
    
        if (employee.getId()==null){
    
    
            employee.setId(initId++);
        }
        employee.setDepartment(departmentDao.getDepartmentById(employee.getDepartment().getId()));

        employees.put(employee.getId(),employee);
    }
    //查询全部员工
    public Collection<Employee> getAll(){
    
    
        return employees.values();
    }
    //通过Id查询员工
    public Employee getEmployeeById(Integer id){
    
    
        return employees.get(id);
    }
    //通过Id删除员工
    public void delete(Integer id){
    
    
        employees.remove(id);
    }



}

导入完毕这些之后,还需要导入我们的前端页面,及静态资源文件!

源码:

链接:https://pan.baidu.com/s/1wLZk_bSeY93iy_8uFt7rgQ
提取码:kk80

  • css,js等放在static文件夹下
  • html放在templates文件夹下

img

准备工作:OK!!!

2、员工管理系统:首页实现

访问首页

方式一:写一个controller实现!

package com.kk.controller;

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

@Controller
public class IndexController {
    
    
//会解析到templates目录下的index.html页面
    @RequestMapping({
    
    "/","/inde.html"})
    public String index(){
    
    
        return "index";
    }
}

方式二:自己编写MVC的扩展配置

package com.kk.config;


import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class MyMvcConfig implements WebMvcConfigurer {
    
    

    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
    
    
        registry.addViewController("/").setViewName("index");
        registry.addViewController("/index.html").setViewName("index");
    }
}

解决资源导入的问题;

==将项目的启动名改掉

server.servlet.context-path=/kk

现在你访问localhost:8080 就不行了,需要访问localhost:8080/kk==

为了保证资源导入稳定,在所有资源导入的时候使用 th:去替换原有的资源路径!

1、先在所有需要使用到th:的html文件导入该配置

xmlns:th="http://www.thymeleaf.org"

2、修改所有本地静态资源的链

<!-- Bootstrap core CSS -->
<link th:href="@{/css/bootstrap.min.css}" rel="stylesheet">

<!-- Custom styles for this template -->
<link th:href="@{/css/signin.css}" rel="stylesheet">

<img class="mb-4" th:src="@{/img/bootstrap-solid.png}" alt="" width="72" height="72">

img

测试成功!

3、员工管理系统:国际化

**第一步 :**编写国际化配置文件,抽取页面需要显示的国际化页面消息。

先在IDEA中统一设置properties的编码问题!

img

**第二步:**在resources资源文件下新建一个i18n目录,建立一个login.propetries文件,还有一个login_zh_CN.properties,login_en_US.properties,发现IDEA自动识别了我们要做国际化操作;文件夹变了

img

建立好了之后可以从这里新建文件

img

img

第三步:这个视图我们点击 + 号就可以直接添加属性了;新建一个login.tip,可以看到边上有三个文件框可以输入

img

第四步:查看我们的配置文件;

img

login.properties : 默认

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

en_US:

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

zh_CN:

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

第五步 :看一下SpringBoot对国际化的自动配置!

​ 涉及到一个类: MessageSourceAutoConfiguration ,里面有一个方法,发现SpringBoot已经自动配置好了管理我们国际化资源文件的组件 ResourceBundleMessageSource;

public class MessageSourceAutoConfiguration {
    
    
    private static final Resource[] NO_RESOURCES = new Resource[0];

    public MessageSourceAutoConfiguration() {
    
    
    }

    @Bean
    @ConfigurationProperties(prefix = "spring.messages") //我们的配置文件可以直接放在类路径下叫: messages.properties, 就可以进行国际化操作了
    public MessageSourceProperties messageSourceProperties() {
    
    
        return new MessageSourceProperties();
    }

    @Bean
    public MessageSource messageSource(MessageSourceProperties properties) {
    
    
        ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
        if (StringUtils.hasText(properties.getBasename())) {
    
            //设置国际化文件的基础名
            messageSource.setBasenames(StringUtils.commaDelimitedListToStringArray(StringUtils.trimAllWhitespace(properties.getBasename())));
        }

        if (properties.getEncoding() != null) {
    
    
            messageSource.setDefaultEncoding(properties.getEncoding().name());
        }

        messageSource.setFallbackToSystemLocale(properties.isFallbackToSystemLocale());
        Duration cacheDuration = properties.getCacheDuration();
        if (cacheDuration != null) {
    
    
            messageSource.setCacheMillis(cacheDuration.toMillis());
        }

        messageSource.setAlwaysUseMessageFormat(properties.isAlwaysUseMessageFormat());
        messageSource.setUseCodeAsDefaultMessage(properties.isUseCodeAsDefaultMessage());
        return messageSource;
    }
}

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

# 配置文件放置的真实位置
spring.messages.basename=i18n.login

第六步 : 去页面获取国际化的值;

查看Thymeleaf的文档,找到message取值操作为: #{…}。

<h1 class="h3 mb-3 font-weight-normal" th:text="#{login.tip}">Please sign in</h1>

<input type="text" class="form-control" th:placeholder="#{login.username}" required="" autofocus="">
<input type="password" class="form-control" th:placeholder="#{login.password}" required="">
		
 <input type="checkbox" value="remember-me" >[[#{login.remember}]]
		    
<button class="btn btn-lg btn-primary btn-block" type="submit" >[[#{login.btn}]]</button>

页面测试:

img

根据按钮自动切换中文英文!

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

在webmvc自动配置文件就可以看到SpringBoot默认配置了

 @Bean
        @ConditionalOnMissingBean
        @ConditionalOnProperty(
            prefix = "spring.mvc",
            name = {
    
    "locale"})
        public LocaleResolver localeResolver() {
    
                //容器中没有就自己配,有的话就用用户配置的
            if (this.mvcProperties.getLocaleResolver() == org.springframework.boot.autoconfigure.web.servlet.WebMvcProperties.LocaleResolver.FIXED) {
    
    
                return new FixedLocaleResolver(this.mvcProperties.getLocale());
            } else {
    
    
                AcceptHeaderLocaleResolver localeResolver = new AcceptHeaderLocaleResolver();
                localeResolver.setDefaultLocale(this.mvcProperties.getLocale());
                return localeResolver;
            }
        }

AcceptHeaderLocaleResolver 这个类中有一个方法

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

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

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

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

在config目录下写一个处理的组件类

package com.kk.config;

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

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

public class MyLocaleResolver implements LocaleResolver {
    
    

    //解析请求
    @Override
    public Locale resolveLocale(HttpServletRequest request) {
    
    
        //获取请求中的语言参数
        String language = request.getParameter("l");
        Locale locale = Locale.getDefault();  //如果没有,就使用默认的

        //如果请求的链接携带了国际区的参数
        if (!StringUtils.isEmpty(language)){
    
    
            //zh_CN
            String[] split = language.split("_"); //使用_隔开
            //国家 地区
            locale  = new Locale(split[0], split[1]);
        }
        return locale;

    }

    @Override
    public void setLocale(HttpServletRequest request, HttpServletResponse response, Locale locale) {
    
    

    }
}

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

//自定义的国际化组件就生效了
@Bean
public LocaleResolver localeResolver(){
    
    
    return new MyLocaleResolver();
}

测试

img

img

4、员工管理系统:登陆功能实现

问题:输入任意用户名都可以登录成功!

原因:templates下的页面只能通过Controller跳转实现,而static下的页面是能直接被外界访问的,就能正常访问

把登录页面的表单提交地址写一个controller,所有表单标签都需要加上一个name属性

<form class="form-signin" th:action="@{/user/login}"  >
    
<input type="text" name="username" class="form-control" th:placeholder="#{login.username}" required="" autofocus="">
    
<input type="password" name="password" class="form-control" th:placeholder="#{login.password}" required="">

编写对应的controller

package com.kk.controller;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.servlet.http.HttpSession;

@Controller
public class LoginController {
    
    

    @RequestMapping("/user/login")
    public String login(
            @RequestParam("username") String username,
            @RequestParam("password")String password,
            Model model, HttpSession session){
    
    
      //具体业务
        if (!StringUtils.isEmpty(username) && "123456".equals(password)){
    
    
            session.setAttribute("loginUser",username);
            //将username的session保存在loginUser里

//            session.setAttribute("sessionName",Object);
//            用来设置session值的,sessionName是名称,object是你要保存的对象。

            return "redirect:/main.html";
        }else{
    
    
            //告诉用户登录失败
            model.addAttribute("msg","用户名或者密码错误!");
            return "index";
        }
    }
}

登录失败的话,我们需要将后台信息输出到前台,可以在首页标题下面加上判断!

<!--         msg的消息为空  则不显示-->
         <p style="color: red" th:text="${msg}" th:if="${not #strings.isEmpty(msg)}"></p>

img

img

将 Controller 的代码改为重定向;

//登录成功!防止表单重复提交,我们重定向
return "redirect:/main.html";

5、员工管理系统:登陆拦截器

定向成功之后!我们解决了之前资源没有加载进来的问题!后台主页正常显示!

但是又发现新的问题,我们可以直接登录到后台主页,不用登录也可以实现!

怎么处理这个问题呢?我们可以使用拦截器机制,实现登录检查!

1、自定义一个拦截器

package com.kk.config;

import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class LoginHandlerInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        //登录成功之后,应该有用户的session
        Object loginUser = request.getSession().getAttribute("loginUser");
        if (loginUser==null){
            request.setAttribute("msg","没有权限,请先登录!");
            request.getRequestDispatcher("/index.html").forward(request,response);
            return false;
        }else{
            return true;
        }
    }


}

2、将拦截器注册到我们的SpringMVC配置类当中!

//拦截器
@Override
public void addInterceptors(InterceptorRegistry registry) {
    
    
    registry.addInterceptor(new LoginHandlerInterceptor())
            .addPathPatterns("/**")
            .excludePathPatterns("/index.html","/","/user/login","/css/**","/js/**","/img/**");
}

3、在Controller层添加session

package com.kk.controller;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.servlet.http.HttpSession;

@Controller
public class LoginController {
    
    

    @RequestMapping("/user/login")
    public String login(
            @RequestParam("username") String username,
            @RequestParam("password")String password,
            Model model, HttpSession session){
    
    
      //具体业务
        if (!StringUtils.isEmpty(username) && "123456".equals(password)){
    
    
            session.setAttribute("loginUser",username);
            //将username的session保存在loginUser里

//            session.setAttribute("sessionName",Object);
//            用来设置session值的,sessionName是名称,object是你要保存的对象。

            return "redirect:/main.html";
        }else{
    
    
            //告诉用户登录失败
            model.addAttribute("msg","用户名或者密码错误!");
            return "index";
        }
    }
}

4、在后台主页,获取用户登录的信息

<a class="navbar-brand col-sm-3 col-md-2 mr-0" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">[[${session.loginUser}]]</a>

img

测试成功

6、员工管理系统:展示员工列表

主页点击Customers,显示列表页面;

1.将首页的侧边栏Customers改为员工管理

2.a链接添加请求

<a class="nav-link" href="#" th:href="@{/emps}">员工管理</a>

3.将list放在emp文件夹下

img

4.编写处理请求的controller层

@Autowired
EmployeeDao employeeDao;

@RequestMapping("/emps")
public String list(Model model){
    
    
    Collection<Employee> employees = employeeDao.getAll();
    model.addAttribute("emps",employees);
    return "emp/list";
}

测试是否成功跳转,测试成功!

Thymeleaf 公共页面元素抽取

1.抽取公共片段 th:fragment 定义模板名

2.引入公共片段 th:insert 插入模板名

新建common目录

img

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">


<!--头部导航栏-->
<nav class="navbar navbar-dark sticky-top bg-dark flex-md-nowrap p-0" th:fragment="topbar">
    <a class="navbar-brand col-sm-3 col-md-2 mr-0" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">[[${session.loginUser}]]</a>
    <input class="form-control form-control-dark w-100" type="text" placeholder="Search" aria-label="Search">
    <ul class="navbar-nav px-3">
        <li class="nav-item text-nowrap">
            <a class="nav-link" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">退出</a>
        </li>
    </ul>
</nav>



<!--侧边栏-->

<nav class="col-md-2 d-none d-md-block bg-light sidebar" th:fragment="sidebar">
    <div class="sidebar-sticky">
        <ul class="nav flex-column">
            <li class="nav-item">
                <a th:class="${active=='main.html'?'nav-link active':'nav-link'}" th:href="@{/index.html}">
                    <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-home">
                        <path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"></path>
                        <polyline points="9 22 9 12 15 12 15 22"></polyline>
                    </svg>
                  首页 <span class="sr-only">(current)</span>
                </a>
            </li>
            <li class="nav-item">
                <a class="nav-link" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">
                    <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-file">
                        <path d="M13 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V9z"></path>
                        <polyline points="13 2 13 9 20 9"></polyline>
                    </svg>
                    Orders
                </a>
            </li>
            <li class="nav-item">
                <a class="nav-link" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">
                    <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-shopping-cart">
                        <circle cx="9" cy="21" r="1"></circle>
                        <circle cx="20" cy="21" r="1"></circle>
                        <path d="M1 1h4l2.68 13.39a2 2 0 0 0 2 1.61h9.72a2 2 0 0 0 2-1.61L23 6H6"></path>
                    </svg>
                    Products
                </a>
            </li>
            <li class="nav-item">
                <a th:class="${active=='list.html'?'nav-link active':'nav-link'}" th:href="@{/emps}">
                    员工管理
                </a>
            </li>
            <li class="nav-item">
                <a class="nav-link" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">
                    <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-bar-chart-2">
                        <line x1="18" y1="20" x2="18" y2="10"></line>
                        <line x1="12" y1="20" x2="12" y2="4"></line>
                        <line x1="6" y1="20" x2="6" y2="14"></line>
                    </svg>
                    Reports
                </a>
            </li>
            <li class="nav-item">
                <a class="nav-link" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">
                    <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-layers">
                        <polygon points="12 2 2 7 12 12 22 7 12 2"></polygon>
                        <polyline points="2 17 12 22 22 17"></polyline>
                        <polyline points="2 12 12 17 22 12"></polyline>
                    </svg>
                    Integrations
                </a>
            </li>
        </ul>

        <h6 class="sidebar-heading d-flex justify-content-between align-items-center px-3 mt-4 mb-1 text-muted">
            <span>Saved reports</span>
            <a class="d-flex align-items-center text-muted" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">
                <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-plus-circle"><circle cx="12" cy="12" r="10"></circle><line x1="12" y1="8" x2="12" y2="16"></line><line x1="8" y1="12" x2="16" y2="12"></line></svg>
            </a>
        </h6>
        <ul class="nav flex-column mb-2">
            <li class="nav-item">
                <a class="nav-link" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">
                    <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-file-text">
                        <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
                        <polyline points="14 2 14 8 20 8"></polyline>
                        <line x1="16" y1="13" x2="8" y2="13"></line>
                        <line x1="16" y1="17" x2="8" y2="17"></line>
                        <polyline points="10 9 9 9 8 9"></polyline>
                    </svg>
                    Current month
                </a>
            </li>
            <li class="nav-item">
                <a class="nav-link" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">
                    <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-file-text">
                        <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
                        <polyline points="14 2 14 8 20 8"></polyline>
                        <line x1="16" y1="13" x2="8" y2="13"></line>
                        <line x1="16" y1="17" x2="8" y2="17"></line>
                        <polyline points="10 9 9 9 8 9"></polyline>
                    </svg>
                    Last quarter
                </a>
            </li>
            <li class="nav-item">
                <a class="nav-link" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">
                    <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-file-text">
                        <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
                        <polyline points="14 2 14 8 20 8"></polyline>
                        <line x1="16" y1="13" x2="8" y2="13"></line>
                        <line x1="16" y1="17" x2="8" y2="17"></line>
                        <polyline points="10 9 9 9 8 9"></polyline>
                    </svg>
                    Social engagement
                </a>
            </li>
            <li class="nav-item">
                <a class="nav-link" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">
                    <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-file-text">
                        <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
                        <polyline points="14 2 14 8 20 8"></polyline>
                        <line x1="16" y1="13" x2="8" y2="13"></line>
                        <line x1="16" y1="17" x2="8" y2="17"></line>
                        <polyline points="10 9 9 9 8 9"></polyline>
                    </svg>
                    Year-end sale
                </a>
            </li>
        </ul>
    </div>
</nav>



</html>

img

在list以及dashoboard页面中去引入,可以删掉原来的nav

dashboard.html

<!--头部  -->
<div th:replace="~{commons/commons::topbar}"></div>
   
<!--侧边-->
<div th:replace="~{commons/commons::sidebar(active='main.html')}"></div>

list.html

<!--头部 -->
<div th:replace="~{commons/commons::topbar}"></div>
<!--侧边-->
<div th:replace="~{commons/commons::sidebar(active='list.html')}"></div>

高亮设置

list.html

<!--侧边-->
<div th:replace="~{commons/commons::sidebar(active='list.html')}"></div>

dashboard.html

<!--侧边-->
   <div th:replace="~{commons/commons::sidebar(active='main.html')}"></div>

测试

img

遍历我们的员工信息

<main role="main" class="col-md-9 ml-sm-auto col-lg-10 pt-3 px-4">
   <h2><a class="btn btn-sm btn-success" th:href="@{/emp}">添加员工</a> </h2>



   <div class="table-responsive">
      <table class="table table-striped table-sm">
         <thead>
            <tr>
               <th>id</th>
               <th>lastName</th>
               <th>email</th>
               <th>gender</th>
               <th>department</th>
               <th>birth</th>
               <th>操作</th>
            </tr>
         </thead>
         <tbody>
            <tr th:each="emp:${emps}">
               <td th:text="${emp.getId()}"></td>
               <td th:text="${emp.getLastName()}"></td>
               <td th:text="${emp.getEmail()}"></td>
               <td th:text="${emp.getGender()==0?'':''}"></td>
               <td th:text="${emp.department.getDepartmentName()}"></td>
               <td th:text="${#dates.format(emp.getBirth(),'yyyy-MM-dd HH:mm:ss')}"></td>
               <td>
                  <button class="btn btn-sm btn-primary">编辑</button>
                  <button class="btn btn-sm btn-danger">删除</button>
               </td>
            </tr>
         </tbody>
      </table>
   </div>
</main>

测试

img

7、员工管理系统:添加员工信息

  1. 在list.html页面中将添加员工信息改为超链接
<!--添加员工按钮-->
<h2> <a class="btn btn-sm btn-success" href="emp" th:href="@{/emp}">添加员工</a></h2>

img

2.编写对应的controller层


    @GetMapping("/emp")
    public String toAddpage(Model model) {
    
    
        //查出所有部门的信息
        Collection<Department> departments = departmentDao.getDepartments();
        model.addAttribute("departments",departments);
        return "emp/add";
    }


3.添加前端页面;复制list页面,修改即可

bootstrap官网文档 : https://v4.bootcss.com/docs/4.0/components/forms/ , 找自己喜欢的样式!

            <main role="main" class="col-md-9 ml-sm-auto col-lg-10 pt-3 px-4">

               <form th:action="@{/emp}" method="post">
                  <table class="table table-striped sortable">
                     <thead>
                     </thead>
                     <tbody>
                     <tr>
                        <th>LastName</th>
                        <td><input type="text" name="lastName"/><span class="error"/></td>
                     </tr>
                     <tr>
                        <th>Email</th>
                        <td><input type="email" name="email"/></td>
                     </tr>
                     <tr>
                        <th>Gender</th>
                        <td>
                           <div class="form-check form-check-inline">
                              <input class="form-check-input" type="radio" name="gender" value="1">
                              <label class="form-check-label"></label>
                           </div>
                           <div class="form-check form-check-inline">
                              <input class="form-check-input" type="radio" name="gender" value="0">
                              <label class="form-check-label"></label>
                           </div>
                        </td>
                     </tr>
                     <tr>
                        <th>department</th>
                        <td>
<!--                           我们在controller接收到的时一个Employee  所以我们需要提交的是其中的一个属性-->
                           <select class="form-control" name="department.id">
                           <option th:each="dept:${departments}" th:text="${dept.getDepartmentName()}"
                                 th:value="${dept.getId()}"></option>
                           </select>
                        </td>
                     </tr>
                     <tr>
                        <th>Birth</th>
                        <td><input type="text" name="birth"/></td>
                     </tr>
                     <tr>
                        <td></td>
                        <td>
                           <input class="btn btn-success" type="submit" value="添加"/>&nbsp;&nbsp;&nbsp;
                           <input class="btn btn-danger" type="reset" value="重置"/>
                        </td>
                        <td></td>
                        <td></td>
                        <td></td>
                     </tr>
                     </tbody>
                  </table>
               </form>

            </main>

4.部门信息下拉框应该选择的是我们提供的数据,所以我们要修改一下前端和后端

@GetMapping("/emp")
public String toAddpage(Model model) {
    
    
    //查出所有部门的信息
    Collection<Department> departments = departmentDao.getDepartments();
    model.addAttribute("departments",departments);
    return "emp/add";
}
       <--我们在controller接收到的时一个Employee  所以我们需要提交的是其中的一个属性-->
                           <select class="form-control" name="department.id">
                           <option th:each="dept:${departments}" th:text="${dept.getDepartmentName()}"
                                 th:value="${dept.getId()}"></option>
                           </select>

5、接收前端传过来的属性,将它封装成为对象!首先需要将前端页面空间的name属性编写完毕!然后编写controller层

@PostMapping("/emp")
public String addEmp(Employee employee) {
    
    
    System.out.println("save==>"+employee);
    //添加的操作 forward
    employeeDao.save(employee);  //调用底层业务方法保存员工信息

    return "redirect:/emps";
}

6、在application.properties修改时间格式

# 日期格式化
spring.mvc.date-format=yyyy-MM-dd

测试

img

img

8、员工管理系统:修改员工信息

实现员工修改功能,需要实现两步;

1.点击修改按钮,去到编辑页面,我们可以直接使用添加员工的页面实现

2.显示原数据,修改完毕后跳回列表页面!

首先修改跳转链接的位置;

<a class="btn btn-sm btn-primary" th:href="@{/emp/}+${emp.getId()}">编辑</a>

编写对应的controller

//员工的修改页面
@GetMapping("emp/{id}")
public  String toUpdateEmp(@PathVariable("id")Integer id,Model model){
    
    
    //查出原来的数据
    Employee employee = employeeDao.getEmployeeById(id);
    model.addAttribute("emp",employee);
    //查出所有部门的信息
    Collection<Department> departments = departmentDao.getDepartments();
    model.addAttribute("departments",departments);
    return "emp/update";
}

将add页面复制一份,改为update页面;需要修改页面,将我们后台查询数据回显

<main role="main" class="col-md-9 ml-sm-auto col-lg-10 pt-3 px-4">

    <form th:action="@{/updateEmp}" method="post">
            <input type="hidden" name="id" th:value="${emp.getId()}">
        <table class="table table-striped sortable">
            <thead>
            </thead>
            <tbody>
            <tr>
                <th>LastName</th>
                <td><input type="text" th:value="${emp.getLastName()}" name="lastName"/><span class="error"/></td>
            </tr>
            <tr>
                <th>Email</th>
                <td><input type="email" th:value="${emp.getEmail()}" name="email"/></td>
            </tr>
            <tr>
                <th>Gender</th>
                <td>
                    <div class="form-check form-check-inline">
                        <input th:checked="${emp.getGender()==1}" class="form-check-input" type="radio" name="gender" value="1">
                        <label class="form-check-label"></label>
                    </div>
                    <div class="form-check form-check-inline">
                        <input  th:checked="${emp.getGender()==0}" class="form-check-input" type="radio" name="gender" value="0">
                        <label class="form-check-label"></label>
                    </div>
                </td>
            </tr>
            <tr>
                <th>department</th>
                <td>
                    <!--                           我们在controller接收到的时一个Employee  所以我们需要提交的是其中的一个属性-->
                    <select class="form-control" name="department.id">
                        <option th:selected="${dept.getId()==emp.getDepartment().getId()}"
                                th:each="dept:${departments}" th:text="${dept.getDepartmentName()}"
                                th:value="${dept.getId()}"></option>
                    </select>
                </td>
            </tr>
            <tr>
                <th>Birth</th>
                <td><input th:value="${#dates.format(emp.getBirth(),'yyyy-MM-dd HH:mm')}" type="text" name="birth"/></td>
            </tr>

            </tbody>
        </table>
        <button type="submit" class="btn btn-primary">修改</button>
    </form>

</main>

日期显示不完美,可以使用日期工具,进行日期的格式化!

<td><input th:value="${#dates.format(emp.getBirth(),'yyyy-MM-dd HH:mm')}" type="text" name="birth"/></td>

修改表单提交的地址:

<form th:action="@{/updateEmp}" method="post">

编写对应的controller

//员工信息修改
@PostMapping("/updateEmp")
public String updateEmp(Employee employee){
    
    
    employeeDao.save(employee);
    return "redirect:/emps";
}

现页面提交的没有id;我们在前端加一个隐藏域,提交id;

<input name="id" type="hidden" class="form-control" th:value="${emp.id}">

img

9、员工管理系统:删除员工

list页面,编写提交地址

<a class="btn btn-sm btn-danger" th:href="@{/delEmp/}+${emp.id}">删除</a>

编写Controller

    @GetMapping("/delEmp/{id}")
    public String delEmp(@PathVariable("id") Integer id){
    
    
        employeeDao.delete(id);
        return "redirect:/emps";
    }

10、员工管理系统:404错误页面

img

11、员工管理系统:注销功能

<a class="nav-link" href="#" th:href="@{/user/logout}">退出</a>

对应的controller

    @GetMapping("/user/logout")
    public String loginOut(HttpSession session){
    
    
        session.invalidate();
        return "redirect:/index.html";
    }

15、SpringBoot:Mybatis + Druid 数据访问

1、简介

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

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

SpingData 官网:https://spring.io/projects

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

2、JDBC

新建一个项目测试:springboot-data; 引入相应的模块!基础模块

img

项目建好之后,发现自动帮我们导入了如下的启动器:

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

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <scope>runtime</scope>
</dependency>

实现数据库的访问

  1. 先连接上数据库 , 直接使用IDEA连接即可【操作】

  2. SpringBoot中,我们只需要简单的配置就可以实现数据库的连接了;

我们使用yaml的配置文件进行操作!

spring:
  datasource:
    username: root
    password: 123456
    #?serverTimezone=UTC解决时区的报错
    url: jdbc:mysql://localhost:3306/mybatis?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8

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

package com.kk;

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

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;

@SpringBootTest
class Springboot04DataApplicationTests {
    
    

    @Autowired
    DataSource dataSource;
    @Test
    void contextLoads() throws SQLException {
    
    
        //查看默认的数据源 class com.zaxxer.hikari.HikariDataSource   dbcp
        System.out.println(dataSource.getClass());
        //获得数据库连接
        Connection connection = dataSource.getConnection();
        System.out.println(connection);
        connection.close();


    }

}

在运行的时候可能会遇到的Bug

SpringBoot 中 Invalid character found in the request target 异常

在启动类中添加

@Bean
public ConfigurableServletWebServerFactory webServerFactory() {
    TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory();
    factory.addConnectorCustomizers(new TomcatConnectorCustomizer() {
        @Override
        public void customize(Connector connector) {
            connector.setProperty("relaxedQueryChars", "|{}[]");
        }
    });
    return factory;
}

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

​ 全局搜索一下,找到数据源的所有自动配置都在 :DataSourceProperties 文件下;这里自动配置的原理以及能配置哪些属性?

  • Spring Boot 2.1.7 默认使用 com.zaxxer.hikari.HikariDataSource 数据源,
  • 而以前版本,如 Spring Boot 1.5 默认使用 org.apache.tomcat.jdbc.pool.DataSource 作为数据源;

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

有了数据库连接,显然就可以 CRUD 操作数据库了。

3、CRUD操作

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

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

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

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

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

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

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

测试:

package com.kk.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

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

@RestController
public class JDBCController {
    
    
    
    //JdbcTemplate 是 core 包的核心类,用于简化 JDBC操作,还能避免一些常见的错误,如忘记关闭数据库连接
    //Spring Boot 默认提供了数据源,默认提供了 org.springframework.jdbc.core.JdbcTemplate
    //JdbcTemplate 中会自己注入数据源,使用起来也不用再自己来关闭数据库连接
    @Autowired
    JdbcTemplate jdbcTemplate;
    
   	//查询数据库的所有信息
    //没有实体类,数据库中的东西怎么获取? Map
   	//List 中的1个 Map 对应数据库的 1行数据
    //Map 中的 key 对应数据库的字段名,value 对应数据库的字段值
    @GetMapping("/userList")
    public List<Map<String ,Object>> userList(){
    
    
        String sql="select * from user";
        List<Map<String, Object>> list_maps = jdbcTemplate.queryForList(sql);
        return list_maps;
    }

    @GetMapping("/addUser")
    public String addUser(){
    
    
        String sql="insert into mybatis.user(id,name,pwd) values(2,'dabai','123456')";
       jdbcTemplate.update(sql);
        return "update-ok";
    }

    @GetMapping("/updateUser/{id}")
    public String updateUser(@PathVariable("id") int id){
    
    
        String sql="update mybatis.user set name=?,pwd=? where id="+id;
        //封装
        Object[] objects = new Object[2];
        objects[0]="xiaobai";
        objects[1]="9999999";
        jdbcTemplate.update(sql,objects);
        return "updateUser-ok";
    }

    @GetMapping("/deleteUser/{id}")
    public String deleteUser(@PathVariable("id") int id){
    
    
        String sql="delete from mybatis.user where id=?";
        jdbcTemplate.update(sql,id);
        return "deleteUser-ok";
    }
}

测试成功!

原理探究 :

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

SpringBoot默认支持以下数据源:

  1. com.zaxxer.hikari.HikariDataSource (Spring Boot 2.0 以上,默认使用此数据源)

  2. org.apache.tomcat.jdbc.pool.DataSource

  3. org.apache.commons.dbcp2.BasicDataSource

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

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

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

4、自定义数据源 DruidDataSource

DRUID 简介

  1. ​ Druid 是阿里巴巴开源平台上一个数据库连接池实现,结合了 C3P0、DBCP、PROXOOL 等 DB 池的优点,同时 加入了日志监控。
  2. ​ Druid 可以很好的监控 DB 池连接和 SQL 的执行情况,天生就是针对监控而生的 DB 连接池。
  3. ​ Spring Boot 2.0 以上默认使用 Hikari 数据源,可以说 Hikari 与 Driud 都是当前 Java Web 上最优秀的数据源,我们 来重点介绍 Spring Boot 如何集成 Druid 数据源,如何实现数据库监控。

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

配置 缺省值 说明
name 配置这个属性的意义在于,如果存在多个数据源,监控的时候可以通过名字来区分开来。 如果没有配置,将会生成一个名字,格式是:“DataSource-” + System.identityHashCode(this). 另外配置此属性至少在1.0.5版本中是不起作用的,强行设置name会出错 详情-点此处
url 连接数据库的url,不同数据库不一样。例如: mysql : jdbc:mysql://10.20.153.104:3306/druid2 oracle : jdbc:oracle:thin:@10.20.149.85:1521:ocnauto
username 连接数据库的用户名
password 连接数据库的密码。如果你不希望密码直接写在配置文件中,可以使用ConfigFilter。详细看这里:https://github.com/alibaba/druid/wiki/使用ConfigFilter
driverClassName 根据url自动识别 这一项可配可不配,如果不配置druid会根据url自动识别dbType,然后选择相应的driverClassName
initialSize 0 初始化时建立物理连接的个数。初始化发生在显示调用init方法,或者第一次getConnection时
maxActive 8 最大连接池数量
maxIdle 8 已经不再使用,配置了也没效果
minIdle 最小连接池数量
maxWait 获取连接时最大等待时间,单位毫秒。配置了maxWait之后,缺省启用公平锁,并发效率会有所下降,如果需要可以通过配置useUnfairLock属性为true使用非公平锁。
poolPreparedStatements false 是否缓存preparedStatement,也就是PSCache。PSCache对支持游标的数据库性能提升巨大,比如说oracle。在mysql下建议关闭。
maxOpenPreparedStatements -1 要启用PSCache,必须配置大于0,当大于0时,poolPreparedStatements自动触发修改为true。在Druid中,不会存在Oracle下PSCache占用内存过多的问题,可以把这个数值配置大一些,比如说100
validationQuery 用来检测连接是否有效的sql,要求是一个查询语句。如果validationQuery为null,testOnBorrow、testOnReturn、testWhileIdle都不会其作用。
validationQueryTimeout 单位:秒,检测连接是否有效的超时时间。底层调用jdbc Statement对象的void setQueryTimeout(int seconds)方法
testOnBorrow true 申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。
testOnReturn false 归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能
testWhileIdle false 建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。
timeBetweenEvictionRunsMillis 1分钟(1.0.14) 有两个含义: 1) Destroy线程会检测连接的间隔时间,如果连接空闲时间大于等于minEvictableIdleTimeMillis则关闭物理连接 2) testWhileIdle的判断依据,详细看testWhileIdle属性的说明
numTestsPerEvictionRun 不再使用,一个DruidDataSource只支持一个EvictionRun
minEvictableIdleTimeMillis 30分钟(1.0.14) 连接保持空闲而不被驱逐的最长时间
connectionInitSqls 物理连接初始化的时候执行的sql
exceptionSorter 根据dbType自动识别 当数据库抛出一些不可恢复的异常时,抛弃连接
filters 属性类型是字符串,通过别名的方式配置扩展插件,常用的插件有: 监控统计用的filter:stat 日志用的filter:log4j 防御sql注入的filter:wall
proxyFilters 类型是List<com.alibaba.druid.filter.Filter>,如果同时配置了filters和proxyFilters,是组合关系,并非替换关系

引入数据源

<!-- https://mvnrepository.com/artifact/com.alibaba/druid -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.2.6</version>
</dependency>

查看项目依赖,导入成功!

img

切换数据源;

​ 之前已经说过 Spring Boot 2.0 以上默认使用 com.zaxxer.hikari.HikariDataSource 数据源,可以通过 spring.datasource.type 指定数据源。

type: com.alibaba.druid.pool.DruidDataSource

img

设置数据源连接初始化大小、最大连接数、等待时间、最小连接数 等设置项;

#Spring Boot 默认是不注入这些属性值的,需要自己绑定
#druid 数据源专有配置
initialSize: 5
minIdle: 5
maxActive: 20
maxWait: 60000
timeBetweenEvictionRunsMillis: 60000
minEvictableIdleTimeMillis: 300000
validationQuery: SELECT 1 FROM DUAL
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
poolPreparedStatements: true
#配置监控统计拦截的filters,stat:监控统计、log4j:日志记录、wall:防御sql注入
#如果允许时报错  java.lang.ClassNotFoundException: org.apache.log4j.Priority
#则导入 log4j 依赖即可,Maven 地址: https://mvnrepository.com/artifact/log4j/log4j
filters: stat,wall,log4j
maxPoolPreparedStatementPerConnectionSize: 20
useGlobalDataSourceStat: true
connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500

log4****日志依赖

<!-- https://mvnrepository.com/artifact/log4j/log4j -->
<dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    <version>1.2.17</version>
</dependency>

测试

public class SpringbootDemoDataApplicationTests {
    
    

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

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

        DruidDataSource druidDataSource = (DruidDataSource) dataSource;
        System.out.println("druidDataSource 数据源最大连接数:" + druidDataSource.getMaxActive());
        System.out.println("druidDataSource 数据源初始化连接数:" + druidDataSource.getInitialSize());

        //关闭连接
        connection.close();
    }

}

测试成功!

1、配置 Druid 数据源监控

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

​ 所以第一步需要设置 Druid 的后台管理页面,比如登录账号、密码等配置后台管理;

package com.kk.config;

import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.support.http.StatViewServlet;
import com.alibaba.druid.support.http.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.servlet.Filter;
import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;

@Configuration
public class DruidConfig {
    
    

    @ConfigurationProperties(prefix = "spring.datasource")  //关联application.yaml中的spring.datasource
    @Bean
    public DataSource druidDataSource(){
    
    
        return new DruidDataSource();
    }

    //配置 Druid 监控管理后台的Servlet;
    //内置 Servler 容器时没有web.xml文件,所以使用 Spring Boot 的注册 Servlet 方式
    @Bean
    public ServletRegistrationBean statViewServlet() {
    
    
        ServletRegistrationBean bean = new ServletRegistrationBean(new StatViewServlet(), "/druid/*");

        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("kuangshen", "192.168.1.20");表示禁止此ip访问

        //设置初始化参数
        bean.setInitParameters(initParams);
        return bean;
        //这些参数可以在 com.alibaba.druid.support.http.StatViewServlet 的父类 com.alibaba.druid.support.http.ResourceServlet 中找到
    }



    //filter
    @Bean
    public FilterRegistrationBean webStatFilter(){
    
    
        FilterRegistrationBean<Filter> bean = new FilterRegistrationBean<>();
        bean.setFilter(new WebStatFilter());

        //可以过滤哪些请求?
        HashMap<String, String> initParameters = new HashMap<>();
        //这些东西不进行统计
        initParameters.put("exclusions","*.js,*.css,/druid/*");


        bean.setInitParameters(initParameters);
        return bean;
    }
}

测试访问! http://localhost:8080/druid/login.html

img

img

img

2、配置 Druid web 监控 filter

​ 这个过滤器的作用就是统计 web 应用请求中所有的数据库信息,比如 发出的 sql 语句,sql 执行的时间、请求次数、请求的 url 地址、以及seesion 监控、数据库表的访问次数 等等。

//filter
@Bean
public FilterRegistrationBean webStatFilter(){
    
    
    FilterRegistrationBean<Filter> bean = new FilterRegistrationBean<>();
    bean.setFilter(new WebStatFilter());

    //可以过滤哪些请求?
    HashMap<String, String> initParameters = new HashMap<>();
    //这些东西不进行统计
    initParameters.put("exclusions","*.js,*.css,/druid/*");


    bean.setInitParameters(initParameters);
    return bean;
}

img

执行sql

img

执行sql之后会有记录

img

5、SpringBoot 整合mybatis

1. 导入mybatis所需要的依赖

<!-- https://mvnrepository.com/artifact/org.mybatis.spring.boot/mybatis-spring-boot-starter -->
<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>2.2.0</version>
</dependency>

2.配置数据库连接信息

# 数据库驱动:
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
# 数据源名称
spring.datasource.name=defaultDataSource
# 数据库连接地址
spring.datasource.url=jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC
# 数据库用户名&密码:
spring.datasource.username=root
spring.datasource.password=123456

测试一下连接是否成功!

package com.kk;

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

import javax.sql.DataSource;
import java.sql.SQLException;

@SpringBootTest
class Springboot05MybatisApplicationTests {
    
    

    @Autowired
    DataSource dataSource;
    @Test
    void contextLoads() throws SQLException {
    
    
        System.out.println(dataSource.getClass());
        System.out.println(dataSource.getConnection());
    }

}

测试成功!

3,创建实体类

package com.kk.pojo;

public class User {
    
    
    private int id;
    private String name;
    private String pwd;

    public User() {
    
    
    }

    public User(int id, String name, String pwd) {
    
    
        this.id = id;
        this.name = name;
        this.pwd = pwd;
    }

    public int getId() {
    
    
        return id;
    }

    public void setId(int id) {
    
    
        this.id = id;
    }

    public String getName() {
    
    
        return name;
    }

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

    public String getPwd() {
    
    
        return pwd;
    }

    public void setPwd(String pwd) {
    
    
        this.pwd = pwd;
    }

    @Override
    public String toString() {
    
    
        return "User{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", pwd='" + pwd + '\'' +
                '}';
    }
}

4.配置Mapper接口类

package com.kk.mapper;

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

import java.util.List;

//这个注解表示 这是一个mybatis 的mapper类
@Mapper  //controller层
@Repository  //spring
public interface UserMapper {
    
    



    List<User> queryUserList();

    User queryUserById(int id);

    int addUser(User user);

    int updateUser(User user);

    int deleteUser(int id);




}

5.编写controller

package com.kk.controller;

import com.kk.mapper.UserMapper;
import com.kk.pojo.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@RestController
public class UserController {
    
    

    @Autowired
    private UserMapper userMapper;

    @GetMapping("/queryUserList")
    public List<User> queryUserList(){
    
    
        List<User> userList = userMapper.queryUserList();
        for (User user : userList) {
    
    
            System.out.println(user);
        }
        return userList;
    }

    //根据id选择用户
    @GetMapping("/queryUserById")
    public String selectUserById(){
    
    
        User user = userMapper.queryUserById(1);
        System.out.println(user);
        return "ok";
    }
    //添加一个用户
    @GetMapping("/addUser")
    public String addUser(){
    
    
        userMapper.addUser(new User(1,"大黄","9999999"));
        userMapper.addUser(new User(3,"大黄","9999999"));
        userMapper.addUser(new User(4,"大黄","9999999"));

        return "ok";
    }
    //修改一个用户
    @GetMapping("/updateUser")
    public String updateUser(){
    
    
        userMapper.updateUser(new User(5,"黄毛","999999"));
        return "ok";
    }
    //根据id删除用户
    @GetMapping("/deleteUser")
    public String deleteUser(){
    
    
        userMapper.deleteUser(5);
        return "ok";
    }
}

img

6.SpringBoot 整合

​ 以前 MyBatis 未与 spring 整合时,配置数据源、事务、连接数据库的账号、密码等都是在 myBatis 核心配置文件中进行的
​ myBatis 与 springboot 整合后,配置数据源、事务、连接数据库的账号、密码等就交由 spring 管理。因此,在这里我们即使不使用mybatis配置文件也完全ok!
既然已经提供了 myBatis 的映射配置文件,就需要告诉 spring boot 这些文件的位置

# 整合mybatis
# 设置别名以及设置能让spring识别
#指定myBatis的核心配置文件与Mapper映射文件
# 注意:对应实体类的路径
mybatis.type-aliases-package=com.kk.pojo
mybatis.mapper-locations=classpath:mybatis/mapper/*.xml

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

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

从 org.mybatis.spring.boot.autoconfigure.MybatisProperties 中查看所有配置项

@ConfigurationProperties(
    prefix = "mybatis"
)
public class MybatisProperties {
    
    
    public static final String MYBATIS_PREFIX = "mybatis";
    private static final ResourcePatternResolver resourceResolver = new PathMatchingResourcePatternResolver();
    private String configLocation;
    private String[] mapperLocations;
    private String typeAliasesPackage;
    private Class<?> typeAliasesSuperType;
    private String typeHandlersPackage;
    private boolean checkConfigLocation = false;
    private ExecutorType executorType;
    private Class<? extends LanguageDriver> defaultScriptingLanguageDriver;
    private Properties configurationProperties;
    @NestedConfigurationProperty
    private Configuration configuration;

官方文档

16、SpringSecurity (安全)

在web开发中,安全第一位! 过滤器, 拦截器等

做网站:安全应该在什么时候考虑?设计之初!

●漏洞,隐私泄露

●架构一旦确定

shiro、SpringSecurity: 很像除了类不一样,名字不一样;

认证,授权(vip1, vip2, vip3)

●功能权限
●访问权限
●菜单权限

●… 拦截器,过滤器:大量的原生代码比较冗余

mvc- spring-springboot-框架思想

AOP :横切~配置类

1、SpringSecurity 简介

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

记住几个类:

  1. WebSecurityConfigurerAdapter: 自定义Security策略
  2. AuthenticationManagerBuilder: 自定义认证策略
  3. @EnableWebSecurity: 开启WebSecurity模式,@Enablexxx 开启某个功能

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

  1. “认证”(Authentication)
  2. "授权”(Authorization)

这个概念是通用的,不是只在SpringSecurity中存在。

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

查看我们自己项目中的版本,找到对应的帮助文档:

https://docs. spring.io/spring-security/site/docs/5.2.0.RELEASE/reference/htmlsingle

1、SpringSecurity环境搭建

2、用户认证和授权

17、Shiro(安全)

1、Shiro简介

​ Apache Shiro是一个强大且易用的Java安全框架,执行身份验证、授权、密码和会话管理。使用Shiro的易于理解的API,您可以快速、轻松地获得任何应用程序,从最小的移动应用程序到最大的网络和企业应用程序。

Shiro可以非常容易的开发出足够好的应用,不仅可以用在JavaSE环境,也可以用在JavaEE环境。

下载地址: http://shiro.apache.org/

2、Shiro有哪些功能?

img

  1. Authentication: 身份认证、登录,验证用户是不是拥有相应的身份;
  2. Authorization:授权,即权限验证,验证某个已认证的用户是否拥有某个权限,即判断用户能否进行什么操
  3. 作,如:验证某个用户是否拥有某个角色,或者细粒度的验证某个用户对某个资源是否具有某个权限!
  4. Session Manager:会话管理,即用户登录后就是第一次会话,在没有退出之前,它的所有信息都在会话中;
  5. 会话可以是普通的avaSE环境,也可以是Web环境;
  6. Cnyptography: 加密,保护数据的安全性,如密码加密存储到数据库中,而不是明文存储;
  7. Web Support: Web支持,可以非常容易的集成到Web环境;
  8. Caching: 缓存,比如用户登录后,其用户信息,拥有的角色、权限不必每次去查,这样可以提高效率
  9. Concurrency: Shiro支持多线程应用的并发验证,即,如在一个线程中开启另一 个线程,能把权限自动的传
    播过去
  10. Testing:提供测试支持;
  11. Run As: 允许一 个用户假装为另- -个用户(如果他们允许)的身份进行访问;
  12. Remember Me:记住我,这个是非常常见的功能,即一次登录后,下次再来的话不用登录了

3、Shiro架构(外部)

img

  1. subject: 应用代码直接交互的对象是Subject, 也就是说Shiro的对外API核心就是Subject, Subject代表了当
    前的用户,这个用户不-定是一个具体的人, 与当前应用交互的任何东西都是Subject,如网络爬虫,机器人
    等,与Subject的所有交互都会委托给SecurityManager; Subject其实是一 个门面, SecurityManageer 才是
    实际的执行者
  2. SecurityManager: 安全管理器,即所有与安全有关的操作都会与SercurityManager交互,并且它管理着所有
    的Subject,可以看出它是Shiro的核心,它负责与Shiro的其他组件进行交互,它相当于SpringMVC的
    DispatcherServlet的角色
  3. Realm: Shiro从Realm获取安全数据 (如用户,色,权限),就是说SecurityManager 要验证用户身份,
    那么它需要从Realm获取相应的用户进行比较,来确定用户的身份是否合法;也需要从Realm得到用户相应的
    角色、权限,进行验证用户的操作是否能够进行,可以把Realm看成DataSource;

4、Shiro架构(内部)

img

  1. Subject: 任何可以与应用交互的"用户;3
  2. Security Manager:相当于SpringMVC中的DispatcherServlet;是Shiro的心脏,所有具体的交互都通过
  3. Security Manager进行控制,它管理者所有的Subject,且负责进行认证,授权,会话,及缓存的管理。
  4. Authenticator: 负责Subject认证,是一一个扩 展点,可以自定义实现;可以使用认证策略(AuthenticationStrategy),即什么情况下算用户认证通过了;
  5. Authorizer:授权器,即访问控制器,用来决定主体是否有权限进行相应的操作;即控制着用户能访问应用中
    的那些功能;
  6. Realm: 可以有一个或者多个的realm,可以认为是安全实体数据源,即用于获取安全实体的,可以用DBC实
    现,也可以是内存实现等等,由用户提供;所以- -般在应用中都需要实现自己的realm
  7. SessionManager: 管理Session生命周期的组件,而Shiro并不仅仅可以用在Web环境,也可以用在普通的
    JavaSE环境中
  8. CacheManager: 缓存控制器,来管理如用户,角色,权限等缓存的;因为这些数据基本上很少改变,放到缓
    存中后可以提高访问的性能;
  9. Cryptography:密码模块, Shiro 提高了一些常见的加密组件用于密码加密, 解密等

5、进入实践

打开官网文档: http://shiro.apache.org//tutorial.html

  1. 创建一个普通的maven父工程
  2. 创建一个普通的Maven子工程: shiro-01-helloworld
  3. 根据官方文档,我们来导入Shiro的依赖

1、导入依赖

<dependencies>
    <!-- https://mvnrepository.com/artifact/org.apache.shiro/shiro-core -->
    <dependency>
        <groupId>org.apache.shiro</groupId>
        <artifactId>shiro-core</artifactId>
        <version>1.8.0</version>
    </dependency>


    <!-- configure logging -->
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>jcl-over-slf4j</artifactId>
        <version>1.7.21</version>
    </dependency>
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-log4j12</artifactId>
        <version>1.7.21</version>
    </dependency>
    <!-- https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-core -->
    <dependency>
        <groupId>org.apache.logging.log4j</groupId>
        <artifactId>log4j-core</artifactId>
        <version>2.14.1</version>
    </dependency>

</dependencies>

2、配置文件

resources目录下

log4j.properties

log4j.rootLogger=INFO, stdout

log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - %m %n

# General Apache libraries
log4j.logger.org.apache=WARN

# Spring
log4j.logger.org.springframework=WARN

# Default Shiro logging
log4j.logger.org.apache.shiro=INFO

# Disable verbose logging
log4j.logger.org.apache.shiro.util.ThreadContext=WARN
log4j.logger.org.apache.shiro.cache.ehcache.EhCache=WARN

shiro.ini,需要安装ini插件

img

[users]
# user 'root' with password 'secret' and the 'admin' role
root = secret, admin
# user 'guest' with the password 'guest' and the 'guest' role
guest = guest, guest
# user 'presidentskroob' with password '12345' ("That's the same combination on
# my luggage!!!" ;)), and role 'president'
presidentskroob = 12345, president
# user 'darkhelmet' with password 'ludicrousspeed' and roles 'darklord' and 'schwartz'
darkhelmet = ludicrousspeed, darklord, schwartz
# user 'lonestarr' with password 'vespa' and roles 'goodguy' and 'schwartz'
lonestarr = vespa, goodguy, schwartz

# -----------------------------------------------------------------------------
# Roles with assigned permissions
#
# Each line conforms to the format defined in the
# org.apache.shiro.realm.text.TextConfigurationRealm#setRoleDefinitions JavaDoc
# -----------------------------------------------------------------------------
[roles]
# 'admin' role has all permissions, indicated by the wildcard '*'
admin = *
# The 'schwartz' role can do anything (*) with any lightsaber:
schwartz = lightsaber:*
# The 'goodguy' role is allowed to 'drive' (action) the winnebago (type) with
# license plate 'eagle5' (instance specific id)
goodguy = winnebago:drive:eagle5

3、测试Quickstart

/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

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) {
    
    

        // The easiest way to create a Shiro SecurityManager with configured
        // realms, users, roles and permissions is to use the simple INI config.
        // We'll do that by using a factory that can ingest a .ini file and
        // return a SecurityManager instance:

        // Use the shiro.ini file at the root of the classpath
        // (file: and url: prefixes load from files and urls respectively):

        Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
        SecurityManager securityManager = factory.getInstance();

        // for this simple example quickstart, make the SecurityManager
        // accessible as a JVM singleton.  Most applications wouldn't do this
        // and instead rely on their container configuration or web.xml for
        // webapps.  That is outside the scope of this simple quickstart, so
        // we'll just do the bare minimum so you can continue to get a feel
        // for things.
        SecurityUtils.setSecurityManager(securityManager);

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

        // get the currently executing user:
        //获取当前的用户对象  Subject
        Subject currentUser = SecurityUtils.getSubject();

        // Do some stuff with a Session (no need for a web or EJB container!!!)
        //通过当前对象获取当前用户的Session
        Session session = currentUser.getSession();
        session.setAttribute("someKey", "aValue");
        //将aValue的session保存在someKey中
        String value = (String) session.getAttribute("someKey");
        if (value.equals("aValue")) {
    
    
            log.info("Subject==》session [" + value + "]");
        }

        // let's login the current user so we can check against roles and permissions:
        //判断当前的用户是否被认证
        if (!currentUser.isAuthenticated()) {
    
    
            //Token 令牌
            UsernamePasswordToken token = new UsernamePasswordToken("lonestarr", "vespa");
            token.setRememberMe(true);
            try {
    
    
                currentUser.login(token); //执行 登录操作
            } catch (UnknownAccountException uae) {
    
    //未知账号
                log.info("There is no user with username of " + token.getPrincipal());
            } catch (IncorrectCredentialsException ice) {
    
    //密码错误
                log.info("Password for account " + token.getPrincipal() + " was incorrect!");
            } catch (LockedAccountException lae) {
    
      //锁定账号
                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.");

        //test a role:
        //测试角色  判断当前用户是什么角色
        if (currentUser.hasRole("schwartz")) {
    
    
            log.info("May the Schwartz be with you!");
        } else {
    
    
            log.info("Hello, mere mortal.");
        }

        //test a typed permission (not instance-level)
        //简单 粗粒度
        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!");
        }

        //all done - log out!
        //注销
        currentUser.logout();

        //结束
        System.exit(0);
    }
}

img

这些功能在Spring-Secutiry都有

//获取当前的用户对象  Subject
Subject currentUser = SecurityUtils.getSubject();

    //通过当前对象获取当前用户的Session
   Session session = currentUser.getSession();
   
   //判断当前的用户是否被认证
   currentUser.isAuthenticated()
   
	//获取当前用户信息
   currentUser.getPrincipal()
   
     //测试角色  判断当前用户是什么角色
   currentUser.hasRole("schwartz")
   
   currentUser.isPermitted("lightsaber:wield")
   
     //注销
currentUser.logout();

6、SpringBoot整合Shiro环境搭建

导入相关依赖

<!-- https://mvnrepository.com/artifact/org.apache.shiro/shiro-core -->
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-core</artifactId>
            <version>1.8.0</version>
        </dependency>


<!--shiro整合spring的包-->
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring</artifactId>
            <version>1.8.0</version>
        </dependency>

        <!--thymeleaf模板,我们都是基于3.x开发-->
        <dependency>
            <groupId>org.thymeleaf</groupId>
            <artifactId>thymeleaf-spring5</artifactId>
        </dependency>
        <dependency>
            <groupId>org.thymeleaf.extras</groupId>
            <artifactId>thymeleaf-extras-java8time</artifactId>
        </dependency>

测试SpringBoot环境是否搭建成功

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>首页</h1>
<p th:text="${msg}"></p>
<hr>

<a th:href="@{/user/add}">add</a>

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

controller层

@RequestMapping({
    
    "/","/index"})
public String ToIndex(Model model){
    
    
    model.addAttribute("msg","hello Shiro");
    return "index";
}

img

测试SpringBoot整合Shiro环境搭建成功

Shiro配置

img

package com.kk.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;

//自定义的 UserRealm         extends AuthorizingRealm
public class UserRealm extends AuthorizingRealm {
    
    

    //授权
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
    
    
        System.out.println("执行科=》授权doGetAuthorizationInfo");
        return null;
    }


    //认证
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
    
    
        System.out.println("执行科=》认证doGetAuthenticationInfo");
        return null;
    }
}

ShiroConfig

package com.kk.config;import org.apache.shiro.mgt.DefaultSecurityManager;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.HashMap;import java.util.LinkedHashMap;import java.util.Map;@Configurationpublic class ShiroConfig {
    
        //ShiroFilterFactortBean  第三步    @Bean    public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager defaultWebSecurityManager) {        ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();        //设置安全管理器        bean.setSecurityManager(defaultWebSecurityManager);        return bean;    }    //DefaultWebSecurityManager  第二步    @Bean(name = "securityManager")    public DefaultWebSecurityManager getDefaultwebSecurityManager(@Qualifier("userRealm") UserRealm userRealm) {        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();        //.关联UserReaLm        securityManager.setRealm(userRealm);        return securityManager;    }    //创建 realm 对象  需要自定义  第一步    @Bean    public UserRealm userRealm() {        return new UserRealm();    }}

MyController

package com.kk.controller;import org.springframework.jmx.export.annotation.ManagedResource;import org.springframework.stereotype.Controller;import org.springframework.ui.Model;import org.springframework.web.bind.annotation.RequestMapping;@Controllerpublic class MyController {
    
        @RequestMapping({
    
    "/","/index"})    public String ToIndex(Model model){
    
            model.addAttribute("msg","hello Shiro");        return "index";    }    @RequestMapping("/user/add")    public String add(){
    
            return "user/add";    }    @RequestMapping("/user/update")    public String update(){
    
            return "user/update";    }    @RequestMapping("/toLogin")    public String toLogin(){
    
            return "login";    }}

index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>首页</h1>
<p th:text="${msg}"></p>
<hr>

<a th:href="@{/user/add}">add</a>

<a th:href="@{/user/update}">update</a>
</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>

7、shiro实现登录拦截

package com.kk.config;

import org.apache.shiro.mgt.DefaultSecurityManager;
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.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;

@Configuration
public class ShiroConfig {
    
    


    //ShiroFilterFactortBean  第三步
    @Bean
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager defaultWebSecurityManager) {
    
    
        ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
        //设置安全管理器
        bean.setSecurityManager(defaultWebSecurityManager);

        //添加shiro的内置过滤器
        /*
        anon : 无需认证就可以访问
       authc:必须认证才能访问
       user:必须拥有 记住我 功能才能用
       perms 拥有某个资源的权限才能访问
       role 拥有某个角色的权限才能访问
         */
        Map<String, String> filterMap = new LinkedHashMap<>();
        filterMap.put("/user/add", "authc");
        filterMap.put("/user/update", "authc");
        bean.setFilterChainDefinitionMap(filterMap);

        //如果没有权限 ,则跳转到登陆页面
        bean.setLoginUrl("/toLogin");

        return bean;

    }


    //DefaultWebSecurityManager  第二步
    @Bean(name = "securityManager")
    public DefaultWebSecurityManager getDefaultwebSecurityManager(@Qualifier("userRealm") UserRealm userRealm) {
    
    
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        //.关联UserReaLm
        securityManager.setRealm(userRealm);
        return securityManager;
    }

    //创建 realm 对象  需要自定义  第一步
    @Bean
    public UserRealm userRealm() {
    
    
        return new UserRealm();
    }


}

login.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>登陆</h1>
<hr>
<form action="">
  <p>用户名:<input type="text" name="username"></p>
  <p>密码:<input type="text" name="password"></p>
  <p><input type="submit"></p>



</form>
</body>
</html>

MyController

@RequestMapping("/toLogin")
public String toLogin(){
    
    
    return "login";
}

8、Shiro实现用户认证

MyController

@RequestMapping("/login")
public String login(String username,String password,Model model){
    
    
    //获取当前的用户
    Subject subject = SecurityUtils.getSubject();
    //封装登陆用户的登陆数据
    UsernamePasswordToken token = new UsernamePasswordToken(username, password);

    try{
    
    
        subject.login(token);//执行登陆方法,如果没有异常就说明ok
        return "index";
    }catch (UnknownAccountException e){
    
    //用户名不存在
        model.addAttribute("msg","用户名错误");
        return "login";
    }catch (IncorrectCredentialsException e){
    
    //密码不存在
        model.addAttribute("msg","密码错误");
        return "login";
    }

}

login.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>登陆</h1>
<hr>
<p th:text="${msg}" style="color: red;"></p>
<form th:action="@{/login}">
  <p>用户名:<input type="text" name="username"></p>
  <p>密码:<input type="text" name="password"></p>
  <p><input type="submit"></p>



</form>
</body>
</html>

UserRealm

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

    //用户名 密码 ---数据库
    String name="root";
    String password="123456";

    UsernamePasswordToken userToken = (UsernamePasswordToken) token;

    if (!userToken.getUsername().equals(name)){
    
    
        return null;//抛出异常UnknownAccountException

    }

    //密码认证  shiro
    return new SimpleAuthenticationInfo("",password,"");

}

9、Shiro整合Mybatis

导入依赖

<!--        连接数据库-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>

        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
        </dependency>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.2.6</version>
        </dependency>
<!--        引入mybatis-springboot-->

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

2.连接数据库application.yml

spring:
  datasource:
    username: root
    password: 981204
    #?serverTimezone=UTC解决时区的报错
    url: jdbc:mysql://localhost:3306/mybatis?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8
    driver-class-name: com.mysql.jdbc.Driver
    type: com.alibaba.druid.pool.DruidDataSource

    #Spring Boot 默认是不注入这些属性值的,需要自己绑定
    #druid 数据源专有配置
    initialSize: 5
    minIdle: 5
    maxActive: 20
    maxWait: 60000
    timeBetweenEvictionRunsMillis: 60000
    minEvictableIdleTimeMillis: 300000
    validationQuery: SELECT 1 FROM DUAL
    testWhileIdle: true
    testOnBorrow: false
    testOnReturn: false
    poolPreparedStatements: true

    #配置监控统计拦截的filters,stat:监控统计、log4j:日志记录、wall:防御sql注入
    #如果允许时报错  java.lang.ClassNotFoundException: org.apache.log4j.Priority
    #则导入 log4j 依赖即可,Maven 地址: https://mvnrepository.com/artifact/log4j/log4j
    filters: stat,wall,log4j
    maxPoolPreparedStatementPerConnectionSize: 20
    useGlobalDataSourceStat: true
    connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500

3.设置application.properties

# 整合mybatis
mybatis.type-aliases-package=com.kk.pojo
mybatis.mapper-locations=classpath:mapper/*.xml

4.实体类

package com.kk.pojo;

public class User {
    
    

    private int id;
    private String name;
    private String pwd;

    public User() {
    
    
    }

    public User(int id, String name, String pwd) {
    
    
        this.id = id;
        this.name = name;
        this.pwd = pwd;
    }

    public int getId() {
    
    
        return id;
    }

    public void setId(int id) {
    
    
        this.id = id;
    }

    public String getName() {
    
    
        return name;
    }

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

    public String getPwd() {
    
    
        return pwd;
    }

    public void setPwd(String pwd) {
    
    
        this.pwd = pwd;
    }

    @Override
    public String toString() {
    
    
        return "User{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", pwd='" + pwd + '\'' +
                '}';
    }
}

5.UserMapper

package com.kk.mapper;import org.apache.ibatis.annotations.Mapper;import org.springframework.stereotype.Repository;@Repository@Mapperpublic class UserMapper {
    
        public User queryUserByName(String name);}

6.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">
<mapper namespace="com.kk.mapper.UserMapper">
    <select id="queryUserByName" resultType="User">
        select * from mybatis.user where name = #{name}
    </select>

</mapper>

7.UserService

public interface UserService {
    
    
    public User queryUserByName(String name);
}

8.UserServiceImpl

package com.kk.service;

import org.springframework.stereotype.Service;

@Service
public class UserServiceImp implements UserService{
    
    
    @Autowired
    UserMapper userMapper;
    
    @Override
    public User queryUserByName(String name) {
    
    
        return userMapper.queryUserByName(name);
    }
}

9.测试查出对应User

@SpringBootTestclass ShiroSpringbootApplicationTests {
    
    @Autowired    UserServiceImpl userService;    @Test    void contextLoads() {
    
            System.out.println(userService.queryUserByName("雏田"));    }}

10、测试成功后 将数据绑定到安全配置中,再次启动配置

package com.kk.config;


import com.kk.pojo.User;
import com.kk.service.UserService;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.subject.Subject;
import org.springframework.beans.factory.annotation.Autowired;

//自定义的 UserRealm         extends AuthorizingRealm
public class UserRealm extends AuthorizingRealm {
    
    

    @Autowired
    UserService userService;

    //授权
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
    
    
        System.out.println("执行了=》授权doGetAuthorizationInfo");
        return null;
    }


    //认证
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
    
    
        System.out.println("执行了=》认证doGetAuthenticationInfo");
        UsernamePasswordToken userToken = (UsernamePasswordToken) token;


        //连接真实数据库
        User user = userService.queryUserByName(userToken.getUsername());


     if (user==null){
    
     //没有这个用户
         return null; //抛出异常UnknownAccountException

     }

        //密码认证  shiro
        //可以加密 MD5加密 MD5盐值加密
        return new SimpleAuthenticationInfo("",user.getPwd(),"");

    }
}

10、Shiro请求授权实现

1、授权正常的情况下,没有授权会跳转到未授权页面

package com.kk.config;

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 {
    
    
    //ShiroFilterFactoryBean:3
    @Bean
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager defaultWebSecurityManager) {
    
    
        ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
        //设置安全管理器
        bean.setSecurityManager(defaultWebSecurityManager);

        /*anon:无需认 证就可以访问
        authc:必须认证了 才能让问
        user:.必须拥有 记住我功能才能用
        perms:
        拥有对某个资源的权限才能访间:
        role:拥有某 个角色权限才能访问
        */
        Map<String, String> filterMap = new LinkedHashMap<>();

        //授权,正常的情况下,没有授权会跳转到未授权页面
        filterMap.put("/user/add", "perms[user:add]");
        filterMap.put("/user/update", "perms[user:update]");
        //第二个为权限,只有persm=user:add/user:update] 才可能进入相应的页面

        filterMap.put("/user/*", "authc");
        //filterMap.put("/user/add", "authc");
        //filterMap.put("/user/update", "authc");
        bean.setFilterChainDefinitionMap(filterMap);

        //设置获录的请求
        bean.setLoginUrl("/toLogin");
        //未授权页面
        bean. setUnauthorizedUrl("/noauth");


        return bean;

    }




    //Dafaul tWebSecurityManager:2
    @Bean(name = "securityManager")
    public DefaultWebSecurityManager getDefaultwebSecurityManager(@Qualifier("userRealm") UserRealm userRealm) {
    
    
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        //关联UserReaLm
        securityManager.setRealm(userRealm);
        return securityManager;
    }



    //创建UserRealm对象,需要自定义类:1
    @Bean
    public UserRealm userRealm() {
    
    
        return new UserRealm();
    }
}

controller

@RequestMapping("/noauth")
@ResponseBody
public String unauthorized() {
    
    
    return "未经授权无法访问此页面";
}

2、授予可以访问的权限

package com.kk.config;

import com.kk.pojo.User;
import com.kk.service.UserService;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.subject.Subject;
import org.springframework.beans.factory.annotation.Autowired;

public class UserRealm extends AuthorizingRealm {
    
    
    @Autowired
    UserService userService;

    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
    
    
        System.out.println("执行了=》授权doGetAuthorizationInfo");
        //SimpleAuthorizationInfo
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        info.addStringPermission("user:add");
     
        return info;
    }

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
    
    
        System.out.println("执行了=》验证doGetAuthorizationInfo");
        UsernamePasswordToken userToken = (UsernamePasswordToken) token;
        //连接真实的数据库
        User user = userService.queryUserByName(userToken.getUsername());
        if (user == null) {
    
     //没有这个用户
            return null; //UnknownAccountException
        }
        //可以加密: MD5: e10adc3949ba59abbe56e057f20f883e MD5 盐值加密: e10adc3949ba59abbe56e057f20f883eusername
        //密码认证, shiro 做~
        return new SimpleAuthenticationInfo("", user.getPwd(), "");


    }
}

3、一般在数据库里添加权限的字段,修改数据库,和实体类

img

实体类

private int id;
    private String name;
    private String pwd;
    private String perms;

UserRealm实现授权的分配

package com.kk.config;

import com.kk.pojo.User;
import com.kk.service.UserService;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.subject.Subject;
import org.springframework.beans.factory.annotation.Autowired;

public class UserRealm extends AuthorizingRealm {
    
    
    @Autowired
    UserService userService;

    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
    
    
        System.out.println("执行了=》授权doGetAuthorizationInfo");
        //SimpleAuthorizationInfo
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        //info.addStringPermission("user:add");
        //拿到当登录 的这个对象
        Subject subject = SecurityUtils.getSubject();
        User currentUser = (User) subject.getPrincipal(); //拿到User对象
        //设置当前用户的权限
        info.addStringPermission(currentUser.getPerms());
        return info;
    }

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
    
    
        System.out.println("执行了=》验证doGetAuthorizationInfo");
        UsernamePasswordToken userToken = (UsernamePasswordToken) token;
        //连接真实的数据库
        User user = userService.queryUserByName(userToken.getUsername());
        if (user == null) {
    
     //没有这个用户
            return null; //UnknownAccountException
        }
        //可以加密: MD5: e10adc3949ba59abbe56e057f20f883e MD5 盐值加密: e10adc3949ba59abbe56e057f20f883eusername
        //密码认证, shiro 做~
        return new SimpleAuthenticationInfo(user, user.getPwd(), "");


    }
}

3.使用shirothymeleaf

在html页面中导入

xmlns:shiro="http://www.thymeleaf.org/thymeleaf-extras-shiro
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org"
      xmlns:shiro="http://www.thymeleaf.org/thymeleaf-extras-shiro">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>首页</h1>
<p>
    <a th:href="@{/toLogin}">登录</a>
</p>
<p th:text="${msg}"></p>
<hr>
<div shiro:hasPermission="user:add">
    <a th:href="@{/user/add}">add</a>
</div>
<div shiro:hasPermission="user:update">
    <a th:href="@{/user/update}">update</a>
</div>

</body>
</html>

4.测试

img

存在登录按钮的问题

UserRealm中添加session

@Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
    
    
        System.out.println("执行了=》验证doGetAuthorizationInfo");
        UsernamePasswordToken userToken = (UsernamePasswordToken) token;
        //连接真实的数据库
        User user = userService.queryUserByName(userToken.getUsername());
        if (user == null) {
    
     //没有这个用户
            return null; //UnknownAccountException
        }


        Subject currentSubject = SecurityUtils.getSubject();
        Session session = currentSubject.getSession();
        session.setAttribute("loginUser",user);



        //可以加密: MD5: e10adc3949ba59abbe56e057f20f883e MD5 盐值加密: e10adc3949ba59abbe56e057f20f883eusername
        //密码认证, shiro 做~
        return new SimpleAuthenticationInfo(user, user.getPwd(), "");


    }
}

index.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org"
      xmlns:shiro="http://www.thymeleaf.org/thymeleaf-extras-shiro">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>首页</h1>
<div th:if="${session.loginUser==null}">
    <a th:href="@{/toLogin}">登录</a>
</div>

<p th:text="${msg}"></p>
<hr>
<div shiro:hasPermission="user:add">
    <a th:href="@{/user/add}">add</a>
</div>
<div shiro:hasPermission="user:update">
    <a th:href="@{/user/update}">update</a>
</div>

</body>
</html>

项目结构

img

18、Swagger

1、Swagger的作用和概念

​ 官方地址:https://swagger.io/

​ Swagger 是一个规范且完整的框架,用于生成、描述、调用和可视化 RESTful 风格的 Web 服务以及 集成Swagger自动生成API文档。

​ Swagger 的目标是对 REST API 定义一个标准且和语言无关的接口,可以让人和计算机拥有无须访问源码、文档或网络流量监测就可以发现和理解服务的能力。当通过 Swagger 进行正确定义,用户可以理解远程服务并使用最少实现逻辑与远程服务进行交互。与为底层编程所实现的接口类似,Swagger 消除了调用服务时可能会有的猜测。

1、Swagger 的优势

  • 支持 API 自动生成同步的在线文档:使用 Swagger 后可以直接通过代码生成文档,不再需要自己手动编写接口文档了,对程序员来说非常方便,可以节约写文档的时间去学习新技术。
  • 提供 Web 页面在线测试 API:光有文档还不够,Swagger 生成的文档还支持在线测试。参数和格式都定好了,直接在界面上输入参数对应的值即可在线测试接口。

2、SwaggerUI 特点

  1. 无依赖 UI可以在任何开发环境中使用,无论是本地还是在Web端中。
  2. 人性化允许最终开发人员轻松地进行交互,并尝试API公开的每个操作,以方便使用。
  3. 易于浏览归类整齐的文档可快速查找并使用资源和端点。
  4. 所有浏览器支持 Swagger UI 在所有主要浏览器中均可使用,以适应各种可能的情况。
  5. 完全可定制 通过完整的源代码访问方式以所需方式设置和调整Swagger UI。
  6. 完整的OAS支持 可视化Swagger 2.0或OAS 3.0中定义的API

前后端分离:

现主流前后端开发:Vue + SpringBoot

后端时代:前端只用管理静态页面; html==》后端。模版引擎 JSP=>后端是主力

前后端分离时代:

  • 后端:后端控制层、服务层、数据访问层 【后端团队】

  • 前端:前端控制层、视图层 【前端团队】

    • 伪造后端数据,json。在后端开发前数据以及存在,不需要后端,前端工程师依旧能将项目跑起来。
  • 前后端如何交互?==>API

  • 前后端相对独立,松耦合;

  • 前后端甚至可以部署在不同的服务器上。

产生一个问题

​ 前后端集成联调,前端人员和后端人员无法做到 “及时协商,尽早解决”,最终导致问题集中爆发;

SpringBoot中集成Swagger

解决方案:

  • 首先指定scheme,实时更新最新的API,降低集成的风险。

  • 早些年,制定Word计划文档

  • 前后端分离:

    • 前端测试后端接口使用:Postman工具。
    • 后端提供接口:需要实时更新最新改动和消息。

这时Swagger很好的解决了这个问题

  • 号称世界上最流行的API框架。
  • Restful API文档在线自动生成工具 ,API文档与API定义同步更新
  • 直接运行,可以在线测试API接口。
  • 支持多种语言 如:Java 、Php等高级语言

2、SpringBoot集成Swagger

1、新建一个SpringBoot-web项目

2、导包

<!-- https://mvnrepository.com/artifact/io.springfox/springfox-swagger2 -->
<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger2</artifactId>
    <version>2.9.2</version>
</dependency>

<!-- https://mvnrepository.com/artifact/io.springfox/springfox-swagger-ui -->
<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger-ui</artifactId>
    <version>2.9.2</version>
</dependency>

3、编写HelloController,测试确保运行成功!

@RestController
public class HelloController {
    
    

   @RequestMapping(value = "/hello")
    public String Hello(){
    
    
        return "Hello Swgger!";
    }

}

4、要使用Swagger,需要编写一个配置类SwaggerConfig来配置 Swagger

@Configuration //配置类
@EnableSwagger2// 开启Swagger2的自动配置
public class SwaggerConfig {
    
      
}

目录:

img

5、访问测试 :http://localhost:8080/swagger-ui.html ,看到swagger的界面;

img

3、配置Swagger

1、Swagger实例Bean是Docket,所以通过配置Docket实例来配置Swaggger

img

@Configuration
@EnableSwagger2 // 开启Swagger2的自动配置
public class SwaggerConfig {
    
    
    //配置了Swagger的Docket的bean实例
    @Bean
    public Docket docket(Environment environment){
    
    
      return new Docket(DocumentationType.SWAGGER_2);
        
}

2、通过apiInfo()属性配置文档信息(全部)

package com.kk.swagger.config;


import com.kk.swagger.controller.HelloController;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import org.springframework.core.env.Profiles;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
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 {
    
    


    //分组
    @Bean
    public Docket docket1(){
    
    
        return new Docket(DocumentationType.SWAGGER_2).groupName("KK1");
    }

    @Bean
    public Docket docket2(){
    
    
        return new Docket(DocumentationType.SWAGGER_2).groupName("KK2");
    }

    @Bean
    public Docket docket3(){
    
    
        return new Docket(DocumentationType.SWAGGER_2).groupName("KK3");
    }



    //配置了Swagger的Docket的bean实例
    //enable 是否启动Swagger 如果为false 则Swagger 不能再浏览器中访问
    @Bean
    public Docket docket(Environment environment){
    
    

        //设置要显示Swagger的环境
        Profiles profiles=Profiles.of("dev","test");
        //获取项目的环境  通过environment.acceptsProfiles判断是否处在自己的设定的环境当中
        boolean flag = environment.acceptsProfiles(profiles);


        return new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(apiInfo())
                .groupName("kk")
                .enable(flag)
                .select()
                //RequestHandlerSelectors 配置要扫描接口的方式
                //basePackage 指定要扫描的包
                //any()  扫描全部
                //none()  都不扫描
                //withClassAnnotation 扫描方法上的注解  参数是一个注解的反射对象
            
                .apis(RequestHandlerSelectors.basePackage("com.kk.swagger.controller"))
//                .apis(RequestHandlerSelectors.withClassAnnotation(RestController.class)) 这个只会扫描类上有RestController的方法
            
                //  .paths() 过滤什么路径
//                .paths(PathSelectors.ant("/kk/**"))
            
                .build();
    }
    private static final Contact DEFAULT_CONTACT =new Contact("KK","HTTP","[email protected]");
    //配置Swagger信息  apiInfo
    private ApiInfo apiInfo(){
    
    
        return new ApiInfo("KK的SwaggerAPI文档", "Api Documentation",
                "1.0", "urn:tos", DEFAULT_CONTACT,
                "Apache 2.0",
                "http://www.apache.org/licenses/LICENSE-2.0", new ArrayList());
    }



}

application.properties

# 应用名称
spring.application.name=swagger-springboot
# 应用服务 WEB 访问端口
server.port=8080
spring.profiles.active=dev

application-dev.properties

server.port=8081

application-test.properties

server.port=8082

测试

img

4、实体配置

1、新建一个实体类

package com.kk.swagger.pojo;


import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
//@Api(注释)
@ApiModel("用户实体类")
public class User {
    
    

    @ApiModelProperty("用户名")
    public String username;
    @ApiModelProperty("密码")
    public String password;
}

2、只要这个实体在请求接口的返回值上(即使是泛型),都能映射到实体项中:

//只要接口中,返回值存在实体类,它就会被扫描到Swagger中
@PostMapping(value = "/user")
public User user(){
    
    
    return new User();
}	

img

测试

img

img

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

//Operation 接口  不是放在类上的  而是放在方法上的
@ApiOperation("Hello控制类,Post测试")
@PostMapping(value = "/postt")
public User postt(@ApiParam("用户名") User user){
    
    
  return user;
}

img

5、其他皮肤

导包

<!--        换肤-->
        <!-- https://mvnrepository.com/artifact/com.github.xiaoymin/swagger-bootstrap-ui -->
        <dependency>
            <groupId>com.github.xiaoymin</groupId>
            <artifactId>swagger-bootstrap-ui</artifactId>
            <version>1.9.6</version>
        </dependency>

访问 http://localhost:8080/doc.html

还有很多,可以网上查查

19、任务

1、异步任务

服务器响应时间长,客户端体验不好

模拟代码

service层

package com.kk.service;

import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

@Service
public class AsyncService {
    
    

    public void hello(){
    
    
        try {
    
    
            Thread.sleep(3000);
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }
        System.out.println("数据正在处理... ...");
    }
}

controller层

package com.kk.controller;


import com.kk.service.AsyncService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class AsyncController {
    
    


    //使用自动注入 调用业务层的方法
    @Autowired
    AsyncService asyncService;

    @RequestMapping("/hello")
    public String hello(){
    
    
        asyncService.hello();//停止3秒 转圈
        return "ok";
    }
}

前台刷新3秒

解决方法

实现前台秒刷新,后台处理中

只需两个注解

service层

package com.kk.service;

import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

@Service
public class AsyncService {
    
    

    //解决该问题,需要告诉Spring 这是一个异步方法
    //此时前台直接刷新,后台这边等3秒后接受到数据
    @Async
    public void hello(){
    
    
        try {
    
    
            Thread.sleep(3000);
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }
        System.out.println("数据正在处理... ...");
    }
}

main主方法中:Springboot10TestApplication

package com.kk;

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


//需要在这里开启异步注解功能
@EnableAsync
@SpringBootApplication
public class Springboot10TestApplication {
    
    

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

}

2、邮件任务

1、导入jar包

<!--邮件发送-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-mail</artifactId>
</dependency>

2、获取qq邮箱授权码

img

3、编写配置文件application.properties

[email protected]
spring.mail.password=授权码

spring.mail.host=smtp.qq.com
# 开启加密验证

spring.mail.properties.mail.smtp.ssl.enable=true

3、编写测试类

​ 1.普通邮件

@SpringBootTest
class Springboot10TestApplicationTests {
    
    

    //邮件发送
    @Autowired
    JavaMailSenderImpl mailSender;


    //一个简单的邮箱
    @Test
    void contextLoads() {
    
    
        SimpleMailMessage simpleMailMessage = new SimpleMailMessage();
        simpleMailMessage.setSubject("kk你好");
        simpleMailMessage.setText("Hello 邮件任务!");

        simpleMailMessage.setTo("[email protected]");
        simpleMailMessage.setFrom("[email protected]");

        mailSender.send(simpleMailMessage);
    }

}

测试成功

img

​ 2.带附件以及文本编辑的邮件

@SpringBootTest
class Springboot10TestApplicationTests {
    
    

    //邮件发送
    @Autowired
    JavaMailSenderImpl mailSender;


    //一个复杂的邮箱
    @Test
    void contextLoads2() throws MessagingException {
    
    
        MimeMessage mimeMessage = mailSender.createMimeMessage();
        //组装
        MimeMessageHelper helper = new MimeMessageHelper(mimeMessage,true);

        //正文
        helper.setSubject("111");
        helper.setText("<h1 style='color:red'>222</h1>",true);

        //附件
        helper.addAttachment("806.png",new File("C:\\Users\\陈素娜\\Desktop\\img\\806.png"));
        helper.addAttachment("2.png",new File("C:\\Users\\陈素娜\\Desktop\\img\\806.png"));


        helper.setTo("[email protected]");
        helper.setFrom("[email protected]");


        mailSender.send(mimeMessage);
    }




}

测试成功

img

封装成一个工具类

    //封装成一个工具类

//    /** + 回车

    /**
     * 
     * @param html
     * @param subject
     * @param text
     * @throws MessagingException
     * @Author kk
     */
    public void sendMail(Boolean html,String subject,String text) throws MessagingException{
    
    
        MimeMessage mimeMessage = mailSender.createMimeMessage();
        //组装
        MimeMessageHelper helper = new MimeMessageHelper(mimeMessage,html);

        //正文
        helper.setSubject(subject);
        helper.setText(text,true);

        //附件
        helper.addAttachment("806.png",new File("C:\\Users\\陈素娜\\Desktop\\img\\806.png"));
        helper.addAttachment("2.png",new File("C:\\Users\\陈素娜\\Desktop\\img\\806.png"));


        helper.setTo("[email protected]");
        helper.setFrom("[email protected]");


        mailSender.send(mimeMessage);
    }

3、定时任务

Taskscheduler任务调度者

TaskExecutor任务执行者

在main中添加@Enablescheduling

package com.kk;

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


//需要在这里开启异步注解功能
@EnableAsync

@EnableScheduling//开启定时功能的注解
@SpringBootApplication
public class Springboot10TestApplication {
    
    

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

}

service层

package com.kk.controller;

import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;

@Service
public class ScheduledService {
    
    
    //在一个特定的时间执行这个方法~ Timer
    //cron表达式
    //秒 分 时 日 月 周几~
    /*
    30 15 10 * *  ?   每天10点15分30执行一次
    30 0/5 10,18 * * ?  每天10点和18点, 每隔五分钟执行- -次
    0 15 10 ? * 1-6   每个月的周一-到周六10.15分钟执行一次
    */
    @Scheduled(cron = "50 42 12 * * ?")  //12点42分50秒执行
    public void hello() {
    
    
        System.out.println("hello,你被执行了~");
    }
}

Cron表达式

cron在线生成 http://cron.qqe2.com/

cron表达式详解

字段 允许值 允许的特殊字符
秒(Seconds) 0~59的整数 , - * / 四个字符
分(Minutes) 0~59的整数 , - * / 四个字符
小时(Hours) 0~23的整数 , - * / 四个字符
日(DayofMonth) 1~31的整数(但是你需要考虑你月的天数) ,- * ? / L W C 八个字符
月份(Month) 1~12的整数或者 JAN-DEC - * / 四个字符
周(DayofWeek) 1~7的整数或者 SUN-SAT (1=SUN) , - * ? / L C # 八个字符
年(可选,留空)(Year) 1970~2099 , - * / 四个字符

每一个域都使用数字,但还可以出现如下特殊字符,它们的含义是:

  1. :表示匹配该域的任意值。假如在Minutes域使用, 即表示每分钟都会触发事件。

  2. ?:只能用在DayofMonth和DayofWeek两个域。它也匹配域的任意值,但实际不会。因为DayofMonth和DayofWeek会相互影响。例如想在每月的20日触发调度,不管20日到底是星期几,则只能使用如下写法: 13 13 15 20 * ?, 其中最后一位只能用?,而不能使用*,如果使用*表示不管星期几都会触发,实际上并不是这样。

  3. -:表示范围。例如在Minutes域使用5-20,表示从5分到20分钟每分钟触发一次

  4. /:表示起始时间开始触发,然后每隔固定时间触发一次。例如在Minutes域使用5/20,则意味着5分钟触发一次,而25,45等分别触发一次.

  5. ,:表示列出枚举值。例如:在Minutes域使用5,20,则意味着在5和20分每分钟触发一次。

  6. L:表示最后,只能出现在DayofWeek和DayofMonth域。如果在DayofWeek域使用5L,意味着在最后的一个星期四触发。

  7. W:表示有效工作日(周一到周五),只能出现在DayofMonth域,系统将在离指定日期的最近的有效工作日触发事件。例如:在 DayofMonth使用5W,如果5日是星期六,则将在最近的工作日:星期五,即4日触发。如果5日是星期天,则在6日(周一)触发;如果5日在星期一到星期五中的一天,则就在5日触发。另外一点,W的最近寻找不会跨过月份 。

  8. LW:这两个字符可以连用,表示在某个月最后一个工作日,即最后一个星期五。

  9. #:用于确定每个月第几个星期几,只能出现在DayofMonth域。例如在4#2,表示某月的第二个星期三。

20、分布式 Dubbo+Zookenper+SpringBoot

1、分布式理论

1、什么是分布式系统?

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

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

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

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

2、Dubbo文档

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

在Dubbo的官网文档有这样一张图

img

3、单一应用架构

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

img

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

缺点:

1、性能扩展比较难

2、协同开发问题

3、不利于升级维护

4、垂直应用架构

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

img

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

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

5、分布式服务架构

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

img

6、流动计算架构

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

img

7、RPC

什么是RPC?

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

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

RPC基本原理

img

步骤解析:

img

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

2、Dubbo

1、什么是dubbo?

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

dubbo官网

1.了解Dubbo的特性

2.查看官方文档

2、dubbo基本概念

img

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

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

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

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

调用关系说明

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

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

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

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

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

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

3、Dubbo环境搭建

点进dubbo官方文档,推荐我们使用Zookeeper 注册中心

什么是zookeeper呢?可以查看官方文档

window下安装zookeeper

  1. 下载zookeeper :地址, 我们下载3.6.3, 稳定版版! 解压zookeeper

注意:不要下载代码资源包

img

  1. 运行/bin/zkServer.cmd ,初次运行会报错,没有zoo.cfg配置文件;

img

可能遇到问题:闪退 !

解决方案:编辑zkServer.cmd文件末尾添加pause 。这样运行出错就不会退出,会提示错误信息,方便找到原因。

img

  1. 修改zoo.cfg配置文件

    将conf文件夹下面的zoo_sample.cfg复制一份改名为zoo.cfg即可。

img

注意几个重要位置:

  1. dataDir=./ 临时数据存储的目录(可写相对路径)
  2. clientPort=2181 zookeeper的端口号
  3. 修改完成后再次启动zookeeper

成功!

img

4.使用zkCli.cmd测试

ls /:列出zookeeper根下保存的所有节点

[zk: 127.0.0.1:2181(CONNECTED) 4] ls /
[zookeeper]

create –e /kk 123:创建一个kk节点,值为123

img

window下安装dubbo-admin

dubbo本身并不是一个服务软件。它其实就是一个jar包,能够帮你的java程序连接到zookeeper,并利用zookeeper消费、提供服务。

但是为了让用户更好的管理监控众多的dubbo服务,官方提供了一个可视化的监控程序dubbo-admin,不过这个监控即使不装也不影响使用。

我们这里来安装一下:

1. 下载dubbo-admin

地址 :https://github.com/apache/dubbo-admin/tree/master

dd

img

修改 dubbo-admin\src\main\resources \application.properties 指定zookeeper地址

server.port=7001
spring.velocity.cache=false
spring.velocity.charset=UTF-8
spring.velocity.layout-url=/templates/default.vm
spring.messages.fallback-to-system-locale=false
spring.messages.basename=i18n/message
spring.root.password=root
spring.guest.password=guest

dubbo.registry.address=zookeeper://127.0.0.1:2181

3.在项目目录下打包dubbo-admin

mvn clean package -Dmaven.test.skip=true 

成功!

img

  1. 执行 dubbo-admin-server\target 目录下下的java -jar dubbo-admin-0.0.1-SNAPSHOT.jar
java -jar dubbo-admin-0.0.1-SNAPSHOT.jar

img

【注意:zookeeper的服务一定要打开!】

img

执行完毕,我们去访问一下 http://localhost:7001/ , 这时候我们需要输入登录账户和密码,我们都是默认的root-root;

登录成功后,查看界面

img

安装完成!

3、框架搭建

1、zookeeper

1. 启动zookeeper !

2. IDEA创建一个空项目;

3.创建一个模块,实现服务提供者:provider-server , 选择web依赖即可

4.项目创建完毕,我们写一个服务,比如卖票的服务;

编写接口

package com.kk.service;

public interface TicketService {
    
    
    public String getTicket();
}

编写实现类

package com.kk.service;

public class TicketServiceImpl implements TicketService{
    
    
    @Override
    public String getTicket() {
    
    
        return "kk";
    }
}

5.创建一个模块,实现服务消费者:consumer-server , 选择web依赖即可

6.项目创建完毕,我们写一个服务,比如用户的服务;

编写service

package com.kk.service;

public interface UserService {
    
    

    //想要拿到provider提供的票 需要去拿去注册中心的服务
}

需求:现在我们的用户想使用买票的服务,这要怎么弄呢 ?

服务提供者

  1. 将服务提供者注册到注册中心,我们需要整合Dubbo和zookeeper,所以需要导包

我们从dubbo官网进入github,看下方的帮助文档,找到dubbo-springboot,找到依赖包

<!--        导入Dubbo+zookeeper依赖-->
        <!-- https://mvnrepository.com/artifact/org.apache.dubbo/dubbo-spring-boot-starter -->
        <dependency>
            <groupId>org.apache.dubbo</groupId>
            <artifactId>dubbo-spring-boot-starter</artifactId>
            <version>3.0.2.1</version>
        </dependency>

zookeeper的包我们去maven仓库下载,zkclient;

<!--        Zkclient-->

        <!-- https://mvnrepository.com/artifact/com.github.sgroschupf/zkclient -->
        <dependency>
            <groupId>com.github.sgroschupf</groupId>
            <artifactId>zkclient</artifactId>
            <version>0.1</version>
        </dependency>

【新版的坑】zookeeper及其依赖包,解决日志冲突,还需要剔除日志依赖;

      <!-- 引入zookeeper -->
        <dependency>
            <groupId>org.apache.curator</groupId>
            <artifactId>curator-framework</artifactId>
            <version>2.12.0</version>
        </dependency>
        <dependency>
            <groupId>org.apache.curator</groupId>
            <artifactId>curator-recipes</artifactId>
            <version>2.12.0</version>
        </dependency>
        <dependency>
            <groupId>org.apache.zookeeper</groupId>
            <artifactId>zookeeper</artifactId>
            <version>3.4.14</version>
            <!--排除这个slf4j-log4j12-->
            <exclusions>
                <exclusion>
                    <groupId>org.slf4j</groupId>
                    <artifactId>slf4j-log4j12</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

测试

img

img

2、dubbo

消费者

1.导入依赖;

 <!--dubbo-->
        <!-- Dubbo Spring Boot Starter -->
        <dependency>
            <groupId>org.apache.dubbo</groupId>
            <artifactId>dubbo-spring-boot-starter</artifactId>
            <version>2.7.8</version>
        </dependency>
        <!--zookeeper-->
        <!-- https://mvnrepository.com/artifact/com.github.sgroschupf/zkclient -->
        <dependency>
            <groupId>com.github.sgroschupf</groupId>
            <artifactId>zkclient</artifactId>
            <version>0.1</version>
        </dependency>
        <!-- 引入zookeeper -->
        <dependency>
            <groupId>org.apache.curator</groupId>
            <artifactId>curator-framework</artifactId>
            <version>2.12.0</version>
        </dependency>
        <dependency>
            <groupId>org.apache.curator</groupId>
            <artifactId>curator-recipes</artifactId>
            <version>2.12.0</version>
        </dependency>
        <dependency>
            <groupId>org.apache.zookeeper</groupId>
            <artifactId>zookeeper</artifactId>
            <version>3.4.14</version>
            <!--排除这个slf4j-log4j12-->
            <exclusions>
                <exclusion>
                    <groupId>org.slf4j</groupId>
                    <artifactId>slf4j-log4j12</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

2.配置参数

# 应用名称
# 消费者去哪里拿需要暴露自己的名字
spring.application.name=consumer-server
# 应用服务 WEB 访问端口
server.port=8002

#注册中心地址   可以在任何电脑上
dubbo.registry.address=zookeeper://127.0.0.1:2181

3. 本来正常步骤是需要将服务提供者的接口打包,然后用pom文件导入,这里使用简单的方式,直接将服务的接口拿过来,路径必须保证正确,即和服务提供者相同;

img

4. 消费者的服务类

package com.kk.service;



import org.apache.dubbo.config.annotation.DubboReference;

import org.springframework.stereotype.Service;
@Service  //放到容器中
public class UserService {
    
    

    //想要拿到provider提供的票 需要去拿去注册中心的服务
    @DubboReference //应用
    TicketService ticketService;

    public void buyTicket(){
    
    
        String ticket = ticketService.getTicket();
        System.out.println("在注册中心拿到=》"+ticket);
    }
}

5. 测试类:

package com.kk;


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



@SpringBootTest
class ConsumerServerApplicationTests {
    
    

    @Autowired
    UserService userService;


    @Test
   public void contextLoads() {
    
    
        userService.buyTicket();
    }

}

3、启动测试

img

img

1
spring.velocity.cache=false
spring.velocity.charset=UTF-8
spring.velocity.layout-url=/templates/default.vm
spring.messages.fallback-to-system-locale=false
spring.messages.basename=i18n/message
spring.root.password=root
spring.guest.password=guest

dubbo.registry.address=zookeeper://127.0.0.1:2181


**3.在项目目录下**打包dubbo-admin

```java
mvn clean package -Dmaven.test.skip=true 

成功!

[外链图片转存中…(img-Sce5hpt7-1632703387382)]

  1. 执行 dubbo-admin-server\target 目录下下的java -jar dubbo-admin-0.0.1-SNAPSHOT.jar
java -jar dubbo-admin-0.0.1-SNAPSHOT.jar

[外链图片转存中…(img-3zzjKEPh-1632703387383)]

【注意:zookeeper的服务一定要打开!】

[外链图片转存中…(img-Q35qPbIk-1632703387384)]

执行完毕,我们去访问一下 http://localhost:7001/ , 这时候我们需要输入登录账户和密码,我们都是默认的root-root;

登录成功后,查看界面

[外链图片转存中…(img-jiFPzlRF-1632703387385)]

安装完成!

3、框架搭建

1、zookeeper

1. 启动zookeeper !

2. IDEA创建一个空项目;

3.创建一个模块,实现服务提供者:provider-server , 选择web依赖即可

4.项目创建完毕,我们写一个服务,比如卖票的服务;

编写接口

package com.kk.service;

public interface TicketService {
    
    
    public String getTicket();
}

编写实现类

package com.kk.service;

public class TicketServiceImpl implements TicketService{
    
    
    @Override
    public String getTicket() {
    
    
        return "kk";
    }
}

5.创建一个模块,实现服务消费者:consumer-server , 选择web依赖即可

6.项目创建完毕,我们写一个服务,比如用户的服务;

编写service

package com.kk.service;

public interface UserService {
    
    

    //想要拿到provider提供的票 需要去拿去注册中心的服务
}

需求:现在我们的用户想使用买票的服务,这要怎么弄呢 ?

服务提供者

  1. 将服务提供者注册到注册中心,我们需要整合Dubbo和zookeeper,所以需要导包

我们从dubbo官网进入github,看下方的帮助文档,找到dubbo-springboot,找到依赖包

<!--        导入Dubbo+zookeeper依赖-->
        <!-- https://mvnrepository.com/artifact/org.apache.dubbo/dubbo-spring-boot-starter -->
        <dependency>
            <groupId>org.apache.dubbo</groupId>
            <artifactId>dubbo-spring-boot-starter</artifactId>
            <version>3.0.2.1</version>
        </dependency>

zookeeper的包我们去maven仓库下载,zkclient;

<!--        Zkclient-->

        <!-- https://mvnrepository.com/artifact/com.github.sgroschupf/zkclient -->
        <dependency>
            <groupId>com.github.sgroschupf</groupId>
            <artifactId>zkclient</artifactId>
            <version>0.1</version>
        </dependency>

【新版的坑】zookeeper及其依赖包,解决日志冲突,还需要剔除日志依赖;

      <!-- 引入zookeeper -->
        <dependency>
            <groupId>org.apache.curator</groupId>
            <artifactId>curator-framework</artifactId>
            <version>2.12.0</version>
        </dependency>
        <dependency>
            <groupId>org.apache.curator</groupId>
            <artifactId>curator-recipes</artifactId>
            <version>2.12.0</version>
        </dependency>
        <dependency>
            <groupId>org.apache.zookeeper</groupId>
            <artifactId>zookeeper</artifactId>
            <version>3.4.14</version>
            <!--排除这个slf4j-log4j12-->
            <exclusions>
                <exclusion>
                    <groupId>org.slf4j</groupId>
                    <artifactId>slf4j-log4j12</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

测试

[外链图片转存中…(img-ThpQoRgU-1632703387386)]

[外链图片转存中…(img-SZftI3nd-1632703387387)]

2、dubbo

消费者

1.导入依赖;

 <!--dubbo-->
        <!-- Dubbo Spring Boot Starter -->
        <dependency>
            <groupId>org.apache.dubbo</groupId>
            <artifactId>dubbo-spring-boot-starter</artifactId>
            <version>2.7.8</version>
        </dependency>
        <!--zookeeper-->
        <!-- https://mvnrepository.com/artifact/com.github.sgroschupf/zkclient -->
        <dependency>
            <groupId>com.github.sgroschupf</groupId>
            <artifactId>zkclient</artifactId>
            <version>0.1</version>
        </dependency>
        <!-- 引入zookeeper -->
        <dependency>
            <groupId>org.apache.curator</groupId>
            <artifactId>curator-framework</artifactId>
            <version>2.12.0</version>
        </dependency>
        <dependency>
            <groupId>org.apache.curator</groupId>
            <artifactId>curator-recipes</artifactId>
            <version>2.12.0</version>
        </dependency>
        <dependency>
            <groupId>org.apache.zookeeper</groupId>
            <artifactId>zookeeper</artifactId>
            <version>3.4.14</version>
            <!--排除这个slf4j-log4j12-->
            <exclusions>
                <exclusion>
                    <groupId>org.slf4j</groupId>
                    <artifactId>slf4j-log4j12</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

2.配置参数

# 应用名称
# 消费者去哪里拿需要暴露自己的名字
spring.application.name=consumer-server
# 应用服务 WEB 访问端口
server.port=8002

#注册中心地址   可以在任何电脑上
dubbo.registry.address=zookeeper://127.0.0.1:2181

3. 本来正常步骤是需要将服务提供者的接口打包,然后用pom文件导入,这里使用简单的方式,直接将服务的接口拿过来,路径必须保证正确,即和服务提供者相同;

[外链图片转存中…(img-OCtTEd5R-1632703387389)]

4. 消费者的服务类

package com.kk.service;



import org.apache.dubbo.config.annotation.DubboReference;

import org.springframework.stereotype.Service;
@Service  //放到容器中
public class UserService {
    
    

    //想要拿到provider提供的票 需要去拿去注册中心的服务
    @DubboReference //应用
    TicketService ticketService;

    public void buyTicket(){
    
    
        String ticket = ticketService.getTicket();
        System.out.println("在注册中心拿到=》"+ticket);
    }
}

5. 测试类:

package com.kk;


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



@SpringBootTest
class ConsumerServerApplicationTests {
    
    

    @Autowired
    UserService userService;


    @Test
   public void contextLoads() {
    
    
        userService.buyTicket();
    }

}

3、启动测试

[外链图片转存中…(img-lOGcgrF1-1632703387390)]

[外链图片转存中…(img-odNCu4WU-1632703387391)]

猜你喜欢

转载自blog.csdn.net/weixin_50569789/article/details/120501077