飞道的博客

Spring Boot 知识总结

253人阅读  评论(0)

Spring Boot 知识总结

一、Spring Boot基础

1.1 什么是Spring

Spring是一个开源框架,2003年兴起的一个Java轻量级开发框架,作者:Rod Johnson。

Spring是为了解决企业级应用开发的复杂性而创建的,简化开发。

Spring是如何简化Java开发的

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

  1. 基于POJO的轻量级和最小侵入性编程;
  2. 通过ICO,依赖注入(DI)和面向接口编程实现松耦合;
  3. 基于切面(AOP)和惯例进行声明式编程;
  4. 通过切面和模板减少样式代码

什么是SpringBoot

  1. Spring就是一个javaweb的开发框架,和SpringMVC类似,对比其他Javaweb的好处,官方说是简化开发,约定大于配置,you can “just run”,能迅速开发的web应用,几行代码开发一个http接口
  2. SpringBoot的主要优点:
    • 为所有Spring开发者更快速的入门
    • 开箱即用,提供各种默认配置来简化项目配置
    • 内嵌式容器简化Web项目
    • 没有冗余代码生成和XML配置的要求

1.2 微服务

微服务介绍

  1. 什么是微服务

    微服务是一种架构风格,它要求我们在开发应用的时候,这个应用必须构建成一系列小服务的组合;可以通过http的方式进行互通。

  2. 单体应用架构

    • 所谓单体应用架构(all in one)是指我们将一个应用中的所有应用服务都封装在一个应用服务中
    • 无论是CRM、ERP或是其他什么系统,你都把数据库访问、web访问等等各种功能都放到一个war包内
    • 这样做的好处是易于开发和测试,也十分方便部署;当需要拓展是,只需将war复制多份,然后放到多个服务器上,再做一个负载均衡就可以了
    • 单体架构的缺点是,哪怕我需要改一个非常小的地方,我都需要停掉整个服务,重新打包、部署这个应用war包。特别是对于一个大型项目,我们不可能把所有应用都放在一个应用里面,这样我们如何维护、如何分工合作都是问题

微服务架构

  1. all in one 架构方式,我把所有功能单元都放在一个应用里面,然后我们把整个应用都部署在服务器上。如果负载能力不行,我们把整个应用进行水平复制、进行拓展,然后再负载均衡
  2. 所谓微服务架构就是打破之前all in one的架构风格,把每个功能元素独立出来。把独立出来的功能元素动态组合,需要的功能元素才拿来组合,需要多一些时可以整合多个功能元素。所以微服务架构是对功能元素进行复制,而没有对整个应用进行复制
  3. 这样做的好处:
    • 节省了调用资源
    • 每个功能元素的服务都是一个可替换的、可独立升级的软件代码

如何构建微服务

  1. 一个大型系统的微服务架构,就像是一个复杂交织的神经网络,每一个神经元就是一个功能元素,它们各自完成自己的功能,然后通过http互相请求调用。比如一个电商系统,查缓存、查数据库、浏览页面、结账、支付等服务都是一个个独立的功能服务,都被微化了,它们作为一个个微服务共同构建了一个庞大的系统。如果修改其中的一个功能,只需要更新其中一个功能服务单元即可

  2. 但是这种庞大的架构给部署和运维带来了很大的难度。于是Spring为我们带来了构建大型分布式微服务的全套产品:

    • 构建一个个功能独立的微服务应用单元,可以使用SpringBoot,可以帮我们快速构建一个应用
    • 大型分布式网络服务的调用,这部分由SpringCloud来完成,实现分布式
    • 在分布式中间,进行流式数据计算、批处理,我们有SpringCloudDataFlow
    • Spring为我们想清楚了从开始构建应用到大型分布式应用的全流程方案
  3. 微服务:

    原文是 Martin Fowler 于 2014 年 3 月 25 日写的《Microservices》,翻译链接:

    https://www.kuangstudy.com/bbs/1374644434515275777

1.3 自动装配原理

  1. 自动配置:pom.xml

    spring-boot-dependencies:核心在父工程中
    
  2. 我们在写或者引入一些SpringBoot依赖的时候,不需要制定版本就是因为有这些版本仓库

  3. 启动器

    • 启动器:说白了,就是SpringBoot的使用场景
    • 比如说,spring-boot-starter-web,它就会自动帮我们导入web环境下所有的依赖
    • SpringBoot会将所有的功能场景,变成一个个的启动器
    • 我们要使用什么功能,只需要找到对应的启动器就可以了
    <!--启动器-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter</artifactId>
    </dependency>
    
  4. 主程序

    • 代码

      //@SpringBootApplication:标注这个类是一个SpringBoot的应用
      @SpringBootApplication
      public class HelloApplication {
          //将SpringBoot应用启动
          public static void main(String[] args) {
              SpringApplication.run(HelloApplication.class, args);
          }
      
      }
      
    • 注解解析:

      @SpringBootConfiguration:SpringBoot的配置
         @Configuration:Spring配置类
         @Component:说明这也是一个Spring的组件
      
      @EnableAutoConfiguration:自动配置
         @AutoConfigurationPackage:自动配置包
             @Import({AutoConfigurationPackage.Registrar.class}):自动配置包注册
         @Import({AutoConfigurationImportSelector.class}) :自动配置导入选择器
         
      //获取所有的配置 
      List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
      
      //获取候选配置方法
      protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
              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;
          }
          
      
           
  5. META-INF/spring.factories:自动配置的核心文件
    

  1. 总结

    SpringBoot中所有的自动配置都在启动类中被扫描并加载,spring.factories:所有的自动配置类都在这里了,但不一定生效,要判断条件是否满足,只要导入了对应的start,就有对应的启动器了,有了启动器我们的自动装配就会生效,然后就配置成功了

    1)SpringBoot在启动的时候,从类路径下/META-INF/spring.factories获取指定的值

    2)将这些自动配置的类导入容器,自动配置就会生效,帮我们进行自动配置

    3)以前我们需要自动配置的东西,现在SpringBoot帮我们做了

    4)整个Java EE,解决方案和自动配置的东西都在spring-boot-autoconfigure-2.6.4.jar这个包下

    5)它会把所有需要导入的组件以类名方式返回,这些组件就会被添加到容器

    6)容器中也会存在很多的XXXAutoConfiguration的文件(@Bean:组件),就是这些类给容器中导入了这个场景所需要的所有组件;并自动配置,@Configuration(配置),javaConfig!

    7)有了自动配置类,就免去了我们手动编写配置文件的工作

  2. 关于SpringBoot谈谈你的理解:

    • 自动装配

    • run()

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

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

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

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

1.4 yaml语法详解

配置文件

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

application.properties

○ 语法结构: key=value

# SpringBoot这个配置文件到底有哪些东西呢?
#
# 官方的配置太多了
#
# 了解原理:一通百通
# 将其删除,创建application.yaml文件,因为properties只能保存键值对
name=xiaoShu

student.name=xiaoShu
student.age=3

application.yml

○ 语法结构:key:空格 value

# k=v
#对空格的要求十分高!
#普通的key-value

# 可以注入到配置类中!

name: xiaoShu
# 对象
student:
  name: xiaoShu
  age: 3

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

# 数组
pets:
  - dog
  - cat
  - pig

# pets: [dog,cat,pig]

 

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

YAML

YAML是"YAML Ain’t a Markup Language"(YAML不是一种置标语言)的递归缩写。

在开发的这种语言时,YAML的意思其实是:“Yet Another Markup Language”(仍是一种置标语言)

YAML A Markup Language:是一个标记语言

YAML is not Markup Language:不是一个标记语言

标记语言

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

yaml配置:

  • yaml可以直接给实体类赋值
server:
  port: 8080

xml配置:

<server>
<port>8080<server>
</server>

