【狂神说】SpringBoot

本文章仅个人学习笔记,内容来源b站up主:遇见狂神说


SpringBoot

自动配置:

pom.xml

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

启动器

  • 启动器:说白了是Springboot的启动场景
  • 比如spring-boot-starter-web,他就会帮我们自动导入web环境所有的依赖
  • springboot会将所有的功能场景,变成一个个的启动器
  • 我们要什么用什么功能,就只需要找到对应的启动器就可以了starter

主程序

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

   public static void main(String[] args) {
     //以为是启动了一个方法,没想到启动了一个服务
      SpringApplication.run(SpringbootApplication.class, args);
   }

}

但是**一个简单的启动类并不简单!**我们来分析一下这些注解都干了什么

@SpringBootApplication

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

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

@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(
    excludeFilters = {@Filter(
    type = FilterType.CUSTOM,
    classes = {TypeExcludeFilter.class}
), @Filter(
    type = FilterType.CUSTOM,
    classes = {AutoConfigurationExcludeFilter.class}
)}
)
public @interface SpringBootApplication {
    // ......
}

@ComponentScan

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

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

@SpringBootConfiguration

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

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

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

@Component
public @interface Configuration {}

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

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

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

@EnableAutoConfiguration

@EnableAutoConfiguration :开启自动配置功能

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

点进注解接续查看:

@AutoConfigurationPackage :自动配置包

结论:

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

6、使用Spring Initializer快速创建Spring Boot项目

1、IDEA:使用 Spring Initializer快速创建项目

IDE都支持使用Spring的项目创建向导快速创建一个Spring Boot项目;

选择我们需要的模块;向导会联网创建Spring Boot项目;

默认生成的Spring Boot项目;

主程序已经生成好了,我们只需要我们自己的逻辑
resources文件夹中目录结构
static:保存所有的静态资源; js css images;
templates:保存所有的模板页面;(Spring Boot默认jar包使用嵌入式的Tomcat,默认不支持JSP页面);可以使用模板引擎(freemarker、thymeleaf);
application.properties:Spring Boot应用的配置文件;可以修改一些默认设置;

2、STS使用 Spring Starter Project快速创建项目

第8P yaml语法讲解 https://www.bilibili.com/video/BV1PE411i7CV?p=8

二、配置文件

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

  • application.properties

  • application.yml

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

YAML(YAML Ain’t Markup Language)

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

​ YAML isn’t Markup Language:不是一个标记语言;

标记语言:

​ 以前的配置文件;大多都使用的是 xxxx.xml文件;

​ YAML:以数据为中心,比json、xml等更适合做配置文件;

​ YAML:配置例子

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

2、YAML语法:

1、基本语法

k:(空格)v:表示一对键值对(空格必须有);

空格的缩进来控制层级关系;只要是左对齐的一列数据,都是同一个层级的

server:
    port: 8081
    path: /hello

属性和值也是大小写敏感;

2、值的写法

字面量:普通的值(数字,字符串,布尔)
​ k: v:字面直接来写;

​ 字符串默认不用加上单引号或者双引号;

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

​ name: “zhangsan \n lisi”:输出;zhangsan 换行 lisi

​ ‘’:单引号;会转义特殊字符,特殊字符最终只是一个普通的字符串数据

​ name: ‘zhangsan \n lisi’:输出;zhangsan \n lisi

对象、Map(属性和值)(键值对):
​ k: v:在下一行来写对象的属性和值的关系;注意缩进

​ 对象还是k: v的方式

friends:
		lastName: zhangsan
		age: 20

行内写法:

friends: {lastName: zhangsan,age: 18}

数组(List、Set):
用- 值表示数组中的一个元素

pets:
 - cat
 - dog
 - pig

行内写法

pets: [cat,dog,pig]

