IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> springboot基本使用 -> 正文阅读

[Java知识库]springboot基本使用

1. 前言

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

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

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

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

Spring Boot的主要优点:

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

2. 第一个SpringBoot程序

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nVCXifSU-1629872608430)(C:\Users\hjy\AppData\Roaming\Typora\typora-user-images\image-20210817224611308.png)]

  1. DemoApplication.java
@SpringBootApplication
public class DemoApplication {

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

}

注意 : 这个是启动类,所有类必须在启动类的相邻包或者是子包下

  1. HelloController
@Controller
@ResponseBody
public class HelloController {
    @RequestMapping("hello")
    public String hello(){
        return "hello world";
    }
}
  1. application.properties
# 应用名称
spring.application.name=demo
# 应用服务 WEB 访问端口
server.port=8080
  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.example</groupId>
    <artifactId>demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>demo</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>
        <!--web项目依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!--lombok插件-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <!--springBoot继承junit-->
        <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>
            <!--指定jdk版本的插件-->
            <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>
            <!--再打jar包的时候导入依赖包,必须要有-->
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>2.3.7.RELEASE</version>
                <configuration>
                    <mainClass>com.example.demo.DemoApplication</mainClass>
                </configuration>
                <executions>
                    <execution>
                        <id>repackage</id>
                        <goals>
                            <goal>repackage</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

</project>

3. 基本使用

3.1 改变banner

默认的banner是

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/

我们可以自定义banner

首先打开网站https://www.bootschool.net/ascii

复制


//                          _ooOoo_                               //
//                         o8888888o                              //
//                         88" . "88                              //
//                         (| ^_^ |)                              //
//                         O\  =  /O                              //
//                      ____/`---'\____                           //
//                    .'  \\|     |//  `.                         //
//                   /  \\|||  :  |||//  \                        //
//                  /  _||||| -:- |||||-  \                       //
//                  |   | \\\  -  /// |   |                       //
//                  | \_|  ''\---/''  |   |                       //
//                  \  .-\__  `-`  ___/-. /                       //
//                ___`. .'  /--.--\  `. . ___                     //
//              ."" '<  `.___\_<|>_/___.'  >'"".                  //
//            | | :  `- \`.;`\ _ /`;.`/ - ` : | |                 //
//            \  \ `-.   \_ __\ /__ _/   .-` /  /                 //
//      ========`-.____`-.___\_____/___.-`____.-'========         //
//                           `=---='                              //
//      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^        //
//            佛祖保佑       永不宕机     永无BUG                    //

在resources下新建banner.txt,再把其复制进去即可

在这里插入图片描述

3.2 自动配置

结论:

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

由@SpringBootApplication ==>@EnableAutoConfiguration ==>@Import(AutoConfigurationImportSelector.class)

==> AutoConfigurationImportSelector.class ==>

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
		List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
				getBeanClassLoader());
		Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you "
				+ "are using a custom packaging, make sure that file is correct.");
		return configurations;
	}

== > SpringFactoriesLoader == >

public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
        String factoryTypeName = factoryType.getName();
        return (List)loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
    }

== > loadSpringFactories ==> META-INF/spring.factories

然后打开该文件

在这里插入图片描述

选择org.springframework.boot.autoconfigure.web.servlet.HttpEncodingAutoConfiguration点入

@Configuration(proxyBeanMethods = false)
//自动配置属性(通过application.yaml)
@EnableConfigurationProperties(ServerProperties.class)
//@ConditionalXXX 都是一些条件判断,如果符合条件就导入
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
@ConditionalOnClass(CharacterEncodingFilter.class)
@ConditionalOnProperty(prefix = "server.servlet.encoding", value = "enabled", matchIfMissing = true)
public class HttpEncodingAutoConfiguration {

   private final Encoding properties;

   public HttpEncodingAutoConfiguration(ServerProperties properties) {
      this.properties = properties.getServlet().getEncoding();
   }

== > 点击ServerProperties.class

//可以看见,该配置类与application.yaml主配置文件绑定,前缀为server的配置自动赋值该配置类的字段
@ConfigurationProperties(prefix = "server", ignoreUnknownFields = true)
public class ServerProperties {

精髓

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

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

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

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

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

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

了解:@Conditional

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

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

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

图片

3.3 主启动类的运行

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

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

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

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

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

img

3.4 yml配置注入

SpringBoot使用一个全局的配置文件 (不同格式可以有多个,如application.properties+application.yaml), 配置文件名称是固定的

  • application.properties

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

    • 语法结构 :key:空格 value

3.4.1 语法

说明:语法要求严格!

1、空格不能省略

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

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

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

hello: 11

注意:

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

    比如 :name: "kuang \n shen" 输出 :kuang 换行 shen

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

    比如 :name: 'kuang \n shen' 输出 :kuang \n shen

对象、Map(键值对)

person:
  name: junyang
  age: 18

行内写法

person: {name: junyang,age: 18}

数组( List、set )

animals:
  - pig
  - cat
  - dog

行内写法 :

animals: [pig,cat,dog]

3.4.2 yaml的el表达式

可以用${}占位符

随机数

person:
  name: 骏扬${random.int}

uuid

person:
  name: 骏扬${random.uuid}

如果有值就取,没有值就取默认值

name: yaml
person:
  name: ${name:hehe}
  #如果name有值,person.name = 其值 ,如果没有值,person.name = "hehe"

3.5 配置文件给java类属性赋值

方法一 : 使用@Value

@Component
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Dog {
    @Value("旺财")
    private String name;
    @Value("18")
    private int age;
}

测试

@SpringBootTest
class DemoApplicationTests {

    @Autowired
    private Dog dog ;
    @Test
    void contextLoads() {
        System.out.println(dog);
    }

}

方法二 : 用@ConfigurationProperties

@Component
@Data
@AllArgsConstructor
@NoArgsConstructor
//指定前缀为person的所有数据
@ConfigurationProperties(prefix = "person")
public class Person {
    private String name;
    private int age;
    private Date birthday;
    private List<Object> list;
    private Map<Object,Object> map;
    private Dog dog;
}
  1. 导入依赖(可以不导)
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-configuration-processor</artifactId>
    <optional>true</optional>
</dependency>

该依赖可以为你的配置生成元数据

  1. application.yaml
person:
  name: 骏扬
  age: 18
  birthday: 2002/11/30 # 注意日期的话要用 / 隔开
  list:
    - cat
    - dog
    - mouse
  map:
    k1: v1
    k2: v2
  dog:
    name: 旺财
    age: 3
  1. 测试
@SpringBootTest
class DemoApplicationTests {

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

}

方法三 : 用@PropertySource

注意 : 这种方法只能用于properties文件

@Component
@Data
@AllArgsConstructor
@NoArgsConstructor
//只可以加载properties文件
@PropertySource(value="classpath:dog.properties")
public class Dog {
    @Value("${name}")
    private String name;
    @Value("${age}")
    private int age;
}

dog.properties

name=狗狗
age=5

测试

@SpringBootTest
class DemoApplicationTests {

    @Autowired
    private Dog dog ;
    @Test
    void contextLoads() {
        System.out.println(dog);
    }
}

3.6 JSR-303数据校验

首先加入依赖

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

在要数据验证的类上加上注解@Validated即可

在字段上加注解,如

@Component
@Data
@AllArgsConstructor
@NoArgsConstructor
@ConfigurationProperties(prefix = "dog")
@Validated
public class Dog {

