SpringBoot

一、微服务阶段

JavaSe:OOP

mysql:持久化

html+css+jquery+框架:视图,框架不熟练,css不好

javaWeb:独立开发MVC三层架构的网站:原始

SSM:框架:简化了我们的开发流程,配置也开始较为复杂

war:tomcat运行

spring再简化:SpringBoot-jar;内嵌tomcat;微服务架构!

服务越来越多:springcloud

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

Spring是如何简化Java开发的

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

1、基于POJO的轻量级和最小侵入性编程,所有东西都是bean;

2、通过IOC,依赖注入(DI)和面向接口实现松耦合;

3、基于切面(AOP)和惯例进行声明式编程;

4、通过切面和模版减少样式代码,RedisTemplate,xxxTemplate;

微服务

什么是微服务

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

单体应用架构

所谓单体应用架构(all in one)是指,我们将一个应用中的所有应用服务都封装在一个应用中。

无论ERP,CRM或是其他什么系统,你都把数据库访问,web访问,等等各个都放到一个war中

  • 这样做的好处是,易于开发和测试;也十分方便部署;当需要扩展时,只需要将war复制多份,然后放到多个服务器上,再做个负载均衡就可以了
  • 单体应用架构的缺点是,哪怕我要修改一个非常小的地方,我都需要停掉整个服务,重新打包、部署这个应用的war包。特别是对于一个大型应用,我们不可能把所有内柔都放在一个应用里面,我们如何维护、如何分工合作都是问题

微服务架构

​ all in one的架构方式,我们把所有的功能单元放在一个应用里面。然后我们把整个应用部署到服务器上。如果负载能力不行,我们将整个应用进行水平复制,进行扩展,然后在负载均衡。

​ 所谓微服务架构,就是打破之前的all in one的架构方式,把每个功能元素独立出来。把独立出来的功能元素的动态组合,需要的功能元素才去拿来组合,需要多一些时间可以整合多个功能元素。所以微服务架构是对功能元素进行复制,而没有对整个应用进行复制

这样做的好处是

  1. 节省了条用资源
  2. 每个功能元素的服务都是一个可替换的、可独立升级的软件代码

二、第一个SpingBoot程序

官方:提供了一个快速生成的网站!IDEA集成了这个网站!

  • 可以在官网直接下载,导入IDEA开发
  • 直接使用IDEA创建一个springboot项目(一般开发直接在IDEA中创建)

使用spring-boot-devtools,可以实现指定目录(默认为classpath路径)下的文件进行更改后,项目自动重启更改后的代码自动生效

Banner.txt修改启动图

三、原理初探

自动配置

pom.xml

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

启动器

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter</artifactId>
</dependency>
  • 说白了就是springboot的启动场景
  • 比如spring-boot-starter-web,它就会帮我们导入web环境中功能所有依赖
  • springboot会将所有功能场景都变成一个的启动器
  • 我们要使用什么功能,就只需要导入相关包

主程序

//程序的主入口
//@SpringBootApplication:标注这个类是一个springboot的应用
@SpringBootApplication
public class SpringbootApplication {
    //将springboot应用启动
    public static void main(String[] args) {
        SpringApplication.run(SpringbootApplication.class, args);
    }
}	
  • 注解

    @SpringBootConfiguration:springboot的配置
    		@Configuration:spring配置类
     		@Component:说明这也是个spring组件
    @EnableAutoConfiguration:自动配置
      AutoConfigurationPackage:自动配置包
        	@Import(AutoConfigurationPackages.Registrar.class):自动配`包注册`
      @Import(AutoConfigurationImportSelector.class):自动导入选择器
        
    //获取所有的配置    
    List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
    
    

获取候选的配置

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

META-INF/spring.factorie自动配置的核心文件 org.springframework.boot.autoconfigure.AutoConfiguration.imports

Properties properties = PropertiesLoaderUtils.loadProperties(resource);
所有资源加载到配置类中

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

  1. springboot在启动的时候,从类路径下/META-INF/spring.factories获取指定的值
  2. 将这些自动配置的类导入容器中,自动配置就会生效,帮我们进行自动配置
  3. 以前我们需要自动配置的东西,现在springboot帮我们做了
  4. 整合javaEE,解决方案和自动装配的东西都在spring-boot- autoconfigure-2.2.0.RELEASE.jar这个包中
  5. 他会把所有需要导入的组件,以类名的方式返回,这些组件就被添加到容器
  6. 容器中也会存在非常多的xxxAutoConfiguration文件(@Bean),就是这些类给容器中导入了这个场景需要的所有组件;并自动诶值,@Configration,javaConfig
  7. 有了自动配置类,免去我们手动编写

SpringApplication.run分析

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

SpringApplication

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

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

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

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

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

图片

四、yaml

配置文件

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

  • application.properties

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

    • 语法结构 :key:空格 value

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

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

server.port=8081

yaml概述

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

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

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

传统xml配置:

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

yaml配置:

server:
	prot: 8080

yaml基础语法

说明:语法要求严格!

1、空格不能省略

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

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

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

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

k: v

注意:

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

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

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

    比如 :name: ‘kuang \n shen’ 输出 :kuang \n shen

对象、Map(键值对)

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

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

student:
	name: qinjiang    
	age: 3

行内写法

student: {name: qinjiang,age: 3}

数组( List、set )

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

pets:
	- cat 
	- dog 
	- pig

行内写法

pets: [cat,dog,pig]

修改SpringBoot的默认端口号

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

server: 
	port: 808

yaml可以给实体类赋值

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

2、编写一个实体类 Dog;

package com.kuang.springboot.pojo;
@Component  //注册bean到容器中
public class Dog {    
	private String name;    
	private Integer age;        
//有参无参构造、get、set方法、toString()方法  
}

3、思考,我们原来是如何给bean注入属性值的!@Value,给狗狗类测试一下:

@Component //注册bean
public class Dog 
{    
	@Value("阿黄")    
	private String name;    
	@Value("18")    
	private Integer age;
}

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

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

结果成功输出,@Value注入成功,这是我们原来的办法对吧。

图片

5、我们在编写一个复杂一点的实体类:Person 类

@Component //注册bean到容器中
public class Person 
{    
	private String name;    
	private Integer age;    
	private Boolean happy;    
	private Date birth;    
	private Map<String,Object> maps;    
	private List<Object> lists;    
	private Dog dog;        
//有参无参构造、get、set方法、toString()方法  
}

6、我们来使用yaml配置的方式进行注入,大家写的时候注意区别和优势,我们编写一个yaml配置!

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

7、我们刚才已经把person这个对象的所有值都写好了,我们现在来注入到我们的类中!

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