功能对比图:

  • cp只需要配置一次即可,@value()则需要每个字段都添加

  • 松散绑定:这个是什么意思呢?比如我的yaml中写的last-name,这个和lastName是一样的,-后面跟着的字母默认是大写的。这就是松散绑定。

  • JSR303数据校验:这个就是我们为字段增加一层过滤器验证,保证我们数据的合法性

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

    @Component         //表示Spring的组件:实体类,注册Bean
    @ConfigurationProperties(prefix = "dog")
    @Validated         //数据校验
    public class Dog {
        //@Value("旺财")  //给属性赋值
        @Email //name必须是邮箱格式
        private String name;
        @Value("#{11*2}")    //#{SPEL} Spring表达式
        private Integer age;
    
  • 复杂类型封装:yaml可以支持封装数据对象,@value就不支持

结论:

  • 配置yaml和properties都可以获取到值,一般使用yaml
  • 如果我们在业务中,只需要获取配置文件中的某个值可以使用一下@value
  • 如果说,我们专门编写了一个JavaBean来和配置文件进行映射,就直接使用@ConfigurationProperties(prefix = “XXX”),不要犹豫!

SpringBoot的多环境配置,可以选择激活哪一个

application.properties配置:

application.yaml配置:

自动配置原理总结:

一句话总结:根据当前不同的条件判断,决定这个配置类是否生效
一旦这个配置类生效,这个配置类就会给容器中添加各种组件;这些组件的属性是从对应的properties类中获取的,这些类的
每一个属性又是和配置文件绑定的

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

#可以通过debug: true来查看哪些自动配置类生效了,哪些没有生效
debug: true

二、Spring Boot:Web开发

2.1 静态资源

(模板库:百度bootstarap模板)

public void addResourceHandlers(ResourceHandlerRegistry registry) {
    if (!this.resourceProperties.isAddMappings()) {
        //自定义路径返回
        logger.debug("Default resource handling disabled");
    } else {
         //系统默认路径返回
        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);
        }
    }
}

 