    private String name;
    private int age;
    @Email(message="邮箱格式错误")
    private String email;
}

这样email字段被赋值的时候会自动数据验证,如果发现数据验证失败就会报错: “邮箱格式错误”

常见的数据验证的注解有

@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 对象是否符合正则表达式的规则

所有注解

在这里插入图片描述

3.7 多环境配置

用properties文件实现多环境

在这里插入图片描述

在application.properties中配置,即可选择不同的配置文件

spring.profiles.active=dev # 这里选择了application-dev.properties

用yaml文件实现多环境

可以只在一个文件里面实现多个配置文件的效果,只要用---隔开即可

# 优先使用最上面这一块的配置
server:
  port: 8080
spring:
  profiles:
    active: dev

# 不同的配置之间用 --- 隔开
---
server:
  port: 8081
spring:
  profiles: dev #配置环境的名称
---
server:
  port: 8082
spring:
  profiles: test #配置环境的名称

3.8 确认自动配置类是否生效

在配置文件中加上

debug: true

即可

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

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

Unconditional classes: (没有条件的类)

3.9 自定义starter

创建一个项目demo1

在这里插入图片描述

HelloProperties

package com.hjy;

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

@ConfigurationProperties(prefix = "hjy.hello")
public class HelloProperties {

    private String prefix;
    private String suffix;

    public String getPrefix() {
        return prefix;
    }

    public void setPrefix(String prefix) {
        this.prefix = prefix;
    }

    public String getSuffix() {
        return suffix;
    }

    public void setSuffix(String suffix) {
        this.suffix = suffix;
    }
}

HelloService

package com.hjy;

public class HelloService {

    private HelloProperties helloProperties;

    public HelloProperties getHelloProperties() {
        return helloProperties;
    }

    public void setHelloProperties(HelloProperties helloProperties) {
        this.helloProperties = helloProperties;
    }

    public String sayHello(String name){
        return helloProperties.getPrefix() + name + helloProperties.getSuffix();
    }
}

HelloServiceAutoConfiguration

package com.hjy;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
@ConditionalOnWebApplication
//把HelloProperties类实例注入容器
@EnableConfigurationProperties(HelloProperties.class)
public class HelloServiceAutoConfiguration {
    @Autowired
    private HelloProperties helloProperties;

    @Bean
    public HelloService helloService(){
        HelloService helloService = new HelloService();
        helloService.setHelloProperties(helloProperties);
        return helloService;
    }
}

配置META-INF/spring.factories

# 由于导入一个bean需要ComponentScan的支持,而导入一个jar包却不知道程序是否写了导入该jar包的路径,所以配置
# spring.factories的目的就是让有@EnableAutoConfiguration的启动类在一开始就扫描spring.factories,获得其
# 字节码,再以反射的方式加入容器,实现动态的注入javaBean
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.hjy.HelloServiceAutoConfiguration

这一步可以使即使程序的包扫描没有扫描到自定义的starter jar包,也可以把jar包里面的javabean注入容器

打包

在这里插入图片描述

测试

新建一个项目,导入依赖

        <dependency>
            <groupId>com.example</groupId>
            <artifactId>demo1</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </dependency>

然后编写controller

@Controller
public class HelloController {
    @Autowired
    private HelloService helloService;

    @RequestMapping("hello")
    @ResponseBody
    public String hello(){
        return helloService.sayHello("何骏扬");
    }
}

配置文件

hjy.hello.prefix=帅气的
hjy.hello.suffix=今天真开心

结果 :

在这里插入图片描述

总结流程:

  1. 要编写一个starter,首先要写一个xxxproperties来获取配置文件的赋值
  2. 在编写一个service类来实际操作
  3. 最后编写一个configuration总配置类导入xxxproperties到ioc容器,并且导入service类进容器
  4. 编写META-INF/spring.factories
    1. 键为org.springframework.boot.autoconfigure.EnableAutoConfiguration
    2. 值为最后的configuration总配置类的全路径名
  5. 打包(install)
  6. 一个新的项目引入该依赖
  7. 在新项目启动的时候,@SpringBootApplication == > @EnableAutoConfiguration ==> AutoConfigurationImportSelector.class ==> getCandidateConfigurations ==>SpringFactoriesLoader.loadFactoryNames ==>loadSpringFactories ,在最后获取所有配置的字节码并且把其实例装载进容器
  8. 最后就可以愉快地使用了

4. web开发

4.1 静态资源导入

在WebMvcAutoConfiguration.java中,有方法

@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
    //如果自己配置了,就失效,就是:spring.mvc.static-path-pattern= xxx 
   if (!this.resourceProperties.isAddMappings()) {
      logger.debug("Default resource handling disabled");
      return;
   }
    //在resourceProperties类中获取配置文件的内容
   Duration cachePeriod = this.resourceProperties.getCache().getPeriod();
    //这段可以不看,关于webjars
   CacheControl cacheControl = this.resourceProperties.getCache().getCachecontrol().toHttpCacheControl();
   if (!registry.hasMappingForPattern("/webjars/**")) {
      customizeResourceHandlerRegistration(registry.addResourceHandler("/webjars/**")
            .addResourceLocations("classpath:/META-INF/resources/webjars/")
            .setCachePeriod(getSeconds(cachePeriod)).setCacheControl(cacheControl));
   }
    //获取WebMvcProperties的static-path-pattern字段,默认为 /**
    //获取ResourceProperties中的CLASSPATH_RESOURCE_LOCATIONS字段,其值为
    /*
    private static final String[] CLASSPATH_RESOURCE_LOCATIONS = { 
    		"classpath:/META-INF/resources/",
			"classpath:/resources/", 
			"classpath:/static/", 
			"classpath:/public/" 
			};
    
    */
    //把上述路径下的资源映射到localhost:8080/**的url路径下来
   String staticPathPattern = this.mvcProperties.getStaticPathPattern();
   if (!registry.hasMappingForPattern(staticPathPattern)) {
      customizeResourceHandlerRegistration(registry.addResourceHandler(staticPathPattern)
            .addResourceLocations(getResourceLocations(this.resourceProperties.getStaticLocations()))
            .setCachePeriod(getSeconds(cachePeriod)).setCacheControl(cacheControl));
   }
}

由此方法可知,在resources下有四个地方可以导入静态资源文件,分别为

在这里插入图片描述

只要访问localhost:8080/**就可以直接访问到这些静态文件

优先级 : 上图的 1 > 3 > 4 > 2 (其实也就是按顺序,顺序越前面,优先级越大)

4.2 设置首页

在WebMvcAutoConfiguration.java中,有方法

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

private Optional<Resource> getWelcomePage() {
    String[] locations = getResourceLocations(this.resourceProperties.getStaticLocations());
    return Arrays.stream(locations).map(this::getIndexHtml).filter(this::isReadable).findFirst();
}

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

方法的作用是设置location + "index.html"的资源为首页

其中location是"classpath:/META-INF/resources/",“classpath:/resources/”, “classpath:/static/”, “classpath:/public/” };

这个页面放在哪个文件夹下都可以

自定义首页

若想自定义首页,就在跳转默认首页的时候再跳转一次页面

@Controller
public class HelloController {
  @RequestMapping("/")
  	public String hello(){
    	return "forward:login.html";
  	}
}

4.3 模板引擎thymeleaf

4.3.1 基本使用

导入依赖

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

写文件

注意 : 1. thymeleaf的文件一定要写在templates文件夹下,而且要导入头文件

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

	2. templates文件夹下的文件是不可以直接访问的,只能通过服务器内部的请求来跳转其页面(thymeleaf有自己的视图解析器),
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
   <head>
      <meta charset="UTF-8">
      <title>Title</title>
   </head>
   <body>
      <p th:text="${message}">Welcome to BeiJing!</p>
   </body>
</html>
@Controller
public class HelloController {
    @RequestMapping("hello")
    public String hello(Model model){
        model.addAttribute("message","你好,世界");
        return "hello";
    }
}

原理

找到ThymeleafProperties.java文件,在里面找到两个字段,这是其视图解析器

	public static final String DEFAULT_PREFIX = "classpath:/templates/";

	public static final String DEFAULT_SUFFIX = ".html";

所以用thymeleaf的文件必须写在templates文件夹下,且后缀为.html

4.4 自定义类扩展MVC的功能

模板

@Configuration
public class MyMvcConfig implements WebMvcConfigurer {
    // 重写方法
}

原理

进入WebMvcAutoConfiguration.java ,找到其静态内部类WebMvcAutoConfigurationAdapter,它导入了一个类EnableWebMvcConfiguration,EnableWebMvcConfiguration又继承了DelegatingWebMvcConfiguration,这个类中有这么一个方法

@Autowired(required = false)
public void setConfigurers(List<WebMvcConfigurer> configurers) {
   if (!CollectionUtils.isEmpty(configurers)) {
      this.configurers.addWebMvcConfigurers(configurers);
   }
}

从容器中获取了所有的配置

4.4.1 日期格式

在WebMvcAutoConfiguration中,有方法

@Bean
@Override
public FormattingConversionService mvcConversionService() {
   Format format = this.mvcProperties.getFormat();
   WebConversionService conversionService = new WebConversionService(new DateTimeFormatters()
         .dateFormat(format.getDate()).timeFormat(format.getTime()).dateTimeFormat(format.getDateTime()));
   addFormatters(conversionService);
   return conversionService;
}

再进入WebMvcProperties.java,找到Format内部类,可以看见

	//表示赋值	date 属性是spring.mvc.format.date才可以赋值
	@Deprecated
	@DeprecatedConfigurationProperty(replacement = "spring.mvc.format.date")
	public String getDateFormat() {
		return this.format.getDate();
	}		


		/**
		 * Date format to use, for example `dd/MM/yyyy`.
		 */
		private String date;

修改:

spring:
  mvc:
    format:
      date: yyyy-MM-dd

4.4.2 视图跳转功能

我们可以省去一些时间写controller,而选择该方法快速的实现请求路径映射

@Configuration
public class MyMvcConfig implements WebMvcConfigurer {
    public void addViewControllers(ViewControllerRegistry registry) {
        // 请求 / 自动转发到thymeleaf视图解析的index.html
        registry.addViewController("/").setViewName("index");
        // 请求 /index.html 自动转发到thymeleaf视图解析的index.html
        registry.addViewController("/index.html").setViewName("index");
        // 请求 /main.html 自动转发到thymeleaf视图解析的dashboard.html
        registry.addViewController("/main.html").setViewName("dashboard");
    }
}

4.5 国际化

4.5.1 基本使用

参考文档

在resources下设立i18n包,里面放三个文件

在这里插入图片描述

login.properties :默认

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

英文:

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

中文:

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

然后在配置文件中配置

spring:
  messages:
    basename: i18n.login

使用

在前端页面中,用#{…}来获取值,可以用在各种地方

<h1 class="h3 mb-3 font-weight-normal" th:text="#{login.tip}">Please sign in</h1>
<!--或者-->
<button class="btn btn-lg btn-primary btn-block" type="submit">[[#{login.btn}]]</button>

但这样只可以得到默认的文件,怎么样实现国际化呢

实现国际化

中英切换的两个按钮

<a class="btn btn-sm" th:href="@{/index.html(language='zh_CN')}">中文</a>
<a class="btn btn-sm" th:href="@{/index.html(language='en_US')}">English</a>

然后自定义一个类,实现LocaleResolver接口

package com.hjy.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 {

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

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

    }
}

最后在MVC拓展类中加入容器

package com.hjy.config;

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

@Configuration
public class MyMvcConfig implements WebMvcConfigurer {
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/").setViewName("index");
        registry.addViewController("/index.html").setViewName("index");
    }

    //加入容器
    @Bean
    public LocaleResolver localeResolver(){
        return new MyLocaleResolver();
    }
}

这样点击即可切换其配置文件了

4.5.2 原理

在MessageSourceAutoConfiguration.java下,有方法

@Bean
public MessageSource messageSource(MessageSourceProperties properties) {
   ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
   if (StringUtils.hasText(properties.getBasename())) {
       //设置国际化文件的基础名(去掉语言国家代码的),所以我的是login
      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;
}

再看MessageSourceProperties.java,有private String basename = "messages";,所以配置这个即可

在WebMvcAutoConfiguration.java,有方法

@Bean
@ConditionalOnMissingBean
@ConditionalOnProperty(prefix = "spring.mvc", name = "locale")
public LocaleResolver localeResolver() {
   if (this.mvcProperties.getLocaleResolver() == WebMvcProperties.LocaleResolver.FIXED) {
      return new FixedLocaleResolver(this.mvcProperties.getLocale());
   }
    // 接收头国际化分解
   AcceptHeaderLocaleResolver localeResolver = new AcceptHeaderLocaleResolver();
   localeResolver.setDefaultLocale(this.mvcProperties.getLocale());
   return localeResolver;
}

再点开AcceptHeaderLocaleResolver.java,找到方法resolveLocale

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

由于AcceptHeaderLocaleResolver继承了LocaleResolver接口,所以我们可以自定义自己的LocaleResolver完成操作

4.6 一些常见的web开发问题

首页跳转

有两种方法,方法一为拓展springmvc

@Configuration
public class MyMvcConfig implements WebMvcConfigurer {
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/").setViewName("index");
        registry.addViewController("/index.html").setViewName("index");
    }
}

方法二为controller跳转

@Controller
public class IndexController {

    @RequestMapping({"/","/index.html"})
    public String index(){
        return "index";
    }
}

静态资源访问

在前端页面中,访问静态资源与传统的ssm有些不同

由于springboot的项目静态资源是在

“classpath:/META-INF/resources/”,
“classpath:/resources/”,
“classpath:/static/”,
"classpath:/public/"下的,不是直接在resources下了,所以url中不用加上这些目录的目录名称

例:在这里插入图片描述

要访问bootstrap.min.css,只要这样

<!--/为项目根目录: localhost:8080/项目虚拟名称/ ,后面跟着静态资源目录下的文件url即可 -->
<link th:href="@{/css/bootstrap.min.css}" rel="stylesheet">

设置项目虚拟路径

server:
  servlet:
    context-path: /hello

删除thymeleaf的缓存

spring:
  thymeleaf:
    cache: false

伪装url

我们登录完后跳转的页面的主页的url一般不好看,这时候我们可以采用伪装url的方式美化且伪装主页的url,原理的重定向+转发

@Controller
@RequestMapping("user")
public class UserController {