8、IDEA 提示,springboot配置注解处理器没有找到,让我们看文档,我们可以查看文档,找到一个依赖!

图片

图片

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

9、确认以上配置都OK之后,我们去测试类中测试一下:

@SpringBootTestclass DemoApplicationTests {
    @Autowired    
    Person person; //将person自动注入进来
    @Test    
    public void contextLoads() {        
    System.out.println(person); //打印person信息    
    }
}

结果:所有值全部注入成功!

图片

yaml配置注入到实体类完全OK!

课堂测试:

1、将配置文件的key 值 和 属性的值设置为不一样,则结果输出为null,注入失败

2、在配置一个person2,然后将 @ConfigurationProperties(prefix = "person2") 指向我们的person2;

加载指定的配置文件

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

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

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

name=kuangshen

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

@PropertySource(value = "classpath:person.properties")@Component //注册beanpublic class Person {
    @Value("${name}")    
    private String name;
    ......  }

3、再次输出测试一下:指定配置文件绑定成功!

图片

配置文件占位符

配置文件还可以编写占位符生成随机数

person:    
  name: qinjiang${random.uuid} # 随机uuid    
  age: ${random.int}  # 随机int    
  happy: false    
  birth: 2000/01/01   
  maps: {k1: v1,k2: v2}    
  lists:      
    - code      
    - girl      
    - music    
  dog:      
    name: ${person.hello:other}_旺财     
    age: 1

回顾properties配置

我们上面采用的yaml方法都是最简单的方式,开发中最常用的;也是springboot所推荐的!那我们来唠唠其他的实现方式,道理都是相同的;写还是那样写;配置文件除了yml还有我们之前常用的properties , 我们没有讲,我们来唠唠!

【注意】properties配置文件在写中文的时候,会有乱码 , 我们需要去IDEA中设置编码格式为UTF-8;

settings-->FileEncodings 中配置;

图片

测试步骤:

1、新建一个实体类User

@Component //注册beanpublic class User 
{    
  private String name;    
  private int age;    
  private String sex;
}

2、编辑配置文件 user.properties

user1.name=kuangshenuser1.age=18user1.sex=男

3、我们在User类上使用@Value来进行注入!

@Component //注册bean
@PropertySource(value = "classpath:user.properties")
public class User {/
  /直接使用@value   
  @Value("${user.name}") //从配置文件中取值   
  private String name;    
  @Value("#{9*2}")  // #{SPEL} Spring表达式    
  private int age;   
  @Value("男")  // 字面量    
  private String sex;
}

4、Springboot测试

@SpringBootTestclass DemoApplicationTests {
    @Autowired    
    User user;
    @Test    
    public void contextLoads() 
    {        
    	System.out.println(user);    
    }
}

结果正常输出:

图片

对比小结

@Value这个使用起来并不友好!我们需要为每个属性单独注解赋值,比较麻烦;我们来看个功能对比图

图片

1、@ConfigurationProperties只需要写一次即可 , @Value则需要每个字段都添加

2、松散绑定:这个什么意思呢? 比如我的yml中写的last-name,这个和lastName是一样的, - 后面跟着的字母默认是大写的。这就是松散绑定。可以测试一下

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

4、复杂类型封装,yml中可以封装对象 , 使用value就不支持

结论:

配置yml和配置properties都可以获取到值 , 强烈推荐 yml;

如果我们在某个业务中,只需要获取配置文件中的某个值,可以使用一下 @value;

如果说,我们专门编写了一个JavaBean来和配置文件进行一一映射,就直接@configurationProperties,不要犹豫!

五、JSR303校验与多环境切换

1.JSR303数据校验

先看看如何使用

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

@Component //注册bean
@ConfigurationProperties(prefix = "person")
@Validated  //数据校验
public class Person {
    @Email(message="邮箱格式错误") //name必须是邮箱格式    
    private String name;}

运行结果 :default message [不是一个合法的电子邮件地址];

图片

使用数据校验,可以保证数据的正确性;

常见参数

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

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

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

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

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

2.多环境切换

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

多配置文件

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

例如:

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

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

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

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

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

六、SpringBoot配置

6.1、配置文件加载位置

外部加载配置文件的方式十分多,我们选择最常用的即可,在开发的资源文件中进行配置!

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

图片

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

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

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

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

6.2、配置文件

一但这个配置类生效;这个配置类就会给容器中添加各种组件;

这些组件的属性是从对应的properties类中获取的,这些类里面的每一个属性又是和配置文件绑定的;

所有在配置文件中能配置的属性都是在xxxxProperties类中封装着;

配置文件能配置什么就可以参照某个功能对应的这个属性类

精髓

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

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

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

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

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

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

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

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

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

七、SpringBoot Web开发

jar:webapp

自动装配

SpringBoot到底帮我们配置了什么?我们能不能修改?能修改哪些东西?能不能扩展?

  • xxxAutoConfiguration..向容器中自动配置组件
  • xxxProperties:自动配置类,装配配置文件中自定义的一些内容

要解决问题:

  • 导入静态资源,..
  • 首页
  • jsp,模版引擎Thymeleaf
  • 装配扩展SpringMVC
  • 增删改查
  • 拦截器
  • 国际化

静态资源

系统默认可以放在以下位置,自定义后会失效

"classpath:/META-INF/resources/"====>通过webjars实现
"classpath:/resources/"
"classpath:/resources/static/"
"classpath:/resources/public/"

首页定制

"classpath:/resources/static/"
"classpath:/resources/public/"

模版引擎Thymeleaf

导入包

<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-thymeleaf -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>


<!-- Maven依赖关系以获取Thymeleaf 3(核心)-->
<dependency>
    <groupId>org.thymeleaf</groupId>
    <artifactId>thymeleaf</artifactId>
</dependency>

写一个controller

//自动装配
@Controller
public class HelloController {
    //接口:http://localhost:8080/hello
    @ResponseBody
    @RequestMapping("/hello")
    public String hello(){
        //调用业务,接收前端的数据!
        return "hello world";
    }
    @RequestMapping("/test")
    public String test(){
        return "test";//在templates中找到test.html
    }
}

在templates文件夹中创建test.html

练习测试:

1、 我们编写一个Controller,放一些数据

@RequestMapping("/t2")
public String test2(Map<String,Object> map){    //存入数据    
  map.put("msg","<h1>Hello</h1>");    
  map.put("users", Arrays.asList("qinjiang","kuangshen"));    //classpath:/templates/test.html    
  return "test";
}