总结:

  1. 在springboot,我们可以使用以下方式处理静态资源(index.html放在这些路径下访问:http://localhost:8080/即可打开首页)
  • webjars localhost:8080/webjars/

  • public,static,/**,resources localhost:8080/

    注:优先级: resources>static (默认) >public

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

2.2 模板引擎ThymeLeaf

前端交给我们的页面,是html页面。如果是我们以前开发,我们需要把他们转成jsp页面,jsp好处就是当我们查出一些数据转发到JSP页面以后,我们可以用jsp轻松实现数据的显示,及交互等。jsp支持非常强大的功能,包括能写Java代码,但是呢,我们现在的这种情况,
SpringBoot这个项目首先是以jar的方式,不是war,像第二,我们用的还是嵌入式的Tomcat,所以呢,他现在默认是不支持jsp的。

那不支持jsp,如果我们直接用纯静态页面的方式,那给我们开发会带来非常大的麻烦,那怎办呢,SpringBoot推荐你可以来使用模板引擎

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

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

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

  1. 导入ThymeLeaf依赖
 <!--ThymeLeaf依赖-->
<dependency>
<groupId>org.thymeleaf</groupId>
<artifactId>thymeleaf-spring5</artifactId>
</dependency>
<dependency>
    <groupId>org.thymeleaf.extras</groupId>
    <artifactId>thymeleaf-extras-java8time</artifactId>
</dependency>
  1. 测试

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

结论:只要需要需要使用thymeleaf,导入对应的依赖就可以了;然后我们将html页面放在templates目录下即可

//index.html
<!--所有的html元素都可以被thymeLeaf 替换接管:th:元素名-->
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<div th:text="${msg}"></div>
</body>
</html>

//test.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>test</title>
</head>
<body>
test
<div th:text="${msg1}"></div>
<div th:each="user : ${users}" th:text="${user}"></div>
</body>
</html>

 

快捷键:Ctrl+F 类内搜索

扩展IndexController编写:

package com.shu.controller;

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

import java.util.Arrays;

@Controller
//在templates目录下的所有页面只能通过controller才能跳转
//这个需要模板引擎的支持  thymeleaf
public class IndexController {
    @RequestMapping("/index")
    public String index(Model model){
        model.addAttribute("msg","index");
        return "index";
    }
    @RequestMapping("/test")
    public String test(Model model){
        model.addAttribute("msg1","Hello,World!");
        model.addAttribute("users", Arrays.asList("xiao","Shu"));
        return "test";
    }
}

 

2.3 扩展SpringMVC

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

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

package com.shu.config;

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.WebMvcConfigurer;

import java.util.Locale;

@Configuration  //使这个类变成配置类
//如果你想自定义一些定制化的功能,只需要写这个组件,将它交给SpringBoot,SpringBoot就会帮我们自动装配!
//需要实现WebMvcConfigurer接口
public class MyMvcConfig implements WebMvcConfigurer {
    //ViewResolver:实现了视图解析器的类我们就可以把它叫做视图解析器
    @Bean
    public ViewResolver MyViewResolver(){
        return new MyViewResolver();
    }
    public static class MyViewResolver implements ViewResolver{
        @Override
        public View resolveViewName(String viewName, Locale locale) throws Exception {
            return null;
        }
    }
}

 

使用扩展SpringMVC实现视图跳转功能

package com.shu.config;

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

//如果我们要扩展一个SpringMVC,官方建议我们这样去做!
@Configuration  //使这个类变成配置类
public class MyMvcConfig implements WebMvcConfigurer {
    //视图跳转
    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/xiaoshu").setViewName("/test");
    }
}

在springboot中,有非常多的xxxx Configuration帮助我们进行扩展配置,只要看见了这个东西,我们就要注意了!

2.4 员工管理系统小案例

  1. application.yml文件相关配置
#关闭默认图标
#spring.mvc.favicon.enabled=false
spring:
  mvc:
    favicon:
      enabled: false

#关闭模板引擎的缓存
#spring.thymeleaf.cache=false
  thymeleaf:
    cache: false
#自定义项目访问路径
server:
  servlet:
    context-path: /xiaoshu


 

2.页面国际化:

简写:

i18n ——>internationalization:首字母 + 尾字母 + 单词数量

例:k8s

总结:

1.首页配置

  • 注意点,所有页面的静态资源都需要使用thymeleaf接管
  • url: @{}
<a class="btn btn-sm" th:href="@{/index(l='zh_CN')}">中文</a>
<a class="btn btn-sm" th:href="@{/index(l='en_US')}">English</a>

2.页面国际化︰

  • 我们需要配置i18n文件
  • 我们如果需要在项目中进行按钮自动切换,我们需要自定义一个组件LocaleResolver
  • 记得将自己写的组件配置到spring容器@Bean
  • #{}
package com.shu.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.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

//如果我们要扩展一个SpringMVC,官方建议我们这样去做!
@Configuration  //使这个类变成配置类
public class MyMvcConfig implements WebMvcConfigurer {
    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/").setViewName("index");
        registry.addViewController("/index").setViewName("index");
    }
    //自定义的国际化组件就生效了!
    @Bean
    public LocaleResolver localeResolver(){
        return new MyLocaleResolver();
    }
}

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

    }
}

 
  1. thymeleaf实现代码复用

    • dashboard.html

      <!--侧边栏   抽取公共部分:th:fragment="sidebar"-->
      <nav class="col-md-2 d-none d-md-block bg-light sidebar" th:fragment="sidebar"></>
      
    • list.html

      <!--侧边栏-->
      <div th:insert="~{dashboard :: sidebar}"></div>
      
      <!--侧边栏-->
      <div th:replace="~{commons/commons.html :: sidebar(active='list.html')}"></div>
      
  2. 员工列表展示

    • 提取公共页面

    • th:fragment=“sidebar”

    • th:replace=“~{commons/commons : :topbar}”

    • 如果要传递参数,可以直接使用()传参,接收判断即可!2.列表循环展示

  3. 添加员工

    1. 按钮提交
    2. 跳转到添加页面
    3. 添加员工成功
    4. 返回首页
  4. CRUD

  5. 404

三、Spring Data

3.1简介

对于数据访问层,无论是 SQL(关系型数据库)还是 NOSQL(非关系型数据库),Spring Boot 底层都是采用Spring Data 的方式进行统一处理。
Spring Boot 底层都是采用 Spring Data 的方式进行统一处理各种数据库,Spring Data 也是 Spring 中与 Spring Boot、Spring Cloud 等齐名的知名项目。
Sping Data 官网: https://spring.io/projects/spring-data

数据库相关的启动器:

可以参考官方文档: https://docs.spring.io/spring-boot/docs/2.1.7.RELEASE/reference/htmlsingle/#using-boot-starter

3.2 JDBC

准备工作

  1. 我们去新建一个项目测试: Springboot_Data ;引入相应的模块! 基础模块

  1. 添加相关依赖

  1. 配置数据库参数

    spring:
      datasource:
        username: root
        password: 123456
        # 假如时区报错了,就增加一个时区的配置就行:serverTimeone=UTC
        url: jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=utf-8&serverTimeone=UTC
        driver-class-name: com.mysql.jdbc.Driver
    

  1. 数据库创建
CREATE DATABASE if NOT EXISTS `mybatis`;
USE `mybatis`;
CREATE TABLE `user`(
  `id` INT(10) NOT NULL COMMENT '用户id',
	`name` VARCHAR(50) NOT NULL COMMENT '用户名',
	`pwd` VARCHAR(50) NOT NULL COMMENT '密码',
	PRIMARY KEY(`id`)
)ENGINE=INNODB DEFAULT CHARSET=utf8;
INSERT INTO `user`(`id`,`name`,`pwd`) 
VALUES(1,'张三','123456'),
(2,'李四','123456'),
(3,'王五','123456');
  1. 测试连接
package com.shu;

import jakarta.annotation.Resource;
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 SpringbootDataApplicationTests {
    //自动装配包括@Autowired,@Resource,注意此处不能使用@Autowired进行自动装配
    //@Autowired是按类型查找Bean,@Resource是按名查找Bean
    //yaml配置文件应该不支持使用@Autowired进行自动装配,如果此处使用@Autowired就会报错
    @Resource
    DataSource dataSource;
    @Test
    void contextLoads() {
        System.out.println();
        //查看默认的数据源  class com.zaxxer.hikari.HikariDataSource:dbcp,Springboot默认的数据源
        System.out.println(dataSource.getClass());

        //获得数据库连接
        try {
            Connection connection = dataSource.getConnection();
            System.out.println(connection);

            //xxx Template:Springboot已经配置好模板bean,拿来即用;下面用一下jdbc Template给大家看一下
            //jdbc Template
            //redis Template

            connection.close();
        } catch (Exception ex) {
            ex.printStackTrace();
        }

    }

}

 
  1. 连接成功

jdbc增删改查

  1. 新建一个controller

    package com.shu.controller;
    
    import jakarta.annotation.Resource;
    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 {
        @Resource
        JdbcTemplate jdbcTemplate;
        //查询数据库的所有信息并显示到网页
        //没有实体类,数据库中的东西怎么获取? Map
        @GetMapping("/queryUser")
        public List<Map<String,Object>> userList(){
            String sql = "SELECT * FROM `user`";
            List<Map<String, Object>> maps = jdbcTemplate.queryForList(sql);
            return maps;
        }
        @GetMapping("/addUser")
        public String addUser(){
            String sql = "INSERT `user`(name,pwd) VALUES('新区','122456')";
            //jdbcTemplate.execute(sql);
            jdbcTemplate.update(sql);
            return "新增成功";
        }
        @GetMapping("/updateUser/{id}")
        public String updateUser(@PathVariable("id") int id){
            String sql = "UPDATE `user` SET `name`=?,pwd=? WHERE id="+id;
            //jdbcTemplate.execute(sql);
            Object[] objects = new Object[2];
            objects[0]="xxx";
            objects[1]="123456";
    
            jdbcTemplate.update(sql,objects);
            return "更新成功";
        }
        @GetMapping("/deleteUser/{id}")
        public String deleteUser(@PathVariable("id") int id){
            String sql = "DELETE FROM `user` WHERE id = ?";
            //jdbcTemplate.execute(sql);
            jdbcTemplate.update(sql,id);
            return "删除成功";
        }
    }
    
       
  2. 注意,使用@RestController注解需要引入一个在pom.xm文件中引入一个web依赖

    <!--web依赖-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    
  3. 查询成功,其他就不一一测了

3.3 整合Druid数据源

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

Druid可以很好的监控DB池连接和SQL的执行情况,天生就是针对监控而生的DB 连接池。
Spring Boot 2.0以上默认使用Hikari 数据源,接下是我们使用Spring Boot集成Druid数据源,实现数据库监控。

  1. 第一步需要在应用的 pom.xml 文件中添加上 Druid 数据源依赖,而这个依赖可以从 Maven 仓库官网中获取

Maven Repository :https://mavenrepository.com/

<!-- https://mvnrepository.com/artifact/com.alibaba/druid -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.2.15</version>
</dependency>
  1. 导入上面的依赖到我们的项目中

  2. 在配置文件中指定使用的数据源为druid,并测试查看:

  1. druid数据源配置
spring:
  datasource:
    username: root
    password: 123456
    # 假如时区报错了,就增加一个时区的配置就行:serverTimeone=UTC
    url: jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=utf-8&serverTimeone=UTC
    driver-class-name: com.mysql.cj.jdbc.Driver
    # 通过type属性切换数据源:使用druid数据源
    type: com.alibaba.druid.pool.DruidDataSource
    
    #Spring Boot 默认是不注入这些属性值的,需要自己绑定
    #druid 数据源专有配置
    initialSize: 5
    minIdle: 5
    maxActive: 20
    maxWait: 60000
    timeBetweenEvictionRunsMillis: 60000
    minEvictableIdleTimeMillis: 300000
    validationQuery: SELECT 1 FROM DUAL
    testWhileIdle: true
    testOnBorrow: false
    testOnReturn: false
    poolPreparedStatements: true

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

 

5.导入log4j依赖

<!--log4j-->
<dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    <version>1.2.12</version>
</dependency>

6.编写控制器

package com.shu.config;


import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.support.http.StatViewServlet;
import jakarta.servlet.Servlet;
import jakarta.servlet.http.HttpServlet;
import org.springframework.boot.context.properties.ConfigurationProperties;
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.HashMap;

@Configuration
public class DruidConfig {

    //将druid数据源及其配置参数注册到容器
    @ConfigurationProperties(prefix = "spring.datasource")  //与yaml文件进行绑定
    @Bean
    public DataSource druidDataSource(){
        return new DruidDataSource();
    }
    /*//后台监控:web.xml   此处有问题,未能解决
    @Bean
    public ServletRegistrationBean statViewServlet(){
        ServletRegistrationBean<StatViewServlet> bean = new ServletRegistrationBean<>(new StatViewServlet(), "/druid/*");

        //后台需要有人登录,账号密码配置
        HashMap<String, String> initParameters = new HashMap<>();

        //增加配置
        initParameters.put("loginUsername","admin");//key固定
        initParameters.put("loginPassword","123456");

        //允许谁可以访问
        initParameters.put("allow","");//第二个参数表示可以允许的用户,不填表示都可以访问
        //禁止谁能访问   initParameters.put("","","");参数表示禁止的用户

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

 

3.4 整合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>3.0.0</version>
    </dependency>
    
  2. 导入lombok依赖

    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
    </dependency>
    
  3. 新建User类

    package com.shu.entity;
    
    import lombok.AllArgsConstructor;
    import lombok.Data;
    import lombok.NoArgsConstructor;
    import org.springframework.beans.factory.annotation.Autowired;
    @Data
    @NoArgsConstructor //无参构造
    @AllArgsConstructor //有参构造
    public class User {
        private int id;
        private String name;
        private String pwd;
    }
    
  4. 新建UserMapper类

    package com.shu.mapper;
    
    import com.shu.entity.User;
    import org.apache.ibatis.annotations.Mapper;
    import org.springframework.stereotype.Repository;
    
    import java.util.List;
    
    @Mapper  //这个注解表示了这是一个mybtis的mapper类,另外一种实现:在启动类上加,@MapperScan("com.shu.mapper")
    @Repository  //mapper是属于DAO层,所以需要这个注解
    public interface UserMapper {
        //扩展知识,接口中的属性:
        /*
        在interface里面的变量默认都是public static final 的。所以可以直接省略修饰符,编译时编译器会自动加上:
        String param = "xiaoshu";//变量需要初始化
        */
        int age = 18;  // 即:public static final int age = 18;
    
        List<User> queryUserList();
        User queryUserById(int id);
        void addUser(User user);
        void updateUser(User user);
        void deleteUser(int id);
    }
    
       
  5. 配置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.shu.mapper.UserMapper">
        <!--id,resultType要生效,需要在yaml配置文件中设置相关配置,否则不会生效-->
        <select id="queryUserList" resultType="User">
          select * from user
        </select>
        <select id="queryUserById" resultType="User">
            select * from user where id=#{id}
        </select>
        <insert id="addUser" parameterType="User">
             insert into user(name,pwd) values (#{name},#{pwd})
        </insert>
        <update id="updateUser" parameterType="User">
            update user set name =#{name},pwd=#{pwd} where id = #{id}
        </update>
        <delete id="deleteUser" parameterType="User">
            delete from user where id=#{id}
        </delete>
    </mapper>
    
       
  6. 新增yaml中的mybatis配置

    #spring 配置
    spring:
      datasource:
        username: root
        password: 123456
        # 假如时区报错了,就增加一个时区的配置就行:serverTimeone=UTC
        url: jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=utf-8&serverTimeone=UTC
        driver-class-name: com.mysql.cj.jdbc.Driver
        # 通过type属性切换数据源:使用druid数据源
        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,需要在这里配置,否则xml不会生效
    mybatis:
      type-aliases-package: com.shu.entity
      mapper-locations: classpath:mybatis/mapper/*.xml
    
       
  7. 编写controller

    package com.shu.controller;
    
    import com.shu.entity.User;
    import com.shu.mapper.UserMapper;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.PathVariable;
    import org.springframework.web.bind.annotation.PostMapping;
    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> users = userMapper.queryUserList();
            for (User user: users) {
                System.out.println(user);
            }
            return users;
        }
        @GetMapping("/queryUserById/{id}")
        public User queryUserById(@PathVariable int id){
            User user = userMapper.queryUserById(id);
            return user;
        }
        @GetMapping("/addUsers")
        public String addUser(){
            userMapper.addUser(new User(6,"小雨","123456"));
            return "新增成功";
        }
        @GetMapping("/updateUsers")
        public String updateUsers(){
            userMapper.updateUser(new User(14,"小雨","666666"));
            return "修改成功";
        }
        @GetMapping("/deleteUsers/{id}")
        public String deleteUser(@PathVariable int id){
            userMapper.deleteUser(id);
            return "删除成功";
        }
    }
    
       
  8. 自己去测试即可

四、Security:安全

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

功能性需求:否

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

  • 漏洞,用户隐私泄漏
  • 架构一旦确定,代码也写完了再去写安全,会改动大量代码

市面上知名的安全框架:shiro(主要学习这一种)、SpringSecurity,它们很像,除了类不一样,名字不一样

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

权限:

  • 功能权限
  • 访问权限
  • 菜单权限
  • 拦截器、过滤器:大量的原生代码~冗余

AOP:横切~ 配置类

4.1 SpringSecurity简介(了解)

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

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

记住几个类:

  • WebSecurityConfigurerAdapter: 自定义Security策略(已弃用,暂时不做学习了,这里只是提一下)

  • AuthenticationManagerBuilder: 自定义认证策略

  • @EnableWebSecurity: 开启WebSecurity模式 @Enablexxx 开启某个功能

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

  • 认证(Authentication)

  • 授权(Authorization)

这个概念是通用的,而不是只在Spring Security 中存在
参考官网: https://spring.io/projects/spring-security ,查看我们自己项目中的版本,找到对应的帮助文档

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

4.2 Shiro简介(重点掌握)

4.2.1 什么是Shiro?

  • Apache Shird是一个ava 的安全 (权限)框架。
  • Shiro 可以非常容易的开发出足够好的应用,其不仅可以用在JavaSE环境,也可以用在JavaEE环境。
  • Shiro可以完成,认证,授权,加密,会话管理,Web集成,缓存等。
  • 下载地址: http://shiro.apache.org

4.2.2 有哪些功能?

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

4.2.3 Shiro架构 (外部)

从外部来看Shiro,即从应用程序角度来观察如何使用shiro完成工作:

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

4.2.4 Shiro架构(内部)

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

4.2.5 HelloWorld

快速实践

查看官网文档: http://shiro.apache.org/tutorial.html
官方的quickstart: https://github.com/apache/shiro/tree/master/samples/quickstart/

  1. 创建一个maven父工程:SpringBoot_Shiro,用于学习Shiro,删掉不必要的东西
  2. 创建一个普通的Maven子工程: Hello_Shiro
  3. 根据官方文档,我们来导入shiro的依赖
<dependencies>
    <dependency>
        <groupId>org.apache.shiro</groupId>
        <artifactId>shiro-core</artifactId>
        <version>1.10.1</version>
    </dependency>
    <!-- Shiro uses SLF4J for logging.  We'll use the 'simple' binding
         in this example app.  See http://www.slf4j.org for more info. -->
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-simple</artifactId>
        <version>1.7.21</version>
    </dependency>
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>jcl-over-slf4j</artifactId>
        <version>1.7.21</version>
    </dependency>
    <dependency>
        <groupId>commons-logging</groupId>
        <artifactId>commons-logging</artifactId>
        <version>1.2</version>
    </dependency>
</dependencies>

 
  1. 新建log4j2.xml文件
<!--
  ~ 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.
  -->

<Configuration name="ConfigTest" status="ERROR" monitorInterval="5">
    <!--
      ~ 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.
      -->

    <Appenders>
        <Console name="Console" target="SYSTEM_OUT">
            <PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
        </Console>
    </Appenders>
    <Loggers>
        <Logger name="org.springframework" level="warn" additivity="false">
            <AppenderRef ref="Console"/>
        </Logger>
        <Logger name="org.apache" level="warn" additivity="false">
            <AppenderRef ref="Console"/>
        </Logger>
        <Logger name="net.sf.ehcache" level="warn" additivity="false">
            <AppenderRef ref="Console"/>
        </Logger>
        <Logger name="org.apache.shiro.util.ThreadContext" level="warn" additivity="false">
            <AppenderRef ref="Console"/>
        </Logger>
        <Root level="info">
            <AppenderRef ref="Console"/>
        </Root>
    </Loggers>
</Configuration>

 
  1. 新建shiro.ini文件
#
# 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.
#
# =============================================================================
# Quickstart INI Realm configuration
#
# For those that might not understand the references in this file, the
# definitions are all based on the classic Mel Brooks' film "Spaceballs". ;)
# =============================================================================

# -----------------------------------------------------------------------------
# Users and their assigned roles
#
# Each line conforms to the format defined in the
# org.apache.shiro.realm.text.TextConfigurationRealm#setUserDefinitions JavaDoc
# -----------------------------------------------------------------------------
[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

 
  1. 新建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.mgt.DefaultSecurityManager;
    import org.apache.shiro.mgt.SecurityManager;
    import org.apache.shiro.realm.text.IniRealm;
    import org.apache.shiro.session.Session;
    import org.apache.shiro.subject.Subject;
    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):
            //官方初始化加载方法已经过时了,新方法:初始化加载shiro.ini配置文件
            DefaultSecurityManager securityManager = new DefaultSecurityManager();
            IniRealm iniRealm = new IniRealm("classpath:shiro.ini");
            securityManager.setRealm(iniRealm);
    
            // 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:
            //获取当前的用户对象,currentUser: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");
            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);
        }
    }
    
       
  2. 启动项目,出现红色部分的日志信息说明启动成功

4.2.6 SpringBoot中集成

  1. 导入Shiro依赖,项目用到的其他依赖也放进去了(需要自取)

    <dependencies>
        <!--
        Shiro核心三大对象:
        1、Subject: 用户
        2、SecurityManager:管理所有用户
        3、Realm:连接数据
        -->
        <!--导入shiro整合spring的包:shiro-spring-->
        <!-- https://mvnrepository.com/artifact/org.apache.shiro/shiro-spring -->
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring</artifactId>
            <version>1.10.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>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>3.1.0</version>
        </dependency>
    
        <dependency>
            <groupId>com.mysql</groupId>
            <artifactId>mysql-connector-j</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>
        </dependency>
    </dependencies>
    
       
  2. 新建config包

    • ShiroConfig类

      package com.shu.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;
      
      @Configuration
      public class ShiroConfig {
          //ShiroFilterFactoryBean 第三步
          @Bean
          public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager defaultWebSecurityManager){
              ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
              //设置安全管理器
              shiroFilterFactoryBean.setSecurityManager(defaultWebSecurityManager);
      
              return  shiroFilterFactoryBean;
          }
      
          //DefaultWebSecurityManager
          @Bean(name = "securityManager") //第二步
          //@Qualifier("userRealm"):与自己创建的 realm 对象进行绑定
          public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm){
              DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
      
              //关联UserRealm
              defaultWebSecurityManager.setRealm(userRealm);
      
              return defaultWebSecurityManager;
          }
      
          //创建 realm 对象,需要自定义
          //@Bean(name = "userRealm") //注册自定义的Bean,给spring进行托管,加上name属性也可以
          @Bean //第一步:注册自定义的Bean,给spring进行托管,不加也行
          public UserRealm userRealm(){
              return new UserRealm();
          }
      }
      
           
    • UserRealm类

      package com.shu.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;
          }
      }
      
           
  3. 在templates文件夹下新建index.html

    <!DOCTYPE html>
    <html lang="en" xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <body>
    <h1>首页</h1>
    <p th:text="${msg}"></p>
    <p>Hello,Shiro</p>
    <hr/>
    <a th:href="@{/user/add}">add</a> | <a th:href="@{/user/update}">update</a>
    </body>
    </html>
    
  4. 在templates文件夹下新建user文件夹

    • 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>
      
  5. 新建MyController路由跳转页面

    package com.shu.controller;
    
    import org.springframework.stereotype.Controller;
    import org.springframework.ui.Model;
    import org.springframework.web.bind.annotation.RequestMapping;
    
    @Controller
    public class MyController {
        @RequestMapping({"/","/index"})
        public String toIndex(Model model){
            model.addAttribute("msg","hello,xiaoshu");
            return "index";
        }
        @RequestMapping("/user/add")
        public String add(){
            return "user/add";
        }
        @RequestMapping("/user/update")
        public String update(){
            return "user/update";
        }
    }
    
       
  6. 启动项目看能否正常启动,点击链接看能否正常跳转(我的是可以的)

报错解决

如果报下面这个错

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::                (v3.0.0)

2022-12-22T10:00:57.345+08:00  INFO 22008 --- [           main] c.shu.SpringBootShiroStudyApplication    : Starting SpringBootShiroStudyApplication using Java 17.0.5 with PID 22008 (D:\Spring Boot\SpringBoot_Shiro\SpringBoot_ShiroStudy\target\classes started by 上官剑南 in D:\Spring Boot\SpringBoot_Shiro)
2022-12-22T10:00:57.351+08:00  INFO 22008 --- [           main] c.shu.SpringBootShiroStudyApplication    : No active profile set, falling back to 1 default profile: "default"
2022-12-22T10:00:57.794+08:00 ERROR 22008 --- [           main] o.s.boot.SpringApplication               : Application run failed

java.lang.NoClassDefFoundError: javax/servlet/Filter
	at java.base/java.lang.ClassLoader.defineClass1(Native Method) ~[na:na]
	at java.base/java.lang.ClassLoader._jr$defineClass(ClassLoader.java:1012) ~[na:na]
    ...
	... 83 common frames omitted

Exception in thread "main" java.lang.NoClassDefFoundError: javax/servlet/Filter
	at java.base/java.lang.ClassLoader.defineClass1(Native Method)
	at java.base/java.lang.ClassLoader._jr$defineClass(ClassLoader.java:1012)
	at java.base/java.lang.ClassLoader.defineClass(ClassLoader.java:43016)
	...
	at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:188)
	at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:520)
	... 71 more
与目标 VM 断开连接, 地址为: ''127.0.0.1:57537',传输: '套接字''

进程已结束,退出代码为 1

 

导入依赖即可

<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>javax.servlet-api</artifactId>
    <version>3.1.0</version>
</dependency>

正常启动

4.2.7 模板网站

  • layui:有免费有付费(需要自取)

  • my-site

    • swagger访问页面:http://localhost:8080/swagger-ui.html

    • 后台登录页面:http://localhost:8080/admin/login

      账号名:admin

      密码:123456

五、Swagger

前后端分离时代:

  • 后端:后端控制层,服务层,数据访问层【后端团队】
  • 前端:前端控制层,视图层【前端团队】
    • 伪造后端数据,json。已经存在了,不需要后端,前端工程依旧能够跑起来
  • 前后端如何交互?=>API接口
  • 前后端相对独立,松耦合
  • 前后端甚至可以部署在不同的服务器上

产生一个问题:

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

解决方案:

  • 首先制定一个schema[计划的提纲],实时更新最新API,降低集成风险
  • 早些年:制定word计划文档
  • 前后端分离:
    • 前端测试后端接口:PostMan
    • 后端提供接口,需要实时更新最新的消息及时改动!

5.1 Swagger简介

  • 号称世界上最流行的Api框架
  • Restful Api 文档在线自动生成工具=>Api文档与Api定义同步更新
  • 直接运行,可以在线测试Api接口
  • 支持多种语言(java,php,C#…)
  • 官网:https://swagger.io/

在项目中使用Swagger需要springfox jar包

  • springfox-swagger2

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

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

5.2 SpringBoot集成Swagger

  1. 新建一个SpringBoot_Swagger项目,需要勾选Spring Web

  2. 导入jar包

    <!-- https://mvnrepository.com/artifact/io.springfox/springfox-swagger2 -->
    <dependency>
        <groupId>io.springfox</groupId>
        <artifactId>springfox-swagger2</artifactId>
        <version>3.0.0</version>
    </dependency>
    <!-- https://mvnrepository.com/artifact/io.springfox/springfox-swagger-ui -->
    <dependency>
        <groupId>io.springfox</groupId>
        <artifactId>springfox-swagger-ui</artifactId>
        <version>3.0.0</version>
    </dependency>
    
  3. 编写一个Hello工程

    package com.shu.controller;
    
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    @RestController
    public class HelloController {
         //项目请求分析:除了下面这个请求地址,还有一个默认的 /error页面
        @RequestMapping(value = "/hello")
        public String hello(){
            return "hello";
        }
    }
    
  4. 配置Swagger=>config

    package com.shu.config;
    
    import org.springframework.context.annotation.Configuration;
    import springfox.documentation.swagger2.annotations.EnableSwagger2;
    
    @Configuration //等价于:@Component,只要加了这个注解就会把它自动配置到spring的配置里面
    @EnableSwagger2 //开启Swagger2
    public class SwaggerConfig {
    
    }
    
  5. 测试运行

报错一

java.lang.TypeNotPresentException: Type javax.servlet.http.HttpServletRequest not present
	at java.base/sun.reflect.generics.factory.CoreReflectionFactory.makeNamedType(CoreReflectionFactory.java:117) ~[na:na]
	at java.base/sun.reflect.generics.visitor.Reifier.visitClassTypeSignature(Reifier.java:125) ~[na:na]
	at java.base/sun.reflect.generics.tree.ClassTypeSignature.accept(ClassTypeSignature.java:49) ~[na:na]
	...
	at java.base/java.lang.Class.forName(Class.java:467) ~[na:na]
	at java.base/sun.reflect.generics.factory.CoreReflectionFactory.makeNamedType(CoreReflectionFactory.java:114) ~[na:na]
	... 34 common frames omitted

报错一解决:导入以下依赖

<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>javax.servlet-api</artifactId>
    <version>3.1.0</version>
</dependency>

报错2 原因

这是因为Springfox使用的路径匹配是基于AntPathMatcher的,而Spring Boot 2.6.X使用的是PathPatternMatcher。
解决:在application.properties里配置:spring.mvc.pathmatch.matching-strategy=ANT_PATH_MATCHER。

java.lang.TypeNotPresentException: Type javax.servlet.http.HttpServletRequest not present
	at java.base/sun.reflect.generics.factory.CoreReflectionFactory.makeNamedType(CoreReflectionFactory.java:117) ~[na:na]
	...
	at java.base/java.lang.Class.forName(Class.java:467) ~[na:na]
	at java.base/sun.reflect.generics.factory.CoreReflectionFactory.makeNamedType(CoreReflectionFactory.java:114) ~[na:na]
	... 34 common frames omitted

在application.properties文件中加入以下配置

spring.mvc.pathmatch.matching-strategy=ANT_PATH_MATCHER

解决上面的报错便可以启动项目,但是还不能访问http://localhost:8080/swagger-ui.html (未能解决)

六、任务

6.1 异步任务

  1. 新建一个service包,包下建一个AsyncTask类

    package com.shu.service;
    
    import org.springframework.scheduling.annotation.Async;
    import org.springframework.stereotype.Service;
    
    @Service
    public class AsyncTask {
        //告诉Spring这是一个异步的方法
        @Async
        public void hello(){
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("数据正在处理...");
        }
    }
    
       
  2. 新建一个controller包,包下建一个AsyncController类

    package com.shu.controller;
    
    import com.shu.service.AsyncTask;
    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
        AsyncTask asyncTask;
    
        @RequestMapping("/hello")
        public String hello(){
            asyncTask.hello();如果不开启异步异步任务会停止三秒,转圈~;开启异步任务秒刷新,不会转圈
            return "OK";
        }
    }
    
       
  3. 在启动类上增加@EnableAsync 注解

    package com.shu;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.scheduling.annotation.EnableAsync;
    
    @EnableAsync //开启异步注解的功能
    @SpringBootApplication
    public class SpringBootTaskApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(SpringBootTaskApplication.class, args);
        }
    
    }
    
  4. 测试秒刷新,不会等三秒才会出现OK!

6.2 定时任务

TaskScheduler //任务调度者
TaskExecutor //任务执行者
@EnableScheduling //开启定时功能的注解,启动类上加
@Scheduled //什么时候执行

6.2.1 扩展:cron表达式

Cron表达式是一个字符串,字符串以5或6个空格隔开,分为6或7个域,每一个域代表一个含义,Cron有如下两种语法格式:

(1) Seconds Minutes Hours DayofMonth Month DayofWeek Year

(2)Seconds Minutes Hours DayofMonth Month DayofWeek

一、结构

corn从左到右(用空格隔开):秒 分 小时 月份中的日期 月份 星期中的日期 年份

二、各字段的含义

注意事项:

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

(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,表示某月的第二个星期三。

三、常用表达式例子

(1)0 0 2 1 * ? * 表示在每月的1日的凌晨2点调整任务

(2)0 15 10 ? * MON-FRI 表示周一到周五每天上午10:15执行作业

(3)0 15 10 ? 6L 2002-2006 表示2002-2006年的每个月的最后一个星期五上午10:15执行作

(4)0 0 10,14,16 * * ? 每天上午10点,下午2点,4点

(5)0 0/30 9-17 * * ? 朝九晚五工作时间内每半小时

(6)0 0 12 ? * WED 表示每个星期三中午12点

(7)0 0 12 * * ? 每天中午12点触发

(8)0 15 10 ? * * 每天上午10:15触发

(9)0 15 10 * * ? 每天上午10:15触发

(10)0 15 10 * * ? * 每天上午10:15触发

(11)0 15 10 * * ? 2005 2005年的每天上午10:15触发

(12)0 * 14 * * ? 在每天下午2点到下午2:59期间的每1分钟触发

(13)0 0/5 14 * * ? 在每天下午2点到下午2:55期间的每5分钟触发

(14)0 0/5 14,18 * * ? 在每天下午2点到2:55期间和下午6点到6:55期间的每5分钟触发

(15)0 0-5 14 * * ? 在每天下午2点到下午2:05期间的每1分钟触发

(16)0 10,44 14 ? 3 WED 每年三月的星期三的下午2:10和2:44触发

(17)0 15 10 ? * MON-FRI 周一至周五的上午10:15触发

(18)0 15 10 15 * ? 每月15日上午10:15触发

(19)0 15 10 L * ? 每月最后一日的上午10:15触发

(20)0 15 10 ? * 6L 每月的最后一个星期五上午10:15触发

(21)0 15 10 ? * 6L 2002-2005 2002年至2005年的每月的最后一个星期五上午10:15触发

(22)0 15 10 ? * 6#3 每月的第三个星期五上午10:15触发

注:

(1)有些子表达式能包含一些范围或列表

例如:子表达式(天(星期))可以为 “MON-FRI”,“MON,WED,FRI”,“MON-WED,SAT”

“*”字符代表所有可能的值

因此,“”在子表达式(月)里表示每个月的含义,“”在子表达式(天(星期))表示星期的每一天

“/”字符用来指定数值的增量
  例如:在子表达式(分钟)里的“0/15”表示从第0分钟开始,每15分钟
在子表达式(分钟)里的“3/20”表示从第3分钟开始,每20分钟(它和“3,23,43”)的含义一样

“?”字符仅被用于天(月)和天(星期)两个子表达式,表示不指定值
  当2个子表达式其中之一被指定了值以后,为了避免冲突,需要将另一个子表达式的值设为“?”

“L” 字符仅被用于天(月)和天(星期)两个子表达式,它是单词“last”的缩写
  但是它在两个子表达式里的含义是不同的。
  在天(月)子表达式中,“L”表示一个月的最后一天
  在天(星期)自表达式中,“L”表示一个星期的最后一天,也就是SAT

如果在“L”前有具体的内容,它就具有其他的含义了

例如:“6L”表示这个月的倒数第6天,“FRIL”表示这个月的最一个星期五
  注意:在使用“L”参数时,不要指定列表或范围,因为这会导致问题

6.2.2 案例实践

  1. 开启定时功能的注解

    package com.shu;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.scheduling.annotation.EnableAsync;
    import org.springframework.scheduling.annotation.EnableScheduling;
    
    @EnableScheduling //开启定时功能的注解
    @EnableAsync //开启异步注解的功能
    @SpringBootApplication
    public class SpringBootTaskApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(SpringBootTaskApplication.class, args);
        }
    
    }
    
       
  2. 编写代码

    package com.shu.service;
    
    import org.springframework.scheduling.annotation.Scheduled;
    import org.springframework.stereotype.Service;
    
    import java.text.SimpleDateFormat;
    import java.time.LocalDateTime;
    import java.util.Date;
    
    import static com.fasterxml.jackson.databind.type.LogicalType.DateTime;
    
    @Service
    public class ScheduledService {
        //在一个固定的时间执行这个方法~ Timer
    
        //cron 表达式
        // 0 * * * * 0-7 : 合起来就表示,每一天的任何时候的第0秒都会执行一次
        // 秒 分 时 日 月 周几:0-7 就表示每一天
        //@Scheduled(cron = "0 * * * * 0-7")
        @Scheduled(cron = "0 26 18 * * ?")  //表示:每一天 18:16:00 的时候执行一次
        //@Scheduled(cron = "30 0/5 10,18 * * ?")  //表示:每天10点,18点,每隔五分钟执行一次
        public void hello(){
            //打印当前时间
            //方法一
            LocalDateTime localDateTime = LocalDateTime.now();
            System.out.println(localDateTime);
            //方法二
            SimpleDateFormat format=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            System.out.println(format.format(new Date()));
    
            System.out.println("hello,你被执行了~");
        }
    }
    
       
  3. 测试

6.3 邮件任务

  1. 导入mail依赖

    <!--导入 javax.mail 包:配置-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-mail</artifactId>
    </dependency>
    
  2. 在application.properties文件中添加如下配置

    spring.mail.username=1795386527@qq.com
    spring.mail.password=govpyumjgyfmehia
    spring.mail.host=smtp.qq.com
    #开启加密验证:QQ才有!其他邮箱不需要
    spring.mail.properties.mail.smtp.ssl.enable = true
    
  3. password获取方法

    • 登录qq邮箱

    • 找到账户

    • 开启POP3/SMTP服务

    • 得到密码

  4. 在测试类中编写如下代码

    package com.shu;
    
    import jakarta.annotation.Resource;
    import org.junit.jupiter.api.Test;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.test.context.SpringBootTest;
    import org.springframework.mail.SimpleMailMessage;
    import org.springframework.mail.javamail.JavaMailSenderImpl;
    
    @SpringBootTest
    class SpringBootTaskApplicationTests {
        @Resource
        JavaMailSenderImpl mailSender;
    
        @Test
        void contextLoads() {
    
            //一个简单的邮件发送
            SimpleMailMessage mailMessage = new SimpleMailMessage();
    
            mailMessage.setSubject("小舒,你好!");
            mailMessage.setText("今天也要元气满满哟!");
            mailMessage.setTo("1795386527@qq.com"); //可以发送给自己
            mailMessage.setFrom("1795386527@qq.com"); 
    
            mailSender.send(mailMessage);
        }
    
    }
    
       
  5. 执行测试类,测试通过即可收到邮件

  6. 一个复杂邮件发送

    @Test
    void contextLoads2() throws MessagingException {
    
        //一个复杂的邮件发送
        MimeMessage mimeMessage = mailSender.createMimeMessage();
        //组装
        MimeMessageHelper helper = new MimeMessageHelper(mimeMessage,true,"utf-8");
        //正文
        helper.setSubject("小舒,你好~plus");
        helper.setText("<p style='color:red'>今天也要元气满满哟~plus</p>",true);
    
        //附件
        helper.addAttachment("Go.jpg",new File("C:\\Users\\kevin\\Desktop\\Go.jpg"));
    
        helper.setTo("1795386527@qq.com"); //可以发送给自己
        helper.setFrom("1795386527@qq.com");
        mailSender.send(mimeMessage);
    }
    
       
  7. 测试执行成功即可收到邮件

  8. 封装成一个方法

    /**
     * 发送邮件
     * @param html
     * @param subject
     * @param text
     * @throws MessagingException
     * @Author xiaoshu
     */
    public void sendMail(Boolean html,String subject,String text) throws MessagingException{
        //一个复杂的邮件发送
        MimeMessage mimeMessage = mailSender.createMimeMessage();
        //组装
        MimeMessageHelper helper = new MimeMessageHelper(mimeMessage,html,"utf-8");
        //正文
        helper.setSubject(subject);
        helper.setText(text,true);
    
        //附件
        helper.addAttachment("Go.jpg",new File("C:\\Users\\kevin\\Desktop\\Go.jpg"));
    
        helper.setTo("1795386527@qq.com"); //可以发送给自己
        helper.setFrom("1795386527@qq.com");
        mailSender.send(mimeMessage);
    }
    
       

七、分布式 Dubbo+Zookeeper+SpringBoot

7.1 分布式理论

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

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

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

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

Dubbo文档

官网:https://cn.dubbo.apache.org/zh/

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

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

适用于小型网站,小型管理系统,将所有功能都部署到一个功能里,简单易用。缺点:
1、性能扩展比较难
2、协同开发问题
3、不利于升级维护

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

通过切分业务来实现各个模块独立部署,降低了维护和部署的难度,团队各司其职更易管理,性能扩展也更方便,更有针对性。
缺点:公用模块无法重复利用,开发性的浪费

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

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

7.2 RPC

什么是RPC?
RPC[Remote Procedure Cal] 是指远程过程调用,是一种进程间通信方式,他是一种技术的思想,而不是规范。它允许程序调用另一个地址空间(通常是共享网络的另一台机器上)的过程或函数,而不用程序员显式编码这个远程调用的细节。即程序员无论是调用本地的还是远程的函数,本质上编写的调用代码基本相同。
也就是说两台服务器A,B,一个应用部署在A服务器上,想要调用B服务器上应用提供的函数/方法,由于不在一个内存空间,不能直接调用,需要通过网络来表达调用的语义和传达调用的数据。为什么要用RPC呢? 就是无法在一个进程内,甚至个计算机内通过本地调用的方式完成的需求,比如不同的系统间的通讯,其至不同的组织间的通讯,由于计算能力需要椅向扩展,需要在多台机器组成的集群上部署应用。RPC就是要像调用本地的函数一样去调远程函数

推荐阅读文章: https://www.jianshu.com/p/2accc2840a1b

RPC基本原理

步骤解析:

RPC的两个核心模块:通信、序列化(方便数据传输:数据传输需要转换)

7.3 Dubbo概念

Apache Dubbo 是一款 RPC 服务开发框架,用于解决微服务架构下的服务治理与通信问题,官方提供了 Java、Golang 等多语言 SDK 实现。使用 Dubbo 开发的微服务原生具备相互之间的远程地址发现与通信能力, 利用 Dubbo 提供的丰富服务治理特性,可以实现诸如服务发现、负载均衡、流量调度等服务治理诉求。Dubbo 被设计为高度可扩展,用户可以方便的实现流量拦截、选址的各种定制逻辑。

Dubbo3 定义为面向云原生的下一代 RPC 服务框架。3.0 基于 Dubbo 2.x 演进而来,在保持原有核心功能特性的同时, Dubbo3 在易用性、超大规模微服务实践、云原生基础设施适配、安全性等几大方向上进行了全面升级。

7.3.1 Dubbo 是什么

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

Apache Dubbo 最初在 2008 年由 Alibaba 捐献开源,很快成为了国内开源服务框架选型的事实标准框架 ,得到了各行各业的广泛应用。在 2017 年,Dubbo 正式捐献到 Apache 软件基金会并成为 Apache 顶级项目,目前 Dubbo3 已经是一站式的微服务解决方案提供:

  • 基于 HTTP/2 的 Triple 协议以及面向代理 API 的编程体验。
  • 强大的流量治理能力,如地址发现、负载均衡、路由选址、动态配置等。
  • 多语言 SDK 实现,涵盖 Java、Golang、Javascript 等,更多语言实现将会陆续发布。
  • 灵活的适配与扩展能力,可轻松与微服务体系其他组件如 Tracing、Transaction 等适配。
  • Dubbo Mesh 解决方案,同时支持 Sidecar、Proxyless 等灵活的 Mesh 部署方案。

Dubbo架构图

  • 服条提供者 (Provider): 暴露服务的服务提供方,服务提供者在启动时,向注册中心注册自己提供的服务
  • 服务消费者(Consumer):调用远程服务的服务消费方,服务消费者在启动时,向注册中心订阅自己所需的服务,服务消费者,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,再选另一台调用。
  • 注册中心(Registry):注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者
  • 监控中心(Monitor):服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心

调用关系说明

  • 服务容器负责启动,加载,运行服务提供者。
  • 服务提供者在启动时,向注册中心注册自己提供的服务
  • 服务消费者在启动时,向注册中心订阅自己所需的服务
  • 注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者。
  • 服务消费者,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,再选另一台调用。
  • 服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心。

Apache Dubbo 总体架构能很好的满足企业的大规模微服务实践,因为它从设计之初就是为了解决超大规模微服务集群实践问题,不论是阿里巴巴还是工商银行、中国平安、携程等社区用户,它们都通过多年的大规模生产环境流量对 Dubbo 的稳定性与性能进行了充分验证,因此,Dubbo 在解决业务落地与规模化实践方面有着无可比拟的优势:

  • 开箱即用
    • 易用性高,如 Java 版本的面向接口代理特性能实现本地透明调用
    • 功能丰富,基于原生库或轻量扩展即可实现绝大多数的微服务治理能力
  • 面向超大规模微服务集群设计
    • 极致性能,高性能的 RPC 通信协议设计与实现
    • 横向可扩展,轻松支持百万规模集群实例的地址发现与流量治理
  • 高度可扩展
    • 调用过程中对流量及协议的拦截扩展,如 Filter、Router、LB 等
    • 微服务治理组件扩展,如 Registry、Config Center、Metadata Center 等
  • 企业级微服务治理能力
    • 国内公有云厂商支持的事实标准服务框架
    • 多年企业实践经验考验,参考用户实践案例

7.3.2 Dubbo环境搭建

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

https://cn.dubbo.apache.org/zh/docs3-v2/java-sdk/reference-manual/registry/zookeeper/

什么是Zookeeper呢?可以自行查看上面的官方文档

windows下安装Zookeeper

  1. 下载地址,我们下载最新版:

    https://www.apache.org/dyn/closer.lua/zookeeper/zookeeper-3.8.0/apache-zookeeper-3.8.0-bin.tar.gz

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

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

    • 如下

    • 报错

    • 去路径下将 zoo_sample.cfg 配置文件复制一份命名为 zoo.cfg

    • 配置文件了解

      # The number of milliseconds of each tick
      tickTime=2000
      # The number of ticks that the initial 
      # synchronization phase can take
      initLimit=10
      # The number of ticks that can pass between 
      # sending a request and getting an acknowledgement
      syncLimit=5
      # the directory where the snapshot is stored.
      # do not use /tmp for storage, /tmp here is just 
      # example sakes.
      dataDir=/tmp/zookeeper
      # the port at which the clients will connect
      clientPort=2181
      # the maximum number of client connections.
      # increase this if you need to handle more clients
      #maxClientCnxns=60
      #
      # Be sure to read the maintenance section of the 
      # administrator guide before turning on autopurge.
      #
      # https://zookeeper.apache.org/doc/current/zookeeperAdmin.html#sc_maintenance
      #
      # The number of snapshots to retain in dataDir
      #autopurge.snapRetainCount=3
      # Purge task interval in hours
      # Set to "0" to disable auto purge feature
      #autopurge.purgeInterval=1
      
      ## Metrics Providers
      #
      # https://prometheus.io Metrics Exporter
      #metricsProvider.className=org.apache.zookeeper.metrics.prometheus.PrometheusMetricsProvider
      #metricsProvider.httpHost=0.0.0.0
      #metricsProvider.httpPort=7000
      #metricsProvider.exportJvmInfo=true
      
           

    • 重新运行/bin/zkServer.cmd,便可启动成功

  3. 注意修改zoo.cfg配置文件
    将conf文件夹下面的zoo sample.cfg复制一份改名为zoo.cfg即可
    注意几个重要位置:
    dataDir=/tmp/zookeeper 临时数据存储的目录 (可写相对路径)
    clientPort=2181 zookeeper的端口号
    修改完成后再次运行/bin/zkServer.cmd

  4. 运行/bin/zkServer.cmd,使用zkCli.cmd测试

    不运行会报错

    连接成功

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

    create -e /xiaoshu 123: 创建一个xiaoshu节点,值为123

    get /xiaoshu: 获取/xiaoshu节点的值

windows下安装dubbo-admin

dubbo本身并不是一个服务软件。它其实就是一个jar包,能够帮你的java程序连接到zookeeper,并利用zookeeper消费、提供服务。
但是为了让用户更好的管理监控众多的dubbo服务,官方提供了一个可视化的监控程序dubbo-admin,不过这个监控即使不装也不影响使用。
我们这里来安装一下,下载dubbo-admin
地址 : https://github.com/apache/dubbo-admin/tree/master

通过源码打包运行

  1. 下载代码: git clone https://github.com/apache/dubbo-admin.git

  2. dubbo-admin-server/src/main/resources/application.properties中指定注册中心地址

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

    • mvn clean package -Dmaven.test.skip=true

    出现如下错误,则需要安装Apache Maven的环境(环境变量也要配置,类似于java环境配置)

    1. 下载地址:https://maven.apache.org/download.cgi

    2. 下载apache-maven-3.8.6-bin.zip

    3. 新建环境变量

    4. 在path中新增

    5. 最后测试一下是否安装成功

    6. 出现上面的页面说明安装成功了

    7. 重新进命令mvn clean package -Dmaven.test.skip=true

    8. 出现上面的页面就说明命令执行成功了,但是第一次打包东西有点多,有点慢;完事之后就会生成一个jar包

    9. 出现上面的页面说明打包成功,然后继续执行下面的步骤

  4. 启动

    • mvn --projects dubbo-admin-server spring-boot:run 或者
    • cd dubbo-admin-distribution/target; java -jar dubbo-admin-${project.version}.jar

  5. 这时候我没有开Zookeeper所以会报如下错误

  6. 所以需要打开Zookeeper,步骤在上面有说

  7. 然后重新执行jar包,这边显示我的8080端口被占用了,我们使用netstat -ano | findstr "8080"命令查看端口号的进程pid

  8. 过滤出需要释放的端口后,在cmd窗口输入taskkill命令可释放被占用的端口,然后杀死该进程重新启动

    taskkill -f -t /pid "占用端口的程序的pid"       
    参数详解:
    /T                     终止指定的进程和由它启用的子进程。
    /F                     指定强制终止进程。
    注:其余参数可使用 TASKKILL /? 命令查看
    

  9. 重新运行jar包,还是不行(Tomcat默认开启的端口号就是8080,一直显示被占用,我也不知道是什么原因),不管了,我们去把服务端口号给改成7001后重新打包后运行jar包

  10. 访问 http://localhost:7001

    默认用户名、密码都是:root,登录之后进入Dubbo Admin页面

总结:

Zookeeper:注册中心

Dubbo-admin:是一个监控管理后台,可以查看我们注册了哪些服务,哪些服务被消费了(可以不用,但是注册中心必须有)

Dubbo:jar包

核心:Zookeeper和Dubbo的jar包

7.4 服务注册发现实战(可自行查看官方教程)

官方教程:https://cn.dubbo.apache.org/zh/docs3-v2/java-sdk/quick-start/spring-boot/


转载:https://blog.csdn.net/weixin_51497502/article/details/128446697
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场