    @RequestMapping("login")
    public String login(String username, String password, Model model){
        if ("admin".equals(username)&&"123456".equals(password)){
            //在我们成功登录后,我们不直接跳转首页,而是重定向到这个请求
            return "redirect:/main.html";
        }
        model.addAttribute("message","用户名或者密码错误");
        return "index";
    }
}
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/").setViewName("index");
        registry.addViewController("/index.html").setViewName("index");
        //这个请求实际上直接转发到首页
        registry.addViewController("/main.html").setViewName("dashboard");
    }

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

这样我们首页上显示的URL是XXX/main.html,十分美观

自定义登录拦截器

public class LoginInterceptor implements HandlerInterceptor {

    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        Object username = request.getSession().getAttribute("username");
        if (username==null){
            request.setAttribute("message","您未登录,权限不够");
            //注意,由于templates下的文件不可以直接访问,所以我们要通过服务器内部的请求再转发过去
            //由于配置了registry.addViewController("/index.html").setViewName("index");,所以我们可以直接转发到这个请求,再转发到页面
            request.getRequestDispatcher("/index.html").forward(request,response);
            return false;
        }else {
            return true;
        }
    }
}

再放在继承WebMvcConfigurer接口的类中

@Configuration
public class MyMvcConfig implements WebMvcConfigurer {
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/").setViewName("index");
        registry.addViewController("/index.html").setViewName("index");
        registry.addViewController("/main.html").setViewName("dashboard");
    }

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

    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LoginInterceptor())
                .addPathPatterns("/**") //所有路径都被拦截
                .excludePathPatterns("/","/index.html", "/user/login",
                        "/css/**","/js/**","/img/**");
        //排除登录所需要的路径和访问静态资源的路径
        // /** 代表当前文件夹及其子文件夹的所有请求
    }
}

公共代码片段使用问题

我们使用thymeleaf模板引擎时,可以提取一段代码片段,达到代码的复用

在这里插入图片描述

在commons.html下有代码片段,用th:fragment="topbar"为其命名

<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.username}]]</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" th:href="@{/user/logout}">注销</a>
        </li>
    </ul>
</nav>

我们可以在别的文件中引入这个代码片段

<div th:replace="~{commons/commons::topbar}"></div>

格式 : th:replace="~{代码片段的位置::代码片段的名字}"

thymeleaf处理后端数据

判断性别

<td th:text="${emp.getGender()==0?'':''}"></td>

处理日期格式

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

Springboot中自动日期格式解析

spring接受前端表单的提交数据自动解析所需要的格式是yyyy/MM/dd, 比如说 2002/11/30

我们可以自定义日期格式解析,在配置文件中设置,可使日期格式解析为 2002-11-30

spring:
  mvc:
    format:
      date: yyyy-MM-dd

也可以是(已经过时)

spring:
  mvc:
    date-format: yyyy-MM-dd HH:mm:ss

错误页面跳转

在templates文件夹下建一个error文件夹,里面的文件用状态码作为文件名,如 404.html

这样爆错的时候会根据状态码找到不同的页面进行跳转

在这里插入图片描述

这里设置了404文件夹,如果爆了404错误,自动跳转该页面

5. 整合数据库

5.1 整合JDBC

导入依赖

<?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.example</groupId>
    <artifactId>demo3</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>demo3</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>
        <!--spring data 的JDBC依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!--mysql的驱动-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </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.hjy.Demo3Application</mainClass>
                </configuration>
                <executions>
                    <execution>
                        <id>repackage</id>
                        <goals>
                            <goal>repackage</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

</project>

配置文件

# 数据库驱动:
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
# 数据库连接地址(这里配置了时区)
spring.datasource.url=jdbc:mysql://localhost:3306/mybatis?serverTimezone=UTC&useSSL=true&useUnicode=true&characterEncoding=UTF-8
# 数据库用户名&密码:
spring.datasource.username=root
spring.datasource.password=root

配置完后,springboot会自动注册一个DataSource 和 JdbcTemplate 进入容器,使用即可

@SpringBootTest
class Demo3ApplicationTests {

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

}
@Controller
@ResponseBody
public class JdbcController {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    @RequestMapping("hello")
    public List<Map<String, Object>> hello(){
        String sql = "select * from user";
        List<Map<String, Object>> maps = jdbcTemplate.queryForList(sql);
        return maps;
    }
}

jdbcTemplate如果是查询,方法名以query开头,如果是别的操作,方法名以update开头

5.2 整合Druid数据源

导入依赖

<dependencies>
    <!--spring data 的JDBC依赖-->
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid</artifactId>
        <version>1.2.6</version>
    </dependency>
    <dependency>
        <groupId>log4j</groupId>
        <artifactId>log4j</artifactId>
        <version>1.2.17</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-jdbc</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-thymeleaf</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <!--mysql的驱动-->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <scope>runtime</scope>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </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>

配置文件

spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/mybatis?serverTimezone=UTC&useSSL=true&useUnicode=true&characterEncoding=UTF-8
    username: root
    password: root
    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

把配置文件的配置导入数据源

@Configuration
public class DruidConfig {

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

在这里插入图片描述

配置监控页面

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.sql.DataSource;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

@Configuration
public class DruidConfig {

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

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

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