2、测试页面取出数据

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
  <head>    
    <meta charset="UTF-8">    
    <title>狂神说</title>
  </head>
  <body>
    <h1>测试页面</h1>
		<div th:text="${msg}">
    </div>
    <!--不转义-->
    <div th:utext="${msg}"></div>
		<!--遍历数据--><!--th:each每次遍历都会生成当前这个标签:官网#9-->
    <h4 th:each="user :${users}" th:text="${user}"></h4>
		<h4>    <!--行内写法:官网#12-->    
 			<span th:each="user:${users}">[[${user}]]</span>
    </h4>
	</body>
</html>

扩展SpringMVC

自定义视图解析器 java 的config目录

import java.util.Locale;

@Configuration
public class MyMvcConfig implements WebMvcConfigurer {
    //ViewResolver:实现了视图解析器接口的类,我们就可以把它看作视图解析器

    @Bean
    public ViewResolver myViewResolve(){
        return new MyViewResolve();
    }
    //自定义了一个自己的视图解析器的静态内部类 MyViewResolve
    public  static  class MyViewResolve implements ViewResolver{
        @Override
        public View resolveViewName(String viewName, Locale locale) throws Exception {
            return null;
        }
    }

具体实现:

//如果我们要扩展springmvc,官方建议我们这样去做!
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {
    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("test1").setViewName("test");
    }
}

自定义的配置日期格式化

#自定义的配置日期格式化
spring:
  mvc:
    format:
      date-time: yyyy-MM-dd HH:mm:ss

小demo

https://gitee.com/xiangsir12700/springboot-demo.git

八、Data

1. springboot原生JDBC

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

除此之外还有有数据库的driver

==============================================

配置yaml:

spring:
  datasource:
    username: root
    password: root1@12
    url: jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=utf8&useSSL=true&serverTimezone=UTC
    driver-class-name: com.mysql.cj.jdbc.Driver

test测试:

@Autowired
DataSource dataSource;
@Test
void contextLoads() throws SQLException {

    //查看一下默认的数据源:class com.zaxxer.hikari.HikariDataSource
    System.out.println(dataSource.getClass());

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

    //xxxx Template:SpringBoot已经配置好模版bean,拿来即用  CURD

    //关闭数据库的连接
    connection.close();
}

使用template查询数据库多的user表的所有数据

@RestController
public class JDBCController {

    @Autowired
    JdbcTemplate jdbcTemplate;

    //查询数据库的所有信息
    @GetMapping("userlist")
    public List<Map<String,Object>> userList(){
        String sql ="select * from user";
        List<Map<String, Object>> listMaps = jdbcTemplate.queryForList(sql);
        return listMaps;
    }
}

2. Druid

Druid为监控而生的数据库连接池,它是阿里巴巴开源平台上的一个项目。Druid是Java语言中最好的数据库连接池,Druid能够提供强大的监控和扩展功能.它可以替换DBCP和C3P0连接池。Druid提供了一个高效、功能强大、可扩展性好的数据库连接池。

配置Druid监控页面

导入相关的包

<!-- https://mvnrepository.com/artifact/com.alibaba/druid -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid-spring-boot-starter</artifactId>
    <version>1.1.10</version>
</dependency>

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

写入yaml

spring:
  datasource:
    username: root
    password: root1@12
    url: jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=utf8&useSSL=true&serverTimezone=UTC
    driver-class-name: com.mysql.cj.jdbc.Driver
    # 指定数据源
    type: com.alibaba.druid.pool.DruidDataSource

      #   数据源其他配置
    druid:
      #SpringBoot默认是不注入这些的,需要自己绑定
      #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.Properity
      #则导入log4j 依赖就行
      filters: stat,wall,log4j
      maxPoolPreparedStatementPerConnectionSize: 20
      useGlobalDataSourceStat: true
      connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500

写一个JDBC的请求

@RestController
public class JDBCController {

    @Autowired
    JdbcTemplate jdbcTemplate;

    //查询数据库的所有信息
    @GetMapping("userlist")
    public List<Map<String,Object>> userList(){
        String sql ="select * from mybatis.user";
        List<Map<String, Object>> listMaps = jdbcTemplate.queryForList(sql);
        return listMaps;
    }
}

配置config

@Configuration

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

    //后台监控功能
    @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("xxx")
        bean.setInitParameters(initParameters);//设置初始化参数
        return bean;
    }
    //fifter
    @Bean
    public FilterRegistrationBean webStatFilter(){
        FilterRegistrationBean bean = new FilterRegistrationBean();

        bean.setFilter(new WebStatFilter());
        //可以通过哪些请求
        Map<String,String> initParameters = new HashMap<>();
        //这些东西不进行统计
        initParameters.put("exclusions","*.js,*.css,/druid/**");
        bean.setInitParameters(initParameters);
        return bean;
    }
}

image

九、Mybatis整合

导入包

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>2.2.2

配置application

spring:
  datasource:
    username: root
    password: root1@12
    url: jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=utf8&useSSL=true&serverTimezone=UTC
    driver-class-name: com.mysql.cj.jdbc.Driver