第9P 给属性赋值的几种方式 [ https://www.bilibili.com/video/BV1PE411i7CV?p=8]

3、配置文件值注入

配置文件

person:
    lastName: hello
    age: 18
    boss: false
    birth: 2017/12/12
    maps: {k1: v1,k2: 12}
    lists:
      - lisi
      - zhaoliu
    dog:
      name: 小狗
      age: 12

javaBean:

/**
 * 将配置文件中配置的每一个属性的值,映射到这个组件中
 * @ConfigurationProperties:告诉SpringBoot将本类中的所有属性和配置文件中相关的配置进行绑定;
 *      prefix = "person":配置文件中哪个下面的所有属性进行一一映射
 *
 * 只有这个组件是容器中的组件,才能容器提供的@ConfigurationProperties功能;
 *
 */
@Component
@ConfigurationProperties(prefix = "person")
public class Person {

    private String lastName;
    private Integer age;
    private Boolean boss;
    private Date birth;

    private Map<String,Object> maps;
    private List<Object> lists;
    private Dog dog;

1、properties配置文件在idea中默认utf-8可能会乱码
2、@Value获取值和@ConfigurationProperties获取值比较
在这里插入图片描述
配置文件yml还是properties他们都能获取到值;
如果说,我们只是在某个业务逻辑中需要获取一下配置文件中的某项值,使用@Value;
如果说,我们专门编写了一个javaBean来和配置文件进行映射,我们就直接使用@ConfigurationProperties;

第10P JSR303校验 [ https://www.bilibili.com/video/BV1PE411i7CV?p=10]

3、配置文件注入值数据校验

@Component
@ConfigurationProperties(prefix = "person")
@Validated
public class Person {

    /**
     * <bean class="Person">
     *      <property name="lastName" value="字面量/${key}从环境变量、配置文件中获取值/#{SpEL}"></property>
     * <bean/>
     */

   //lastName必须是邮箱格式
    @Email
    //@Value("${person.last-name}")
    private String lastName;
    //@Value("#{11*2}")
    private Integer age;
    //@Value("true")
    private Boolean boss;

    private Date birth;
    private Map<String,Object> maps;
    private List<Object> lists;
    private Dog dog;

@ImportResource:导入Spring的配置文件,让配置文件里面的内容生效;

Spring Boot里面没有Spring的配置文件,我们自己编写的配置文件,也不能自动识别;

想让Spring的配置文件生效,加载进来;@ImportResource标注在一个配置类上

@ImportResource(locations = {"classpath:beans.xml"})
导入Spring的配置文件让其生效

SpringBoot推荐给容器中添加组件的方式;推荐使用全注解的方式

1、配置类**@Configuration**------>Spring配置文件

2、使用**@Bean**给容器中添加组件

/**
 * @Configuration:指明当前类是一个配置类;就是来替代之前的Spring配置文件
 *
 * 在配置文件中用<bean><bean/>标签添加组件
 *
 */
@Configuration
public class MyAppConfig {

    //将方法的返回值添加到容器中;容器中这个组件默认的id就是方法名
    @Bean
    public HelloService helloService02(){
        System.out.println("配置类@Bean给容器中添加组件了...");
        return new HelloService();
    }
}

4、配置文件占位符

1、随机数
${random.value}、${random.int}、${random.long}
${random.int(10)}、${random.int[1024,65536]}
2、占位符获取之前配置的值,如果没有可以是用:指定默认值
person.last-name=张三${random.uuid}
person.age=${random.int}
person.birth=2017/12/15
person.boss=false
person.maps.k1=v1
person.maps.k2=14
person.lists=a,b,c
person.dog.name=${person.hello:hello}_dog
person.dog.age=15

第11P 多环境配置及配置文件位置 [ https://www.bilibili.com/video/BV1PE411i7CV?p=8]

5、Profile

1、多Profile文件

我们在主配置文件编写的时候,文件名可以是 application-{profile}.properties/yml

默认使用application.properties的配置;

2、yml支持多文档块方式

server:
  port: 8081
spring:
  profiles:
    active: prod

---
server:
  port: 8083
spring:
  profiles: dev


---

server:
  port: 8084
spring:
  profiles: prod  #指定属于哪个环境

3、激活指定profile

​ 1、在配置文件中指定 spring.profiles.active=dev

​ 2、命令行:

​ java -jar spring-boot-02-config-0.0.1-SNAPSHOT.jar --spring.profiles.active=dev;

​ 可以直接在测试的时候,配置传入命令行参数

​ 3、虚拟机参数;

​ -Dspring.profiles.active=dev

6、配置文件加载位置

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

–file:./config/

–file:./

–classpath:/config/

–classpath:/

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

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

7、外部配置加载顺序

SpringBoot也可以从以下位置加载配置; 优先级从高到低;高优先级的配置覆盖低优先级的配置,所有的配置会形成互补配置

1.命令行参数

所有的配置都可以在命令行上进行指定

java -jar spring-boot-02-config-02-0.0.1-SNAPSHOT.jar --server.port=8087 --server.context-path=/abc

多个配置用空格分开; --配置项=值

2.来自java:comp/env的JNDI属性

3.Java系统属性(System.getProperties())

4.操作系统环境变量

5.RandomValuePropertySource配置的random.*属性值

由jar包外向jar包内进行寻找;

优先加载带profile

6.jar包外部的application-{profile}.properties或application.yml(带spring.profile)配置文件

7.jar包内部的application-{profile}.properties或application.yml(带spring.profile)配置文件

再来加载不带profile

8.jar包外部的application.properties或application.yml(不带spring.profile)配置文件

9.jar包内部的application.properties或application.yml(不带spring.profile)配置文件

10.@Configuration注解类上的@PropertySource

11.通过SpringApplication.setDefaultProperties指定的默认属性

所有支持的配置加载来源;

第12P 多环境配置及配置文件位置 [ https://www.bilibili.com/video/BV1PE411i7CV?p=8]

8、自动配置原理

配置文件到底能写什么?怎么写?自动配置原理;
配置文件能配置的属性参照

1、原理

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

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

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

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

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

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

开启debug可以在启动的时候在控制台输出配置

debug: true

第35P 用户认证和授权 [ https://www.bilibili.com/video/BV1PE411i7CV?p=35]

SpringSecurity(安全)

安全简介

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

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

认证,授权

认识SpringSecurity

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

记住几个类:

  • WebSecurityConfigurerAdapter:自定义Security策略
  • AuthenticationManagerBuilder:自定义认证策略
  • @EnableWebSecurity:开启WebSecurity模式
  1. 引入 Spring Security 模块
<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-security</artifactId>
</dependency>
  1. 编写基础配置类
package com.kuang.config;

@EnableWebSecurity // 开启WebSecurity模式
public class SecurityConfig extends WebSecurityConfigurerAdapter {
  @Override
  protected void configure(HttpSecurity http) throws Exception {

  }
}
  1. 定制请求的授权规则
@Override
protected void configure(HttpSecurity http) throws Exception {
   // 定制请求的授权规则
   // 首页所有人可以访问
   http.authorizeRequests().antMatchers("/").permitAll()
  .antMatchers("/level1/**").hasRole("vip1")
  .antMatchers("/level2/**").hasRole("vip2")
  .antMatchers("/level3/**").hasRole("vip3");
}

  1. 测试一下:发现除了首页都进不去了!因为我们目前没有登录的角色,因为请求需要登录的角色拥有对应的权限才可以!
  2. 在configure()方法中加入以下配置,开启自动配置的登录功能!
// 开启自动配置的登录功能
// "/login" 请求来到登录页
// /login?error 重定向到这里表示登录失败
http.formLogin();

此时如果没有权限就会跳转至登陆页

  1. 定义认定规则,重写configure(AuthenticationManagerBuilder auth)方法
//认证
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
  //数据正常应该从数据库中取,现在从内存中取
  auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder())
    .withUser("admin").password(new BCryptPasswordEncoder().encode("123")).roles("vip1","vip2")
    .and()
    .withUser("root").password(new BCryptPasswordEncoder().encode("123")).roles("vip1","vip2","vip3")
    .and()
    .withUser("guest").password(new BCryptPasswordEncoder().encode("123")).roles("vip1");

这里设置密码加密auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder())否则会报There is no PasswordEncoder mapped for the id "null"错误,创建的每个用户也必须添加密码加密**.password(new BCryptPasswordEncoder().encode(“123”))**

  1. 测试,发现,登录成功,并且每个角色只能访问自己认证下的规则!搞定

第36P 权限控制和注销 [ https://www.bilibili.com/video/BV1PE411i7CV?p=8]

权限控制和注销

注销

  1. 开启自动配置的注销的功能
//定制请求的授权规则
@Override
protected void configure(HttpSecurity http) throws Exception {
   //....
   //开启自动配置的注销的功能
      // /logout 注销请求
   http.logout();
}

  1. 我们在前端,增加一个注销的按钮,index.html 导航栏中
<a class="btn btn-primary" th:href="@{/logout}">注销</a>

  1. 测试发现,点击注销按钮后会跳转到登陆页面,此时想要在注销成功后跳转到指定页面需要在请求后添加.logoutSuccessUrl(“/”);
// .logoutSuccessUrl("/"); 注销成功来到首页
http.logout().logoutSuccessUrl("/");

权限控制

**需求:**用户没有登录的时候,导航栏上只显示登录按钮,用户登录之后,导航栏可以显示登录的用户信息及注销按钮!还有就是,比如admin这个用户,它只有 vip2,vip3功能,那么登录则只显示这两个功能,而vip1的功能菜单不显示!

  1. 导入maven依赖
<!-- https://mvnrepository.com/artifact/org.thymeleaf.extras/thymeleaf-extras-springsecurity4 -->
<dependency>
   <groupId>org.thymeleaf.extras</groupId>
   <artifactId>thymeleaf-extras-springsecurity5</artifactId>
   <version>3.0.4.RELEASE</version>
</dependency>

  1. 导入命名空间
xmlns:sec="http://www.thymeleaf.org/extras/spring-security"

  1. 修改index页面,增加判断
<div>
  <!--如果未登录则显示以下内容-->
  <div sec:authorize="!isAuthenticated()">
    <a class="btn btn-primary" th:href="@{/toLogin}">登陆</a>
  </div>
  <!--如果已登录则显示以下内容-->
  <div sec:authorize="isAuthenticated()">
    用户名<p sec:authentication="name"></p>
    角色<p sec:authentication="principal.authorities"></p>
    <a class="btn btn-primary" th:href="@{/logout}">注销</a>
  </div>
</div>
<!--如果用户拥有这个角色,则显示该div内的内容,如果没有则不显示-->
<div class="div" sec:authorize="hasRole('vip1')">
  <a th:href="@{/level1/1}">level1-1</a><br/>
  <a th:href="@{/level1/2}">level1-2</a>
</div>
<div class="div" sec:authorize="hasRole('vip2')">
  <a th:href="@{/level2/1}">level2-1</a><br/>
  <a th:href="@{/level2/2}">level2-2</a>
</div>
<div class="div" sec:authorize="hasRole('vip3')">
  <a th:href="@{/level3/1}">level3-1</a><br/>
  <a th:href="@{/level3/2}">level3-2</a>
</div>

  1. 如果注销404了,就是因为它默认防止csrf跨站请求伪造,因为会产生安全问题,我们可以将请求改为post表单提交,或者在spring security中关闭csrf功能;我们试试:在 配置中增加 http.csrf().disable();

第37P 记住我及首页配置 [ https://www.bilibili.com/video/BV1PE411i7CV?p=8]

记住我

  1. 开启记住我功能
//定制请求的授权规则
@Override
protected void configure(HttpSecurity http) throws Exception {
//。。。。。。。。。。。
   //记住我,保存2周
   http.rememberMe();
}

  1. 测试,关闭浏览器再次打开用户依旧存在
    本质上是保存到cookie,通过浏览器审查元素的application中可以看到
  2. 如果使用自己的页面中的按钮,可以给按钮设置name,再在配置后面加上如下方法
http.rememberMe().rememberMeParameter("remember");

定制登录页

  1. 在配置中设置,用.loginProcessingUrl(“/login”)并将前端form表单的action设置为括号内相同即可,但如果前端用户名和密码前后端不一样,则需要进行设置,括号内的属性为前端页面的name属性
http.formLogin().loginPage("/toLogin").usernameParameter("username").passwordParameter("password").loginProcessingUrl("/login");

第38P Shiro 快速开始 [ https://www.bilibili.com/video/BV1PE411i7CV?p=39]

Shiro

shiro简介

基本功能点

Shiro 可以非常容易的开发出足够好的应用,其不仅可以用在 JavaSE 环境,也可以用在 JavaEE 环境。Shiro 可以帮助我们完成:认证、授权、加密、会话管理、与 Web 集成、缓存等。其基本功能点如下图所示:
在这里插入图片描述

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

记住一点,Shiro 不会去维护用户、维护权限;这些需要我们自己去设计 / 提供;然后通过相应的接口注入给 Shiro 即可。

Shiro的架构

外部

我们从外部来看 Shiro ,即从应用程序角度的来观察如何使用 Shiro 完成工作。如下图:
在这里插入图片描述
可以看到:应用代码直接交互的对象是 Subject,也就是说 Shiro 的对外 API 核心就是 Subject;其每个 API 的含义:
**Subject:**主体,代表了当前 “用户”,这个用户不一定是一个具体的人,与当前应用交互的任何东西都Subject,如网络爬虫,机器人等;即一个抽象概念;所有 Subject 都绑定到 SecurityManager,与 Subject 的所有交互都会委托给 SecurityManager;可以把 Subject 认为是一个门面;SecurityManager 才是实际的执行者;

**SecurityManager:**安全管理器;即所有与安全有关的操作都会与 SecurityManager 交互;且它管理着所有 Subject;可以看出它是 Shiro 的核心,它负责与后边介绍的其他组件进行交互,如果学习过 SpringMVC,你可以把它看成 DispatcherServlet 前端控制器;

**Realm:**域,Shiro 从 Realm 获取安全数据(如用户、角色、权限),就是说 SecurityManager 要验证用户身份,那么它需要从 Realm 获取相应的用户进行比较以确定用户身份是否合法;也需要从 Realm 得到用户相应的角色 / 权限进行验证用户是否能进行操作;可以把 Realm 看成 DataSource,即安全数据源。

也就是说对于我们而言,最简单的一个 Shiro 应用:

  1. 应用代码通过 Subject 来进行认证和授权,而 Subject 又委托给 SecurityManager;
  2. 我们需要给 Shiro 的 SecurityManager 注入 Realm,从而让 SecurityManager 能得到合法的用户及其权限进行判断。

从以上也可以看出,Shiro 不提供维护用户 / 权限,而是通过 Realm 让开发人员自己注入。

内部

接下来我们来从 Shiro 内部来看下 Shiro 的架构,如下图所示:
在这里插入图片描述
**Subject:**主体,可以看到主体可以是任何可以与应用交互的 “用户”;

**SecurityManager:**相当于 SpringMVC 中的 DispatcherServlet 或者 Struts2 中的 FilterDispatcher;是 Shiro 的心脏;所有具体的交互都通过 SecurityManager 进行控制;它管理着所有 Subject、且负责进行认证和授权、及会话、缓存的管理。

**Authenticator:**认证器,负责主体认证的,这是一个扩展点,如果用户觉得 Shiro 默认的不好,可以自定义实现;其需要认证策略(Authentication Strategy),即什么情况下算用户认证通过了;

**Authrizer:**授权器,或者访问控制器,用来决定主体是否有权限进行相应的操作;即控制着用户能访问应用中的哪些功能;

**Realm:**可以有 1 个或多个 Realm,可以认为是安全实体数据源,即用于获取安全实体的;可以是 JDBC 实现,也可以是 LDAP 实现,或者内存实现等等;由用户提供;注意:Shiro 不知道你的用户 / 权限存储在哪及以何种格式存储;所以我们一般在应用中都需要实现自己的 Realm;

**SessionManager:**如果写过 Servlet 就应该知道 Session 的概念,Session 呢需要有人去管理它的生命周期,这个组件就是 SessionManager;而 Shiro 并不仅仅可以用在 Web 环境,也可以用在如普通的 JavaSE 环境、EJB 等环境;所有呢,Shiro 就抽象了一个自己的 Session 来管理主体与应用之间交互的数据;这样的话,比如我们在 Web 环境用,刚开始是一台 Web 服务器;接着又上了台 EJB 服务器;这时想把两台服务器的会话数据放到一个地方,这个时候就可以实现自己的分布式会话(如把数据放到 Memcached 服务器);

**SessionDAO:**DAO 大家都用过,数据访问对象,用于会话的 CRUD,比如我们想把 Session 保存到数据库,那么可以实现自己的 SessionDAO,通过如 JDBC 写到数据库;比如想把 Session 放到 Memcached 中,可以实现自己的 Memcached SessionDAO;另外 SessionDAO 中可以使用 Cache 进行缓存,以提高性能;

**CacheManager:**缓存控制器,来管理如用户、角色、权限等的缓存的;因为这些数据基本上很少去改变,放到缓存中后可以提高访问的性能

**Cryptography:**密码模块,Shiro 提高了一些常见的加密组件用于如密码加密 / 解密的。

shiro组件

身份验证

身份验证,即在应用中谁能证明他就是他本人。一般提供如他们的身份 ID 一些标识信息来表明他就是他本人,如提供身份证,用户名 / 密码来证明。

在 shiro 中,用户需要提供 principals (身份)和 credentials(证明)给 shiro,从而应用能验证用户身份:

**principals:**身份,即主体的标识属性,可以是任何东西,如用户名、邮箱等,唯一即可。一个主体可以有多个 principals,但只有一个 Primary principals,一般是用户名 / 密码 / 手机号。

**credentials:**证明 / 凭证,即只有主体知道的安全值,如密码 / 数字证书等。

最常见的 principals 和 credentials 组合就是用户名 / 密码了。接下来先进行一个基本的身份认证。

另外两个相关的概念是之前提到的 SubjectRealm,分别是主体及验证主体的数据源。

快速开始(helloworld)

  1. 导入依赖
<dependency>
  <groupId>org.apache.shiro</groupId>
  <artifactId>shiro-core</artifactId>
  <version>1.4.1</version>
</dependency>
<dependency>
  <groupId>org.slf4j</groupId>
  <artifactId>slf4j-log4j12</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>log4j</groupId>
  <artifactId>log4j</artifactId>
  <version>1.2.17</version>
</dependency>
<dependency>
  <groupId>commons-logging</groupId>
  <artifactId>commons-logging</artifactId>
  <version>1.1.1</version>
</dependency>

  1. 配置文件(shiro.ini)
[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]
# '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. log4j.properties
log4j.rootLogger=INFO, stdout

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

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

# Spring
log4j.logger.org.springframework=WARN

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

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

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

/**
 * @author lee
 * @date 2020/8/11 - 6:24 下午
 */

public class Quickstart {


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

  public static void main(String[] args) {

	//----------------a方法 开始----------------
	//此段可能会过期,如过期用b方法
    Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
    SecurityManager securityManager = factory.getInstance();
    SecurityUtils.setSecurityManager(securityManager);
    //----------------a方法 结束----------------

	//---------------b方法  开始-----------------
	DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();
    IniRealm iniRealm = new IniRealm("classpath:shiro.ini");
    defaultSecurityManager.setRealm(iniRealm);
	//---------------b方法  结束-----------------

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

    // 通过当前用户拿到Session
    Session session = currentUser.getSession();
    session.setAttribute("someKey", "aValue");
    String value = (String) session.getAttribute("someKey");
    if (value.equals("aValue")) {
      log.info("Subject=>session[" + value + "]");
    }

    //判断当前用户是否被认证
    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);
  }
}


以下方法Spring Security都有:

Subject currentUser = SecurityUtils.getSubject();
Session session = currentUser.getSession();
currentUser.isAuthenticated()
currentUser.getPrincipal()
currentUser.hasRole("schwartz")
currentUser.isPermitted("lightsaber:wield")
currentUser.logout();

第39P Shiro的Subject分析 [ https://www.bilibili.com/video/BV1PE411i7CV?p=8]

第40P 在SpringBoot中集成Shiro [ https://www.bilibili.com/video/BV1PE411i7CV?p=8]

  1. 引入maven
<!--
            Subject 用户
            SecurityManager  管理所有用户
            Realm  连接数据
        -->

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

        <!--   thymeleaf模板-->
        <dependency>
            <groupId>org.thymeleaf</groupId>
            <artifactId>thymeleaf-spring5</artifactId>
        </dependency>

        <dependency>
            <groupId>org.thymeleaf.extras</groupId>
            <artifactId>thymeleaf-extras-java8time</artifactId>
        </dependency>
  1. 编写UserRealm类
//自定义的UserRealm
public class UserRealm extends AuthorizingRealm {
  //授权
  @Override
  protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
    System.out.println("执行了=》授权");
    return null;
  }
  //认证
  @Override
  protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
    System.out.println("执行了=》认证");
    return null;
  }
}

  1. 编写Shiroconfig类(三个方法,从下往上写)
package com.example.demo.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:3
    @Bean
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager defaultWebSecurityManager){
        ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
        //设置安全管理器
        bean.setSecurityManager(defaultWebSecurityManager);
        return bean;
    }
    //DefaultWebSecurityManager:2
    @Bean(name="securityManager")
    public DefaultWebSecurityManager getdefaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm){
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(userRealm);
        return securityManager;
    }

    //创建realm对象:1
    @Bean
    public UserRealm userRealm(){
        return new UserRealm();
    }
}

  1. 做一些前期准备

MyController.java

package com.example.demo.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,shiro");
        return "index";
    }


    @RequestMapping("/user/add")
    public String toAdd(Model model){
        model.addAttribute("msg","add");
        return "user/add";
    }

    @RequestMapping("/user/update")
    public String toUpdate(Model model){
        model.addAttribute("msg","add");
        return "user/update";
    }


}

index.html

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

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


</body>
</html>

add.html

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

</body>
</html>

update.html

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

</body>
</html>

第41P Shiro实现登录拦截 [ https://www.bilibili.com/video/BV1PE411i7CV?p=8]

实现登陆拦截

  1. 配置ShiroConfig配置类
 //添加shiro的内置过滤器

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

        Map<String,String> filterMap = new LinkedHashMap<>();
        filterMap.put("/user/*","authc");
        bean.setFilterChainDefinitionMap(filterMap);

        //设置登录的请求
        bean.setLoginUrl("/toLogin");
  1. 在controller类实现/toLogin请求
@RequestMapping("/toLogin")
    public String toLogin(){
        return "login";
    }

  1. 登录页
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

<h1>登录</h1>

<hr>
<form action="">
    <p>用户名<input type="text" name="username"></p>
    <p>密码<input type="text" name="password"></p>
    <input type="submit">
</form>

</body>
</html>

第42P Shiro实现用户认证 [ https://www.bilibili.com/video/BV1PE411i7CV?p=8]

实现用户认证

  1. 在controller配置请求
@RequestMapping("/login")
public String login(String username ,String password,Model model){

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

  //封装用户的登陆数据
  UsernamePasswordToken token = new UsernamePasswordToken(username,password);

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

}

  1. 在UserRealm类中配置认证
//认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
  System.out.println("执行了=》认证");

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

  UsernamePasswordToken userToken = (UsernamePasswordToken) token;
  System.out.println(userToken.toString());
  if(!userToken.getUsername().equals(name)){
    return null; //抛出异常  UnknownAccountException
  }

  return new SimpleAuthenticationInfo("",password,"");
}

  1. login.html 改造
<p th:text="${msg}" style="color: red"></p>
<form th:action="@{/login}">

用户名验证自己来做,密码验证shiro来做

第43P Shiro整合Mybatis [ https://www.bilibili.com/video/BV1PE411i7CV?p=8]

整合Mybatis

1.引入pom

<dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.16.10</version>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.21</version>
        </dependency>

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

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.12</version>
        </dependency>

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

2.创建实体类User

package com.example.demo.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
    private String id;
    private String name;
    private String pwd;
}

3.创建Mapper.java

package com.example.demo.mapper;

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

@Repository
@Mapper
public interface UserMapper {
    public User queryUserByName(String name);
}

4.创建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=绑定一个对应的Dao/Mapper接口-->
<mapper namespace="com.example.demo.mapper.UserMapper">
    <!--    查询语句 id相当与要重写的方法名-->
    <select id="queryUserByName" resultType="User" parameterType="String">
        select * from  User where name = #{name}
    </select>

</mapper>

5、创建service及imp

package com.example.demo.service;

import com.example.demo.pojo.User;

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

package com.example.demo.service;

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

@Service
public class UserServiceImpl implements UserService{
    @Autowired
    UserMapper userMapper;

    @Override
    public User queryUserByName(String username) {

        return userMapper.queryUserByName(username);
    }
}

6.测试

package com.example.demo;

import com.example.demo.service.UserServiceImpl;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
class DemoApplicationTests {

    @Autowired
    UserServiceImpl userService;

    @Test
    void contextLoads() {
       System.out.println( userService.queryUserByName("a"));

    }

}

第44P Shiro请求授权实现 [ https://www.bilibili.com/video/BV1PE411i7CV?p=8]

实现授权

  1. 对资源设置权限
//拦截
Map<String, String> filterMap = new LinkedHashMap<>();
filterMap.put("/add","perms[user:add]");
filterMap.put("/update","perms[user:update]");
bean.setFilterChainDefinitionMap(filterMap);

//未授权就跳转到此请求
bean.setUnauthorizedUrl("/noauth");

  1. 对登陆用户进行授权,该用户拥有的权限从数据库取得
//授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
  System.out.println("执行了=》授权");

  SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();

  //拿到当前登陆的对象
  Subject subject = SecurityUtils.getSubject();
  User currentUser = (User)subject.getPrincipal();//拿到User
  info.addStringPermission(currentUser.getPerms());//授权

  return info;
}

第45P Shiro整合Thymeleaf [ https://www.bilibili.com/video/BV1PE411i7CV?p=8]

第45P Swagger介绍及集成 [ https://www.bilibili.com/video/BV1PE411i7CV?p=8]

Swagger

接口文档对于前后端开发人员都十分重要。尤其近几年流行前后端分离后接口文档又变成重中之重。接口文档固然重要,但是由于项 目周期等原因后端人员经常出现无法及时更新,导致前端人员抱怨接 口文档和实际情况不一致。

很多人员会抱怨别人写的接口文档不规范,不及时更新。当时当 自己写的时候确实最烦去写接口文档。这种痛苦只有亲身经历才会牢 记于心。

如果接口文档可以实时动态生成就不会出现上面问题。

Swagger 可以完美的解决上面的问题。

Swagger 简介:

Swagger 是一套围绕 Open API 规范构建的开源工具,可以帮助设 计,构建,记录和使用 REST API。

Swagger 工具包括的组件:

  1. Swagger Editor :基于浏览器编辑器,可以在里面编写 Open API规范。类似 Markdown
    具有实时预览描述文件的功能。
  2. Swagger UI:将 Open API 规范呈现为交互式 API 文档。用可视化UI 展示描述文件。
  3. Swagger Codegen:将 OpenAPI 规范生成为服务器存根和客户端 库。通过 Swagger Codegen
    可以将描述文件生成 html 格式和 cwiki 形 式的接口文档,同时也可以生成多种言语的客户端和服务端代码。

Swagger Inspector:和 Swagger UI 有点类似,但是可以返回更多 信息,也会保存请求的实际参数数据。

Swagger Hub:集成了上面所有项目的各个功能,你可以以项目 和版本为单位,将你的描述文件上传到 Swagger Hub 中。在 Swagger Hub 中可以完成上面项目的所有工作,需要注册账号,分免费版和收费版。

使用 Swagger,就是把相关的信息存储在它定义的描述文件里面(yml 或 json 格式),再通过维护这个描述文件可以去更新接口文档, 以及生成各端代码。

Swagger集成

  1. 导入maven依赖
<!-- https://mvnrepository.com/artifact/io.springfox/springfox-swagger-ui -->
<dependency>
  <groupId>io.springfox</groupId>
  <artifactId>springfox-swagger-ui</artifactId>
  <version>2.9.2</version>
</dependency>
<!-- https://mvnrepository.com/artifact/io.springfox/springfox-swagger2 -->
<dependency>
  <groupId>io.springfox</groupId>
  <artifactId>springfox-swagger2</artifactId>
  <version>2.9.2</version>
</dependency>

  1. 创建SwaggerConfig.java
@Configuration
@EnableSwagger2   //开启Swagger2
public class SwaggerConfig{

}

第48P 配置swagger信息 [ https://www.bilibili.com/video/BV1PE411i7CV?p=8]

Swagger配置

在SwaggerConfig进行如下配置即可自定义Swagger配置

@Configuration
@EnableSwagger2   //开启Swagger2
public class SwaggerConfig{
  @Bean
  public Docket docket(){

    return new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo());
  }
  private ApiInfo apiInfo(){
    //作者信息
    Contact contact = new Contact("李海洋", "codekitty.cn:8000", "L18249290950@126.com");

    return new ApiInfo("codekitty's Swagger API Documentation", "千里之行,始于足下", "1.0", "codekitty.cn:8000",contact , "Apache 2.0", "http://www.apache.org/licenses/LICENSE-2.0", new ArrayList());
  }
}

第49P 配置扫描接口及开关 [ https://www.bilibili.com/video/BV1PE411i7CV?p=8]

Swagger扫描接口

  1. Docket(DocumentationType.SWAGGER_2).select()
new Docket(DocumentationType.SWAGGER_2)
  .apiInfo(apiInfo())
  .enable(b)//是否启用Swagger,false则不启用
  .select()
  //RequestHandlerSelectors,配置要扫描接口的方式
  //basePackage,指定扫描包
  //                .apis(RequestHandlerSelectors.any()),扫描全部
  //                .apis(RequestHandlerSelectors.none()),不扫描
  //                withMethodAnnotation(GetMapping.class)) //扫描类上的注解
  //                withClassAnnotation(GetMapping.class)) //扫描方法上的注解
  .apis(RequestHandlerSelectors.basePackage("com.controller"))
  //Path过滤路径
  //                .paths(PathSelectors.ant("/com/**"))

  .build();

  1. 实现开发环境中使用Swagger,运行上线环境中不使用Swagger
public Docket docket(Environment environment){
//设置要显示的Swagger环境
Profiles profiles = Profiles.of("dev","test");
//获取项目的环境:判断是否处在自己设定的环境中
boolean flag = environment.acceptsProfiles(profiles);
}

在properties中选择使用的环境,将flag传入Enable()

  1. 通过groupName(“”)配置多个Docket,(在多人开发中,每个开发者配置一个自己的Swagger,方便管理)
@Bean
public Docket docket1(){
  return new Docket(DocumentationType.SWAGGER_2).groupName("A");
}
@Bean
public Docket docket2(){
  return new Docket(DocumentationType.SWAGGER_2).groupName("B");
}
@Bean
public Docket docket3(){
  return new Docket(DocumentationType.SWAGGER_2).groupName("C");
}

实体类配置

@ApiModel("用户实体类")
public class User {
    @ApiModelProperty("用户名")
    private String username;
    @ApiModelProperty("密码")
    private String password;

在Controller进行配置请求

@ApiOperation("user请求")
    //只要接口中返回值存在实体类,它就会被扫描到Swagger中
    @PostMapping(value = "/user")
    public String user(@ApiParam("用户名")String username){
        return "hello" + username;
    }

就可以在Swagger显示实体类的信息,如果属性是private需要加get,set方法

总结:

  1. 我们可以通过Swagger给一些比较难理解的属性或接口增加注释信息
  2. 接口文档实时更新
  3. 可以在线测试

【注意】在正式发布时要关闭Swagger!!!可以保证安全和避免浪费性能

第51P 异步任务 [ https://www.bilibili.com/video/BV1PE411i7CV?p=8]

任务

异步任务

定义一个Service

@Service
public class AsyncService {
	//告诉spring这是异步任务
    @Async
    public void hello(){
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("数据正在处理");
    }
}

定义一个请求,在请求中调用service中的方法

@ResponseBody
@RequestMapping("/hello")
public String hello(){
  asyncService.hello();
  return "OK";
}

在主入口上使用@EnableAsync开启异步任务

@EnableAsync
@SpringBootApplication
public class DemoApplication {

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

}

两个步骤:

  1. 在Service的方法中使用@Async
  2. 在主入口上使用@EnableAsync开启异步任务

第52P 邮件发送 [ https://www.bilibili.com/video/BV1PE411i7CV?p=8]

邮件发送

在properties中配置自己的邮件信息

spring.mail.username=1012074120@qq.com
spring.mail.password=jtnhlvabxxqsbbga
spring.mail.host=smtp.qq.com

#开启加密验证(qq邮箱)
spring.mail.properties.mail.smtp.ssl.enable=true

简单邮件发送
直接调用SpringBoot的JavaMailSenderImpl类,使用SimpleMailMessage发送简单邮件

@Autowired
JavaMailSenderImpl mailSender;

@Test
void contextLoads() {
  SimpleMailMessage simpleMailMessage = new SimpleMailMessage();
  simpleMailMessage.setSubject("你好");
  simpleMailMessage.setText("Hello world");
  simpleMailMessage.setTo("L18249290950@126.com");
  simpleMailMessage.setFrom("1012074120@qq.com");
  mailSender.send(simpleMailMessage);
}

复杂邮件发送
调用mailSender.createMimeMessage()并使用MimeMessageHelper配置邮件内容,发送即可,邮件内容后设置为true可以解析html格式的内容

public void SendMail(Boolean html,String title, String text, File file, String sendTo, String sendFrom) throws MessagingException {
        //复杂邮件
        MimeMessage mimeMessage = mailSender.createMimeMessage();
        //组装
        MimeMessageHelper mimeMessageHelper = new MimeMessageHelper(mimeMessage,true);

        mimeMessageHelper.setSubject(title);
        mimeMessageHelper.setText(text,true);//true,开启html解析
       mimeMessageHelper.addAttachment("1.jpg",file);

        mimeMessageHelper.setTo(sendTo);
        mimeMessageHelper.setFrom(sendFrom);
        mailSender.send(mimeMessage);
    }

第53P 定时执行任务 [ https://www.bilibili.com/video/BV1PE411i7CV?p=8]

定时任务

Scheduled

TaskScheduler //任务调度程序
TaskExecutor	//任务执行者
@EnableScheduling //开启定时功能的注解,放在主入口
@Scheduled		//什么时候执行
  
cron表达式

在方法上面加上 @Scheduled(cron = "0 43 14 * * ?") 并加上相应的表达式即可
常用表达式例子:

10 0 2 1 * ? *   表示在每月的1日的凌晨2点调整任务
(20 15 10 ? * MON-FRI   表示周一到周五每天上午10:15执行作
(30 15 10 ? 6L 2002-2006   表示2002-2006年的每个月的最后一个星期五上午10:15执行作
(40 0 10,14,16 * * ?   每天上午10点,下午2点,4点
(50 0/30 9-17 * * ?   朝九晚五工作时间内每半小时
(60 0 12 ? * WED    表示每个星期三中午12点
(70 0 12 * * ?   每天中午12点触发
(80 15 10 ? * *    每天上午10:15触发
(90 15 10 * * ?     每天上午10:15触发
(100 15 10 * * ? *    每天上午10:15触发
(110 15 10 * * ? 2005    2005年的每天上午10:15触发
(120 * 14 * * ?     在每天下午2点到下午2:59期间的每1分钟触发
(130 0/5 14 * * ?    在每天下午2点到下午2:55期间的每5分钟触发
(140 0/5 14,18 * * ?     在每天下午2点到2:55期间和下午6点到6:55期间的每5分钟触发
(150 0-5 14 * * ?    在每天下午2点到下午2:05期间的每1分钟触发
(160 10,44 14 ? 3 WED    每年三月的星期三的下午2:102:44触发
(170 15 10 ? * MON-FRI    周一至周五的上午10:15触发
(180 15 10 15 * ?    每月15日上午10:15触发
(190 15 10 L * ?    每月最后一日的上午10:15触发
(200 15 10 ? * 6L    每月的最后一个星期五上午10:15触发
(210 15 10 ? * 6L 2002-2005   2002年至2005年的每月的最后一个星期五上午10:15触发
(220 15 10 ? * 6#3   每月的第三个星期五上午10:15触发

quartz

参考:https://blog.csdn.net/qq_42829628/article/details/106767803

分布式Dubbo+Zokeeper+SpringBoot

第56P 分布式系统理论 [ https://www.bilibili.com/video/BV1PE411i7CV?p=8]

什么是分布式系统?

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

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

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

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

第57P 什么是RPC [ https://www.bilibili.com/video/BV1PE411i7CV?p=8]

什么是RPC

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

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

Dubbo基本概念

在这里插入图片描述

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

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

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

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

调用关系说明

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

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

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

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

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

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

第59P Dubbo-admin安装测试 [ https://www.bilibili.com/video/BV1PE411i7CV?p=8]

安装Dubbo-admin

  1. 下载dubbo-admin
    地址 :https://github.com/apache/dubbo-admin/tree/master
  2. 进行打包(可以是Idea打包,也可以是命令行打包)
  3. 使用命令java -jar dubbo-admin-0.0.1-SNAPSHOT.jar
    【注意:zookeeper的服务一定要打开!】
  4. 访问localhost:7001即可访问Dubbo-admin页面

在这里插入图片描述
第60P 服务注册发现实战 [ https://www.bilibili.com/video/BV1PE411i7CV?p=8]

实现跨项目访问类

  1. 提供者配置文件
#服务应用名字
dubbo.application.name=provider-server
#注册中心地址
dubbo.registry.address=zookeeper://127.0.0.1:2181
#哪些服务要被注册(扫描包)
dubbo.scan.base-packages=com.servic
  1. 编写提供者的接口和实现类
    在这里插入图片描述

  2. 消费者配置文件

#消费者去哪里拿服务需要暴露自己的名字
dubbo.application.name=customer-server

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

  1. 在消费者和提供者相同的包下建立提供者的接口
  2. 消费者服务类
@Service //注入到容器中
public class UserService {

   @Reference //远程引用指定的服务,他会按照全类名进行匹配,看谁给注册中心注册了这个全类名
   TicketService ticketService;

   public void bugTicket(){
       String ticket = ticketService.getTicket();
       System.out.println("在注册中心买到"+ticket);
  }

  1. 编写测试类
@Autowired
UserService userService;

@Test
public void contextLoads() {

  userService.bugTicket();

}

启动测试

  1. 开启zookeeper

  2. 打开dubbo-admin实现监控【可以不用做】

  3. 开启服务者

  4. 消费者消费测试

在这里插入图片描述

第61P 回顾,架构! [ https://www.bilibili.com/video/BV1PE411i7CV?p=8]

回顾,架构!

层架构+MVC

架构 -->解耦

开发框架

​ Spring

​ IOC : 控制反转

​ 泡温泉,泡茶泡友

​ 附近的人,打招呼。加微信,聊天,天天聊>约泡

​ 浴场(容器):温泉,茶庄泡友

​ 直接进温泉,就有人和你一起了!

​ 原来我们都是自己一步步操作,现在交给容器了!我们需要什么就去拿就可以了

AOP:切面(本质,动态代理)

为了解什么?不影响业本来的情况下,实现动态增加功能,大量应用在日志,事务等等

​ Spring是一个轻量级的Java开源框架,容器

​ 目的:解决企业开发的复杂性问题

​ Spring是春天,但配置文件繁琐

SpringBoot

SpringBoot ,新代javaEE的开发标准,开箱即用!>拿过来就可以用,它自

动帮我们配置了非常多的东西,我们拿来即用,特性:约定大于配置!

随着公司体系越来越大,用户越来越多

微服务架构—>新架构

​ 模块化,功能化!

​ 用户,支付,签到,娱乐…;

​ 人多余多一台服务器解决不了就再增加一台服务器! --横向扩展

​ 假设A服务器占用98%资源B服务器只占用了10%.–负载均衡;

​ 将原来的整体项,分成模块化,用户就是一个单独的项目,签到也是一个单独的项目,项目和项目之前需要通信,如何通信

​ 用户非常多而到十分少给用户多一点服务器,给签到少一点服务器

微服务架构问题?

分布式架构会遇到的四个核心题?

  1. 这么多服务,客户端该如何去访?
  2. 这么多服务,服务之间如何进行通信?
  3. 这么多服务,如何治理呢?
  4. 服务挂了,怎么办?

解决方案:SpringCloud

Springcloud是一套生态,就是来解决以上分布式架构的4个问题

想使用Spring Clould ,必须要掌握 springBoot , 因为Springcloud是基于springBoot ;

  1. spring Cloud NetFlix ,出来了一套解决方案!一站式解决方案。可以直接使用

    1. Api网关 , zuul组件
    2. Feign --> Httpclient —> http通信方式,同步并阻塞
    3. 服务注册与发现 , Eureka
    4. 熔断机制 , Hystrix
      2018年年底,NetFlix 宣布无限期停止维护。生态不再维护,就会脱节。
  2. Apache Dubbo zookeeper

    1. API:没有!要么找第三方组件,要么自己实现
    2. Dubbo 是一个高性能的基于ava实现的RPC通信框架!2.6.x
    3. 服务注册与发现 , zookeeper :动物管理者 ( Hadoop , Hive )
    4. 没有:借助了Hystrix
  3. SpringCloud Alibaba 一站式解决方案

总而言之,要解决的问题就是4个

  1. API网关 , 服务路由
  2. HTTP,RPC框架,异步调用
  3. 服务注册与发现,高可用
  4. 熔断机制,服务降级

为什么要解决这个问题?因为网络是不可靠的

  • 1
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值