        //后台允许谁可以访问
        //initParams.put("allow", "localhost"):表示只有本机可以访问
        //initParams.put("allow", ""):为空或者为null时,表示允许所有访问
        initParams.put("allow", "");
        //设置初始化参数
        bean.setInitParameters(initParams);
        return bean;
    }


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

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

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

访问 localhost:8080/druid 即可登录查看数据源监控

5.3 整合mybatis

导入依赖

<?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.example</groupId>
    <artifactId>demo3</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>demo3</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>
        <!--mybatis依赖-->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.4</version>
        </dependency>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.2.6</version>
        </dependency>

        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!--mysql的驱动-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </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.hjy.Demo3Application</mainClass>
                </configuration>
                <executions>
                    <execution>
                        <id>repackage</id>
                        <goals>
                            <goal>repackage</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

</project>

配置文件

spring:
  datasource:
    username: root
    password: root
    url: jdbc:mysql://localhost:3306/mybatis?serverTimezone=UTC&useSSL=true&useUnicode=true&characterEncoding=UTF-8
    driver-class-name: com.mysql.cj.jdbc.Driver
    type: com.alibaba.druid.pool.DruidDataSource
    #Spring Boot 默认是不注入这些属性值的,需要自己绑定
    #druid 数据源专有配置
    initialSize: 5
    minIdle: 5
    maxActive: 20
    maxWait: 60000
    timeBetweenEvictionRunsMillis: 60000
    minEvictableIdleTimeMillis: 300000
    validationQuery: SELECT 1 FROM DUAL
    testWhileIdle: true
    testOnBorrow: false
    testOnReturn: false
    poolPreparedStatements: true

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

mybatis:
  # 自动扫描mapper的对应的xml文件
  mapper-locations: classpath:mybatis/mapper/*.xml
  # 指定mybatis的主配置文件
  configLocation: classpath:mybatis-config.xml

mapper

//表示这是一个mapper文件
@Mapper
@Repository
public interface UserMapper {

    public List<User> findAll();
}

也可以在启动类上加注解

@SpringBootApplication
@MapperScan("com.hjy.mapper")
public class Demo3Application {

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

}

mapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--namespace 一定为对应的mapper的全类名-->
<mapper namespace="com.hjy.mapper.UserMapper">
    <!--resultType只要写类型就行了,不用写泛型(List)-->

    <select id="findAll" resultType="com.hjy.pojo.User">
        select * from user ;
    </select>
</mapper>

mybatis-config

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>

    <settings>
        <!--日志输出-->
        <setting name="logImpl" value="STDOUT_LOGGING"/>
        <!--自动驼峰映射-->
        <setting name="mapUnderscoreToCamelCase" value="true"/>
    </settings>
    
    <typeAliases>
        <package name="com.hjy.pojo"/>
    </typeAliases>
</configuration>

在这里插入图片描述

使用

@Autowired
private UserMapper userMapper;

@Test
public void test4(){
    List<User> all = userMapper.findAll();
    for (User user : all) {
        System.out.println(user);
    }
}

6. 安全

6.1 springSecurity

6.1.1 准备工作

导入依赖

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

    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </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>
    <dependency>
        <groupId>org.springframework.security</groupId>
        <artifactId>spring-security-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

页面结构

在这里插入图片描述

controller

@Controller
public class TestController {

    @RequestMapping({"/","/index.html"})
    public String index(){

        return "index";
    }

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

    @RequestMapping("/level1/{id}")
    public String level1(@PathVariable("id") int id){
        return "views/level1/"+id;
    }

    @RequestMapping("/level2/{id}")
    public String level2(@PathVariable("id") int id){
        return "views/level2/"+id;
    }

    @RequestMapping("/level3/{id}")
    public String level3(@PathVariable("id") int id){
        return "views/level3/"+id;
    }
}

6.1.2 用户认证和授权

写在config包下

//里面有@configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
	//定义认证规则
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
		//在内存中定义,也可以在jdbc中去拿....
        //在内存中配置信息,三个账号和其权限
        auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder())
                .withUser("kuangshen").password(new BCryptPasswordEncoder().encode("123456")).roles("vip2","vip3")
                .and()
                .withUser("root").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1","vip2","vip3")
                .and()
                .withUser("guest").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1","vip2");
    }

    
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // 首页所有人可以访问,其他请求就需要权限了
        http.authorizeRequests().antMatchers("/").permitAll()
                .antMatchers("/level1/**").hasRole("vip1")
                .antMatchers("/level2/**").hasRole("vip2")
                .antMatchers("/level3/**").hasRole("vip3");
        // 开启自动配置的登录功能
        //如果当时没有登录,会自动发送/login请求,系统自带了一套页面,也可以用自己的替代
        http.formLogin();
    }


}

如果定义用户权限用数据库的话,就取出数据库的一条用户数据,然后自动向类中注入一个数据源,以下面格式即可

auth.jdbcAuthentication().dataSource(datasource).passwordEncoder(new BCryptPasswordEncoder())..

6.1.3 注销与权限控制

实现注销

@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        //在内存中配置信息,三个账号和其权限
        auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder())
                .withUser("kuangshen").password(new BCryptPasswordEncoder().encode("123456")).roles("vip2","vip3")
                .and()
                .withUser("root").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1","vip2","vip3")
                .and()
                .withUser("guest").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1","vip2");
    }

    //定义认证规则
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //在内存中定义,也可以在jdbc中去拿....
        http.authorizeRequests().antMatchers("/").permitAll()
                .antMatchers("/level1/**").hasRole("vip1")
                .antMatchers("/level2/**").hasRole("vip2")
                .antMatchers("/level3/**").hasRole("vip3");
        // 开启自动配置的登录功能
        //如果当时没有登录,会自动发送/login请求,系统自带了一套页面,也可以用自己的替代
        http.formLogin();
        //开启自动配置的注销的功能
        //请求为 /logout
        // .logoutSuccessUrl("/"); 注销成功来到首页
        http.logout().logoutSuccessUrl("/");
    }


}

加入http.logout().logoutSuccessUrl("/");,其注销后的跳转页面默认为登录页面,你可以自定义其注销成功后重定向的页面

权限控制

在页面中,我们可以依据登录用户的权限动态显示页面,首先需要thymeleaf和security集成

导入依赖

        <!--thymeleaf整合security框架-->
        <dependency>
            <groupId>org.thymeleaf.extras</groupId>
            <artifactId>thymeleaf-extras-springsecurity5</artifactId>
            <version>3.0.4.RELEASE</version>
        </dependency>

在每个前端页面加上

<html lang="en" xmlns:th="http://www.thymeleaf.org"
      xmlns:sec="http://www.thymeleaf.org/extras/spring-security">

比如说首页,在没登录的时候显示登录按钮,登录后显示注销按钮

<!--登录注销-->
<div class="right menu">
    <!--如果没有认证(登录)-->
 <div sec:authorize="!isAuthenticated()">
  <a class="item" th:href="@{/toLogin}">
   <i class="address card icon"></i> 登录
  </a>
 </div>
    <!--如果已经认证-->
 <div sec:authorize="isAuthenticated()">
  <a class="item">
   <i class="address card icon"></i>
   用户名:<span sec:authentication="principal.username"></span>
   角色:<span sec:authentication="principal.authorities"></span>
  </a>
 </div>

 <div sec:authorize="isAuthenticated()">
  <a class="item" th:href="@{/logout}">
   <i class="sign-out icon"></i> 注销
  </a>
 </div>

还可以依据权限等级来显示页面

<!--如果你的权限是vip1,显示一下界面-->
<div sec:authorize="hasRole('vip1')">
 <div class="ui raised segment">
  <div class="ui">
   <div class="content">
    <h5 class="content">Level 1</h5>
    <hr>
    <div><a th:href="@{/level1/1}"><i class="bullhorn icon"></i> Level-1-1</a></div>
    <div><a th:href="@{/level1/2}"><i class="bullhorn icon"></i> Level-1-2</a></div>
    <div><a th:href="@{/level1/3}"><i class="bullhorn icon"></i> Level-1-3</a></div>
   </div>
  </div>
 </div>
</div>

一、sec:authorize

authorize是用来判断普通权限的,通过判断用户是否具有对应的权限而控制其所包含内容的显示,其可以指定如下属性。

1??sec:authorize="isAnonymous()" //用户为游客则显示
2??sec:authorize="isAuthenticated()" //用户通过验证则显示
3??sec:authorize="hasRole('common')" //用户为common角色则显示
4??sec:authorize="hasAuthority('ROLE_vip')"//用户为ROLE_vip权限则显示

二、sec:authentication

authentication标签用来代表当前Authentication对象,主要用于获取当前Authentication的相关信息。

如通常我们的Authentication对象中存放的principle是一个UserDetails对象,所以我们可以通过如下的方式来获取当前用户的用户名。

1??sec:authentication="name"  //当前用户的用户名
2??sec:authentication="principal.username" //同上
3??sec:authentication="principal.authorities" //当前用户的身份
4??sec:authentication="principal.password" //当前用户的密码(显示不出来,但确实有)

6.1.4 记住我和自定义登录页面

@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        //在内存中配置信息,三个账号和其权限
        auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder())
                .withUser("kuangshen").password(new BCryptPasswordEncoder().encode("123456")).roles("vip2","vip3")
                .and()
                .withUser("root").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1","vip2","vip3")
                .and()
                .withUser("guest").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1","vip2");
    }

    //定义认证规则
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //在内存中定义,也可以在jdbc中去拿....
        http.authorizeRequests().antMatchers("/").permitAll()
                .antMatchers("/level1/**").hasRole("vip1")
                .antMatchers("/level2/**").hasRole("vip2")
                .antMatchers("/level3/**").hasRole("vip3");
        // 开启自动配置的登录功能
        //如果当时没有登录,会自动发送/login请求,系统自带了一套页面,也可以用自己的替代
        http.formLogin();
        //开启自动配置的注销的功能
        //请求为 /logout
        // .logoutSuccessUrl("/"); 注销成功来到首页
        http.logout().logoutSuccessUrl("/");

        http.formLogin().loginPage("/toLogin") //指定哪个页面为登录页面
                .usernameParameter("username") //前端传的账号参数的字段名
                .passwordParameter("password") //前端传的密码参数的字段名
                .loginProcessingUrl("/login"); //提交到默认的请求,前端表单上传的也是这个请求

        //记住我,实质上是一个cookie
        http.rememberMe().rememberMeParameter("remember");
    }


}

前端form表单必须是post请求,请求地址也必须是/login

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

记住我

<div class="field">
 <input type="radio" name="remember"> 记住我
</div>

6.2 shiro

主要功能

shiro主要有三大功能模块:

  1. Subject:主体,一般指用户。

  2. SecurityManager:安全管理器,管理所有Subject,可以配合内部安全组件。(类似于SpringMVC中的DispatcherServlet)

  3. Realms:用于进行权限信息的验证,一般需要自己实现。

细分功能

  1. Authentication:身份认证/登录(账号密码验证)。

  2. Authorization:授权,即角色或者权限验证。

  3. Session Manager:会话管理,用户登录后的session相关管理。

  4. Cryptography:加密,密码加密等。

  5. Web Support:Web支持,集成Web环境。

  6. Caching:缓存,用户信息、角色、权限等缓存到如redis等缓存中。

  7. Concurrency:多线程并发验证,在一个线程中开启另一个线程,可以把权限自动传播过去。

  8. Testing:测试支持;

  9. Run As:允许一个用户假装为另一个用户(如果他们允许)的身份进行访问。

  10. Remember Me:记住我,登录后,下次再来的话不用登录了。

6.2.1 设置用户认证和登录拦截和跳转登录页面

导入依赖

<dependencies>
    <dependency>
        <groupId>org.apache.shiro</groupId>
        <artifactId>shiro-spring-boot-web-starter</artifactId>
        <version>1.7.1</version>
    </dependency>

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

    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </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>

controller

@Controller
public class UserController {

    @RequestMapping({"/","index","index.html"})
    public String index(Model model){
        model.addAttribute("message","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";
    }
}

自定义的realm

public class Realms extends AuthorizingRealm {
    //授权
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        System.out.println("授权==>AuthorizationInfo");
        return null;
    }

    //认证
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        System.out.println("授权==>AuthorizationInfo");
        return null;
    }
}

shiroConfig

@Configuration
public class ShiroConfig {

    //配置realm对象
    @Bean
    public Realms realms(){
        return new Realms();
    }


    //配合securityManager对象
    //javaConfig方法上的参数相当于@Autowired,也可以在参数前加@Qualifier
    @Bean
    public DefaultWebSecurityManager defaultWebSecurityManager(Realms realms){
        DefaultWebSecurityManager bean = new DefaultWebSecurityManager();
        bean.setRealm(realms);
        return bean;
    }

    //Filter工厂,设置对应的过滤条件和跳转条件
    @Bean
    public ShiroFilterFactoryBean shiroFilterFactoryBean(DefaultWebSecurityManager defaultWebSecurityManager){
        ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
        bean.setSecurityManager(defaultWebSecurityManager);
        //设置shiro的内置拦截器
		/*
        anon : 无需认证就可访问
        authc : 必须认证才可以访问
        user : 必须拥有记住我功能才能用
        perms[role:permission] : 拥有对某个资源的权限才可以访问
        roles[role] : 拥有某个角色权限才可以访问
         */
        Map<String, String> map = new HashMap<>();
        //设置请求拦截
        map.put("/user/**", "authc");
        bean.setFilterChainDefinitionMap(map);