## 整合mybatis
mybatis:
  type-aliases-package: com.pojo
  mapper-locations: classpath:mybatis/mapper/*.xml

写入实体类

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

写一个mapper接口

package com.mapper;

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

import java.util.List;

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

    List<User> queryUserList();

    User queryUserById(int id);

    int addUser(User user);

    int updateUser(User user);

    int deleteUser(int id);
}

在在资源目录mybatis/mapper/中写去UserMapper的实现mapper

<?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 namespace="com.mapper.UserMapper">
    <select id="queryUserList" resultType="User" >
        select * from mybatis.user
    </select>
    <select id="queryUserById" parameterType="int" resultType="User">
        select * from mybatis.user where id=#{id};
    </select>
<!--    &lt;!&ndash;对象中的属性,可以直接取出来&ndash;&gt;-->
    <insert id="addUser" parameterType="User" >
        insert into mybatis.user (id,name,pwd) values (#{id},#{name},#{pwd});
    </insert>
    <update id="updateUser" parameterType="User">
        update mybatis.user set name=#{name},pwd=#{pwd}  where id=#{id};
    </update>
    <delete id="deleteUser" parameterType="int">
        delete from mybatis.user where id=#{id};
    </delete>
</mapper>

最后写一个controller

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("/addUser")
    public  String addUser(){

        int state = userMapper.addUser(new User(4, "赵六", "123456"));
        if(state>0){
            System.out.println(state+"行受影响");
        }
      return "ok";
    }
}

image

十、Spring Security

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

功能性需求:否

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

  • 漏洞,隐私泄露
  • 架构一但确定~

shiro、Spring Security:两个很像,除了类不一样,名字不一样

认证,授权

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

AOP:横切~配置类

简介

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

  • @EnableWebSecurity:开启Web Security模式

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

‘’认证“(Authentication)

”授权“(Authoriztion)

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

参考官网https://spring.io/projects/spring-security,官方文档:https://docs.spring.io/spring-security/reference/index.html

测试:

package com.config;

import org.springframework.context.annotation.Bean;
import org.springframework.security.config.annotation.AbstractConfiguredSecurityBuilder;
import org.springframework.security.config.annotation.SecurityConfigurer;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;

import static org.springframework.security.config.Customizer.withDefaults;

@EnableWebSecurity
public class SecurityConfig  {
    //授权
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        //首页所有人可以访问,功能页只有对于权限的人才能访问
        //请求授权的规则
        http
                .authorizeRequests().antMatchers("/").permitAll()
                .antMatchers("/level1/**").hasRole("vip1")
                .antMatchers("/level2/**").hasRole("vip2")
                .antMatchers("/level3/**").hasRole("vip3");
        //没有权限默认会到登录页面,需要开启登录的页面
        http.formLogin();
        // 定制
        //  http.formLogin().loginpage();
        //注销功能
        http.logout().logoutSuccessUrl("/");
        //开启记住我功能
        http.rememberMe();
        return http.build();
    }
    //认证
    @Bean
    public UserDetailsService userDetailsService() throws Exception {
        // ensure the passwords are encoded properly
        User.UserBuilder users = User.withDefaultPasswordEncoder();
        InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
        manager.createUser(users.username("admin").password("123456").roles("vip1","vip2","vip3").build());
        manager.createUser(users.username("user").password("123456").roles("vip2","vip3").build());
        return manager;
    }
}

管理员权限可以访问全部内容,user访问vip1内容会报403代码

image

开启无权限的模块屏蔽以及用户名的展示

导入thymeleaf-extras-springsecurity5

<dependency>
    <groupId>org.thymeleaf.extras</groupId>
    <artifactId>thymeleaf-extras-springsecurity5</artifactId>
    <version>3.0.4.RELEASE</version>
</dependency>
<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"
      xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity5">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
    <title>首页</title>
    <!--semantic-ui-->
    <link href="https://cdn.bootcss.com/semantic-ui/2.4.1/semantic.min.css" rel="stylesheet">
    <link th:href="@{/qinjiang/css/qinstyle.css}" rel="stylesheet">
</head>
<body>

<!--主容器-->
<div class="ui container">

    <div class="ui segment" id="index-header-nav" th:fragment="nav-menu">
        <div class="ui secondary menu">
            <a class="item"  th:href="@{/index}">首页</a>

            <!--登录注销-->
            <div class="right menu">
                <!--未登录:登录-->
                <div sec:authorize="!isAuthenticated()">
                    <a class="item" th:href="@{/login}">
                        <i class="address card icon"></i> 登录
                    </a>
                </div>
                <!--Spring Security页面获取用户名-->
                <div sec:authorize="isAuthenticated()">
                    <a class="item">
                        登录的用户为:<span sec:authentication="name"></span>&nbsp;
                        <!-- 用户角色为:<span sec:authentication="principal.authorities"></span>-->
                    </a>
                </div>

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

            </div>
        </div>
    </div>

    <div class="ui segment" style="text-align: center">
        <h3>Spring Security Study </h3>
    </div>

    <div>
        <br>
        <div class="ui three column stackable grid">
            <div class="column" sec:authorize="hasAnyRole('ROLE_vip1')">
                <div class="ui raised segment">
                    <div class="ui">
                        <div class="content">
                            <h5 class="content">Level 1</h5>
                            <hr>
                            <div><a th:href="@{/level1/1}"><i class="bullhorn icon"></i> Level-1-1</a></div>
                            <div><a th:href="@{/level1/2}"><i class="bullhorn icon"></i> Level-1-2</a></div>
                            <div><a th:href="@{/level1/3}"><i class="bullhorn icon"></i> Level-1-3</a></div>
                        </div>
                    </div>
                </div>
            </div>

            <div class="column" sec:authorize="hasAnyRole('ROLE_vip2')">
                <div class="ui raised segment">
                    <div class="ui">
                        <div class="content">
                            <h5 class="content">Level 2</h5>
                            <hr>
                            <div><a th:href="@{/level2/1}"><i class="bullhorn icon"></i> Level-2-1</a></div>
                            <div><a th:href="@{/level2/2}"><i class="bullhorn icon"></i> Level-2-2</a></div>
                            <div><a th:href="@{/level2/3}"><i class="bullhorn icon"></i> Level-2-3</a></div>
                        </div>
                    </div>
                </div>
            </div>

            <div class="column" sec:authorize="hasAnyRole('ROLE_vip3')">
                <div class="ui raised segment">
                    <div class="ui">
                        <div class="content">
                            <h5 class="content">Level 3</h5>
                            <hr>
                            <div><a th:href="@{/level3/1}"><i class="bullhorn icon"></i> Level-3-1</a></div>
                            <div><a th:href="@{/level3/2}"><i class="bullhorn icon"></i> Level-3-2</a></div>
                            <div><a th:href="@{/level3/3}"><i class="bullhorn icon"></i> Level-3-3</a></div>
                        </div>
                    </div>
                </div>
            </div>

        </div>
    </div>
    
</div>


<script th:src="@{/qinjiang/js/jquery-3.1.1.min.js}"></script>
<script th:src="@{/qinjiang/js/semantic.min.js}"></script>

</body>
</html>

十一、Shiro

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

Shiro核心三大对象

Subject 用户
SecurityManager 管理所有用户
Realm 连接数据

Quickstart核心:

 //获取当前用户对象  Subject
Subject currentUser = SecurityUtils.getSubject();
//通过当前用户 获取获取session
Session session = currentUser.getSession();
//判断用户是否认证
if (!currentUser.isAuthenticated()) { 

// 获取当前用户的认证 存取信息
currentUser.getPrincipal() 
//判断用户是否有某个角色
if (currentUser.hasRole("schwartz")) {
//检测你是否有什么样的权限
if (currentUser.isPermitted("lightsaber:wield")) { 
//注销
currentUser.logout();

1.官方Quickstart案例

package com;

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;

//import org.apache.shiro.ini.IniSecurityManagerFactory;
import org.apache.shiro.mgt.DefaultSecurityManager;
import org.apache.shiro.realm.text.IniRealm;

import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.Subject;
//import org.apache.shiro.lang.util.Factory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


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

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


    public static void main(String[] args) {

        //新方法   shiro更新问题,获取ini
        DefaultSecurityManager securityManager = new DefaultSecurityManager();
        IniRealm iniRealm = new IniRealm("classpath:shiro.ini");
        securityManager.setRealm(iniRealm);

        SecurityUtils.setSecurityManager(securityManager);

        //获取当前的用户对象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("Retrieved the correct value! [" + value + "]");
        }

        //判断当前的用户是否被认证
        if (!currentUser.isAuthenticated()) {
            //token:令牌,没有获取,随机
            UsernamePasswordToken token = new UsernamePasswordToken("lonestarr", "vespa");
            token.setRememberMe(true);//设置记住我
            try {
                currentUser.login(token);//执行登录操作
            } catch (UnknownAccountException uae) {//用户名不存在
                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?
            }
        }

        //获取用户信息
        log.info("User [" + currentUser.getPrincipal() + "] logged in successfully.");

        //获取角色
        if (currentUser.hasRole("schwartz")) {
            log.info("May the Schwartz be with you!");
        } else {
            log.info("Hello, mere mortal.");
        }

        //粗粒度
        //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.整合到SpringBoot,Shiro认证

导入jar包

<!--
Subject 用户
SecurityManager 管理所有用户
Realm 连接数据
-->
<!--         shiro整合spring的包-->
      <!-- Shiro整合Spring -->
      <dependency>
         <groupId>org.apache.shiro</groupId>
         <artifactId>shiro-spring</artifactId>
         <version>1.5.3</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>

编写一个controller并写出带有thymeleaf的html

package com.demo.controller;

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

@Controller
public class MyController {
    @RequestMapping({"/","/idnex"})
    public String toIndex(Model model){
        model.addAttribute("msg","hello,Shiro");
        return "index";
    }

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

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

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

    @RequestMapping("/login")
    public String login(String username,String password,Model model){
        //获取当前用户
        Subject subject = SecurityUtils.getSubject();
        System.out.println(username+password+"1111");
        //获取用户登录数据
        UsernamePasswordToken token = new UsernamePasswordToken(username, password);

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

login.html:

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

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

user/add 与user/update

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

配置shiroConfig

@Configuration
public class ShiroConfig {
    /**
     * @param defaultWebSecurityManager
     * @return
     */
    @Bean
    //ShiroFilterFactoryBean
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager defaultWebSecurityManager){
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        //设置安全管理器
        shiroFilterFactoryBean.setSecurityManager(defaultWebSecurityManager);

        //添加shiro的内置过滤器
        /**
         * anon:无需认证就可以访问
         * authc:必须认证才能访问
         * user:必须拥有记住我功能才能用
         * perms:拥有对某个资源的全选才能访问
         * role:拥有某个角色才能访问
         */
        Map<String,String> filterChainDefinitionMap = new LinkedHashMap<>();

        filterChainDefinitionMap.put("/user/add","authc");
        filterChainDefinitionMap.put("/user/update","authc");

        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);

        //设置登陆界面
        shiroFilterFactoryBean.setLoginUrl("/toLogin");

        return shiroFilterFactoryBean;
    }

    //DefaultWebSecurityManager
    @Bean(name = "securityManager")
    public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm){
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        //关联UserRealm
        securityManager.setRealm(userRealm);
        return securityManager;
    }

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

