SpringBoot的完整学习

2023-11-11

springBoot

  1. 配置如何编写 yaml
  2. 自动装配原理
  3. 集成web开发:业务的核心
  4. 集成数据库:Druid
  5. 分布式开发:Dubbo+zookeeper
  6. swagger:接口文档
  7. 任务调度
  8. SpringSecunity Shiro

1、微服务

微服务架构打破之前的all in one 的架构方式,把每个功能元素对出来。

好处:

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

1.1 HelloWorld

可以从Spring Initializr上下载一个项目,耶可以从IDEA中创建springboot项目!

要在Application的同级目录下建包,不然不会生效!

在这里插入图片描述

在pom.xml里有一个web依赖:用来启动tomcat

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

springboot所有的依赖都是以spring-boot-starter开头!

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.7.1</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	<groupId>com.liu</groupId>
	<artifactId>helloWorld</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>helloWorld</name>
	<description>Demo project for Spring Boot</description>
	<properties>
		<java.version>1.8</java.version>
	</properties>
	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>

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

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>

</project>

  • 如上所示:主要分为四部分
  • 项目元数据信息:maven项目的基本元素:gav,name,description等
  • parent:继承spring-boot-starter-parent的依赖管理、控制版本和打包等
  • dependencies:项目具体依赖,这里包含了web依赖用于实现http接口(包含了springmvc)
  • build:构建配置部分:默认使用spring-boot-maven-plugin,配合spring-boot-starter-parent就可以把springboot应用打包成jar来运行。

1.2 打包成jar包

在maven的lifecycle里有一个package可以打包成jar包!

在这里插入图片描述

在target项目下可以得到生成的jar包!在这里插入图片描述

1.3 IDEA创建springboot项目

第一步:新建项目

在这里插入图片描述

第二步:选择spring-web项目

在这里插入图片描述

1.4 更改项目的端口号

在这里插入图片描述

1.5 更改banner

在resources目录下新建banner.txt文件

放入你要生成的banner图示:

在这里插入图片描述

2、原理

2.1、自动配置

pom.xml

  • spring-boot-dependencies:核心依赖在父工程中

  • 依赖不需要配置版本号是因为有版本仓库

启动器

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter</artifactId>
</dependency>
  • 启动器:就是springBoot的启动场景

  • 要使用什么功能就找到对应的启动器就可以了start

主程序

@SpringBootApplication : 标注这个类是一个springboot的应用
    
@SpringBootConfiguration:springboot的配置类,启动类下的所有资源被导入
    @Configuration:配置
         @Component:组件
@EnableAutoConfiguration:自动配置
    @AutoConfigurationPackage:自动配置包
         @Import({Registrar.class}):导入`包注册`
    @Import({AutoConfigurationImportSelector.class}) :自动导入选择器
//获取所有的配置
List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);

//获取候选的配置的方法
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
    List<String> configurations = new ArrayList(SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader()));
    ImportCandidates.load(AutoConfiguration.class, this.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.factories:自动配置的核心文件

在这里插入图片描述

2.2 底层原理:

在这里插入图片描述

getAutoConfigurationEntry:获取自动配置的实体
getCandidateConfigurations:获取候选的配置
    protected Class<?> getAnnotationClass() {
        return EnableAutoConfiguration.class;
    } 
List<String> getCandidateConfigurations 所有的加载的配置
loadSpringFactories:项目资源:"META-INF/spring.factories"
                     系统资源
                     从这些资源中遍历所有的element并封装成properties供我们使用

2.3 结论:

  • springboot所有的自动配置都在启动类中被扫描并加载:spring.factories所有的自动配置类都在这里,但是不一定生效,要判断条件是否成立,只要导入了对应的start,就有对应的启动器了,有了启动器,自动装配就生效,然后配置成功!
  1. springboot在启动的时候,从类路径下META-INF/spring.factories获取指定的值
  2. 将这些自动配置的类导入容器,自动配置就会生效,进行自动配置
  3. 整合javaEE,解决方案和自动配置的东西都在spring-boot-autoconfigure-2.2.0.RELEASE.jar包下
  4. 它会把所有需要导入的组件以类名的方式返回,这些组件就会被添加到容器中
  5. 容器中也会存在非常多的xxxAutoConfiguration的文件(@Bean),就是这些类给容器中导入了这个场景需要的所有组件并自动配置

2.4 Run

@SpringBootApplication
public class Springboot01HelloworldApplication {

    //该方法返回一个configurableApplicationContext对象
    //参数一:应用入口的类   参数类:命令行传参
    public static void main(String[] args) {
        SpringApplication.run(Springboot01HelloworldApplication.class, args);
    }

}

2.5 springApplication

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

  1. 推断应用的类型是普通的项目还是web项目
  2. 查找并加载所有可用初始化器,设置到initializers属性中
  3. 找出所有的应用程序监听器,设置到listeners属性中,获取上下文,处理bean
  4. 推断并设置main方法的定义类,找到运行的主类

查看构造器

    this.sources = new LinkedHashSet();
    this.bannerMode = Mode.CONSOLE;
    this.logStartupInfo = true;
    this.addCommandLineProperties = true;
    this.addConversionService = true;
    this.headless = true;
    this.registerShutdownHook = true;
    this.additionalProfiles = Collections.emptySet();
    this.isCustomEnvironment = false;
    this.lazyInitialization = false;
    this.applicationContextFactory = ApplicationContextFactory.DEFAULT;
    this.applicationStartup = ApplicationStartup.DEFAULT;
    this.resourceLoader = resourceLoader;
    Assert.notNull(primarySources, "PrimarySources must not be null");
    this.primarySources = new LinkedHashSet(Arrays.asList(primarySources));
    this.webApplicationType = WebApplicationType.deduceFromClasspath();
    this.bootstrapRegistryInitializers = new ArrayList(this.getSpringFactoriesInstances(BootstrapRegistryInitializer.class));
    this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class));
    this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class));
    this.mainApplicationClass = this.deduceMainApplicationClass();

3、springboot配置

3.1 配置文件

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

  • application.properties
    • 语法结构:key = value
  • application.yml
    • 语法结构:key: 空格 value
  • 配置文件的作用:修改springboot自动配置的默认值,因为springboot在底层给我们自动配置好了

3.2 yaml

1.格式

server:
  port: 8080

2.标记语言

  • 以前的配置文件大多数使用xml来配置,比如一个简单的端口配置
  • xml配置:
<server>
   <port>8081</port>
</server>
# 普通的key-value
name: liuxiang

# 对象
student:
  name: liuxiang
  age: 3

#行内写法
student1: {name: liuxiang,age: 3}

#数组
pets:
  - cat
  - dog
  - pig

pets1: [cat,dog,pig]

3.3 给实体类赋值

1.原生的赋值:需要给每一个属性赋值,麻烦

  @Value("旺财")
    private String name;
    @Value("3")
    private  Integer age;

2.用yaml赋值

person:
  name: liuxiang${random.uuid}
  age: ${random.int}
  happy: true
  birth: 1997/12/12
  maps: {k1: v1,k2: v2}
  list:
    - code
    - music
    - boy
  dog:
    name: ${person.hello:hello}_旺财 #前面值如果不存在就选择后面的值
    age: 3

在这里插入图片描述

springboot测试:

package com.liu;

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

@SpringBootTest
class Springboot02ConfigApplicationTests {
    @Autowired
    private Person person;

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

结果:注入成功!

Person{name='liuxiang', age=18, happy=true, birth=Fri Dec 12 00:00:00 CST 1997, maps={k1=v1, k2=v2}, list=[code, music, boy], dog=Dog{name='旺财', age=3}}

实体类:

package com.liu.pojo;

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

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

@Component
@ConfigurationProperties(prefix = "person")
/* 
@ConfigurationProperties作用:
将配置文件中配置的每一个属性的值映射到这个组件中
告诉springboot将本类中的所有属性和配置文件中相关的配置进行绑定
参数prefix = "person":将配置文件中的person下面的所有属性一一对应

只有这个组件是容器中的组件才能用@ConfigurationProperties
*/
public class Person {

    private String name;
    private Integer age;
    private Boolean happy;
    private Date birth;
    private Map<String,Object> maps;
    private List<Object> list;
    private Dog dog;

    public Person() {
    }

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