        //设置拦截成功后跳转的登录页面
        bean.setLoginUrl("/toLogin");
        return bean;
    }
}

在这里插入图片描述

6.2.2 用户认证

登录页面

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
   <head>
      <meta charset="UTF-8">
      <title>Title</title>
   </head>
   <body>
      <div>
         <p th:text="${msg}"></p>
         <form action="login" method="post">
            <p>用户名: <input type="text" name="username"></p>
            <p>密码: <input type="password" name="password"></p>
            <p><input type="submit" value="登录"></p>
         </form>
      </div>
   </body>
</html>

controller处理登录请求

@RequestMapping("login")
public String login(Model model,String username,String password){
    //获取当前用户
    Subject subject = SecurityUtils.getSubject();
    //封装用户的登录数据
    UsernamePasswordToken token = new UsernamePasswordToken(username, password);
    try {
        //执行登录方法,自动跳转到自定义的Realms类中的认证方法
        subject.login(token);
        return "index";
    }catch (UnknownAccountException e){ //用户名不存在
        model.addAttribute("msg","用户名错误");
        return "login";
    }catch (IncorrectCredentialsException e){ //密码不存在
        model.addAttribute("msg","密码错误");
        return "login";
    }
}

自定义的realms类中认证用户

public class Realms extends AuthorizingRealm {
    //授权
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        System.out.println("授权==>AuthorizationInfo");
        return null;
    }

    //认证
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        System.out.println("认证==>AuthorizationInfo");
        //从数据库中取出账户数据
        String username = "admin";
        String password = "123456";

        UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
        //用户名验证
        if (!token.getUsername().equals(username)){
            return null; //抛出异常
        }
        //密码验证,这个是shiro做
        return new SimpleAuthenticationInfo("",password,"");
    }
}

6.2.3 整合mybatis

在整合mybatis后,只需要修改认证的部分即可

import com.hjy.mapper.UserMapper;
import com.hjy.pojo.User;
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.springframework.beans.factory.annotation.Autowired;

public class Realms extends AuthorizingRealm {
    //授权
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        System.out.println("授权==>AuthorizationInfo");
        return null;
    }

    @Autowired
    private UserMapper userMapper;

    //认证
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {

        UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;

        User byName = userMapper.findByName(token.getUsername());
        if (byName == null) {
            return null;
        }
  
        return new SimpleAuthenticationInfo("", byName.getPassword(), "");
    }
}

在这里插入图片描述

6.2.4 用户授权

以下为所有配置

Realms

package com.hjy.config;

import com.hjy.mapper.UserMapper;
import com.hjy.pojo.User;
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 Realms extends AuthorizingRealm {
    //授权,在访问被内置拦截器拦截的请求的时候执行
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {

        SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();

        //获取当前用户和其Principal
        Subject subject = SecurityUtils.getSubject();
        User user = (User) subject.getPrincipal();
        //给当前请求设置角色和权限 role:permission
        String[] s = user.getPerms().split(" ");
        for (String s1 : s) {
            authorizationInfo.addStringPermission(s1);
        }

        return authorizationInfo;
    }

    @Autowired
    private UserMapper userMapper;

    //认证
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {

        UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;

        User user = userMapper.findByName(token.getUsername());
        if (user == null) {
            return null;
        }
        //密码验证,这个是shiro做,这次要把user带上
        return new SimpleAuthenticationInfo(user, user.getPassword(), "");
    }
}