实现UserRealm

package com.demo.config;

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.subject.Subject;

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

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

        //用户名,密码
        String name = "root";
        String password = "123456";

        UsernamePasswordToken userToken = (UsernamePasswordToken) token;
        if(!userToken.getUsername().equals(name)){
            return null; //UnknownAccountException
        }
        //密码认证,shiro做
        return  new SimpleAuthenticationInfo("",password,"");
    }
}

这个demo确实可以做到拦截功能但是并没有对用户功能进行分类,因此改进成下面的demo

3.数据库用户分类的整合,Shiro授权

创建一个数据库

DROP TABLE IF EXISTS `user`;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `user` (
  `id` int NOT NULL,
  `name` varchar(30) DEFAULT NULL,
  `pwd` varchar(30) DEFAULT NULL,
  `perms` varchar(100) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3;
/*!40101 SET character_set_client = @saved_cs_client */;

--
-- Dumping data for table `user`
--

LOCK TABLES `user` WRITE;
/*!40000 ALTER TABLE `user` DISABLE KEYS */;
INSERT INTO `user` VALUES (1,'张三','123123','user:update'),(2,'李四','123456',NULL),(3,'王五','123456',NULL),(4,'赵六','123456',NULL),(6,'找牛','1221212',NULL),(7,'admin','123456','user:add');
/*!40000 ALTER TABLE `user` ENABLE KEYS */;
UNLOCK TABLES;

导入数据库的依赖以及druid链接池

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <scope>runtime</scope>
</dependency>
<dependency>
  <groupId>com.alibaba</groupId>
  <artifactId>druid-spring-boot-starter</artifactId>
  <version>1.1.10</version>
</dependency>

数据库配置

spring:
  datasource:
    username: root
    password: root1@12
    url: jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=utf8&useSSL=true&serverTimezone=UTC
    driver-class-name: com.mysql.cj.jdbc.Driver
    # 指定数据源
    type: com.alibaba.druid.pool.DruidDataSource

      #   数据源其他配置
    druid:
      #SpringBoot默认是不注入这些的,需要自己绑定
      #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.Properity
      #则导入log4j 依赖就行
      filters: stat,wall,log4j
      maxPoolPreparedStatementPerConnectionSize: 20
      useGlobalDataSourceStat: true
      connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500

mybatis配置

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

修改index界面

<!DOCTYPE html>

<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">

<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <h1>首页</h1>
    <p th:text="${msg}"></p>
    <div th:if="${session.loginUser==null}">
        <a th:href="@{/toLogin}">登录</a>
    </div>
    <hr>
    <div shiro:hasPermission="'user:add'">
        <a th:href="@{/user/add}">add</a>
    </div>

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

写一个简单的未认证界面

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

配置数据库实体类

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

写一个mapper接口

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

实现:

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

service接口

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

}

实现:

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

重写controller

package com.controller;

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

@Controller
public class MyController {
    @RequestMapping({"/","/idnex"})
    public String toIndex(Model model){
        model.addAttribute("msg","hello,Shiro");
        return "index";
    }

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

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

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

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

        try{
            subject.login(token);//执行登录方法,如果没有异常就OK了

            return "index";
        }
        catch (UnknownAccountException e){//用户名不存在
            model.addAttribute("msg","用户名错误");
            return "login";
        }
        catch (IncorrectCredentialsException e){//密码不存在
            model.addAttribute("msg","密码不错误");
            return "login";
        }
    }
    @RequestMapping("/noauth")
    public String unantuorized(){
        return  "unauthorization";
    }
}

重写shiroConfig

package com.config;

import at.pollux.thymeleaf.shiro.dialect.ShiroDialect;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.LinkedHashMap;
import java.util.Map;