  • 松散绑定:比如yaml中写的last-name,和lastName是一样的,-后面跟着的字母默认是大写的
  • JSR303数据校验,可以在字段增加一层过滤器验证,可以保证数据的合法性
  • 复杂类型封装,yml中可以封装对象,使用@value就不支持

3.4 JSR303校验

@Validated //数据校验

参考配置:

@Null 限制只能为null
@NotNull 限制必须不为null
@AssertFalse 限制必须为false
@AssertTrue 限制必须为true
@DecimalMax(value) 限制必须为一个不大于指定值的数字
@DecimalMin(value) 限制必须为一个不小于指定值的数字
@Digits(integer,fraction) 限制必须为一个小数,且整数部分的位数不能超过integer,小数部分的位数不能超过fraction
@Future 限制必须是一个将来的日期
@Max(value) 限制必须为一个不大于指定值的数字
@Min(value) 限制必须为一个不小于指定值的数字
@Past 限制必须是一个过去的日期
@Pattern(value) 限制必须符合指定的正则表达式
@Size(max,min) 限制字符长度必须在min到max之间
@Past 验证注解的元素值(日期类型)比当前时间早
@NotEmpty 验证注解的元素值不为null且不为空(字符串长度不为0、集合大小不为0)
@NotBlank 验证注解的元素值不为空(不为null、去除首位空格后长度为0),不同于@NotEmpty,@NotBlank只应用于字符串且在比较时会去除字符串的空格
@Email 验证注解的元素值是Email,也可以通过正则表达式和flag指定自定义的email格式

4、多环境配置及配置文件地址

配置文件存放地址:

  1. file:./config/ : 项目目录下的config下的优先级最高
  2. file:./ :优先级其次
  3. classpath:/config/:优先级第三
  4. classpath:/:优先级最低

在这里插入图片描述

application.properties文件:

# springboot的多环境配置:可以选择激活哪一个配置文件
spring.profiles.active=dev

application-dev.properties 线下环境:

server.port=8082

application-test.properties 测试环境:

server.port=8081

yaml配置实现:(推荐)

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

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

5、自动配置再理解

在这里插入图片描述

一定要满足条件才会生效,导入相关依赖,找到对应的start启动器即可!

配置文件yaml和spring.factories的关系:

  • 都是通过ConfigurationProperties(prefix = “xxx”)来实现
  • xxxAutoConfiguration:有默认值,都有一个xxxProperties的文件和配置文件绑定,就可以使用自定义的配置了
  • 这样配置文件就可以动态的修改spring的内容

5.1 自动装配的原理

  1. springboot启动会加载大量的自动配置类
  2. 看我们需要的功能有没有再springboot默认写好的自动配置类当中
  3. 再看这个自动配置类中到底配置了哪些组件(要用的在其中就不需要再配置了)
  4. 给容器中自动配置类添加组件时,会从properties类中获取某些属性,我们只需要在配置文件中指定这些属性的值即可
  5. xxxAutoConfiguration:自动配置类;给容器中添加组件
  6. xxxProperties:封装配置文件中相关属性

可以在yaml文件下通过debug=true来查看,哪些自动配置类生效了,一部分生效,一部分不生效,选择最好的

debug: true

6、springBoot Web开发

要解决的问题:

  • 导入静态资源
  • 首页
  • jsp,模板引擎Thymeleaf
  • 装配扩展springmvc
  • 增删改查
  • 拦截器

6.1、静态资源导入

在这里插入图片描述

这四个文件c夹下的所有的资源都可以被访问到,例如:

在这里插入图片描述

优先级:

resources>static>public

  • 在springboot还可以通过webjars访问:localhost:8080/webjars/

6.2 thymeleaf模板引擎

  • jsp就是一个模板引擎

  • 前端页面变量报红添加注释

  • <!--suppress ThymeleafVariablesResolveInspection -->
    

引入thymeleaf:

  1. Thymeleaf

  2. GitHub - thymeleaf/thymeleaf: Thymeleaf is a modern server-side Java template engine for both web and standalone environments.

  3. Spring Boot Reference Guide spring导入start文档

依赖jar包:

<dependency>
    <groupId>org.thymeleaf</groupId>
    <artifactId>thymeleaf-spring5</artifactId>
</dependency>
<dependency>
    <groupId>org.thymeleaf.extras</groupId>
    <artifactId>thymeleaf-extras-java8time</artifactId>
</dependency>

结论:

需要使用thymeleaf,只需要将html放在templates下,在controller下设置跳转页面路径即可

  • 用thymeleaf需要导入头文件约束 xmlns:th=“http://www.thymeleaf.org”

  • 超连接,文本等等需要放在th下

  • 常用命名空间:

  • <xmlns:th="http://www.thymeleaf.org">
    <xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
    <xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">
    
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<!--用thymeleaf需要导入头文件约束 xmlns:th="http://www.thymeleaf.org"-->
<!--所有的html元素都可以被thymeleaf替代,用th:元素名-->
<div th:text="${msg}"></div>
<div th:utext="${msg}"></div> <!--转义-->

<hr>
<!--遍历数组-->
<h3 th:each="user:${users}" th:text="${user}"></h3>

</body>
</html>

变量表达式:

  • 变量:${}:与EL表达式一样

  • 消息:#{}

  • URL:@{}

  • Fragment:~{}:片段表达式

  • 文本:‘’

  • 数字:1,2

  • 布尔值:true

  • Null:null

文本操作:

  • string:+

数学运算:

  • +,-,*,/,%

其他操作:

  • 等于操作:==,!=

  • 比较:>,<,<=,>=

  • 三元运算符:(if) ? (then) ? (else)

6.3、装配扩展springMVC

29. Developing Web Applications (spring.io)

If you want to keep Spring Boot MVC features and you want to add additional MVC configuration (interceptors, formatters, view controllers, and other features), you can add your own @Configuration class of type WebMvcConfigurer but without @EnableWebMvc. If you wish to provide custom instances of RequestMappingHandlerMapping, RequestMappingHandlerAdapter, or ExceptionHandlerExceptionResolver, you can declare a WebMvcRegistrationsAdapter instance to provide such components.

视图解析器的源码:

  • 在springboot2.7.1版本中,将此部分改成了判断是否有下一个视图,有就添加
  • 采用了迭代器而不是之前的全部遍历
  • 获取候选的,再选择最好的视图
public class ContentNegotiatingViewResolver extends WebApplicationObjectSupport implements ViewResolver, Ordered, InitializingBean{}
private List<View> getCandidateViews(String viewName, Locale locale, List<MediaType> requestedMediaTypes) throws Exception {
List<View> candidateViews = new ArrayList();
if (this.viewResolvers != null) {
    Assert.state(this.contentNegotiationManager != null, "No ContentNegotiationManager set");
    Iterator var5 = this.viewResolvers.iterator();

    while(var5.hasNext()) {
        ViewResolver viewResolver = (ViewResolver)var5.next();
        View view = viewResolver.resolveViewName(viewName, locale);
        if (view != null) {
            candidateViews.add(view);
        }

        Iterator var8 = requestedMediaTypes.iterator();

自定义视图解析器:继承视图解析器的接口并注册到bean中会自动装配

package com.liu.config;


import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.View;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import java.util.Locale;

//全面扩展mvc
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {
    //ViewResolver 实现了视图解析器接口的类,可以把它看作视图解析器
    @Bean
    public ViewResolver myViewResolver(){
        return new MyViewResolver();
    }

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

格式Formatter

public String getDateFormat() {
        return this.format.getDate();
    }

可以再yml文件中自己配置日期格式!

  • springboot再自动配置很多组件的时候,先看容器中有没有用户自己配置的(如果有用户自己配置的@bean),就用用户的,没有就用自动配置的,比如视图解析器,将用户配置的和自己默认的组合起来作为候选的,再选择最好的。

视图跳转

//扩展springMVC,官方这样操作
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {
    //视图跳转
    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/liu").setViewName("test");
    }
}

@EnableWebMvc注解:自动配置的时候不能加这个注解

@EnableWebMvc //导入了这个类DelegatingWebMvcConfiguration  作用:从容器中获取所有的webmvc config

原因:在WebMvcAutoConfiguration这个类中有三个条件满足才生效,而在EnableWebMvc这个注解当中导入了这个类DelegatingWebMvcConfiguration,不满足自动装配的条件了

@ConditionalOnWebApplication(
    type = Type.SERVLET
)
@ConditionalOnClass({Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class})
@ConditionalOnMissingBean({WebMvcConfigurationSupport.class})

总结:

  • 在springboot中,有许多的xxxConfiguration配置类帮助进行扩展配置,看见这个就是改变了原始的配置,要注意扩展了什么功能!

7、员工管理系统

  • 关闭模板引擎的缓存
#关闭模板引擎的缓存
spring.thymeleaf.cache=false

7.1 首页配置

  • 所有的静态资源都需要用thymeleaf接管,链接用@{}

7.2 国际化(中英文切换)

要确保全部是UTF-8格式!

在这里插入图片描述

可视化配置:

在这里插入图片描述

页面的每个原始都需要这么配置:

在这里插入图片描述

在yml文件中配置真实的配置文件地址:

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

国际化消息用:#{}取值

在这里插入图片描述

7.2.1 自定义国际化组件

前端的两个跳转链接:

<a class="btn btn-sm" th:href="@{index.html(l='zh_CN')}">中文</a>
<a class="btn btn-sm" th:href="@{index.html(l='en_US')}">English</a>
package com.liu.config;

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

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

public class MyLocaleResolver implements LocaleResolver {

    //解析请求
    @Override
    public Locale resolveLocale(HttpServletRequest request) {
        //获取请求中的语言参数 带l都走这个请求
        String language = request.getParameter("l");

        Locale locale = Locale.getDefault();//默认的,如果没有就使用默认的

        //如果请求的链接携带了地区化的参数
        if (!StringUtils.isEmpty(language)){
            //zh_CN 分割键值对
            String[] split = language.split("_");
            //国家地区
            locale = new Locale(split[0], split[1]);
        }
        return locale;
    }

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

    }
}

要将这个写的类注册到spring中:@Bean

package com.liu.config;

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

@Configuration
public class MyMvcConfig implements WebMvcConfigurer {

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

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

步骤:

  1. 需要配置i18n文件
  2. 如果需要在项目进行按钮自动切换,需要自定义一个组件LocaleResolver
  3. 将组件配置到spring容器中@Bean
  4. #{}

7.3 登录功能

index.html

<p style="color: red" th:text="${msg}" th:if="${not #strings.isEmpty(msg)}"></p>
  • 判断条件格式:

  • ${#strings.isEmpty(name)}
    ${#strings.arrayIsEmpty(nameArr)}
    ${# strings.listIsEmpty(nameList)}
    ${# strings.setIsEmpty(nameSet)}
    

后端controller层:

  • 接收前端name属性传过来的值,用@RequestParam保证路径不出错!
  • main.html在myMvcConfig文件中设置了访问到dashboard.html页面!
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {

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

    //自定义的国际化组件
    @Bean
    public LocaleResolver localeResolver(){
        return new MyLocaleResolver();
    }
}
@Controller
public class LoginController {

    @RequestMapping("/user/login")
    public String login(@RequestParam("username") String username, @RequestParam("password") String password, Model model, HttpSession session){
        if (!StringUtils.isEmpty(username) && "123456".equals(password)){

            //登陆成功,session
            session.setAttribute("loginUser",username);
            return "redirect:/main.html";
        }else {
            model.addAttribute("msg","用户名或者密码错误!");
            return "index";
        }
    }

}

7.4 拦截器

package com.liu.config;


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

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

public class LoginHandlerInterceptor implements HandlerInterceptor {
    //return true 放行 反之不放行
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        //登录成功后,获取session
        Object loginUser = request.getSession().getAttribute("loginUser");

        if (loginUser==null){
            //没有登录
            request.setAttribute("msg","没有权限,请先登录");
            request.getRequestDispatcher("/index.html").forward(request,response);//重定向回去
            return false;
        }else {
            return true;
        }

    }

}
  • 到spring中注册Bean,也就是带@Configuration注解,继承了WebMvcConfigurer的类
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {

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

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

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

7.5 前端侧边栏

实现代码的复用:用thymeleaf模板进行片段的插入

将公共部分的代码提取到common文件下:

<nav class="navbar navbar-dark sticky-top bg-dark flex-md-nowrap p-0" th:fragment="topbar">
<nav class="col-md-2 d-none d-md-block bg-light sidebar" th:fragment="sidebar">

dashboard.html:侧边栏

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

list.html:在同样的位置进行插入片段!用~{}方式

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

dashboard.html:导航栏

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

list.html:在同样的位置进行插入片段!用~{}方式

<div th:replace="~{/common/commons::topbar}"></div>
  • fragment inclusion的两种方式
    • th:insert
    • th:replace

7.6 提取公共页面

commons.html

<a th:class="${active=='main.html'?'nav-link active':'nav-link'}" th:href="@{/index.html}">
<a th:class="${active=='list.html'?'nav-link active':'nav-link'}" th:href="@{/emps}">

dashboard.html

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

list.html

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

thymeleaf前端变量报红的解决办法:在html页面的头部加上下面这个注释

<!--suppress ThymeleafVariablesResolveInspection -->

7.7 展示数据库数据

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

日期转换:去参考thymeleaf官方文档

  • 地址:Tutorial: Using Thymeleaf

  • ${#dates.format(date, 'dd/MMM/yyyy HH:mm')}
    ${#dates.arrayFormat(datesArray, 'dd/MMM/yyyy HH:mm')}
    ${#dates.listFormat(datesList, 'dd/MMM/yyyy HH:mm')}
    ${#dates.setFormat(datesSet, 'dd/MMM/yyyy HH:mm')}
    

7.8 添加员工

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

问题报错:

Failed to convert property value of type ‘java.lang.String’ to required type ‘java.util.Date’ for property ‘birth’;

解决:

在pojo实体类的属性上添加注解:

@DateTimeFormat(pattern = "yyyy-MM-dd")
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")

或者在配置文件中修改:

spring.mvc.date-format=yyyy-MM-dd
<main role="main" class="col-md-9 ml-sm-auto col-lg-10 pt-3 px-4">
<form th:action="@{emp}" method="post">
    <div class="form-group">
        <label>LastName</label>
        <input type="text" name="lastName" class="form-control" placeholder="name">
    </div>
    <div class="form-group">
        <label>Email</label>
        <input type="email" name="email" class="form-control" placeholder="email">
    </div>
    <div class="form-group">
        <label>Gender</label>
        <div class="form-check form-check-inline">
            <input class="form-check-input" type="radio" name="gender" value="1">
            <label class="form-check-label"></label>
        </div>
        <div class="form-check form-check-inline">
            <input class="form-check-input" type="radio" name="gender" value="0">
            <label class="form-check-label"></label>
        </div>
    </div>
    <div class="form-group">
        <label>department</label>
        <select class="form-control" name="department.id">
            <option th:each="dept:${departments}" th:text="${dept.getDepartmentName()}" th:value="${dept.getId()}"></option>
        </select>
    </div>
    <div class="form-group">
        <label>Birth</label>
        <input type="text" name="birth" class="form-control" placeholder="2022-07-13">
    </div>

    <button type="submit" class="btn btn-default btn-success">添加</button>
</form>
</main>
@GetMapping("/emp")
    public String toAddPage(Model model){
        //查出所有部门的信息
        Collection<Department> departments = departmentDao.getDepartments();
        model.addAttribute("departments",departments);
        return "emp/add";
    }

    @PostMapping("/emp")
    public String addEmp(Employee employee){
        //添加的操作
        System.out.println("add=>"+employee);
        employeeDao.add(employee);//保存员工信息
        return "redirect:/emps";
    }

7.9 修改员工信息

list.html页面

<td>
    <a class="btn btn-sm btn-primary" th:href="@{'/emp/'+${emp.getId()}}">编辑</a>
</td>
  • 因为有自增id,所以要隐藏,不然一修改就自动新增了!
    <main role="main" class="col-md-9 ml-sm-auto col-lg-10 pt-3 px-4">
    <form th:action="@{updateEmp}" method="post">
    <input type="hidden" name="${id}" th:value="${emp.getId()}">
    <div class="form-group">
        <label>LastName</label>
        <input th:value="${emp.getLastName()}" type="text" name="lastName" class="form-control" placeholder="name">
    </div>
    <div class="form-group">
        <label>Email</label>
        <input th:value="${emp.getEmail()}" type="email" name="email" class="form-control" placeholder="email">
    </div>
    <div class="form-group">
        <label>Gender</label>
        <div class="form-check form-check-inline">
            <input th:checked="${emp.getGender()==1}" class="form-check-input" type="radio" name="gender" value="1">
            <label class="form-check-label"></label>
        </div>
        <div class="form-check form-check-inline">
            <input th:checked="${emp.getGender()==0}" class="form-check-input" type="radio" name="gender" value="0">
            <label class="form-check-label"></label>
        </div>
    </div>
    <div class="form-group">
        <label>department</label>
        <select class="form-control" name="department.id">
            <option th:selected="${dept.getId()==emp.department.getId()}"
                    th:each="dept:${departments}" th:text="${dept.getDepartmentName()}"
                    th:value="${dept.getId()}"></option>
        </select>
    </div>
    <div class="form-group">
        <label>Birth</label>
        <input th:value="${#dates.format(emp.getBirth(),'yyyy-MM-dd')}" type="text" name="birth" class="form-control" placeholder="2022-07-13">
    </div>

    <button type="submit" class="btn btn-default btn-success">修改</button>
    </form>
    </main>
//跳转到修改页面
    @GetMapping("/emp/{id}") //restful风格
    public String tuUpdateEmp(@PathVariable("id")Integer id,Model model){
        //查出原来的数据
        Employee employeeById = employeeDao.getEmployeeById(id);
        model.addAttribute("emp",employeeById);

        //查询部门所有信息
        Collection<Department> departments = departmentDao.getDepartments();
        model.addAttribute("departments",departments);

        return "emp/update";
    }

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

7.10 删除员工

index.html

<a class="btn btn-sm btn-danger" th:href="@{'/delete/'+${emp.getId()}}">删除</a>
 //删除员工信息
    @RequestMapping("/delete/{id}")
    public String deleteById(@PathVariable("id")Integer id){
        employeeDao.deleteEmployeeById(id);
        return "redirect:/emps";
    }

8、Data

对于数据访问层,无论是SQL还是NOsql,springboot底层都是采用springData的方式进行统一处理。

springData官网:https://spring.io/projects/spring-data

数据库相关的启动器:官方文档:Spring Boot Reference Guide

8.1、JDBC

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jdbc</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jdbc</artifactId>
</dependency>
  • Could not autowire. No beans of ‘DataSource’ type found问题解决办法:

  • @Autowired(required = false)
    

配置文件:

  • DataSourceProperties
  • 对于的DataSourceAutoConfiguration

8.2 SpringBoot封装JDBC

spring:
datasource:
username: root
password: 123456789
url: jdbc:mysql://localhost:3306/mybatis?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&useSSL=false
driver-class-name: com.mysql.cj.jdbc.Driver #数据源HikariDataSource
package com.liu.controller;

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

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

@RestController
public class JdbcController {

    @Autowired(required = false)
    JdbcTemplate jdbcTemplate;

    //查询数据库的所有信息
    //无实体类时用万能的map查询数据
    @GetMapping("/userList")
    public List<Map<String,Object>> userList(){
        String sql = "select * from mybatis.user";
        List<Map<String, Object>> maps = jdbcTemplate.queryForList(sql);
        return maps;
    }

    @GetMapping("/addUser")
    public String addUser(){
        String sql = "insert into mybatis.user(id,name,pwd) values (5,'小明','12345678')";
        int i = jdbcTemplate.update(sql);
        return "addOK!";
    }

    @GetMapping("/updateUser/{id}")
    public String updateUser(@PathVariable("id")int id){
        String sql = "update mybatis.user set name=?,pwd=? where id="+id;

        //封装数据
        Object[] objects = new Object[2];
        objects[0] = "小华";    //修改的名字
        objects[1] = "zxczxc"; //密码
        jdbcTemplate.update(sql,objects);

        return "updateOK";
    }

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

}

8.3 数据源Druid

  • 阿里巴巴平台的数据库连接池实现,结合C3P0,DBCP,PROXOOL等DB池的有点,同时加入日志监控。

  • 天生针对监控而生的DB连接池

  • HikariDataSource是速度最快的数据源

依赖包:

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.1.21</version>
</dependency>
<!--log4j的依赖包-->
<dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    <version>1.2.17</version>
</dependency>

yaml配置中修改数据源的使用:只用加type即可

spring:
  datasource:
    username: root
    password: 123456789
    url: jdbc:mysql://localhost:3306/mybatis?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&useSSL=false
    driver-class-name: com.mysql.cj.jdbc.Driver
    type: com.alibaba.druid.pool.DruidDataSource

    initial-size: 5
    min-idle: 5
    max-active: 20
    max-wait: 60000
    time-between-eviction-runs-millis: 60000
    min-evictable-idle-time-millis: 300000
    validation-query: SELECT 1 FROM DUAL
    test-while-idle: true
    test-on-borrow: false
    test-on-return: false
    pool-prepared-statements: true
    filters: stat,wall,log4j
    max-pool-prepared-statement-per-connection-size: 20
    use-global-data-source-stat: true
    connect-properties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500

测试数据源:

@SpringBootTest
class Springboot04DataApplicationTests {
    @Autowired(required = false)
    DataSource dataSource;
    @Test
    void contextLoads() throws SQLException {
        System.out.println(dataSource.getClass());
        //获得数据库连接
        Connection connection = dataSource.getConnection();
        System.out.println(connection);
        connection.close();
    }
}

结果:

在这里插入图片描述

强大之处:自动配置

建立一个自动配置的文件

  • 松散绑定:不用注意大小写,下划线什么的
@Configuration
public class DruidConfig {

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

与yaml配置文件绑定:只需要再前缀处加入数据源的名称和@Bean注解

@ConfigurationProperties(prefix = "spring.datasource")
@Bean

8.3.1 后台监控

  • 因为springboot内置了servlet容器,所以没有web.xml,替代方法:ServletRegistrationBean
//后台监控
//因为springboot内置了servlet容器,所以没有web.xml,替代方法:ServletRegistrationBean
    @Bean
    public ServletRegistrationBean StatViewServlet(){
        ServletRegistrationBean<StatViewServlet> bean = new ServletRegistrationBean<>(new StatViewServlet(), "/druid/*");

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

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

        //允许谁可以访问
        initParameters.put("allow","");

        //静止谁访问
        initParameters.put("liuxiang","192.168.11.123");

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

        @Bean
    public FilterRegistrationBean webStatFilter() {
        FilterRegistrationBean bean = new FilterRegistrationBean();
        bean.setFilter(new WebStatFilter());
        Map<String, String> map = new HashMap<>();
        map.put("exclusions", "*.js*,*.css,/druid/*");
        bean.setInitParameters(map);
        return bean;
    }
    }

访问druid monitor自动跳转到login,html页面

输入用户名和密码进入后台监控:

在这里插入图片描述

出现filter下的数据即可监控统计sql了

在这里插入图片描述

当发起后台sql请求时,可以看到监控统计:

在这里插入图片描述

9、整合mybatis

导入依赖包:

不是springboot官方的,不是以spring开头

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

以前是写一个mapper和一个对应的mapper.xml,现在统一放在resource目录下

在这里插入图片描述

配置文件下:

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

UserMapper

@org.apache.ibatis.annotations.Mapper
@Repository
public interface Mapper {

    List<User> queryUserList();

    User queryUserById(int id);

    int updateUser(User user);

    int addUser(User user);

    int deleteUser(int id);

}

UserMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.liu.mapper.Mapper">

    <select id="queryUserList" resultType="User">
        select *
        from mybatis.user;
    </select>

    <select id="queryUserById" resultType="User">
        select *
        from mybatis.user
        where user.id=#{id};
    </select>

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

    <insert id="addUser" parameterType="User">
        insert into mybatis.user(id, name, pwd)
        values (#{id},#{name},#{pwd});
    </insert>
</mapper>

controller层

@RestController
public class UserController {

    @Autowired(required = false)
    private UserMapper userMapper;

    @GetMapping("/queryUserList")
    public List<User> queryUserList(){
        List<User> userList = userMapper.queryUserList();
        return userList;
    }
    @GetMapping("/queryUserById/{id}")
    public User queryUserById(@PathVariable("id") int id){
        User user = userMapper.queryUserById(id);
        return user;
    }

    @GetMapping("/updateUser")
    public String updateUser(){
        userMapper.updateUser(new User(2,"hyt","3423213"));
        return "OK";
    }

    @GetMapping("/addUser")
    public String addUser(){
        userMapper.addUser(new User(4,"dsajda","esdfsdf"));
        return "OK";
    }

    @GetMapping("/deleteUser")
    public String deleteUser(){
        userMapper.deleteUser(4);
        return "ok";
    }
}

10、SpringSecurity(安全)

依赖包

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

认证授权和权限分级

  • 要用数据库连接,在认证部分需要用JDBC连接,而不是在内存中,用Autowired注解自动注入数据源
package com.liu.config;

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.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;

//AOP思想
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //首页所有人可以访问,功能页只有对应有权限的人才能访问
        //授权
        http.authorizeHttpRequests().antMatchers("/").permitAll()
        .antMatchers("/level1/**").hasRole("vip1")
        .antMatchers("/level2/**").hasRole("vip2")
        .antMatchers("/level3/**").hasRole("vip3");

        //没有权限默认会登录页面
        http.formLogin();
        //注销
        http.logout();
    }

    //认证
    //密码编码:passwordEncoder
    //在spring Security 5.0+ 新增很多加密方法
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder())
                .withUser("liuxiang")
                .password(new BCryptPasswordEncoder().encode("123456")).roles("vip2","vip3")
                .and()
                .withUser("root").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1","vip2","vip3")
                .and()
                .withUser("guest").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1");
    }
}

controller层

@Controller
public class RouterController {
   @RequestMapping({"/index","/"})
    public String index(){
        return "index";
    }

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

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

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

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

}

更改前端页面的图标icon

推荐一个网站:https://semantic-ui.com/elements/icon.html

如下所示前端代码即可更改样式:

<a class="item" th:href="@{/toLogin}">
    <i class="hand point right icon"></i> 登录
</a>
<!--注销-->
<a class="item" th:href="@{/logout}">
    <i class="share square icon"></i> 注销
</a>

thymeleaf和springsecurity的整合

导依赖包

<!-- https://mvnrepository.com/artifact/org.thymeleaf.extras/thymeleaf-extras-springsecurity4 -->
<dependency>
    <groupId>org.thymeleaf.extras</groupId>
    <artifactId>thymeleaf-extras-springsecurity4</artifactId>
    <version>3.0.4.RELEASE</version>
</dependency>

导入命名空间:

<html lang="en" xmlns:th="http://www.thymeleaf.org"
      xmlns:sec="http://www.thymeleaf.org/extras/spring-security">"
  • springboot版本太高,无法显示!

11、Shiro

  • Apache Shiro是Java的安全(权限)框架
  • 下载地址:http://shiro.apache.org/

功能:

  • Authentication:认证
  • Authorization:授权
  • session management:session管理
  • cryptograhy:加密
  • web support:web支持
  • 缓存

11.1、HelloWorld

1.导入依赖

 <dependencies>
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-core</artifactId>
            <version>1.4.1</version>
        </dependency>

        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>jcl-over-slf4j</artifactId>
            <version>1.7.21</version>
        </dependency>

        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
            <version>1.7.21</version>
        </dependency>
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
        </dependency>
    </dependencies>

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

3.Quickstart


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


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

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


    public static void main(String[] args) {
        Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
        SecurityManager securityManager = factory.getInstance();
        SecurityUtils.setSecurityManager(securityManager);

        // get the currently executing user:
        //获取当前用户对象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 + "]");
        }

        // let's login the current user so we can check against roles and permissions:
        if (!currentUser.isAuthenticated()) { //令牌
            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);
    }
}

4.log4j.properties

#输出日志文件到console和file目的地
log4j.rootLogger=DEBUG,console,file

# 控制台(console)
log4j.appender.console=org.apache.log4j.ConsoleAppender
log4j.appender.console.Threshold=DEBUG
log4j.appender.console.Target=System.out
log4j.appender.console.layout=org.apache.log4j.PatternLayout
log4j.appender.console.layout.ConversionPattern=[%c]-%m%n

# 文件输出的相关设置(file)
log4j.appender.file=org.apache.log4j.RollingFileAppender
log4j.appender.file.Threshold=DEBUG
log4j.appender.file.File=D:/logs/log.log4j
log4j.appender.file.MaxFileSize=10mb
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern=[%p] [%d{yy-MM-dd}][%c]%m%n

#日志输出级别
log4j.logger.org.mybatis=DEBUG
log4j.logger.java.sql=DEBUG
log4j.logger.java.sql.Statement=DEBUG
log4j.logger.java.sql.ResultSet=DEBUG
log4j.logger.java.sql.PreparedStatement=DEBUG

总结方法:

 //1.获取当前用户对象subject
Subject currentUser = SecurityUtils.getSubject();
// 2.通过当前用户拿到session
Session session = currentUser.getSession();
// 3.判断当前的用户是否被认证
if (!currentUser.isAuthenticated()){UsernamePasswordToken token = new UsernamePasswordToken("lonestarr", "vespa");}
// 4.获得当前用户的认证
currentUser.getPrincipal()
// 5.拥有什么角色
if (currentUser.hasRole("schwartz")){}
// 6.获取什么权限
 if (currentUser.isPermitted("lightsaber:wield")) {}
//7.注销
currentUser.logout();

SpringBoot中集成Shiro

三大对象:(面试必问)

  1. subject:用户
  2. securityManager:管理所有用户
  3. realm:连接数据

导入依赖:

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

index.html

<!DOCTYPE html>
<!--suppress ThymeleafVariablesResolveInspection -->
<html lang="en" xmlns:th="http://www.thymeleaf.org"
                xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">
<head>
    <meta charset="UTF-8">
    <title>首页</title>
</head>
<body>

<h1>首页</h1>

<div th:if="${session.loginUser==null}">
    <a th:href="@{toLogin}">登录</a>
</div>
<p th:text="${msg}"></p>
<hr>
<div shiro:hasPermission="user:add">
    <a th:href="@{/user/add}">add</a>
</div>

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

</body>
</html>

连接数据部分:授权和认证UserRealm

public class UserRealm extends AuthorizingRealm {
    @Autowired
    UserService userService;

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

        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
//        info.addStringPermission("user:add"); 所有人都有权限

        //拿到当前登录的这个对象
        Subject subject = SecurityUtils.getSubject();
        User currentUser = (User) subject.getPrincipal(); //拿到user对象
        //设置当前用户的权限
        info.addStringPermission(currentUser.getPerms());

        return info;
    }

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

        UsernamePasswordToken userToken = (UsernamePasswordToken) token;
        //用户名,密码:连接数据库
        User user = userService.queryUserByName(userToken.getUsername());
        if (user==null){
            return null;//抛去用户名不存在的异常
        }
        Subject currentSubject = SecurityUtils.getSubject();
        Session session = currentSubject.getSession();
        session.setAttribute("loginUser",user);
        //密码认证:加密,MD5
        return new SimpleAuthenticationInfo(user,user.getPwd(),"");
    }
}

Controller层

@Controller
public class MyController {

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

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


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

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

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

    @RequestMapping("/unauth")
    @ResponseBody
    public String unAuthor(){
        return "未经授权不予登录!";
    }

}

shiroConfig

@Configuration
public class ShiroConfig {

    //3.ShiroFilterFactoryBean
    @Bean(name = "filterShiroFilterRegistrationBean")
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("getDefaultWebSecurityManager") DefaultWebSecurityManager getDefaultWebSecurityManager){
        ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
        //设置安全管理器
        bean.setSecurityManager(getDefaultWebSecurityManager);

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

//        filterChainDefinitionMap.put("/user/add","authc");
//        filterChainDefinitionMap.put("/user/update","authc");
        //授权
        filterChainDefinitionMap.put("/user/add","perms[user:add]");
        filterChainDefinitionMap.put("/user/update","perms[user:update]");

        //拦截请求
        filterChainDefinitionMap.put("/user/*","authc");
        bean.setFilterChainDefinitionMap(filterChainDefinitionMap);

        //设置登录的请求
        bean.setLoginUrl("/toLogin");
        //设置未被认证的请求
        bean.setUnauthorizedUrl("/unauth");


        return bean;
    }

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

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

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

}

连接数据库,用druid数据源,整合mybatis,整合shiro

导入依赖

<dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.2.8</version>
        </dependency>
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.2.0</version>
        </dependency>
		<dependency>
            <groupId>com.github.theborakompanioni</groupId>
            <artifactId>thymeleaf-extras-shiro</artifactId>
            <version>2.0.0</version>
        </dependency>

编写配置文件:yaml

spring:
  datasource:
    username: root
    password: 123456789
    url: jdbc:mysql://localhost:3306/mybatis?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&useSSL=false
    driver-class-name: com.mysql.cj.jdbc.Driver
    type: com.alibaba.druid.pool.DruidDataSource

    initial-size: 5
    min-idle: 5
    max-active: 20
    max-wait: 60000
    time-between-eviction-runs-millis: 60000
    min-evictable-idle-time-millis: 300000
    validation-query: SELECT 1 FROM DUAL
    test-while-idle: true
    test-on-borrow: false
    test-on-return: false
    pool-prepared-statements: true
    filters: stat,wall,log4j
    max-pool-prepared-statement-per-connection-size: 20
    use-global-data-source-stat: true
    connect-properties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
    
mybatis:
  type-aliases-package: com.liu.pojo
  mapper-locations: classpath:mapper/*.xml

User

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

UserMapper接口

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

UserMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.liu.mapper.UserMapper">

    <select id="queryUserByName" resultType="User">
        select *
        from mybatis.user
        where user.name=#{name};
    </select>

</mapper>

service接口

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

service实现类

@Service
public class UserServiceImpl implements UserService{

    //调mapper层
    @Autowired
    UserMapper userMapper;

    @Override
    public User queryUserByName(String name) {
        return userMapper.queryUserByName(name);
    }
}

12、Swagger

官网:API Documentation & Design Tools for Teams | Swagger

前后端分离:

  • 前端测试后端接口:postman
  • 后端提供接口

api框架:

  • RestFul API文档在线自动生成工具—api文档与api定义同步更新
  • 直接运行,可以在线测试api接口

在项目中使用swagger需要springbox:

  • swagger2
  • ui

12.1、springBoot集成Swagger

依赖包

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

配置swagger:

@Configuration
@EnableSwagger2 //开启swagger2
public class SwaggerConfig {
}

产生的问题:Failed to start bean ‘documentationPluginsBootstrapper’; nested exception is java.lang.NullPointerException

因为版本不兼容

解决:

  • 在springboot的配置文件中配置如下:因为高版本后的路径匹配修改了

  • spring:
      mvc:
        pathmatch:
          matching-strategy: ant_path_matcher
    
  • 降低springboot版本到2.5.6

测试运行:Swagger UI

12.2、配置swagger

  • Swagger的bean实例 Docket
@Configuration
@EnableSwagger2 //开启swagger2
public class SwaggerConfig {

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

    //配置swagger信息=apiInfo
    private ApiInfo apiInfo(){
        //作者信息
        Contact contact = new Contact("liuxiang", "http://localhost:8080/", "971223772@qq.com");
        return new ApiInfo("刘想的swagger日记",
                "牛牛的Java学习之旅",
                "1.0",
                "http://localhost:8080/",
                contact,
                "Apache 2.0",
                "http://www.apache.org/licenses/LICENSE-2.0",
                new ArrayList());
    }

}

Swagger配置扫描接口

 @Bean
    public Docket docket(){
        return new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(apiInfo())
                .select()
                //basePackage:指定要扫描的包
                //any():扫描全部
                //none():不扫描
                //withClassAnnotation():扫描类上的注解
                //withMethodAnnotation():扫描方法上的注解
                .apis(RequestHandlerSelectors.basePackage("com.liu.swagger.controller"))
                //paths():过滤路径
                .paths(PathSelectors.ant("/liu/**"))
                .build();
    }

配置是否启动swagger

@Bean
public Docket docket(){
    return new Docket(DocumentationType.SWAGGER_2)
            .apiInfo(apiInfo())
            .enable(false) //是否启动swagger
            .select()
          .apis(RequestHandlerSelectors.basePackage("com.liu.swagger.controller"))
            .build();
}

测试题

如果想要在生产环境下开启swagger,在测试环境下不开启

方法:

  • 通过设置两套模式application-dev.properties和application-prod.properties,分别配置不同的端口号,在application.properties中选择开启哪套环境

  • spring.profiles.active=dev
    
  • 在swaggerConfig中配置:

  • @Bean
    public Docket docket(Environment environment){
    
    //设置要显示的swagger环境
    Profiles profiles = Profiles.of("dev", "test");
    //通过environment.acceptsProfiles判断是否处在设定的环境下
    boolean flag = environment.acceptsProfiles(profiles);
    
    return new Docket(DocumentationType.SWAGGER_2)
            .apiInfo(apiInfo())
            .enable(flag)
            .select()
            .apis(RequestHandlerSelectors.basePackage("com.liu.swagger.controller"))
            .build();
    }
    

配置API文档分组

配置多个分组,多个Docket即可!

@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("用户名")
    public String username;
    @ApiModelProperty("密码")
    public String password;

}

controller

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

其他的注解

@ApiOperation("hello控制类") //用在方法上
    @GetMapping(value = "/hello")
    public String hello(@ApiParam("用户名") String username){
        return "hello"+username;
    }

结果:有中文了

在这里插入图片描述

13、任务

13.1 异步任务

在方法上加注解@Async

@Service
public class AsyncService {

    //告诉spring这是一个异步的方法
    @Async
    public void hello(){
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("数据正在处理中");
    }
}

在main方法上加注解@EnableAsync开启异步功能

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

就可以一瞬间响应,无需等待!

13.2 邮件任务

导入依赖

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

一个简单的邮件

1.先配置相关信息,先在qq邮件设置里将POP3/SMTP服务开启

spring.mail.username=971223772@qq.com
spring.mail.password=zyvegqhkctuobeif
spring.mail.host=smtp.qq.com
#开启加密验证
spring.mail.properties.mail.smtp,ssl.enable=true

2.测试

@SpringBootTest
class Springboot10TestApplicationTests {

    @Autowired(required = false)
    JavaMailSenderImpl mailSender;

    @Test
    void contextLoads() {
        SimpleMailMessage mailMessage = new SimpleMailMessage();

        mailMessage.setSubject("牛牛的Java学习之旅!");
        mailMessage.setText("加油继续努力!");
        mailMessage.setTo("971223772@qq.com");
        mailMessage.setFrom("971223772@qq.com");

        mailSender.send(mailMessage);

    }

}

一个复杂的邮件发送

@SpringBootTest
class Springboot10TestApplicationTests {

    @Autowired(required = false)
    JavaMailSenderImpl mailSender;

    @Test
    void contextLoads() throws MessagingException {

        MimeMessage mimeMessage = mailSender.createMimeMessage();
        //组件
        MimeMessageHelper messageHelper = new MimeMessageHelper(mimeMessage,true);
        //设置主题
        messageHelper.setSubject("牛牛的Java的学习");
        messageHelper.setText("<p style='color:red'>继续加油学习!冲鸭!</p>",true);
        //附件 绝对地址
        messageHelper.addAttachment("1.jpg",new File("C:\\Users\\liuxiang\\Desktop\\1.jpg"));
        messageHelper.setTo("971223772@qq.com");
        messageHelper.setFrom("971223772@qq.com");
        mailSender.send(mimeMessage);
    }
}

封装成工具类

 //封装成工具类
    /**
     *
     * @param html
     * @param subject
     * @param text
     * @param fileName
     * @param fileUrl
     * @param ReceiveAddress
     * @param sendAddress
     * @throws MessagingException
     * @Author liuxiang
     */
    public void sendMail(Boolean html,String subject,String text,String fileName,String fileUrl,String ReceiveAddress,String sendAddress) throws MessagingException{

        MimeMessage mimeMessage = mailSender.createMimeMessage();
        //组件
        MimeMessageHelper messageHelper = new MimeMessageHelper(mimeMessage,html);
        //设置主题
        messageHelper.setSubject(subject);
        messageHelper.setText(text,html);
        //附件 绝对地址
        messageHelper.addAttachment(fileName,new File(fileUrl));
        messageHelper.setTo(ReceiveAddress);
        messageHelper.setFrom(sendAddress);
        mailSender.send(mimeMessage);
    }

13.3 定时任务

  1. 在main线程开启定时功能的注解
@EnableAsync
@EnableScheduling //开始定时功能的注解
@SpringBootApplication
public class Springboot10TestApplication {
    public static void main(String[] args) {
        SpringApplication.run(Springboot10TestApplication.class, args);
    }
}
  1. 使用 @Scheduled(cron = “0 * * * * 0-7”)注解和cron表达式
@Service
public class ScheduleService {

    //在一个特定的事件执行这个方法
    //cron表达式 任务调度
    //秒 分 时 日 月 周几
    @Scheduled(cron = "0 * * * * 0-7")
    public void hello(){
        System.out.println("hello");
    }
}

文件上传和下载

springMVC中没有装配MultipartResolver,所以默认情况下不能处理文件上传工作,如果想要使用文件上传功能,需要在上下文中配置MultipartResolver。

前端表单要求:必须将表单的method设置为POST,并将enctype设置为multipart/form-data,只有在这种情况下,浏览器才会把用户选择的文件以二进制数据发送给服务器。

  • application/x-www=form-urlencoded:默认方式,只处理表单域中的value属性值,采用这种编码方式的表单会将表单域中的值处理成URL编码方式。
  • multipart/form-data:这种编码方式会以二进制流的方式来处理表单数据,这种编码丰富会把文件域指定文件的内容也封装到请求参数中,不会对字符编码
  • text/plain:除了把空格转换为"+"号外,其他字符不作编码处理,适用 直接通过表单发送右键
<form action="${pageContext.request.contextPath}/upload" enctype="multipart/form-data" method="post">
    <input type="file" name="file">
    <input type="submit">
  </form>

后端导入文件上传的jar包:commons-fileupload

<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.3.3</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
</dependency>

在resources目录下的springmvc-servlet.xml配置:

<!--文件上传配置-->
    <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
        <!--请求的编码格式,必须和jsp的pageEncoding属性一致,默认是ISO-8859-1-->
        <property name="defaultEncoding" value="utf-8"/>
        <!--上传文件大小上限,单位为字节(10485760=10M)-->
        <property name="maxUploadSize" value="10485760"/>
        <property name="maxInMemorySize" value="40960"/>
    </bean>

controller层

package com.liu.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.commons.CommonsMultipartFile;

import javax.servlet.http.HttpServletRequest;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;

@RestController
public class FileController {

    //@RequestMapping("file") 将name=file控件得到的文件封装成CommonsMultipartFile对象
    //批量上传CommonsMultipartFile则为数组即可
    @RequestMapping("/upload")
    public String fileUpLoad(@RequestParam("file")CommonsMultipartFile file, HttpServletRequest request) throws IOException {

        //获取文件名:file.getOriginalFilename();
        String uploadFileName = file.getOriginalFilename();

        //如果文件名为空,直接回到首页
        if ("".equals(uploadFileName)){
            return "redirect:/index.jsp";
        }
        System.out.println("上传文件名:"+uploadFileName);

        //上传路径保存设置
        String path = request.getServletContext().getRealPath("/upload");
        //如果路径不存在,创建一个
        File realPath = new File(path);
        if (!realPath.exists()){
            realPath.mkdir();
        }
        System.out.println("上传文件保存地址:"+realPath);

        InputStream is = file.getInputStream();//文件输入流
        FileOutputStream os = new FileOutputStream(new File(realPath, uploadFileName));//输出流

        //读取写出
        int len = 0;
        byte[] buffer = new byte[1024];
        while ((len=is.read(buffer))!=-1){
            os.write(buffer,0,len);
            os.flush();
        }
        os.close();
        is.close();

        return "redirect:/index.jsp";
    }
}

方法二:

  //采用file.TransferTo来保存上传的文件
    @RequestMapping("/upload2")
    public String fileUpLoad2(@RequestParam("file")CommonsMultipartFile file, HttpServletRequest request) throws IOException {

        //上传路径保存设置
        String path = request.getServletContext().getRealPath("/upload");
        //如果路径不存在,创建一个
        File realPath = new File(path);
        if (!realPath.exists()) {
            realPath.mkdir();
        }
        System.out.println("上传文件保存地址:"+realPath);

        //通过CommonsMultipartFile的方法直接写文件
        file.transferTo(new File(realPath +"/" + file.getOriginalFilename()));

        return "redirect:/index.jsp";
    }

文件下载

  1. 设置response响应头
  2. 读取文件InputStream
  3. 写出文件OutputStream
  4. 执行操作
  5. 关闭流
//下载图片方法
    @RequestMapping("/downLoad")
    public String downLoads(HttpServletResponse response,HttpServletRequest request) throws IOException {
        //要下载的图片
        String path = request.getServletContext().getRealPath("/upload");
        String fileName = "基础语法.jpg";

        //1.设置response响应头
        response.reset();//设置页面不缓存,清空buffer
        response.setCharacterEncoding("utf-8");
        response.setContentType("multipart/form-data");//二进制传输数据
        //设置响应头
        response.setHeader("Content-Disposition",
                "attachment;fileName="+ URLEncoder.encode(fileName,"UTF-8"));
        File file = new File(path, fileName);
        //2.读取文件--输入流
        InputStream is = new FileInputStream(file);
        //3.写出文件-输出流
        OutputStream fos = response.getOutputStream();

        byte[] buffer = new byte[1024];
        int index = 0;
        while ((index = is.read(buffer))!=-1){
            fos.write(buffer,0,index);
            fos.flush();
        }

        fos.close();
        is.close();

        return null;
    }
 <a href="${pageContext.request.contextPath}/downLoad">下载图片</a>

14、分布式Dubbo+Zookeeper+SpringBoot

分布式理论:

分布式系统是若干独立计算机的集合,这些计算机对于用户来说就像单个相关系统

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

dubbo官方文档:文档 | Apache Dubbo

ORM单一应用架构:

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

缺点:

  • 性能扩展难
  • 协同开发问题
  • 不利于维护升级

垂直应用架构:

当访问量逐渐变大,单一应用增加机器带来的加速度越来越小,将应用拆分不相干的几个应用,以提升效率,MVC架构是关键

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

分布式服务架构:

将核心业务提取出来作为独立的服务,组件形成稳定的服务中心,使前端应用能更快速的响应多变的市场需求,RPC分布式服务框架是关键。

流动计算架构:

小服务资源的浪费等问题产生,需增加一个调度中心基于访问压力实时管理集群容量,提高集群利用率。提高机器利用率的资源调度和治理中心(SOA)是关键

什么是RPC

RPC:Remote Procedure Call 远程过程调用

  • 允许程序调用另外一个地址空间的过程或函数,而不用程序员显示编码这个远程调用的细节。

  • 核心:通讯、序列化

在这里插入图片描述

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

Consumer:o调用远程服务的服务消费方,服务消费者在启动时向注册中心订阅自己所需的服务

Registry:注册中心返回服务提供者地址列表给消费者

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

Dubbo和zookeeper的安装

zookeeper下载地址:

http://archive.apache.org/dist/zookeeper/zookeeper-3.4.14/zookeeper-3.4.14.tar.gz

管理员身份进入cmd命令:

在这里插入图片描述

可能遇到的问题:闪退

解决:

  • 将conf文件夹下面的zoo_sample.cfg复制一份改名未zoo.cfg即可
  • zookeeper的端口号:clientPort=2181

dubbo-admin下载

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

端口号:7001

在项目目录下打包dubbo-admin

D:\Environment\dubbo-admin-master>mvn clean package -Dmaven.test.skip=true

出现build success即可

在这里插入图片描述

cmd命令下执行jar包:

java -jar dubbo-admin-server-0.3.0.jar

账户密码是 root-root

服务注册

  • 不用上述的下载也可以,通过导入jar包即可
<dependency>
    <groupId>org.apache.dubbo</groupId>
    <artifactId>dubbo-spring-boot-starter</artifactId>
    <version>3.0.9</version>
</dependency>
<!--zookeeper-->
<dependency>
    <groupId>com.101tec</groupId>
    <artifactId>zkclient</artifactId>
    <version>0.2</version>
</dependency>

需要排除日志,不然会起冲突,报错:

<dependency>
        <groupId>org.apache.curator</groupId>
        <artifactId>curator-framework</artifactId>
        <version>2.12.0</version>
    </dependency>
    <dependency>
        <groupId>org.apache.curator</groupId>
        <artifactId>curator-recipes</artifactId>
        <version>2.12.0</version>
    </dependency>
    <dependency>
        <groupId>org.apache.zookeeper</groupId>
        <artifactId>zookeeper</artifactId>
        <version>3.4.14</version>
        <exclusions>
            <exclusion>
                <groupId>org.slf4j</groupId>
                <artifactId>slf4j-log4j12</artifactId>
            </exclusion>
        </exclusions>
    </dependency>

注册中心:

server.port=8001

#服务应用名字
dubbo.application.name=privider-server
#注册中心地址
dubbo.registry.address=zookeeper://127.0.0.1:2181
#哪些服务要被注册
dubbo.scan.base-packages=com.liu.service

service层:

@DubboService
@Component  //使用了dubbo后不要使用service
public class TicketServiceImpl implements TicketService{
    @Override
    public String getTicket() {
        return "llx";
    }
}

消费者配置:

server.port=8002

#消费者应用名字
dubbo.application.name=consumer-server

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

service

@Service //是spring的注解service
public class UserService {
    //想拿到provider-service的票,去注册中心拿到服务
    @Reference //定义路径相同的的接口名 引用 从远程注入服务
    TicketService ticketService;
    public void buyTicket(){
        String ticket = ticketService.getTicket();
        System.out.println(ticket);
    }
}

复利用,开发性的浪费

分布式服务架构:

将核心业务提取出来作为独立的服务,组件形成稳定的服务中心,使前端应用能更快速的响应多变的市场需求,RPC分布式服务框架是关键。

流动计算架构:

小服务资源的浪费等问题产生,需增加一个调度中心基于访问压力实时管理集群容量,提高集群利用率。提高机器利用率的资源调度和治理中心(SOA)是关键

什么是RPC

RPC:Remote Procedure Call 远程过程调用

  • 允许程序调用另外一个地址空间的过程或函数,而不用程序员显示编码这个远程调用的细节。

  • 核心:通讯、序列化

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

Consumer:o调用远程服务的服务消费方,服务消费者在启动时向注册中心订阅自己所需的服务

Registry:注册中心返回服务提供者地址列表给消费者

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

Dubbo和zookeeper的安装

zookeeper下载地址:

http://archive.apache.org/dist/zookeeper/zookeeper-3.4.14/zookeeper-3.4.14.tar.gz

管理员身份进入cmd命令:

[外链图片转存中…(img-oR9kCsl1-1658244095119)]

可能遇到的问题:闪退

解决:

  • 将conf文件夹下面的zoo_sample.cfg复制一份改名未zoo.cfg即可
  • zookeeper的端口号:clientPort=2181

dubbo-admin下载

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

端口号:7001

在项目目录下打包dubbo-admin

D:\Environment\dubbo-admin-master>mvn clean package -Dmaven.test.skip=true

出现build success即可

[外链图片转存中…(img-Qmxxx9gy-1658244095119)]

cmd命令下执行jar包:

java -jar dubbo-admin-server-0.3.0.jar

账户密码是 root-root

服务注册

  • 不用上述的下载也可以,通过导入jar包即可
<dependency>
    <groupId>org.apache.dubbo</groupId>
    <artifactId>dubbo-spring-boot-starter</artifactId>
    <version>3.0.9</version>
</dependency>
<!--zookeeper-->
<dependency>
    <groupId>com.101tec</groupId>
    <artifactId>zkclient</artifactId>
    <version>0.2</version>
</dependency>

需要排除日志,不然会起冲突,报错:

<dependency>
        <groupId>org.apache.curator</groupId>
        <artifactId>curator-framework</artifactId>
        <version>2.12.0</version>
    </dependency>
    <dependency>
        <groupId>org.apache.curator</groupId>
        <artifactId>curator-recipes</artifactId>
        <version>2.12.0</version>
    </dependency>
    <dependency>
        <groupId>org.apache.zookeeper</groupId>
        <artifactId>zookeeper</artifactId>
        <version>3.4.14</version>
        <exclusions>
            <exclusion>
                <groupId>org.slf4j</groupId>
                <artifactId>slf4j-log4j12</artifactId>
            </exclusion>
        </exclusions>
    </dependency>

注册中心:

server.port=8001

#服务应用名字
dubbo.application.name=privider-server
#注册中心地址
dubbo.registry.address=zookeeper://127.0.0.1:2181
#哪些服务要被注册
dubbo.scan.base-packages=com.liu.service

service层:

@DubboService
@Component  //使用了dubbo后不要使用service
public class TicketServiceImpl implements TicketService{
    @Override
    public String getTicket() {
        return "llx";
    }
}

消费者配置:

server.port=8002

#消费者应用名字
dubbo.application.name=consumer-server

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

service

@Service //是spring的注解service
public class UserService {
    //想拿到provider-service的票,去注册中心拿到服务
    @Reference //定义路径相同的的接口名 引用 从远程注入服务
    TicketService ticketService;
    public void buyTicket(){
        String ticket = ticketService.getTicket();
        System.out.println(ticket);
    }
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

SpringBoot的完整学习 的相关文章

随机推荐

  • android源码学习-Handler机制及其六个核心点

    前言 该文属于安卓源码探究专栏中的文章 专栏所有文章清单链接如下 欢迎大家阅读 安卓源码探究 这里是图片001 https blog csdn net rzleilei category 6506586 html spm 1001 2014
  • 【MYSQL基础(一)】——数据类型的详细解析. 数据库基本操作

    个人主页 努力学习的少年 版权 本文由 努力学习的少年 原创 在CSDN首发 需要转载请联系博主 如果文章对你有帮助 欢迎关注 点赞 收藏 一键三连 和订阅专栏哦 目录 一 为什么要使用MySQL数据库 二 数据库的基本概念 数据库和数据库
  • win10计算机程序员怎么用,如何用好 Windows 10 中的多功能「计算器」应用程序

    从 1985 年 Microsoft 首次推出 Windows 1 0 以来 至今 系统内置的 Widows 计算器已经走过了漫长的功能扩展道路 Windows 10 的多功能 计算器 针对不同用户和使用场景 内置了多种功能模式 其中就包括
  • C# 三菱FX PLC XYS读写,串口读写

    花了两三天写了一个这个 本来想着自己用的 看到有很多替代品 果断开源了吧 下载地址 https github com t39q MitsubishiFX PLC XYS 以下是原理 后面有帮助类和调用方法 调用方法 private void
  • Python通过私信消息提取博主的赠书活动地址

    文章目录 前言 背景 设计 开发 1 引入模块 2 获取私信内容 3 根据文本提取url的方法 4 获取包含 书 的url 5 程序入口 效果 总结 最后 前言 博主 空空star 主页 空空star的主页 大家好 我是空空star 本篇给
  • java canvas 画图片_[Java教程][HTML5] Canvas绘制简单图片

    Java教程 HTML5 Canvas绘制简单图片 0 2016 05 13 13 00 04 获取Image对象 new出来 定义Image对象的src属性 参数 图片路径 定义Image对象的onload方法 调用context对象的d
  • 图的深度优先遍历和广度优先遍历

    1 深度优先遍历 DFS 1 从某个顶点V出发 访问顶点并标记为已访问 2 访问V的其中一个邻接点 通常最左边的那个 如果没有访问过 访问该顶点并标记为已访问 然后再访问该顶点的邻接点 递归执行 先一直往后走 如果该顶点已访问过 退回上一个
  • CAD快捷键——标注类

    CAD快捷键 标注类 直线标注 DLI 空格 斜线标注 DAL 空格 半径标注 DRA 空格 直径标注 DDI 空格 角度标注 DAN 空格 连续标注 DCO 空格 快速连续标注 QDIM 空格 中心标注 DCE 空格 直线标注 DLI 空
  • Windows下的开发辅助神器——Chocolate Package Manager

    对于开发人员而言 搭建开发环境是所有开发环节中的第一步 然而在Windows环境下 各种安装工具 软件版本五花八门 而且容易下载到病毒软件 因此对于初学者来说 下载到正确的开发软件 搭建好开发环境还是有一定难度和技巧性的 Chocolate
  • [已解决]ROS无法连接raw.githubusercontent.com和raw.github.com的问题

    首先通过以下网站查询raw githubusercontent com和raw github com对应的IP https tool lu ip 复制上面的IP 然后通过下面命令打开hosts修改源 sudo vi etc hosts 以下
  • Spring Boot参考教程(七)Spring Boot Jar方式读取资源文件

    5 Spring Boot Jar方式读取资源文件 在2 2 2章节中已说明SpringBoot的一个特性就是独立运行 内嵌Servlet容器 在Spring Boot工程以jar方式独立运行开发时会遇到一些问题 本章节主要说明读取静态资源
  • Reformer RoPE,旋转位置编码,关于Transformer当中的位置编码的优化考察

    1 工作简介 这篇文章是苏剑林的一篇关于Transformer当中的位置编码的优化考察 众所周知 transformer的attention机制本身是不带有位置信息的 因此对于文本序列 attention机制本身就会丢失掉原文当中的序列信息
  • Python二叉树构建(完全二叉树)

    Python完全二叉树的构建 包含广度优先插入节点 广度遍历 先序 中序 后序遍历等函数 class Node object 节点 def init self item self elem item self lchild None sel
  • 【AI目标检测】MMROTATE踩坑记录

    MMROTATE介绍 MMRotate 是一款基于 PyTorch 的旋转框检测的开源工具箱 是 OpenMMLab 项目的成员之一 MMROTATE安装 mmrotate的安装同mmdet等类似 参考mmrotate安装 创建环境 con
  • 单调栈和滑动窗口【题解】

    全文目录 单调栈 代码 滑动窗口 单调队列 代码 单调栈 单调栈 顾名思义就是具有单调性的栈 通常是用来求数组中的左边第一个比自身小或者大的数 使用场景还是比较有限的 但是对于解题还是有着很大的帮助的 我们还是通过题目来进行讲解 对于这种题
  • php一个字段多条件查询,ThinkPHP使用数组条件进行查询之同一字段多个条件

    对同一表中多个字段的查询 在thinkPHP中使用数组条件进行查询 有三个好处 第一可以批量设置多个查询字段 第二可以设置多个查询条件 第三结构化你的代码 让代码更具可读性 数组条件查询有简单数组查询 数组表达式查询 一般使用 map保存数
  • VSCode远程连接Linux服务器时遇到连接超时问题

    1 确保Linux服务器已经安装并运行了SSH服务 可以使用以下命令检查SSH服务的状态 sudo systemctl status ssh 可以设置开机自启 sudo systemctl enable ssh 2 使用ping命令检查ip
  • 快速掌握Nodejs安装以及入门

    一 Nodejs 1 1 Nodejs介绍 官网 http nodejs cn Node 是一个让 JavaScript 运行在服务端的开发平台 它让 JavaScript 成为与PHP Python Perl Ruby 等服务端语言平起平
  • LeetCode小算法记录(二十二)最长回文串

    给定一个包含大写字母和小写字母的字符串 找到通过这些字母构造成的最长的回文串 在构造过程中 请注意区分大小写 比如 Aa 不能当做一个回文字符串 注意 假设字符串的长度不会超过 1010 示例 1 输入 abccccdd 输出 7 解释 我
  • SpringBoot的完整学习

    springBoot 配置如何编写 yaml 自动装配原理 集成web开发 业务的核心 集成数据库 Druid 分布式开发 Dubbo zookeeper swagger 接口文档 任务调度 SpringSecunity Shiro 1 微