ShiroConfig

package com.hjy.config;

import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

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

@Configuration
public class ShiroConfig {

    //配置realm对象

    @Bean
    public Realms realms(){
        return new Realms();
    }


    //配合securityManager对象
    //javaConfig方法上的参数相当于@Autowired
    @Bean
    public DefaultWebSecurityManager defaultWebSecurityManager(Realms realms){
        DefaultWebSecurityManager bean = new DefaultWebSecurityManager();
        bean.setRealm(realms);
        return bean;
    }

    //Filter工厂,设置对应的过滤条件和跳转条件
    @Bean
    public ShiroFilterFactoryBean shiroFilterFactoryBean(DefaultWebSecurityManager defaultWebSecurityManager){
        ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
        bean.setSecurityManager(defaultWebSecurityManager);
        //设置shiro的内置拦截器
        /*
        anon : 无需认证就可访问
        authc : 必须认证才可以访问
        user : 必须拥有记住我功能才能用
        perms[role:permission] : 拥有对某个资源的权限才可以访问
        roles[role] : 拥有某个角色权限才可以访问
         */
        Map<String, String> map = new HashMap<>();
        map.put("/user/add", "perms[user:add]");
        map.put("/user/update","perms[user:update]");
        bean.setFilterChainDefinitionMap(map);

        //设置拦截成功后跳转的登录页面
        bean.setLoginUrl("/toLogin");

        //设置未授权的请求
        bean.setUnauthorizedUrl("/unAuthorize");
        return bean;
    }
}

UserController

package com.hjy.controller;

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
public class UserController {

    @RequestMapping({"/","index","index.html"})
    public String index(Model model){
        model.addAttribute("message","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";
    }

    @RequestMapping("login")
    public String login(Model model,String username,String password){
        //获取当前用户
        Subject subject = SecurityUtils.getSubject();
        //封装用户的登录数据
        UsernamePasswordToken token = new UsernamePasswordToken(username, password);
        try {
            //执行登录方法,自动跳转到自定义的Realms类中的认证方法
            subject.login(token);
            return "index";
        }catch (UnknownAccountException e){ //用户名不存在
            model.addAttribute("msg","用户名错误");
            return "login";
        }catch (IncorrectCredentialsException e){ //密码不存在
            model.addAttribute("msg","密码错误");
            return "login";
        }
    }

    @RequestMapping("unAuthorize")
    @ResponseBody
    public String unAuthorize(){
        return "您未授权,请获得授权后再进行操作";
    }
}

7. swagger

7.1 快速开始

swagger可以管理后端中的接口,可以在页面中测试每个扫描到的请求,类似postman

导入依赖

<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger-ui</artifactId>
    <version>2.9.2</version>
</dependency>
<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger2</artifactId>
    <version>2.9.2</version>
</dependency>

配置类SwaggerConfig

import org.springframework.context.annotation.Configuration;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

@Configuration
@EnableSwagger2
public class SwaggerConfig {
}

访问页面

http://localhost:8080/swagger-ui.html

7.2 自定义信息

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Contact;
import springfox.documentation.service.VendorExtension;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

import java.util.ArrayList;

@Configuration
@EnableSwagger2
public class SwaggerConfig {

    @Bean
    public Docket docket(){
        return new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo());
    }

    private ApiInfo apiInfo(){
        Contact contact = new Contact("何骏扬", "https://blog.csdn.net/qq_51677409", "2402668109@qq.com");
        return new ApiInfo(
                "何骏扬的接口文档",
                "请叫我何帅哥",
                "1.0",
                "https://blog.csdn.net/qq_51677409",   //服务的组URL,可以写你的博客
                contact,
                "Apache 2.0",
                "http://www.apache.org/licenses/LICENSE-2.0",
                new ArrayList<VendorExtension>());
    }
}

7.3 配置扫描的接口

@Configuration
@EnableSwagger2
public class SwaggerConfig {

    @Bean
    public Docket docket(){
        return new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(apiInfo())
                .select()
                //设置扫描的包
                .apis(RequestHandlerSelectors.basePackage("com.hjy.controller"))
                //只有这些请求会被成功添加
                .paths(PathSelectors.ant("/hjy/**"))
                .build();
    }

    private ApiInfo apiInfo(){
        Contact contact = new Contact("何骏扬", "https://blog.csdn.net/qq_51677409", "2402668109@qq.com");
        return new ApiInfo(
                "何骏扬的接口文档",
                "请叫我何帅哥",
                "1.0",
                "https://blog.csdn.net/qq_51677409",   //服务的组URL,可以写你的博客
                contact,
                "Apache 2.0",
                "http://www.apache.org/licenses/LICENSE-2.0",
                new ArrayList<VendorExtension>());
    }
}

使用建造者模式

7.4 使用swagger的开关

在项目正式上线的时候,我们肯定是不会使用swagger技术,所以我们可以动态的管理swagger的开关,使用enable(boolean)

@Configuration
@EnableSwagger2
public class SwaggerConfig {

    //获取配置文件中的配置,动态更新是否使用swagger
    @Value("${flag}")
    private boolean flag;

    @Bean
    public Docket docket(){
        return new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(apiInfo())
            	//配置是否使用swagger
                .enable(flag)
                .select()
                //设置扫描的包
                .apis(RequestHandlerSelectors.basePackage("com.hjy.controller"))
                //只有这些请求会被成功添加
                .paths(PathSelectors.ant("/hjy/**"))
                .build();
    }

    private ApiInfo apiInfo(){
        Contact contact = new Contact("何骏扬", "https://blog.csdn.net/qq_51677409", "2402668109@qq.com");
        return new ApiInfo(
                "何骏扬的接口文档",
                "请叫我何帅哥",
                "1.0",
                "https://blog.csdn.net/qq_51677409",   //服务的组URL,可以写你的博客
                contact,
                "Apache 2.0",
                "http://www.apache.org/licenses/LICENSE-2.0",
                new ArrayList<VendorExtension>());
    }
}

或者使用多环境配置,通过判断你使用的环境判断是否开启swagger

@Configuration
@EnableSwagger2
public class SwaggerConfig {
    
    @Bean
    public Docket docket(Environment environment){
        //是dev或者test环境就开启swagger
        Profiles profiles =Profiles.of("dev","test");
        boolean flag = environment.acceptsProfiles(profiles);
        return new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(apiInfo())
                .enable(flag)
                .select()
                //设置扫描的包
                .apis(RequestHandlerSelectors.basePackage("com.hjy.controller"))
                //只有这些请求会被成功添加
                .paths(PathSelectors.ant("/hjy/**"))
                .build();
    }

    private ApiInfo apiInfo(){
        Contact contact = new Contact("何骏扬", "https://blog.csdn.net/qq_51677409", "2402668109@qq.com");
        return new ApiInfo(
                "何骏扬的接口文档",
                "请叫我何帅哥",
                "1.0",
                "https://blog.csdn.net/qq_51677409",   //服务的组URL,可以写你的博客
                contact,
                "Apache 2.0",
                "http://www.apache.org/licenses/LICENSE-2.0",
                new ArrayList<VendorExtension>());
    }
}