@Configuration
public class ShiroConfig {
    @Bean
    //ShiroFilterFactoryBean
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager defaultWebSecurityManager){
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        //设置安全管理器
        shiroFilterFactoryBean.setSecurityManager(defaultWebSecurityManager);

        //添加shiro的内置过滤器
        /**
         *
         * anon:无需认证就可以访问
         * authc:必须认证才能访问
         * user:必须拥有记住我功能才能用
         * perms:拥有对某个资源的全选才能访问
         * role:拥有某个角色才能访问
         */
        Map<String,String> filterChainDefinitionMap = new LinkedHashMap<>();

        //授权,正常情况下,没有授权会跳转到为授权页面
        filterChainDefinitionMap.put("/user/add","perms[user:add]");
        filterChainDefinitionMap.put("/user/update","perms[user:update]");

        //拦截
        filterChainDefinitionMap.put("/user/*","authc");

        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);

        //设置登陆界面
        shiroFilterFactoryBean.setLoginUrl("/toLogin");

        //设置为授权的页面
        shiroFilterFactoryBean.setUnauthorizedUrl("/noauth");

        return shiroFilterFactoryBean;
    }

    //DefaultWebSecurityManager
    @Bean(name = "securityManager")
    public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm){
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        //关联UserRealm
        securityManager.setRealm(userRealm);
        return securityManager;
    }

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

    @Bean
    //整合shiroDialect;用来整合shiro thymeleof
    public ShiroDialect getShiroDialect(){
        return  new ShiroDialect();
    }
}

重新实现UserRealm

package com.config;

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

import java.util.Collections;

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

    @Autowired
    UserService userService;
    //授权
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        System.out.println("授权===>doGetAuthorizationInfo");
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
//        info.addStringPermission("user:add");

        //拿到当前登录的对象
        Subject subject = SecurityUtils.getSubject();
        //拿到User对象
        User currentUser = (User) subject.getPrincipal();
        //设置当前用户的权限
        info.addStringPermission(currentUser.getPerms());
        return info;
    }
    //认证
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        System.out.println("认证===>doGetAuthorizationInfo");


        UsernamePasswordToken userToken = (UsernamePasswordToken) token;

        //连接数据库
        User user = userService.queryUserByName(userToken.getUsername());
        if (user==null){
            //UnknownAccountException
            return null;
        }
        Subject current = SecurityUtils.getSubject();
        Session session = current.getSession();
        session.setAttribute("loginUser",user);
        //可以加密,MD5,MD5盐值加密(MD5值后面加上一些用户的属性)
        //密码认证,shiro做
        return  new SimpleAuthenticationInfo(user,user.getPwd(),"");
    }
}

测试效果:admin不能进入update页面

image

image
image

十二、Swagger

学习目标:

  • 了解Swagger的作用和概念
  • 了解前后端分离
  • 在SpringBoot中集成Swagger

1、Swagger简介

前后端分离

Vue+SpringBoot

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

前后端分离时代:

  • 后端:后端控制层,服务层,数据访问层
  • 前端:前端控制层,视图层
  • 前后端如何交互?==>API
  • 前后端相对独立,松耦合;
  • 前后端甚至可以部署在不同的服务器中

产生一个问题:

  • 前后端集成联调,前端人员和后端人员无法做到,及时协商,尽早解决

解决方案:

  • 首先指定schema【(计划或理论的)提要,纲要】,实时更新最新API,降低集成的风险

  • 早些年:指定word计划文档

  • 前后端分离:

    • 前端测试后端接口:postman
    • 后端提供接口,需要实时更新最新的消息及改动

2、Swagger

  • 号称世界上最流行的API框架
  • RestFul API文档在线自动生成工具==>API文档与API定义同步更新
  • 直接运行,可以在线测试API接口;
  • 支持多种语言:(Java,PHP)

官网https://swagger.io

在项目使用Swagger需要springfox;

  • swager2
  • swagerUI

3、SpringBoot集成Swagger

  1. 新建一个SpringBoot Web项目

  2. 导入相关依赖

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

    如果版本不匹配,在yaml中配置

    spring:
      mvc:
       pathmatch:
        matching-strategy: ant_path_matcher
    
  3. 编写一个hello工程

  4. 配置Swagger的Config

    import org.springframework.context.annotation.Configuration;
    import springfox.documentation.swagger2.annotations.EnableSwagger2;
    
    @Configuration
    @EnableSwagger2
    public class SwaggerConfig {
    
    }
    

    image

4、配置Swagger

Swagger的bean实例Docket

@Configuration
@EnableSwagger2
public class SwaggerConfig {
    //配置了Swagger的docket的bean实例
    @Bean
    public Docket docket(){
        return  new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(apiInfo());
    }
    //配置Swagger信息Info
    private ApiInfo apiInfo(){
        Contact contact = new Contact("XS","http://localhost:8080/swagger-ui.html#/","x1270059552@163.com");
        return new ApiInfo(
                "XS的测试API文档",
                "Api Documentation",
                "1.0",
                "urn:tos",
                contact,
                "Apache 2.0",
                "http://www.apache.org/licenses/LICENSE-2.0",
                new ArrayList<VendorExtension>());
    }
}

5、Swagger配置扫描接口

Docket.select()

  • RequestHandlerSelectors,配置要扫描的接口方式
  • basePackage:指定要扫描的包
  • any():扫面全部
  • none():不扫描
  • withClassAnnotation:扫描类上的注解,参数是一个注解的反射对象
  • withMethodAnnotation:扫描方法上的注解
//配置了Swagger的docket的bean实例
@Bean
public Docket docket(){
    return  new Docket(DocumentationType.SWAGGER_2)
            .apiInfo(apiInfo())
            .select()
            //RequestHandlerSelectors,配置要扫描的接口方式
            //basePackage:指定要扫描的包
            //any():扫面全部
            //none():不扫描
            //withClassAnnotation:扫描类上的注解,参数是一个注解的反射对象
            //withMethodAnnotation:扫描方法上的注解
            .apis(RequestHandlerSelectors.basePackage("com.controller"))
            //选择路径扫描,
            // .paths(PathSelectors.ant(""))
            .build();
}

Docket.enable

是否启动swagger

   //配置了Swagger的docket的bean实例
    @Bean
    public Docket docket(){
        return  new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(apiInfo())
                .enable(false)//不启动
    }

根据开发环境配置是否启动swager

如在测试环境中

spring:
  mvc:
   pathmatch:
    matching-strategy: ant_path_matcher
  profiles:
    active: dev
@Bean
public Docket docket(Environment environment){

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

    //获取项目的环境,通过environment.acceptsProfiles判断是否处在自己设定的环境当中
    boolean flag = environment.acceptsProfiles(profiles);
    return  new Docket(DocumentationType.SWAGGER_2)
            .apiInfo(apiInfo())
            .enable(flag)
            .select()
            .apis(RequestHandlerSelectors.basePackage("com.controller"))
            .build();
}

配置API文档的分组

Docket.groupName

配置多个分组就创建多个Docket对象

配置实体类

@Data
@AllArgsConstructor
@NoArgsConstructor
//@api()注释
@ApiModel("用户实体类")
public class User {
    @ApiModelProperty("用户名属性")
    private String username;
    @ApiModelProperty("密码属性")
    private String password;
}

重写controller

@RestController
public class HelloController {
    @GetMapping(value = "/hello")
    public String hello(){
        return "hello";
    }

    //只要我们的接口中,返回值存在实体类,它就会被扫描到swagger中
    @PostMapping(value = "/user")
    public User user(){
        return new User();
    }
    //Operation接口
    @GetMapping("hello2")
    @ApiOperation("Hello控制类")
    public String hello(@ApiParam("用户名") String username){
        return  "hello"+username;
    }
}

image

6、总结:

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

Swagger是一个优秀的工具,几乎所有大公司都有使用它

【注意点】在正式发布的时候,注意关闭Swagger

十三、任务

1、异步任务

  1. 写一个异步的service,在方法上配置注解@Async
@Service
public class AsyncService {
    //高数spring这是一个异步的方法
    @Async
    public void hello(){
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println("数据正在处理...");
    }
}
  1. 配置一个controller
@RestController
public class AsyncController {
    @Autowired
    AsyncService asyncService;
    @RequestMapping("/")
    public String hello(){
        asyncService.hello();//延时三秒
        return "ok";
    }
}
  1. 在主类中配置到注解 @EnableAsync
@SpringBootApplication
@EnableAsync//开启异步注解任务
public class TaskSpringBootApplication {

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

}

2、定时任务

两个核心接口

TaskScheduler【任务调度】

TaskExecutor【任务执行】

@EnableScheduling//开启定时功能的注解
@Scheduled//什么时候执行

Cron表达式
@Service
public class ScheduledService {
    //在一个特定的时间执行这个方法
    //秒 分 时 日 月 星期
    @Scheduled(cron = "0 * * * * 0-7")
    public  void hello(){
        System.out.println("你被执行了");
    }
}

3、邮件任务

1.导入springboot的mail包

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

2.配置application.properties

spring.mail.host=smtp.163.com
spring.mail.username=xxxxxx@163.com
spring.mail.password=xxxx
#qq邮箱需要开启加密验证

3.测试简单邮件==>成功

@Autowired
JavaMailSenderImpl mailSender;
@Test
void contextLoads() {
    SimpleMailMessage mailMessage = new SimpleMailMessage();
    mailMessage.setSubject("邮件测试!");
    mailMessage.setText("这是一封测试邮件~");
    mailMessage.setTo("接收@qq.com");
    mailMessage.setFrom("发送@163.com");
    mailSender.send(mailMessage);
}

4.测试复杂邮件MimeMessage

@Autowired
JavaMailSenderImpl mailSender;
@Test
void contextLoads02() throws MessagingException {

    //一个复杂的邮件
    MimeMessage mimeMessage = mailSender.createMimeMessage();

    //组装
    MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, true);
    //主题
    helper.setSubject("邮件测试!");
    //正文
    helper.setText("<h1 style='color:red'>这是一封测试邮件~</h1>", true);

    //附件
    helper.addAttachment("1.jpg", new File("/Users/stone/Downloads/1192605.png"));

    helper.setTo("接收邮箱");
    helper.setFrom("发送@163.com");
    mailSender.send(mimeMessage);
}

image

十四、分布式Dubbo+Zookeeper+SpringBoot

1.什么是分布式

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

image

单一应用架构

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

垂直应用架构

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

分布式服务架构

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

流动计算架构

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

需求

image

在大规模服务化之前,应用可能只是通过 RMI 或 Hessian 等工具,简单的暴露和引用远程服务,通过配置服务的URL地址进行调用,通过 F5 等硬件进行负载均衡。

当服务越来越多时,服务 URL 配置管理变得非常困难,F5 硬件负载均衡器的单点压力也越来越大。 此时需要一个服务注册中心,动态地注册和发现服务,使服务的位置透明。并通过在消费方获取服务提供方地址列表,实现软负载均衡和 Failover,降低对 F5 硬件负载均衡器的依赖,也能减少部分成本。

当进一步发展,服务间依赖关系变得错踪复杂,甚至分不清哪个应用要在哪个应用之前启动,架构师都不能完整的描述应用的架构关系。 这时,需要自动画出应用间的依赖关系图,以帮助架构师理清关系。

接着,服务的调用量越来越大,服务的容量问题就暴露出来,这个服务需要多少机器支撑?什么时候该加机器? 为了解决这些问题,第一步,要将服务现在每天的调用量,响应时间,都统计出来,作为容量规划的参考指标。其次,要可以动态调整权重,在线上,将某台机器的权重一直加大,并在加大的过程中记录响应时间的变化,直到响应时间到达阈值,记录此时的访问量,再以此访问量乘以机器数反推总容量。

以上是 Dubbo 最基本的几个需求。

2.RPC

什么是RPC

RPC(Remote Procedure Call Protocol)远程过程调用协议。一个通俗的描述是:客户端在不知道调用细节的情况下,调用存在于远程计算机上的某个对象,就像调用本地应用程序中的对象一样。

比较正式的描述是:一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议。

那么我们至少从这样的描述中挖掘出几个要点:

  • RPC是协议:既然是协议就只是一套规范,那么就需要有人遵循这套规范来进行实现。目前典型的RPC实现包括:Dubbo、Thrift、GRPC、Hetty等。
  • 网络协议和网络IO模型对其透明:既然RPC的客户端认为自己是在调用本地对象。那么传输层使用的是TCP/UDP还是HTTP协议,又或者是一些其他的网络协议它就不需要关心了。
  • 信息格式对其透明:我们知道在本地应用程序中,对于某个对象的调用需要传递一些参数,并且会返回一个调用结果。至于被调用的对象内部是如何使用这些参数,并计算出处理结果的,调用方是不需要关心的。那么对于远程调用来说,这些参数会以某种信息格式传递给网络上的另外一台计算机,这个信息格式是怎样构成的,调用方是不需要关心的。
  • 应该有跨语言能力:为什么这样说呢?因为调用方实际上也不清楚远程服务器的应用程序是使用什么语言运行的。那么对于调用方来说,无论服务器方使用的是什么语言,本次调用都应该成功,并且返回值也应该按照调用方程序语言所能理解的形式进行描述。

img

为什么要用RPC