7.5 组

@Configuration
@EnableSwagger2
public class SwaggerConfig {

    @Bean
    public Docket docket(){
        return new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(apiInfo())
                //设置自己的组,因为后期每个人都有一个swagger文档,最后都汇集起来
                .groupName("何骏扬")
                .select()
                //设置扫描的包
                .apis(RequestHandlerSelectors.basePackage("com.hjy.controller"))
                //只有这些请求会被成功添加
                .paths(PathSelectors.ant("/hjy/**"))
                .build();
    }

    private ApiInfo apiInfo(){
        Contact contact = new Contact("何骏扬", "https://blog.csdn.net/qq_51677409", "2402668109@qq.com");
        return new ApiInfo(
                "何骏扬的接口文档",
                "请叫我何帅哥",
                "1.0",
                "https://blog.csdn.net/qq_51677409",   //服务的组URL,可以写你的博客
                contact,
                "Apache 2.0",
                "http://www.apache.org/licenses/LICENSE-2.0",
                new ArrayList<VendorExtension>());
    }
}

7.6 注释

注释汇总

作用范围API使用位置
对象属性@ApiModelProperty用在出入参数对象的字段上
协议集描述@Api用于controller类上
协议描述@ApiOperation用在controller的方法上
Response集@ApiResponses用在controller的方法上
Response@ApiResponse用在 @ApiResponses里边
非对象参数集@ApiImplicitParams用在controller的方法上
非对象参数描述@ApiImplicitParam用在@ApiImplicitParams的方法里边
描述返回对象的意义@ApiModel用在返回对象类上
描述参数@ApiParam用在controller方法参数上
@Data
@AllArgsConstructor
@NoArgsConstructor
@ApiModel("用户")
public class User {
    @ApiModelProperty("用户名")
    private String username;
    @ApiModelProperty("密码")
    private String password;
}
@RestController
@Api(tags={"用户操作接口"})
public class HelloController {

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

    @ApiOperation("得到一个user对象")
    @RequestMapping("hjy/user")
    public User user(@ApiParam("用户名") String username,@ApiParam("密码") String password){
        return new User(username,password);
    }
}

8. 任务

8.1 异步任务

  1. 在启动类上加注解@EnableAsync
  2. 在想被异步任务的注解上加上@Async
@SpringBootApplication
@EnableAsync
public class Demo5Application {

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

}
@Service
public class HelloService {

    @Async
    public void hello(){
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("数据执行完毕");
    }
}
@RestController
public class HelloController {

    @Autowired
    private HelloService helloService;

    @RequestMapping("hello")
    public String hello(){
        helloService.hello();
        return "ok";
    }
}

访问http://localhost:8080/hello的时候,直接把helloService.hello();当多线程跳过了,两边同时执行

8.2 邮件任务

  1. 首先设置配置文件
# 固定
spring.mail.host=smtp.qq.com
# 授权码
spring.mail.password=pcafgozgzwgxebdc
# 发送者邮箱
spring.mail.username=2402668109@qq.com
# qq邮箱必须写
spring.mail.properties.mail.smtp.ssl.enable=true
# 后面这两个可以不写
# 邮件发送过程的日志会在控制台打印出来,方便排查错误
spring.mail.properties.mail.debug=true
# 端口
spring.mail.port=465

126邮箱SMTP服务器地址:smtp.126.com,端口号:465或者994
2163邮箱SMTP服务器地址:smtp.163.com,端口号:465或者994
yeah邮箱SMTP服务器地址:smtp.yeah.net,端口号:465或者994
qq邮箱SMTP服务器地址:smtp.qq.com,端口号465或587

  1. 发送邮件
@SpringBootTest
class Demo5ApplicationTests {

    @Autowired
    private JavaMailSenderImpl sender;

    //发送简单邮件
    @Test
    void contextLoads1() throws SQLException {
        SimpleMailMessage simpleMailMessage = new SimpleMailMessage();
        simpleMailMessage.setSubject("爷的主题");
        simpleMailMessage.setText("爷的简单邮件");
        simpleMailMessage.setFrom("2402668109@qq.com");
        simpleMailMessage.setTo("2402668109@qq.com");//可变参数,可以发送给多人
        sender.send(simpleMailMessage);
    }

    //发送复杂邮件
    @Test
    void contextLoads2() throws SQLException, MessagingException {
        MimeMessage mimeMessage = sender.createMimeMessage();
        MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, true, "UTF-8");
        helper.setSubject("爷的主题");
        //使用html格式
        helper.setText("<p style='color:red'>爷的复杂邮件</p>", true);

        //添加附件
        helper.addAttachment("1.png", new File("src/main/resources/1.png"));
        helper.setTo("2402668109@qq.com");
        helper.setFrom("2402668109@qq.com");
        sender.send(mimeMessage);
    }

}

带图片资源邮件

@Test
public void sendImgResMail() throws MessagingException {
	MimeMessage mimeMessage = sender.createMimeMessage();
    MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, true, "UTF-8");
    helper.setSubject("这是一封测试邮件");
    helper.setFrom("2402668109@qq.com");
    helper.setTo("2402668109@qq.com");
    helper.setSentDate(new Date());
    //src='cid:p01' 占位符写法 ,第二个参数true表示这是一个html文本
    helper.setText("<p>hello 大家好,这是一封测试邮件,这封邮件包含两种图片,分别如下</p><p>第一张图片:</p><img src='cid:p01'/><p>第二张图片:</p><img src='cid:p02'/>",true);
    // 第一个参数指的是html中占位符的名字,第二个参数就是文件的位置
    helper.addInline("p01",new FileSystemResource(new File("/Users/gamedev/Desktop/压缩.jpeg")));
    helper.addInline("p02",new FileSystemResource(new File("/Users/gamedev/Desktop/瑞文.jpg")));
    javaMailSender.send(mimeMessage);
}

8.3 定时任务

  1. 启动类上加上注解@EnableScheduling
  2. 定时执行的方法上加上@Scheduled
@SpringBootApplication
@EnableScheduling
public class Demo5Application {

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

}
@Service
public class HelloService {


    //秒 分 时 日 月 周几
    @Scheduled(cron = "0/2 * * * * ?") //从第0秒开始,每两秒执行一次
    public void hello2(){
        System.out.println("任务执行了");
    }
}

cron表达式直接去https://cron.qqe2.com/生成即可

9. Zookeeper和dobbo

9.1 下载

zookeeper : https://zookeeper.apache.org/

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

然后解压,打开config,赋值粘贴zoo_sample.cfg,再改名为zoo.cfg

如果还是不行在zkServer.cmd的最后面加pause,看是哪里出了问题

在这里插入图片描述

在这里插入图片描述

运行zkServer.cmd即可

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

然后在项目C:\Users\hjy\Desktop\dubbo-admin-develop 直接执行

mvn clean package -Dmaven.test.skip=true

C:\Users\hjy\Desktop\dubbo-admin-develop\dubbo-admin-distribution\target

再执行

java -jar .\dubbo-admin-0.3.0.jar

如果jdk版本高,则会报错

9.2 使用

参考地址

  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2021-08-26 11:59:28  更:2021-08-26 12:01:11 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2025年1日历 -2025/1/31 1:54:26-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码