其实这是应用开发到一定的阶段的强烈需求驱动的。如果我们开发简单的单一应用,逻辑简单、用户不多、流量不大,那我们用不着。当我们的系统访问量增大、业务增多时,我们会发现一台单机运行此系统已经无法承受。此时,我们可以将业务拆分成几个互不关联的应用,分别部署在各自机器上,以划清逻辑并减小压力。此时,我们也可以不需要RPC,因为应用之间是互不关联的。

当我们的业务越来越多、应用也越来越多时,自然的,我们会发现有些功能已经不能简单划分开来或者划分不出来。此时,可以将公共业务逻辑抽离出来,将之组成独立的服务Service应用 。而原有的、新增的应用都可以与那些独立的Service应用 交互,以此来完成完整的业务功能。

所以此时,我们急需一种高效的应用程序之间的通讯手段来完成这种需求,所以你看,RPC大显身手的时候来了!

其实描述的场景也是服务化 、微服务和分布式系统架构的基础场景。即RPC框架就是实现以上结构的有力方式。

常用的RPC框架

  • Thrift:thrift是一个软件框架,用来进行可扩展且跨语言的服务的开发。它结合了功能强大的软件堆栈和代码生成引擎,以构建在 C++, Java, Python, PHP, Ruby, Erlang, Perl, Haskell, C#, Cocoa, JavaScript, Node.js, Smalltalk, and OCaml 这些编程语言间无缝结合的、高效的服务。
  • gRPC:一开始由 google 开发,是一款语言中立、平台中立、开源的远程过程调用(RPC)系统。
  • Dubbo:Dubbo是一个分布式服务框架,以及SOA治理方案。其功能主要包括:高性能NIO通讯及多协议集成,服务动态寻址与路由,软负载均衡与容错,依赖分析与降级等。Dubbo是阿里巴巴内部的SOA服务化治理方案的核心框架,Dubbo自2011年开源后,已被许多非阿里系公司使用。

RPC调用流程

要让网络通信细节对使用者透明,我们需要对通信细节进行封装,我们先看下一个RPC调用的流程涉及到哪些通信细节:

img

  1. 服务消费方(client)调用以本地调用方式调用服务;
  2. client stub接收到调用后负责将方法、参数等组装成能够进行网络传输的消息体;
  3. client stub找到服务地址,并将消息发送到服务端;
  4. server stub收到消息后进行解码;
  5. server stub根据解码结果调用本地的服务;
  6. 本地服务执行并将结果返回给server stub;
  7. server stub将返回结果打包成消息并发送至消费方;
  8. client stub接收到消息,并进行解码;
  9. 服务消费方得到最终结果。

RPC的目标就是要2~8这些步骤都封装起来,让用户对这些细节透明。

下面是网上的另外一幅图,感觉一目了然:

img

3.Dubbo

一、Dubbo是什么?
Dubbo是阿里巴巴开源的基于 Java 的高性能 RPC(一种远程调用) 分布式服务框架(SOA),致力于提供高性能和透明化的RPC远程服务调用方案,以及SOA服务治理方案。
二、为什么要用Dubbo?
因为是阿里开源项目,国内很多互联网公司都在用,已经经过很多线上考验。内部使用了 Netty、Zookeeper,保证了高性能高可用性。

三、Dubbo里面有哪几种节点角色?

1、provide:暴露服务的服务提供方
2、consumer:调用远程服务的服务消费方
3、registry:服务注册于发现的注册中心
4、monitor:统计服务调用次数和调用时间的监控中心
5、container:服务运行容器

四、dubbo服务注册与发现的流程图

img

下载源码

git clone -b master https://github.com/apache/dubbo-samples.git

进入示例目录编译

cd dubbo-samples/dubbo-samples-spring-boot
mvn clean package

运行 Provider 进入 dubbo-samples-spring-boot-provider/target 目录并启动 java 进程,端口号7001

cd ./dubbo-samples-spring-boot-provider
java -jar ./target/dubbo-samples-spring-boot-provider-1.0-SNAPSHOT.jar

4.zookeeper

顾名思义 zookeeper 就是动物园管理员,他是用来管 hadoop(大象)、Hive(蜜蜂)、pig(小 猪)的管理员, Apache Hbase 和 Apache Solr 的分布式集群都用到了 zookeeper;Zookeeper: 是一个分布式的、开源的程序协调服务,是 hadoop 项目下的一个子项目。他提供的主要功 能包括:配置管理、名字服务、分布式锁、集群管理。

下载https://zookeeper.apache.org/releases.html

配置一个conifg文件

# 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

然后开启服务

./zkServer.sh start

接着连接客户

image

测试

image

5.Demo

创建一个空项目,分别创建两个springBoot的模块分别是provideServer与consumerServer

在provideServer新建一个接口并且实现:

package com.privoder.service;

public interface TicketService {
    public String getTicket();
}

==================================================================
package com.privoder.service;

public class TicketServiceImpl implements TicketService{

    @Override
    public String getTicket() {
        return "《TicketService==>getTicket》";
    }
}

在项目中导入dubbo与zookeeper依赖,并解决日志冲突问题

<!--        导入dubbo与zookeeper-->
<!-- https://mvnrepository.com/artifact/org.apache.dubbo/dubbo-spring-boot-starter -->
<dependency>
  <groupId>org.apache.dubbo</groupId>
  <artifactId>dubbo-spring-boot-starter</artifactId>
  <version>3.0.7</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.github.sgroschupf/zkclient -->
<dependency>
  <groupId>com.github.sgroschupf</groupId>
  <artifactId>zkclient</artifactId>
  <version>0.1</version>
</dependency>
<!-- 日志会冲突   -->
<!-- 引入zookeeper   -->
<dependency>
  <groupId>org.apache.curator</groupId>
  <artifactId>curator-framework</artifactId>
  <version>5.3.0</version>
</dependency>
<dependency>
  <groupId>org.apache.curator</groupId>
  <artifactId>curator-recipes</artifactId>
  <version>5.3.0</version>
</dependency>
<dependency>
  <groupId>org.apache.zookeeper</groupId>
  <artifactId>zookeeper</artifactId>
  <version>3.8.0</version>
  <!--        日志会冲突排除log4j-->
  <exclusions>
    <exclusion>
      <groupId>org.slf4j</groupId>
      <artifactId>slf4j-log4j12</artifactId>
    </exclusion>
  </exclusions>
</dependency>

编写各自的application

server.port=8081

#服务应用名字
dubbo.application.name=providerServer
#注册中心地址
dubbo.registry.address=zookeeper://localhost:2181

#哪些服务要被注册
dubbo.scan.base-packages=com.provider.service

最后参考下文https://www.jianshu.com/p/841324b3b128

posted @   项sir  阅读(119)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
历史上的今天:
2021-09-04 债券-债券发行
XIANGSIR
点击右上角即可分享
微信分享提示