SpringBoot

目录

1、什么是SpringBoot?

2、第一个SpringBoot程序

3、项目结构分析:

@AutoConfigurationPackage

总结:

4、yaml语法学习

1、配置文件

2、yaml基础语法

5、多环境配置以及配置文件位置

1、多环境切换

2、yaml的多文档块

3、配置文件加载位置

4、拓展,运维小技巧

5、自动配置原理

总结:

6、SpringBoot Web开发 

1、Web开发探究

2、静态资源处理

1、静态资源映射规则:

3、静态资源映射规则访问资源

7、首页以及图标的定制

1、首页处理

8、Thymeleaf模板引擎

1、Thymeleaf是什么

2、Thymeleaf有何优势

3、Thymeleaf 特点

4、引入Thymeleaf        

8、Thymeleaf 语法学习

9、SpringMVC拓展

实现WebMvcConfigurer接口

10.全面接管SpringMVC

11、员工管理系统

1、员工管理系统:准备工作

3、员工管理系统:国际化

5、员工管理系统:登陆拦截器

6、员工管理系统:展示员工列表

7、员工管理系统:添加员工信息

8、员工管理系统:修改员工信息 

9、员工管理系统:删除员工

10、员工管理系统:404错误页面

11、员工管理系统:注销功能

12、SpringBoot:Mybatis + Druid 数据访问

1、简介

2、JDBC

3、CRUD操作 

 4、自定义数据源 DruidDataSource

 2、配置 Druid web 监控 filter

13.SpringSecurity (安全)

1、SpringSecurity环境搭建

14、Shiro(安全)

1、Shiro简介

2、Shiro有哪些功能?

3、Shiro架构(外部)

6、SpringBoot整合Shiro环境搭建

13、Swagger

1、Swagger的作用和概念

1、Swagger 的优势

2、SwaggerUI 特点

2、SpringBoot集成Swagger

3、配置Swagger

5、其他皮肤

14、任务

1、异步任务

2、邮件任务

3.定时任务:有点像linux里面的

15、分布式 Dubbo+Zookenper+SpringBoot

1、分布式服务架构

2、流动计算架构

3、RPC

4、Dubbo

5、dubbo基本概念


 

1、什么是SpringBoot?

 

  1. Spring Boot是由Pivotal团队提供的全新框架
  2. 其设计目的是用来简化Spring应用的初始搭建以及开发过程。
  3. 该框架使用了特定的方式来进行配置,从而使开发人员不再需要定义样板化的配置。
  4. 通过这种方式,Spring Boot致力于在蓬勃发展的快速应用开发领域(rapid application development)成为领导者

==SpringBoot基于Spring4.0设计,不仅继承了Spring框架原有的优秀特性,而且还通过简化配置来进一步简化了Spring应用的整个搭建和开发过程。另外SpringBoot通过集成大量的框架使得依赖包的版本冲突,以及引用的不稳定性等问题得到了很好的解决。 ==

SpringBoot所具备的特征有:

1)可以创建独立的Spring应用程序,并且基于其Maven或Gradle插件,可以创建可执行的JARs和WARs;

(2)内嵌Tomcat或Jetty等Servlet容器;

(3)提供自动配置的“starter”项目对象模型(POMS)以简化Maven配置;

(4)尽可能自动配置Spring容器;

(5)提供准备好的特性,如指标、健康检查和外部化配置;

(6)绝对没有代码生成,不需要XML配置。 [1] [2]

2、第一个SpringBoot程序

 环境:

jdk1.8

maven 最新

spring 最新

原始方法从Spirng.io官网中下载

 

解压它

 

SpringBoot所有的依赖都是以:

spring-boot-starter 开头

第二种方式:

 

在HelloworldApplication 主入口的同级目录下创建包什么dao controller pojo service包等等

只有在它同级目录下创建包它才会去扫描。

启动项目:

 成功访问:

如何打包?

双击package后会生成一个jar包这样就可以在任意地方执行。 

如:

 

 SpringBoot较SpringMVC来说,简化了配置

更改端口号: 

 

更改banner

更改banner网站:https://www.bootschool.net/ascii 

3、项目结构分析:

通过上面步骤完成了基础项目的创建。就会自动生成以下文件。

1、程序的主启动类

2、一个 application.properties 配置文件

3、一个 测试类

4、一个 pom.xml

SpringBoot的底层机制

主启动类:

如:

@SpringBootApplication
public class HelloworldApplication {

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

}

点进入@SpringBootApplication

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
		@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
}

可以看到SpringBootAppliccation类下也有很多注解

@SpringBootConfiguration:SpringBoot的配置文件        

@EnableAutoCofigutation:自动配置

@ComponentScant:扫描同级目录下的包,即跟主启动类同级目录下的包,该注解主要用来扫描@Component及其集成它的类装配到容器中(默认启动类包路径下).例如@Controller、@Service、@Repository等Bean声明注解.

先来看@SpringBootConfiguration这个注解,点进去

package org.springframework.boot;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.AliasFor;
import org.springframework.stereotype.Indexed;

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
@Indexed
public @interface SpringBootConfiguration {
    @AliasFor(
        annotation = Configuration.class
    )
    boolean proxyBeanMethods() default true;
}

 重要的注解有@Configuration,点进去

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Configuration {
    @AliasFor(
        annotation = Component.class
    )
    String value() default "";

    boolean proxyBeanMethods() default true;
}

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

@Component:证明这是一个组件,说白了SpringBootConfiguration是一个组件,他交给了Spring容器管理,启动类本身也是Spring中的一个组件(Bean)而已,负责启动应用!

@SpringBootConfiguration的小结:

 @SpringBootApplication-> @SpringBootConfiguration -> @Configuration -> @Component 

通过这个注解可以得出一个结论:@SpringBootApplication注解标注的主启动类就是一个配置类,启动类本身也是Spring中的一个组件(Bean)

再来看@EnableAutoConfiguration:开启自动配置功能,点进去

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
}

重要的有

@AutoConfigurationPackage:自动配置包,它负责加载当前包路径下所有的@Configuration配置Bean.该注解加载Bean至容器中发生于run方法的上下文准备完成后的刷新上下文过程中

@Import(AutoConfigurationImportSelector.class):导入自动配置选择器这个类

先看第一个,点进去

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {
}

@AutoConfigurationPackage

@Import(AutoConfigurationPackages.Registrar.class):引用了一个内部类Registrar

他与前面的@ComponentScan结合起来,在被@ComponentScan扫描到的包,来这里注册,

点进去(AutoConfigurationPackages)

现在点进他的内部类看看做了什么

static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {

		@Override
		public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
			register(registry, new PackageImports(metadata).getPackageNames().toArray(new String[0]));
		}

		@Override
		public Set<Object> determineImports(AnnotationMetadata metadata) {
			return Collections.singleton(new PackageImports(metadata));
		}

	}

registerBeanDefinitions方法: 理解为注册bean的定义

AnnotationMetadata metadata : 获取注解的元数据、简单来说就是可以通过这个来获取启动类的信息

BeanDefinitionRegistry registry:负责bean做注册的

我们先来看这个new PackageImports(metadata).getPackageNames().toArray(new String[0])他是一个什么值

进入这个PackageImports类这个类的构造方法

PackageImports(AnnotationMetadata metadata) {
        //第一块
			AnnotationAttributes attributes = AnnotationAttributes
					.fromMap(metadata.getAnnotationAttributes(AutoConfigurationPackage.class.getName(), false));
			List<String> packageNames = new ArrayList<>(Arrays.asList(attributes.getStringArray("basePackages")));
			for (Class<?> basePackageClass : attributes.getClassArray("basePackageClasses")) {
				packageNames.add(basePackageClass.getPackage().getName());
			}
			if (packageNames.isEmpty()) {
				packageNames.add(ClassUtils.getPackageName(metadata.getClassName()));
			}
			this.packageNames = Collections.unmodifiableList(packageNames);
		}

可以看出获取到主启动类的类名、包括一些其他信息

获取@AutoConfigurationPackage注解中的属性有没有值,通过第一个地方获取出来的值进行遍历添加到packageNames中,判断packageNames是否null、由于在@AutoConfigurationPackage注解中没有添加属性值所以走的就是第三块,因此来获取包名、最后将包名存入packageNames的集合中,并返回给Registrar类中的register方法

register方法—>bean的注册方法

public static void register(BeanDefinitionRegistry registry, String... packageNames) {
		if (registry.containsBeanDefinition(BEAN)) {
			BasePackagesBeanDefinition beanDefinition = (BasePackagesBeanDefinition) registry.getBeanDefinition(BEAN);
			beanDefinition.addBasePackages(packageNames);
		}
		else {
			registry.registerBeanDefinition(BEAN, new BasePackagesBeanDefinition(packageNames));
		}
	}

尤于第一次运行肯定是不存在的所以走的else,就是将所获取包名的同级包以及子包注入将被扫描(特定注解)的类创建bean加入IOC容器中。

自动扫描包的最后做的就是获取启动类的文件位置、将其同包以及子包内的(类似被@Controller标注的)类注入到springioc容器当中

而@Import(AutoConfigurationImportSelector.class)

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

继续点入loadFactoryNames方法

  public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
        ClassLoader classLoaderToUse = classLoader;
        if (classLoader == null) {
            classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
        }

        String factoryTypeName = factoryType.getName();
        return (List)loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
    }

继续点return中的loadSpringFactories方法

private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
        Map<String, List<String>> result = (Map)cache.get(classLoader);
        if (result != null) {
            return result;
        } else {
            HashMap result = new HashMap();

            try {
                Enumeration urls = classLoader.getResources("META-INF/spring.factories");

                while(urls.hasMoreElements()) {
                    URL url = (URL)urls.nextElement();
                    UrlResource resource = new UrlResource(url);
                    Properties properties = PropertiesLoaderUtils.loadProperties(resource);
                    Iterator var6 = properties.entrySet().iterator();

                    while(var6.hasNext()) {
                        Entry<?, ?> entry = (Entry)var6.next();
                        String factoryTypeName = ((String)entry.getKey()).trim();
                        String[] factoryImplementationNames = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());
                        String[] var10 = factoryImplementationNames;
                        int var11 = factoryImplementationNames.length;

                        for(int var12 = 0; var12 < var11; ++var12) {
                            String factoryImplementationName = var10[var12];
                            ((List)result.computeIfAbsent(factoryTypeName, (key) -> {
                                return new ArrayList();
                            })).add(factoryImplementationName.trim());
                        }
                    }
                }

                result.replaceAll((factoryType, implementations) -> {
                    return (List)implementations.stream().distinct().collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList));
                });
                cache.put(classLoader, result);
                return result;
            } catch (IOException var14) {
                throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var14);
            }
        }
    }

 

这个方法会去spring-boot-autoconfigure/META-INF/spring.factories中加载全部的配置文件的全类名:

 但是它不会全部加载进去,而是根据@ConditionalOnClass这个注解来判断符合条件的包并引入相应的场景所需要的组件,注入到SpringIOC容器中

如:org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration

总结:

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

SpringApplication.run分析

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

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

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

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

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

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

run方法流程分析

run()的作用:

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

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

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

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

4、yaml语法学习

1、配置文件

SpringBoot使用一个全局的配置文件 , 配置文件名称是固定的(可以有多个,有优先级)

  • application.properties

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

    • 语法结构 :key:空格 value  对空格非常严格

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

2、yaml基础语法

说明:语法要求严格!

1、空格不能省略

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

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

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

注意:

  1. “ ” 双引号,不会转义字符串里面的特殊字符 , 特殊字符会作为本身想表示的意思;
  2. 比如 :name: “kk\n nh” 输出 :kk换行 nh
  3. ‘’ 单引号,会转义特殊字符 , 特殊字符最终会变成和普通字符一样输出
  4. 比如 :name: ‘kk\n nh’ 输出 :kk\n nh

基本语法例子:

 注意@ConfigurationProperties注解:里面的属性比如person 是你在.yaml文件中创建的对象名

JSR303校验:

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

若在使用@Eamil中如果爆红

则引入下面的依赖

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

eg:我们让person中的名字只能使用邮件的格式:

package com.hua.springboot02.pojo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import org.springframework.validation.annotation.Validated;

import javax.validation.constraints.Email;
import java.util.Date;
import java.util.List;
import java.util.Map;

@Component
@Data
@ConfigurationProperties(prefix = "person")
@AllArgsConstructor
@NoArgsConstructor
@Validated
public class Pserson {

    @Email(message = "错误啦宝贝!!")
    private String name;
    private int age;
    private Date birth;
    private Dog dog;
    private List<Object> list;
    private Map<Object,Integer>map;
}

 合法条件下:

不合法:

 

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

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

5、多环境配置以及配置文件位置

1、多环境切换

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

多配置文件:

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

例如:

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

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

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

文件

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

application.properties

#SpringBoot的多环境配置,可以选择激活哪一个配置
spring.profiles.active=dev

2、yaml的多文档块

和properties配置文件中一样,但是使用yml去实现不需要创建多个配置文件,更加方便!

application.yaml

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

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

3、配置文件加载位置

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

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

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

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

4、拓展,运维小技巧

指定位置加载配置文件,还可以通过spring.config.location来改变默认的配置文件位置

项目打包好以后,我们可以使用命令行参数的形式,启动项目的时候来指定配置文件的新位置;相同配置,外部指定的配置文件优先级最高

java -jar spring-boot-config.jar --spring.config.location=F:/application.prop

5、自动配置原理

首先先解释注解:

@EnableConfigurationProperties:自动配置属性

@ConfigurationProperties:绑定了一个文件,如上面的yaml语法中的基本语法例子,又如:

而我们的application.yaml 与 Spring.factory 的联系

在yaml配置文件中: 每一个Xxx开头都是一个 XxxAutoConfigeruation

比如我们用ActiveMQAutoConfiguration为例子: 

 

 而broker-url则对应activemq中的属性,当然它有默认的属性

而这些属性是在properties中获取的

 点进去:

也就是说在自动装配时,它会去Xxx.properties中装配默认值,如果我们要自定义值就可以在SpringBoot的配置文件中改,即.yaml文件等。 

也就是说在位们的配置文件中能配置的东西,都存在一个一般的规律

有一个Xxxautoconfiguration:默认值,xxxProperties和配置文件绑定,这样我们就可以使用自定义的配置啦:

eg:spring.factories中的

org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration,

点进去

点进去webproperties

再点进去Format

总结

  • 当这个配置类生效;这个配置类就会给容器中添加各种组件;
  • 这些组件的属性是从对应的properties类中获取的,这些类里面的每一个属性又是和配置文件绑定的;
  • 所有在配置文件中能配置的属性都是在xxxxProperties类中封装着;
  • 配置文件能配置什么就可以参照某个功能对应的这个属性类

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


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

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

那怎么才能知道哪些自动配置类生效?

这时候就要了解

@ConditionalOnXxx注解:

通过名字,可以理解为这种注解是用于判断的,满足条件是怎么样,点进去可以发现,他们有一个共同的注解@Conditional()

 只有满足@ConditionalOnXxx()里面参数的条件,简单来说就是pom.xml中有导入的包或者导入的场景的,哪么这个类就会呗自动装配并交由spring管理

总结:这些自动装配的类是只有满足某些条件后才会自动装配。比如找到了某些相关基础类。

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

  1. Positive matches:(自动配置类启用的:正匹配)
  2. Negative matches:(没有启动,没有匹配成功的自动配置类:负匹配)
  3. Unconditional classes: (没有条件的类) 

以上就是自动装配的原理!

总结:

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

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

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

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

如有错误请评论让我改正~~~

6、SpringBoot Web开发 

1、Web开发探究

jar:webapp!

自动装配

  • 向容器中自动配置组件 : Autoconfiguration
  • 自动配置类,封装配置文件的内容:***Properties

2、静态资源处理

1、静态资源映射规则:

SpringBoot中,SpringMVC的web配置都在 WebMvcAutoConfiguration 这个配置类里面;前往看看

在这个配置类中 有这么一个方法:addResourceHandlers 添加静态资源处理器

public void addResourceHandlers(ResourceHandlerRegistry registry) {
            if (!this.resourceProperties.isAddMappings()) {
                logger.debug("Default resource handling disabled");
            } else {
                this.addResourceHandler(registry, "/webjars/**", "classpath:/META-INF/resources/webjars/");
                this.addResourceHandler(registry, this.mvcProperties.getStaticPathPattern(), (registration) -> {
                    registration.addResourceLocations(this.resourceProperties.getStaticLocations());
                    if (this.servletContext != null) {
                        ServletContextResource resource = new ServletContextResource(this.servletContext, "/");
                        registration.addResourceLocations(new Resource[]{resource});
                    }

                });
            }
        }

源代码:比如所有的 /webjars/** , 都要去 classpath:/META-INF/resources/webjars/ 找对应的资源;

2、什么是webjars 呢?

webjars本质就是以jar包的方式引入我们的静态资源 , 以前要导入一个静态资源文件,现在直接导入即可。

使用SpringBoot需要使用Webjars,网站:https://www.webjars.org

要使用jQuery,我们只需要引入jQuery对应版本的pom依赖即可!
 

<dependency>    
    <groupId>org.webjars</groupId>   
    <artifactId>jquery</artifactId>    
    <version>3.6.0</version> 
</dependency>

访问:只要是静态资源,SpringBoot就会去对应的路径寻找资源,这里访问:http://localhost:8080/webjars/jquery/3.4.1/jquery.js

3、静态资源映射规则访问资源

找到staticPathPattern发现第二种映射规则 :/** , 访问当前的项目任意资源,它会去找 resourceProperties 这个类,点进去看一下分析:

ResourceProperties可以设置和我们静态资源有关的参数;这里面指向了它会去寻找资源的文件夹,即上面数组的内容。

得出结论,以下四个目录存放的静态资源可以被我们识别:

"classpath:/META-INF/resources/"
"classpath:/resources/"
"classpath:/static/"
"classpath:/public/"
public static class Resources {
        private static final String[] CLASSPATH_RESOURCE_LOCATIONS = new String[]{"classpath:/META-INF/resources/", "classpath:/resources/", "classpath:/static/", "classpath:/public/"};

在这个类中可以找到,优先级就是写的代码中的顺序

我们可以在resources根目录下新建对应的文件夹,都可以存放我们的静态文件;

比如我们访问 http://localhost:8080/1.js , 它会去这些文件夹中寻找对应的静态资源文件;

得出结论:优先级:resouces>static(默认)》public

7、首页以及图标的定制

1、首页处理

搜索welcomePageHandlerMapping,找到相关Bean

private Resource getWelcomePage() {
            String[] var1 = this.resourceProperties.getStaticLocations();
            int var2 = var1.length;

            for(int var3 = 0; var3 < var2; ++var3) {
                String location = var1[var3];
                Resource indexHtml = this.getIndexHtml(location);
                if (indexHtml != null) {
                    return indexHtml;
                }
            }

            ServletContext servletContext = this.getServletContext();
            if (servletContext != null) {
                return this.getIndexHtml((Resource)(new ServletContextResource(servletContext, "/")));
            } else {
                return null;
            }
        }

        private Resource getIndexHtml(String location) {
            return this.getIndexHtml(this.resourceLoader.getResource(location));
        }

        private Resource getIndexHtml(Resource location) {
            try {
                Resource resource = location.createRelative("index.html");
                if (resource.exists() && resource.getURL() != null) {
                    return resource;
                }
            } catch (Exception var3) {
            }

            return null;
        }

==欢迎页,静态资源文件夹下的所有 index.html 页面;被 /** 映射。

访问 http://localhost:8080/ ,就会找静态资源文件夹下的 index.html

2、网站图标ico

1、关闭SpringBoot默认图标

application.properties

# 关闭默认图标
spring.mvc.favicon.enabled=false

2、自己放一个图标在静态资源目录下,静态资源 目录下

然后在Springboot的配置文件中关闭默认图标即可

8、Thymeleaf模板引擎

1、Thymeleaf是什么

首先,看一下官网的描述:Thymeleaf is a modern server-side Java template engine for both web and standalone environments, capable of processing HTML, XML, JavaScript, CSS and even plain text.

意思就是Thymeleaf是适用于Web和独立环境的现代服务器端Java模板引擎,能够处理HTML,XML,JavaScript,CSS甚至纯文本。

2、Thymeleaf有何优势

简单点说,就是Thymeleaf提供一种优雅且高度可维护的模板创建方式,可缩小设计团队与开发团队之间的差距。Thymeleaf也已经从一开始就遵从Web标准,尤其是HTML5,这就允许创建一些完全验证的模板。

3、Thymeleaf 特点

  • Thymeleaf 在有网络和无网络的环境下皆可运行,即它可以让美工在浏览器查看页面的静态效果,也可以让程序员在服务器查看带数据的动态页面效果。这是由于它支持 html 原型,然后在 html 标签里增加额外的属性来达到模板 + 数据的展示方式。浏览器解释 html 时会忽略未定义的标签属性,所以 thymeleaf 的模板可以静态地运行;当有数据返回到页面时,Thymeleaf 标签会动态地替换掉静态内容,使页面动态显示;
  • Thymeleaf 开箱即用的特性。它提供标准和 Spring 标准两种方言,可以直接套用模板实现 JSTL、 OGNL 表达式效果,避免每天套模板、改 JSTL、改标签的困扰。同时开发人员也可以扩展和创建自定义的方言。
  • Thymeleaf 提供 Spring 标准方言和一个与 SpringMVC 完美集成的可选模块,可以快速的实现表单绑定、属性编辑器、国际化等功能。

4、引入Thymeleaf        

三方式:

Thymeleaf 官网:https://www.thymeleaf.org/

Thymeleaf 在Github 的主页:https://github.com/thymeleaf/thymeleaf

Spring官方文档:找到我们对应的版本

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

Thymeleaf的自动配置类:ThymeleafProperties

@ConfigurationProperties(
    prefix = "spring.thymeleaf"
)
public class ThymeleafProperties {
    private static final Charset DEFAULT_ENCODING;
    public static final String DEFAULT_PREFIX = "classpath:/templates/";  //前缀
    public static final String DEFAULT_SUFFIX = ".html";                  //后缀
    private boolean checkTemplate = true;
    private boolean checkTemplateLocation = true;
    private String prefix = "classpath:/templates/";
    private String suffix = ".html";
    private String mode = "HTML";
    private Charset encoding;
 //.....   
}
  1. 我们可以看到默认的前缀和后缀!
  2. 只需要把我们的html页面放在类路径下的templates下,thymeleaf就可以帮我们自动渲染了。
  3. 使用thymeleaf什么都不需要配置,只需要将他放在指定的文件夹下即可!

测试:controller类

@Controller
public class IndexController {

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

templates目录下新建test.html

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

8、Thymeleaf 语法学习

学习文档地址

测试:

1、Controller层

@Controller
public class IndexController {

    @RequestMapping("/test")
    public String test(Model model){
        //存入数据
        model.addAttribute("msg","<h1>Hello SpringBoot!</h1>");

        model.addAttribute("users", Arrays.asList("hua","你好帅"));
        return "test";
    }
}

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<!--所有的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>

3、启动项目测试!

大多数Thymeleaf属性允许将它们的值设置为或包含表达式,由于它们使用的方言,我们将其称为标准表达式。这些表达式可以有五种类型:

  • ${...} : 变量表达式。
  • *{...} : 选择表达式。
  • #{...} : 消息 (i18n) 表达式。
  • @{...} : 链接 (URL) 表达式。
  • ~{...} : 片段表达式。

9、SpringMVC拓展

官方文档

SpringBoot已经帮我们自动配置好了大部分的SpringMVC中的配置,但是拦截器,视图控制器等没有配置,这时候我们可以拓展自动配置

怎么进行扩展:

实现WebMvcConfigurer接口

springboot官方对于mvc的扩展时,建议我们实现WebMvcConfigurer接口,并且要将该实现类加入到容器中。

在以前比如 springboot1.x.x 的时候,我们可以继承WebMvcConfigurerAdapter这个抽象类来完成对mvc的扩展,但是这个类值·全部是对WebMvcConfigurer的空实现

并且WebMvcConfigurer接口中的方法全是default方法(java8之后支持在接口中定义default和static方法),所以我们可以只重写我们需要重写的方法:

我们可以复写这个接口中的default方法实现扩展,比如

package com.hua.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.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import java.util.Locale;

@Configuration
//如果你想定制一些定制化的功能,只需要写这个组件,然后将他交给SpringBoot管理,SpringBoot就会帮我们自动装配
public class MyConfig implements WebMvcConfigurer {

    @Bean
    public ViewResolver getMyViewResolver(){
        return new MyViewResolver();
    }
        //localhost:8080/hua  实际上跳转到test.html中
    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/hua").setViewName("/test");
    }
}
//只要实现了ViewResolver接口的类,我们就可以把它看作视图解析器
//自定义自己的试图解析器
class MyViewResolver implements ViewResolver{

    @Override
    public View resolveViewName(String viewName, Locale locale) throws Exception {
        return null;
    }
}

 实现了扩展自己的视图解析器和视图控制器

公式:如果看到了xxxConfigurer证明我们可以对xxx进行功能扩展

10.全面接管SpringMVC

全面接管即:SpringBoot对SpringMVC的自动配置不需要了,所有都是我们自己去配置!

只需在我们的配置类中要加一个@EnableWebMvc。

我们看下如果我们全面接管了SpringMVC了,我们之前SpringBoot给我们配置的静态资源映射一定会无效,我们可以去测试一下;

不加注解之前,访问首页:

给配置类加上注解:@EnableWebMvc

我们发现所有的SpringMVC自动配置都失效了!回归到了最初的样子;

当然,我们开发中,不推荐使用全面接管SpringMVC

思考问题?为什么加了一个注解,自动配置就失效了!我们看下源码:

1、这里发现它是导入了一个类,我们可以继续进去看

@Import({DelegatingWebMvcConfiguration.class})
public @interface EnableWebMvc {
}

 2、它继承了一个父类 WebMvcConfigurationSupport


public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {
  // ......
}

3、我们来回顾一下Webmvc自动配置类


@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })
// 这个注解的意思就是:容器中没有这个组件的时候,这个自动配置类才生效
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
@AutoConfigureAfter({ DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class,
    ValidationAutoConfiguration.class })
public class WebMvcAutoConfiguration {
    

重点:// 这个注解的意思就是:容器中没有这个组件的时候,这个自动配置类才生效
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)

总结一句话:@EnableWebMvc将WebMvcConfigurationSupport组件导入进来了;

而导入的WebMvcConfigurationSupport只是SpringMVC最基本的功能!

在SpringBoot中会有非常多的扩展配置,只要看见了这个,我们就应该多留心注意~

11、员工管理系统

1、员工管理系统:准备工作

导入我们的所有提供的资源!

pojo 及 dao 放到项目对应的路径下:

pojo:

package com.hua.pojo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Department {
    private Integer id;
    private String name;

}
package com.hua.pojo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.Date;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Employee {
    private Integer id;
    private String lastName;
    private String email;
    private Integer gender;//0:女,1:男
    private Department department;
    private Date birth;

}

dao层

package com.hua.Mapper;

import com.hua.pojo.Department;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Repository;


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

@Repository
public class DepatmentDao {
    //模拟数据
    private static Map<Integer, Department> departmentMap;
    static{
        departmentMap=new HashMap<>();//创建一个部门类
        departmentMap.put(101,new Department(101,"教学部"));
        departmentMap.put(102,new Department(101,"市场部"));
        departmentMap.put(103,new Department(101,"教研部"));
        departmentMap.put(104,new Department(101,"运营部"));
        departmentMap.put(105,new Department(101,"后勤部"));
    }
    //获得所有部门信息
    public Collection<Department>getDepartments(){
        return departmentMap.values();
    }
    //通过id得到部门
    public Department getDepartmentById(Integer id){
        return departmentMap.get(id);
    }

}
package com.hua.Mapper;

import com.hua.pojo.Department;
import com.hua.pojo.Employee;
import org.springframework.beans.factory.annotation.Autowired;

import java.util.*;

public class EmployeeDao {
    private static Map<Integer, Employee> employeeMap=null;

    @Autowired
    private static DepatmentDao depatmentDao;

    static{
        employeeMap=new HashMap<Integer,Employee>();
        employeeMap.put(101,new Employee(1,"华","123",
                1,depatmentDao.getDepartmentById(101),new Date()));

        employeeMap.put(102,new Employee(1,"红草","123",
                1,depatmentDao.getDepartmentById(102),new Date()));

        employeeMap.put(103,new Employee(1,"湖东狗","123",
                1,depatmentDao.getDepartmentById(103),new Date()));

        employeeMap.put(104,new Employee(1,"遮浪耀","123",
                1,depatmentDao.getDepartmentById(104),new Date()));

        employeeMap.put(105,new Employee(1,"甲子佳","123",
                1,depatmentDao.getDepartmentById(105),new Date()));
    }
    public static Integer id=106;
    //增加一个员工
    public void addEmployee(Employee employee){
        if (employee.getId()==null){
            employee.setId(id++);
        }else {
            throw new RuntimeException("改员工已经存在");
        }
        employee.setDepartment(depatmentDao.getDepartmentById(employee.getDepartment().getId()));
        employeeMap.put(employee.getId(),employee);
    }
    //查询全部员工
    public Collection<Employee>getEmployees(){
        return employeeMap.values();
    }
    //通过id查员工
    public Employee getEmployeeById(Integer id){
        return employeeMap.get(id);
    }
    //删除员工通过id
    public void deleteEmployeeById(Integer id){
        employeeMap.remove(id);
    }
}

导入完毕这些之后,还需要导入我们的前端页面,及静态资源文件!

  • css,js等放在static文件夹下
  • html放在templates文件夹下

2、员工管理系统:首页实现 

访问首页

方式一:写一个controller实现!

package com.hua.controller;

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

@Controller
public class IndexController {

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

方式二:自己编写MVC的扩展配置

package com.hua.config;

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

import java.util.Locale;

@Configuration
//如果你想定制一些定制化的功能,只需要写这个组件,然后将他交给SpringBoot管理,SpringBoot就会帮我们自动装配
public class MyConfig implements WebMvcConfigurer {

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

解决资源导入的问题;

#关闭thmeleaf的缓存
spring.thymeleaf.cache=false

#设置后 thymeleaf中的@{..}自动变换为@{./hua....}
server.servlet.context-path=/hua

现在你访问localhost:8080 就不行了,需要访问localhost:8080/kk==

为了保证资源导入稳定,在所有资源导入的时候使用 th:去替换原有的资源路径!

1、先在所有需要使用到th:的html文件导入该配置

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

2、修改所有本地静态资源的链

<!-- Bootstrap core CSS -->
<link th:href="@{/css/bootstrap.min.css}" rel="stylesheet">

<!-- Custom styles for this template -->
<link th:href="@{/css/signin.css}" rel="stylesheet">

<img class="mb-4" th:src="@{/img/bootstrap-solid.png}" alt="" width="72" height="72">

3、员工管理系统:国际化

**第一步 :**编写国际化配置文件,抽取页面需要显示的国际化页面消息。

**第二步:**在resources资源文件下新建一个i18n目录,建立一个login.propetries文件,还有一个login_zh_CN.properties,login_en_US.properties,发现IDEA自动识别了我们要做国际化操作;文件夹变了

第三步:编写propertie文件,login.properties为默认的页面文字输出

第四步 :看一下SpringBoot对国际化的自动配置! 

涉及到一个类: MessageSourceAutoConfiguration ,里面有一个方法,发现SpringBoot已经自动配置好了管理我们国际化资源文件的组件 ResourceBundleMessageSource;

public class MessageSourceAutoConfiguration {
    private static final Resource[] NO_RESOURCES = new Resource[0];

    public MessageSourceAutoConfiguration() {
    }

    @Bean
    @ConfigurationProperties(prefix = "spring.messages") //我们的配置文件可以直接放在类路径下叫: messages.properties, 就可以进行国际化操作了
    public MessageSourceProperties messageSourceProperties() {
        return new MessageSourceProperties();
    }

    @Bean
    public MessageSource messageSource(MessageSourceProperties properties) {
        ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
        if (StringUtils.hasText(properties.getBasename())) {        //设置国际化文件的基础名
            messageSource.setBasenames(StringUtils.commaDelimitedListToStringArray(StringUtils.trimAllWhitespace(properties.getBasename())));
        }

        if (properties.getEncoding() != null) {
            messageSource.setDefaultEncoding(properties.getEncoding().name());
        }

        messageSource.setFallbackToSystemLocale(properties.isFallbackToSystemLocale());
        Duration cacheDuration = properties.getCacheDuration();
        if (cacheDuration != null) {
            messageSource.setCacheMillis(cacheDuration.toMillis());
        }

        messageSource.setAlwaysUseMessageFormat(properties.isAlwaysUseMessageFormat());
        messageSource.setUseCodeAsDefaultMessage(properties.isUseCodeAsDefaultMessage());
        return messageSource;
    }
}

真实的情况是放在了i18n目录下,所以我们要去配置这个messages的路径;

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

第五步 : 去页面获取国际化的值;

查看Thymeleaf的文档,找到message取值操作为: #{…}。

<h1 class="h3 mb-3 font-weight-normal" th:text="#{login.tip}">Please sign in</h1>

<input type="text" class="form-control" th:placeholder="#{login.username}" required="" autofocus="">
<input type="password" class="form-control" th:placeholder="#{login.password}" required="">
		
 <input type="checkbox" value="remember-me" >[[#{login.remember}]]
		    
<button class="btn btn-lg btn-primary btn-block" type="submit" >[[#{login.btn}]]</button>

根据按钮自动切换中文英文!

​ 在Spring中有一个国际化的Locale (区域信息对象);里面有一个叫做LocaleResolver (获取区域信息对象)的解析器

在webmvc自动配置文件就可以看到SpringBoot默认配置了

 @Bean
        @ConditionalOnMissingBean
        @ConditionalOnProperty(
            prefix = "spring.mvc",
            name = {"locale"})
        public LocaleResolver localeResolver() {            //容器中没有就自己配,有的话就用用户配置的
            if (this.mvcProperties.getLocaleResolver() == org.springframework.boot.autoconfigure.web.servlet.WebMvcProperties.LocaleResolver.FIXED) {
                return new FixedLocaleResolver(this.mvcProperties.getLocale());
            } else {
                AcceptHeaderLocaleResolver localeResolver = new AcceptHeaderLocaleResolver();
                localeResolver.setDefaultLocale(this.mvcProperties.getLocale());
                return localeResolver;
            }
        }

AcceptHeaderLocaleResolver 这个类中有一个方法

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

写一个自己的LocaleResolver,可以在链接上携带区域信息!

package com.hua.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) {
        String language = request.getParameter("l");
//        System.out.println(language);
        Locale locale = Locale.getDefault();
        if (!StringUtils.isEmpty(language)){
            String[] s = language.split("_");
            locale = new Locale(s[0],s[1]);
        }
        return locale;
    }

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

    }
}

在SpringMVC的扩展文件中注入,@Bean表示交给SpringBoot管理 

写一个自己的LocaleResolver,可以在链接上携带区域信息!

修改一下前端页面的跳转连接;

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

测试页面

4、员工管理系统:登陆功能实现 

问题:输入任意用户名都可以登录成功!

原因:templates下的页面只能通过Controller跳转实现,而static下的页面是能直接被外界访问的,就能正常访问

把登录页面的表单提交地址写一个controller,所有表单标签都需要加上一个name属性

<form class="form-signin" th:action="@{/user/login}"  >
    
<input type="text" name="username" class="form-control" th:placeholder="#{login.username}" required="" autofocus="">
    
<input type="password" name="password" class="form-control" th:placeholder="#{login.password}" required="">

 编写对应的controller

package com.hua.controller;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.thymeleaf.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
public class loginController {

    @RequestMapping("/login")
    public String login(
            @RequestParam("username") String name,
            @RequestParam("pas") String password,
            Model model){
      
        if(!empty && "123".equals(password)){
            System.out.println(2);
            return "dashboard";
        }else {
            System.out.println(1);
            model.addAttribute("msg","密码错误或者用户名错误");
            return "index";
        }
    }
}

登录失败的话,我们需要将后台信息输出到前台,可以在首页标题下面加上判断!

<!--         msg的消息为空  则不显示-->
         <p style="color: red" th:text="${msg}" th:if="${not #strings.isEmpty(msg)}"></p>

img

将 Controller 的代码改为重定向;

//登录成功!防止表单重复提交,我们重定向
return "redirect:/main.html";

5、员工管理系统:登陆拦截器

定向成功之后!我们解决了之前资源没有加载进来的问题!后台主页正常显示!

但是又发现新的问题,我们可以直接登录到后台主页,不用登录也可以实现!

怎么处理这个问题呢?我们可以使用拦截器机制,实现登录检查!

1、自定义一个拦截器

这个定义和我们在SpringMVC中学的是一样的
package com.hua.config;

import org.springframework.web.servlet.HandlerInterceptor;

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

public class LoginHandlerInterceptot implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        if(request.getSession().getAttribute("loginUser")!=null){
            return true;
        }else {
            request.setAttribute("msg","请先登录");
            request.getRequestDispatcher("/index").forward(request,response);
        }
        return false;
    }
}

写一个类去实现HandlerInterceptor,然后在扩展MVC的配置文件中加入,重写他的addInterceptors方法,addPathPatterns里面的参数是拦截的路径,而excludePathPatterns中的参数是不拦截的路径

@Override
        public void addInterceptors(InterceptorRegistry registry) {
            registry.addInterceptor(new LoginHandlerInterceptot()).addPathPatterns("/**")
                    .excludePathPatterns("/index","/","/loging","/css/*","/img/*","/js/*");
        }

这样做了之后就只有登录才可以进入main页面拉

6、员工管理系统:展示员工列表

主页点击Customers,显示列表页面;

1.将首页的侧边栏Customers改为员工管理

2.a链接添加请求

编写处理请求的controller层

@RequestMapping("/goToList")
    public String goToList(Model model){
        Collection<Employee> employees = employeeDao.getEmployees();
        model.addAttribute("epms",employees);
            return "list";
    }

Thymeleaf 公共页面元素抽取 

1.抽取公共片段 th:fragment 定义模板名

2.引入公共片段 th:insert 插入模板名:插入一个div

3.引入公共片段 th:replace插入模板名:替换元素插入

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">

<!--顶部侧边栏-->
<nav class="navbar navbar-dark sticky-top bg-dark flex-md-nowrap p-0" th:fragment="topbar">
    <a class="navbar-brand col-sm-3 col-md-2 mr-0" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">[[${session.loginUser}]]</a>
    <input class="form-control form-control-dark w-100" type="text" placeholder="Search" aria-label="Search">
    <ul class="navbar-nav px-3">
        <li class="nav-item text-nowrap">
            <a class="nav-link" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">Sign out</a>
        </li>
    </ul>
</nav>
<!--左侧侧边栏-->
<nav class="col-md-2 d-none d-md-block bg-light sidebar" th:fragment="sidebar">
    <div class="sidebar-sticky">
        <ul class="nav flex-column">
            <li class="nav-item">
                <a th:class="${active=='main' ?' nav-link active' : 'nav-link'}" th:href="@{/main}">
                    <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-home">
                        <path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"></path>
                        <polyline points="9 22 9 12 15 12 15 22"></polyline>
                    </svg>
                    首页 <span class="sr-only">(current)</span>
                </a>
            </li>
            <li class="nav-item">
                <a class="nav-link" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">
                    <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-file">
                        <path d="M13 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V9z"></path>
                        <polyline points="13 2 13 9 20 9"></polyline>
                    </svg>
                    Orders
                </a>
            </li>
            <li class="nav-item">
                <a class="nav-link" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">
                    <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-shopping-cart">
                        <circle cx="9" cy="21" r="1"></circle>
                        <circle cx="20" cy="21" r="1"></circle>
                        <path d="M1 1h4l2.68 13.39a2 2 0 0 0 2 1.61h9.72a2 2 0 0 0 2-1.61L23 6H6"></path>
                    </svg>
                    Products
                </a>
            </li>
            <li class="nav-item">
                <a th:class="${active=='list'? 'nav-link active' :'nav-link'}" th:href="@{/goToList}">
                    <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-users">
                        <path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"></path>
                        <circle cx="9" cy="7" r="4"></circle>
                        <path d="M23 21v-2a4 4 0 0 0-3-3.87"></path>
                        <path d="M16 3.13a4 4 0 0 1 0 7.75"></path>
                    </svg>
                    员工管理
                </a>
            </li>
            <li class="nav-item">
                <a class="nav-link" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">
                    <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-bar-chart-2">
                        <line x1="18" y1="20" x2="18" y2="10"></line>
                        <line x1="12" y1="20" x2="12" y2="4"></line>
                        <line x1="6" y1="20" x2="6" y2="14"></line>
                    </svg>
                    Reports
                </a>
            </li>
            <li class="nav-item">
                <a class="nav-link" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">
                    <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-layers">
                        <polygon points="12 2 2 7 12 12 22 7 12 2"></polygon>
                        <polyline points="2 17 12 22 22 17"></polyline>
                        <polyline points="2 12 12 17 22 12"></polyline>
                    </svg>
                    Integrations
                </a>
            </li>
        </ul>

        <h6 class="sidebar-heading d-flex justify-content-between align-items-center px-3 mt-4 mb-1 text-muted">
            <span>Saved reports</span>
            <a class="d-flex align-items-center text-muted" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">
                <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-plus-circle"><circle cx="12" cy="12" r="10"></circle><line x1="12" y1="8" x2="12" y2="16"></line><line x1="8" y1="12" x2="16" y2="12"></line></svg>
            </a>
        </h6>
        <ul class="nav flex-column mb-2">
            <li class="nav-item">
                <a class="nav-link" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">
                    <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-file-text">
                        <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
                        <polyline points="14 2 14 8 20 8"></polyline>
                        <line x1="16" y1="13" x2="8" y2="13"></line>
                        <line x1="16" y1="17" x2="8" y2="17"></line>
                        <polyline points="10 9 9 9 8 9"></polyline>
                    </svg>
                    Current month
                </a>
            </li>
            <li class="nav-item">
                <a class="nav-link" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">
                    <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-file-text">
                        <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
                        <polyline points="14 2 14 8 20 8"></polyline>
                        <line x1="16" y1="13" x2="8" y2="13"></line>
                        <line x1="16" y1="17" x2="8" y2="17"></line>
                        <polyline points="10 9 9 9 8 9"></polyline>
                    </svg>
                    Last quarter
                </a>
            </li>
            <li class="nav-item">
                <a class="nav-link" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">
                    <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-file-text">
                        <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
                        <polyline points="14 2 14 8 20 8"></polyline>
                        <line x1="16" y1="13" x2="8" y2="13"></line>
                        <line x1="16" y1="17" x2="8" y2="17"></line>
                        <polyline points="10 9 9 9 8 9"></polyline>
                    </svg>
                    Social engagement
                </a>
            </li>
            <li class="nav-item">
                <a class="nav-link" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">
                    <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-file-text">
                        <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
                        <polyline points="14 2 14 8 20 8"></polyline>
                        <line x1="16" y1="13" x2="8" y2="13"></line>
                        <line x1="16" y1="17" x2="8" y2="17"></line>
                        <polyline points="10 9 9 9 8 9"></polyline>
                    </svg>
                    Year-end sale
                </a>
            </li>
        </ul>
    </div>
</nav>
</html>

 抽取后的主页:

<!DOCTYPE html>
<!-- saved from url=(0052)http://getbootstrap.com/docs/4.0/examples/dashboard/ -->
<html lang="en" xmlns:th="http://www.thymeleaf.org">
	<head>
		<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
		<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
		<meta name="description" content="">
		<meta name="author" content="">

		<title>Dashboard Template for Bootstrap</title>
		<!-- Bootstrap core CSS -->
		<link th:href="@{/css/bootstrap.min.css}" rel="stylesheet">

		<!-- Custom styles for this template -->
		<link th:href="@{/css/dashboard.css}" rel="stylesheet">
		<style type="text/css">
			/* Chart.js */
			
			@-webkit-keyframes chartjs-render-animation {
				from {
					opacity: 0.99
				}
				to {
					opacity: 1
				}
			}
			
			@keyframes chartjs-render-animation {
				from {
					opacity: 0.99
				}
				to {
					opacity: 1
				}
			}
			
			.chartjs-render-monitor {
				-webkit-animation: chartjs-render-animation 0.001s;
				animation: chartjs-render-animation 0.001s;
			}
		</style>
	</head>

	<body>
	<!--顶部侧边栏-->
	<div th:replace="~{common/common::topbar}"></div>
	<!--侧边栏-->
		<div class="container-fluid">
			<div class="row">
					<div th:replace="~{common/common::sidebar(active='main')}"></div>

				<main role="main" class="col-md-9 ml-sm-auto col-lg-10 pt-3 px-4">
					<div class="chartjs-size-monitor" style="position: absolute; left: 0px; top: 0px; right: 0px; bottom: 0px; overflow: hidden; pointer-events: none; visibility: hidden; z-index: -1;">
						<div class="chartjs-size-monitor-expand" style="position:absolute;left:0;top:0;right:0;bottom:0;overflow:hidden;pointer-events:none;visibility:hidden;z-index:-1;">
							<div style="position:absolute;width:1000000px;height:1000000px;left:0;top:0"></div>
						</div>
						<div class="chartjs-size-monitor-shrink" style="position:absolute;left:0;top:0;right:0;bottom:0;overflow:hidden;pointer-events:none;visibility:hidden;z-index:-1;">
							<div style="position:absolute;width:200%;height:200%;left:0; top:0"></div>
						</div>
					</div>
					<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
						<h1 class="h2">Dashboard</h1>
						<div class="btn-toolbar mb-2 mb-md-0">
							<div class="btn-group mr-2">
								<button class="btn btn-sm btn-outline-secondary">Share</button>
								<button class="btn btn-sm btn-outline-secondary">Export</button>
							</div>
							<button class="btn btn-sm btn-outline-secondary dropdown-toggle">
                <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-calendar"><rect x="3" y="4" width="18" height="18" rx="2" ry="2"></rect><line x1="16" y1="2" x2="16" y2="6"></line><line x1="8" y1="2" x2="8" y2="6"></line><line x1="3" y1="10" x2="21" y2="10"></line></svg>
                This week
              </button>
						</div>
					</div>

					<canvas class="my-4 chartjs-render-monitor" id="myChart" width="1076" height="454" style="display: block; width: 1076px; height: 454px;"></canvas>


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

		<!-- Bootstrap core JavaScript
    ================================================== -->
		<!-- Placed at the end of the document so the pages load faster -->
		<script type="text/javascript" th:src="@{/js/jquery-3.2.1.slim.min.js}" ></script>
		<script type="text/javascript" th:src="@{/js/popper.min.js}" ></script>
		<script type="text/javascript" th:src="@{/js/bootstrap.min.js}" ></script>

		<!-- Icons -->
		<script type="text/javascript" th:src="@{/js/feather.min.js}" ></script>
		<script>
			feather.replace()
		</script>

		<!-- Graphs -->
		<script type="text/javascript" th:src="@{/js/Chart.min.js}" ></script>
		<script>
			var ctx = document.getElementById("myChart");
			var myChart = new Chart(ctx, {
				type: 'line',
				data: {
					labels: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"],
					datasets: [{
						data: [15339, 21345, 18483, 24003, 23489, 24092, 12034],
						lineTension: 0,
						backgroundColor: 'transparent',
						borderColor: '#007bff',
						borderWidth: 4,
						pointBackgroundColor: '#007bff'
					}]
				},
				options: {
					scales: {
						yAxes: [{
							ticks: {
								beginAtZero: false
							}
						}]
					},
					legend: {
						display: false,
					}
				}
			});
		</script>

	</body>

</html>

抽取后的员工列表页面:

<!DOCTYPE html>
<!-- saved from url=(0052)http://getbootstrap.com/docs/4.0/examples/dashboard/ -->
<html lang="en" xmlns:th="http://www.thymeleaf.org">

	<head>
		<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
		<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
		<meta name="description" content="">
		<meta name="author" content="">

		<title>Dashboard Template for Bootstrap</title>
		<!-- Bootstrap core CSS -->
		<link th:href="@{/css/bootstrap.min.css}" rel="stylesheet">

		<!-- Custom styles for this template -->
		<link th:href="@{/css/dashboard.css}" rel="stylesheet">
		<style type="text/css">
			/* Chart.js */
			
			@-webkit-keyframes chartjs-render-animation {
				from {
					opacity: 0.99
				}
				to {
					opacity: 1
				}
			}
			
			@keyframes chartjs-render-animation {
				from {
					opacity: 0.99
				}
				to {
					opacity: 1
				}
			}
			
			.chartjs-render-monitor {
				-webkit-animation: chartjs-render-animation 0.001s;
				animation: chartjs-render-animation 0.001s;
			}
		</style>
	</head>

	<body>
<!--顶部侧边栏-->
<div th:replace="~{common/common::topbar}"></div>
<!--侧边栏-->
		<div class="container-fluid">
			<div class="row">
				<div th:replace="~{common/common::sidebar(active='list')}"></div>

				<main role="main" class="col-md-9 ml-sm-auto col-lg-10 pt-3 px-4">
					<h2><a th:href="@{/toAdd}" class="btn btn-sm btn-success">添加员工</a></h2>
					<div class="table-responsive">
						<table class="table table-striped table-sm">
							<thead>
								<tr>
									<th>id</th>
									<th>lastName</th>
									<th>email</th>
									<th>gender</th>
									<th>department</th>
									<th>birth</th>
									<th>operation</th>
								</tr>
							</thead>
							<tbody>
								<tr th:each="emp:${epms}">
									<td th:text="${emp.id}"></td>
									<td th:text="${emp.lastName}"></td>
									<td th:text="${emp.email}"></td>
									<td th:text="${emp.gender==0?'女':'男'}"></td>
									<td th:text="${emp.getDepartment().getName()}"></td>
									<td th:text="${#dates.format(emp.birth,'yyyy-MM-dd')}"></td>
									<td>
										<a th:href="@{/update/}+${emp.getDepartment().getId()}" class="btn btn-sm btn-primary">修改</a>
										<a href="#" class="btn btn-sm btn-danger">删除</a>
									</td>
								</tr>
							</tbody>
						</table>
					</div>
				</main>
			</div>
		</div>

		<!-- Bootstrap core JavaScript
    ================================================== -->
		<!-- Placed at the end of the document so the pages load faster -->
		<script type="text/javascript" th:src="@{/js/jquery-3.2.1.slim.min.js}"></script>
		<script type="text/javascript" th:src="@{/js/popper.min.js}"></script>
		<script type="text/javascript" th:src="@{/js/bootstrap.min.js}"></script>

		<!-- Icons -->
		<script type="text/javascript" th:src="@{/js/feather.min.js}"></script>
		<script>
			feather.replace()
		</script>

		<!-- Graphs -->
		<script type="text/javascript" th:src="@{/js/Chart.min.js}"></script>
		<script>
			var ctx = document.getElementById("myChart");
			var myChart = new Chart(ctx, {
				type: 'line',
				data: {
					labels: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"],
					datasets: [{
						data: [15339, 21345, 18483, 24003, 23489, 24092, 12034],
						lineTension: 0,
						backgroundColor: 'transparent',
						borderColor: '#007bff',
						borderWidth: 4,
						pointBackgroundColor: '#007bff'
					}]
				},
				options: {
					scales: {
						yAxes: [{
							ticks: {
								beginAtZero: false
							}
						}]
					},
					legend: {
						display: false,
					}
				}
			});
		</script>

	</body>

</html>

高亮设置

list.html        

<!--侧边-->
<div th:replace="~{commons/commons::sidebar(active='list')}"></div>

dashboard.html

<!--侧边-->
   <div th:replace="~{commons/commons::sidebar(active='main')}"></div>

 

 抽取页面接收的active:list

 抽取页面接收的active:top

遍历我们的员工信息

						<table class="table table-striped table-sm">
							<thead>
								<tr>
									<th>id</th>
									<th>lastName</th>
									<th>email</th>
									<th>gender</th>
									<th>department</th>
									<th>birth</th>
									<th>operation</th>
								</tr>
							</thead>
							<tbody>
								<tr th:each="emp:${epms}">
									<td th:text="${emp.id}"></td>
									<td th:text="${emp.lastName}"></td>
									<td th:text="${emp.email}"></td>
									<td th:text="${emp.gender==0?'女':'男'}"></td>
									<td th:text="${emp.getDepartment().getName()}"></td>
									<td th:text="${#dates.format(emp.birth,'yyyy-MM-dd')}"></td>
									<td>
										<a th:href="@{/update/}+${emp.getDepartment().getId()}" class="btn btn-sm btn-primary">修改</a>
										<a href="#" class="btn btn-sm btn-danger">删除</a>
									</td>
								</tr>
							</tbody>
						</table>

7、员工管理系统:添加员工信息

1.在list.html页面中将添加员工信息改为超链接

<h2><a th:href="@{/toAdd}" class="btn btn-sm btn-success">添加员工</a></h2>

2.编写对应的controller层

@GetMapping("/toAdd")
public String goToAdd(Model model){
    Collection<Employee> employees = employeeDao.getEmployees();
    model.addAttribute("epms",employees);
    return "add";
}

3.添加前端页面;复制list页面,修改即可

<!DOCTYPE html>
<!-- saved from url=(0052)http://getbootstrap.com/docs/4.0/examples/dashboard/ -->
<html lang="en" xmlns:th="http://www.thymeleaf.org">

	<head>
		<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
		<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
		<meta name="description" content="">
		<meta name="author" content="">

		<title>Dashboard Template for Bootstrap</title>
		<!-- Bootstrap core CSS -->
		<link th:href="@{/css/bootstrap.min.css}" rel="stylesheet">

		<!-- Custom styles for this template -->
		<link th:href="@{/css/dashboard.css}" rel="stylesheet">
		<style type="text/css">
			/* Chart.js */
			
			@-webkit-keyframes chartjs-render-animation {
				from {
					opacity: 0.99
				}
				to {
					opacity: 1
				}
			}
			
			@keyframes chartjs-render-animation {
				from {
					opacity: 0.99
				}
				to {
					opacity: 1
				}
			}
			
			.chartjs-render-monitor {
				-webkit-animation: chartjs-render-animation 0.001s;
				animation: chartjs-render-animation 0.001s;
			}
		</style>
	</head>

	<body>
<!--顶部侧边栏-->
<div th:replace="~{common/common::topbar}"></div>
<!--侧边栏-->
		<div class="container-fluid">
			<div class="row">
				<div th:replace="~{common/common::sidebar(active='list')}"></div>

				<main role="main" class="col-md-9 ml-sm-auto col-lg-10 pt-3 px-4">
					<main role="main" class="col-md-9 ml-sm-auto col-lg-10 pt-3 px-4">

						<form th:action="@{/toAdd}" method="post">
							<table class="table table-striped sortable">
								<thead>
								</thead>
								<tbody>
								<tr>
									<th>LastName</th>
									<td><input type="text" name="lastName"/><span class="error"/></td>
								</tr>
								<tr>
									<th>Email</th>
									<td><input type="email" name="email"/></td>
								</tr>
								<tr>
									<th>Gender</th>
									<td>
										<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>
									</td>
								</tr>
								<tr>
									<th>department</th>
									<td>
										<!--我们在controller接收到的时一个Employee  所以我们需要提交的是其中的一个属性 所以是department.id-->
										<select class="form-control" name="department.id">
											<option th:each="emp:${epms}"
													th:text="${emp.getDepartment().getName()}"
													th:value="${emp.getDepartment().getId()}"></option>
										</select>
									</td>
								</tr>
								<tr>
									<th>Birth</th>
									<td><input type="text" name="birth"/></td>
								</tr>
								<tr>
									<td></td>
									<td>
										<input class="btn btn-success" type="submit" value="添加"/>&nbsp;&nbsp;&nbsp;
										<input class="btn btn-danger" type="reset" value="重置"/>
									</td>
									<td></td>
									<td></td>
									<td></td>
								</tr>
								</tbody>
							</table>
						</form>

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

		<!-- Bootstrap core JavaScript
    ================================================== -->
		<!-- Placed at the end of the document so the pages load faster -->
		<script type="text/javascript" th:src="@{/js/jquery-3.2.1.slim.min.js}"></script>
		<script type="text/javascript" th:src="@{/js/popper.min.js}"></script>
		<script type="text/javascript" th:src="@{/js/bootstrap.min.js}"></script>

		<!-- Icons -->
		<script type="text/javascript" th:src="@{/js/feather.min.js}"></script>
		<script>
			feather.replace()
		</script>

		<!-- Graphs -->
		<script type="text/javascript" th:src="@{/js/Chart.min.js}"></script>
		<script>
			var ctx = document.getElementById("myChart");
			var myChart = new Chart(ctx, {
				type: 'line',
				data: {
					labels: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"],
					datasets: [{
						data: [15339, 21345, 18483, 24003, 23489, 24092, 12034],
						lineTension: 0,
						backgroundColor: 'transparent',
						borderColor: '#007bff',
						borderWidth: 4,
						pointBackgroundColor: '#007bff'
					}]
				},
				options: {
					scales: {
						yAxes: [{
							ticks: {
								beginAtZero: false
							}
						}]
					},
					legend: {
						display: false,
					}
				}
			});
		</script>

	</body>

</html>

4.部门信息下拉框应该选择的是我们提供的数据,所以我们要修改一下前端和后端

@PostMapping("/toAdd")
    public String addEmp(HttpServletRequest  httpServle,Employee employee){
        //自动封装保存数据
        employeeDao.addEmployee(employee);
        return "redirect:goToList";
    }
<!--我们在controller接收到的时一个Employee  所以我们需要提交的是其中的一个属性 所以是department.id-->
<select class="form-control" name="department.id">
<option th:each="emp:${epms}"
th:text="${emp.getDepartment().getName()}"
th:value="${emp.getDepartment().getId()}"></option>
</select>

5、接收前端传过来的属性,将它封装成为对象!首先需要将前端页面空间的name属性编写完毕!然后编写controller层

@PostMapping("/toAdd")
    public String addEmp(HttpServletRequest  httpServle,Employee employee){
        //自动封装保存数据
        employeeDao.addEmployee(employee);
        return "redirect:goToList";
    }

6、在application.properties修改时间格式

# 日期格式化
spring.mvc.date-format=yyyy-MM-dd

8、员工管理系统:修改员工信息 

实现员工修改功能,需要实现两步;

1.点击修改按钮,去到编辑页面,我们可以直接使用添加员工的页面实现

2.显示原数据,修改完毕后跳回列表页面!

首先修改跳转链接的位置;

<a th:href="@{/update/}+${emp.getDepartment().getId()}" class="btn btn-sm btn-primary">修改</a>

编写对应的controller

@GetMapping("/update/{id}")
    //rstful风格参数要用@PathVariable
    public String update(Model model,@PathVariable("id") Integer id){
        Employee employee = employeeDao.getEmployeeById(id);
        model.addAttribute("emp",employee);
        return "udt";
    }

将add页面复制一份,改为update页面;需要修改页面,将我们后台查询数据回显

<main role="main" class="col-md-9 ml-sm-auto col-lg-10 pt-3 px-4">
            <main role="main" class="col-md-9 ml-sm-auto col-lg-10 pt-3 px-4">

                <form th:action="@{/toAdd}" method="post">
                    <table class="table table-striped sortable">
                        <thead>
                        </thead>
                        <tbody>
                        <tr>
                            <th>LastName</th>
                            <td><input type="text" name="lastName"/><span class="error"/></td>
                        </tr>
                        <tr>
                            <th>Email</th>
                            <td><input type="email" name="email"/></td>
                        </tr>
                        <tr>
                            <th>Gender</th>
                            <td>
                                <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>
                            </td>
                        </tr>
                        <tr>
                            <th>department</th>
                            <td>
                                <!--我们在controller接收到的时一个Employee  所以我们需要提交的是其中的一个属性 所以是department.id-->
                                <select class="form-control" name="department.id">
                                    <option th:each="emp:${epms}"
                                            th:text="${emp.getDepartment().getName()}"
                                            th:value="${emp.getDepartment().getId()}"></option>
                                </select>
                            </td>
                        </tr>
                        <tr>
                            <th>Birth</th>
                            <td><input type="text" name="birth"/></td>
                        </tr>
                        <tr>
                            <td></td>
                            <td>
                                <input class="btn btn-success" type="submit" value="添加"/>&nbsp;&nbsp;&nbsp;
                                <input class="btn btn-danger" type="reset" value="重置"/>
                            </td>
                            <td></td>
                            <td></td>
                            <td></td>
                        </tr>
                        </tbody>
                    </table>
                </form>

            </main>
        </main>

日期显示不完美,可以使用日期工具,进行日期的格式化!

<td><input th:value="${#dates.format(emp.birth,'yyyy-MM-dd')}" type="text" name="birth"/></td>

修改表单提交的地址:

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

编写对应的controller

 @PostMapping("/update")
    public String updateEmp(Employee employee){
        employeeDao.addEmployee(employee);
        return "redirect:goToList";
    }

现页面提交的没有id;我们在前端加一个隐藏域,提交id;

<input type="hidden" name="id" th:value="${emp.id}">

 

9、员工管理系统:删除员工

list页面,编写提交地址

<a th:href="@{/del/}+${emp.getId()}" class="btn btn-sm btn-danger">删除</a>

编写Controller

@RequestMapping("/del/{id}")
    public String delEmp(@PathVariable("id") Integer id){
        employeeDao.deleteEmployeeById(id);
        return "redirect:/goToList";
    }

10、员工管理系统:404错误页面

在template中创建一个Error文件夹

500等错误均是这样,

11、员工管理系统:注销功能

<a class="nav-link" th:href="@{/logOut}">Sign out</a>

对应的controller

@RequestMapping("/logOut")
    public String logOug(HttpSession httpSession){
        httpSession.invalidate();
        return "redirect:index";
    }

12、SpringBoot:Mybatis + Druid 数据访问

1、简介

对于数据访问层,无论是SQL(关系型数据库) 还是NOSQL(非关系型数据库),SpringBoot 底层都是采用 SpringData 的方式进行统一处理。

​ Spring Boot 底层都是采用 SpringData 的方式进行统一处理各种数据库,SpringData也是Spring中与SpringBoot、SpringCloud 等齐名的知名项目。

SpingData 官网:https://spring.io/projects

数据库相关的启动器 : 可以参考官方文档:https://docs.spring.io/springboot/docs/2.1.7.RELEASE/reference/htmlsingle/#using-boot-starter

2、JDBC

项目建好之后,发现自动帮我们导入了如下的启动器:

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

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

实现数据库的访问

  1. 先连接上数据库 , 直接使用IDEA连接即可【操作】

  2. SpringBoot中,我们只需要简单的配置就可以实现数据库的连接了;

我们使用yaml的配置文件进行操作!

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

配置完这一些东西后,就可以直接去使用了,因为SpringBoot已经默认帮我们进行了自动配置;我们去测试类测试一下

package com.hua;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.AutoConfigureOrder;
import org.springframework.boot.test.context.SpringBootTest;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;

@SpringBootTest
class SpringBoot04ApplicationTests {

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

}

输出结果:可以看到它默认给我们配置的数据源为 : class com.zaxxer.hikari.HikariDataSource , 我们并没有手动配置

​ 全局搜索一下,找到数据源的所有自动配置都在 :DataSourceProperties 文件下;这里自动配置的原理以及能配置哪些属性?

Spring Boot 2.1.7 默认使用 com.zaxxer.hikari.HikariDataSource 数据源,
而以前版本,如 Spring Boot 1.5 默认使用 org.apache.tomcat.jdbc.pool.DataSource 作为数据源;
 

3、CRUD操作 

​ 1、有了数据源(com.zaxxer.hikari.HikariDataSource),可以拿到数据库连接(java.sql.Connection),有了连接,就可以使用连接和原生的 JDBC 语句来操作数据库

​ 2、即使不使用第三方第数据库操作框架,如 MyBatis等,Spring 本身也对原生的JDBC 做了轻量级的封装,即 org.springframework.jdbc.core.JdbcTemplate。

3、数据库操作的所有 CRUD 方法都在 JdbcTemplate 中。

​ 4、Spring Boot 不仅提供了默认的数据源,同时默认已经配置好了 JdbcTemplate 放在了容器中,程序员只需自己注入即可使用

​ 5、JdbcTemplate 的自动配置原理是依赖 org.springframework.boot.autoconfigure.jdbc 包下的 org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration 类
 

JdbcTemplate主要提供以下几类方法:

  1. execute方法:可以用于执行任何SQL语句,一般用于执行DDL语句;
  2. update方法及batchUpdate方法:update方法用于执行新增、修改、删除等语句;batchUpdate方法用于执行批处理相关语句;
  3. query方法及queryForxxx方法:用于执行查询相关语句;
  4. call方法:用于执行存储过程、函数相关语句。

crontroller

package com.hua.controller;


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

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

@RestController
public class Test {
    //查询
    @Autowired
    JdbcTemplate jdbcTemplate ;
    @RequestMapping("/select")
    public List<Map<String,Object>> getAll(){
        String sql = "select * from book.user";
        List<Map<String, Object>> maps = jdbcTemplate.queryForList(sql);
        return maps;
    }
    @RequestMapping("/add")
    public String add(){
        String sql = "insert into book.user(name,age,gender) values('陈俊华',21,'男')";
        int update = jdbcTemplate.update(sql);
        return "ok";
    }
    @RequestMapping("/update")
    public String update(){
        String sql = "update book.user set name=?,age=?,gender=? where name=?";
        int update = jdbcTemplate.update(sql,"陈俊华",18,"男","陈晓佳");
        return "ok";
    }
    @RequestMapping("/del/{name}")
    public String del(@PathVariable("name") String name){
        String sql = "delete from book.user where name =?";
        int update = jdbcTemplate.update(sql,name);
        return "ok";
    }
}

测试成功!

原理探究 :

org.springframework.boot.autoconfigure.jdbc.DataSourceConfiguration 数据源配置类作用 :根据逻辑判断之后,添加数据源;

SpringBoot默认支持以下数据源:

  • com.zaxxer.hikari.HikariDataSource (Spring Boot 2.0 以上,默认使用此数据源)
  • org.apache.tomcat.jdbc.pool.DataSource
  • org.apache.commons.dbcp2.BasicDataSource

可以使用 spring.datasource.type 指定自定义的数据源类型,值为要使用的连接池实现的完全限定名。默认情况下,它是从类路径自动检测的。

 4、自定义数据源 DruidDataSource

DRUID 简介

  1. ​ Druid 是阿里巴巴开源平台上一个数据库连接池实现,结合了 C3P0、DBCP、PROXOOL 等 DB 池的优点,同时 加入了日志监控。
  2. ​ Druid 可以很好的监控 DB 池连接和 SQL 的执行情况,天生就是针对监控而生的 DB 连接池。
  3. ​ Spring Boot 2.0 以上默认使用 Hikari 数据源,可以说 Hikari 与 Driud 都是当前 Java Web 上最优秀的数据源,我们 来重点介绍 Spring Boot 如何集成 Druid 数据源,如何实现数据库监控。

*com.alibaba.druid.pool.DruidDataSource 基本配置参数如下:* 

 

 

引入数据源

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

切换数据源;

​ 之前已经说过 Spring Boot 2.0 以上默认使用 com.zaxxer.hikari.HikariDataSource 数据源,可以通过 spring.datasource.type 指定数据源。

配置其他参数: 

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

    #Spring Boot 默认是不注入这些属性值的,需要自己绑定
    #druid 数据源专有配置
    initialSize: 5
    minIdle: 5
    maxActive: 20
    maxWait: 60000
    timeBetweenEvictionRunsMillis: 60000
    minEvictableIdleTimeMillis: 300000
    validationQuery: SELECT 1 FROM DUAL
    testWhileIdle: true
    testOnBorrow: false
    testOnReturn: false
    poolPreparedStatements: true
    #配置监控统计拦截的filters,stat:监控统计、log4j:日志记录、wall:防御sql注入
    #如果允许时报错  java.lang.ClassNotFoundException: org.apache.log4j.Priority
    #则导入 log4j 依赖即可,Maven 地址: https://mvnrepository.com/artifact/log4j/log4j
    filters: stat,wall,log4j
    maxPoolPreparedStatementPerConnectionSize: 20
    useGlobalDataSourceStat: true
    connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500

1、配置 Druid 数据源监控

​ Druid 数据源具有监控的功能,并提供了一个web界面方便用户查看,类似安装路由器 时,它也提供了一个默认的 web 页面。 所以第一步需要设置 Druid 的后台管理页面,比如登录账号、密码等配置后台管理

这里和SpringMVC扩展一样,写一个config包在创建一个config类

参数尽量和源码保持一致,以免导入失败

package com.hua.config;

import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.support.http.StatViewServlet;
import com.alibaba.druid.support.http.WebStatFilter;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.sql.DataSource;
import java.util.HashMap;

//配置是死的 根据自身条件更改即可
@Configuration
public class DruidConfig {

    //关联application.yaml中的spring.datasource
    @ConfigurationProperties(prefix = "spring.datasource")
    @Bean
    public DataSource druidDataSource(){
        return new DruidDataSource();
    }

    @Bean
    //测试访问! http://localhost:8080/druid/login.html
    public ServletRegistrationBean statViewServlet(){
        ServletRegistrationBean registra = new ServletRegistrationBean(new StatViewServlet(), "/druid/*");
        HashMap<String, String> initParams  = new HashMap<>();
        initParams .put("loginUsername", "admin"); //后台管理界面的登录账号 key值不能变
        initParams .put("loginPassword", "123456"); //后台管理界面的登录密码 key值不能变

        //后台允许谁可以访问
        initParams .put("allow", "localhost");//表示只有本机可以访问
        //initParams .put("allow", ""):为空或者为null时,表示允许所有访问
        initParams .put("allow", "");
        //deny:Druid 后台拒绝谁访问
        //initParams .put("hua", "192.168.14.20");表示禁止此ip访问
        registra.setInitParameters(initParams);
        return  registra;
    }
}

测试访问! http://localhost:8080/druid/login.html

 

 2、配置 Druid web 监控 filter

这个过滤器的作用就是统计 web 应用请求中所有的数据库信息,比如 发出的 sql 语句,sql 执行的时间、请求次数、请求的 url 地址、以及seesion 监控、数据库表的访问次数 等等。

package com.hua.config;

import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.support.http.StatViewServlet;
import com.alibaba.druid.support.http.WebStatFilter;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.sql.DataSource;
import java.util.HashMap;

//配置是死的 根据自身条件更改即可
@Configuration
public class DruidConfig {

    //关联application.yaml中的spring.datasource
    @ConfigurationProperties(prefix = "spring.datasource")
    @Bean
    public DataSource druidDataSource(){
        return new DruidDataSource();
    }

    @Bean
    //测试访问! http://localhost:8080/druid/login.html
    public ServletRegistrationBean statViewServlet(){
        ServletRegistrationBean registra = new ServletRegistrationBean(new StatViewServlet(), "/druid/*");
        HashMap<String, String> initParams  = new HashMap<>();
        initParams .put("loginUsername", "admin"); //后台管理界面的登录账号 key值不能变
        initParams .put("loginPassword", "123456"); //后台管理界面的登录密码 key值不能变

        //后台允许谁可以访问
        initParams .put("allow", "localhost");//表示只有本机可以访问
        //initParams .put("allow", ""):为空或者为null时,表示允许所有访问
        initParams .put("allow", "");
        //deny:Druid 后台拒绝谁访问
        //initParams .put("hua", "192.168.14.20");表示禁止此ip访问
        registra.setInitParameters(initParams);
        return  registra;
    }
    @Bean
    public FilterRegistrationBean filterRegistrationBean(){
        FilterRegistrationBean<WebStatFilter> bean = new FilterRegistrationBean<>();
        bean.setFilter(new WebStatFilter());

        //可以过滤哪些请求?
        HashMap<String, String> initParams  = new HashMap<>();
        //这些东西不进行统计
        initParams .put("exclusions","*.js,*.css,/druid/*");


        bean.setInitParameters(initParams);
        return bean;
    }
}

如上图所示。

SpringBoot集成Mybatis

导入mybatis依赖

<!-- https://mvnrepository.com/artifact/org.mybatis.spring.boot/mybatis-spring-boot-starter -->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.2.2</version>
</dependency>
<dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
</dependency>

在application.yaml中配置数据库信息,记住如果你用的是idea,要先链接数据库

spring:
  datasource:
    username: mysqlChen
    password: 1234
    url: jdbc:mysql://localhost:3306/book?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8
    driver-class-name: com.mysql.cj.jdbc.Driver

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


mybatis:
  mapper-locations: classpath:mybatis/mapper/*.xml   表示mapper的xml在那个路径下
  type-aliases-package: com.hua.pojo  给实体类取别名

配置完要先测试一下能不能正常获取链接再继续编写代码

实体类:

package com.hua.pojo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
    private String name;
    private int age;
    private String gender;
}

配置mapper接口:@Mapper:将Mapper接口类交给Sprinig进行管理

方式一:使用@Mapper注解

优点:粒度更细

缺点:直接在Mapper接口类中加@Mapper注解,需要在每一个mapper接口类中都需要添加@Mapper注解,较为繁琐

方式二:使用@MapperScan注解:x写在主启动类上

通过@MapperScan可以指定要扫描的Mapper接口类的包路径
 

package com.hua.mapper;

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

import java.util.List;

@Mapper
@Repository
public interface UserMapper {
    List<User> queryAll();

    int add(User user);

    int delById(int age);

    int update(User user);
}

mapper的xml文件放在resource文件夹下:

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

    <select id="queryAll" resultType="User">
    select * from book.user;
    </select>

    <insert id="add" parameterType="User">
        insert into book.user(name,age,gender) value (#{name},#{age},#{gender})
    </insert>

    <update id="update" parameterType="User">
        update book.user set name=#{name},age=#{age},gender=#{gender} where age=#{age};
    </update>

    <delete id="delById" parameterType="User">
        delete from book.user where age=#{age};
    </delete>
</mapper>

 Service层:

package com.hua.service;

import com.hua.pojo.User;
import org.springframework.stereotype.Service;

import java.util.List;


public interface UserService {
    List<User> queryAll();

    int add(User user);

    int delById(int id);

    int update(User user);
}

serviceImpl: 

package com.hua.service;

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

import java.util.List;

@Service
public class UserServiceImpl implements UserService{
    @Autowired
    private UserMapper userMapper;
    @Override
    public List<User> queryAll() {
        return userMapper.queryAll();
    }

    @Override
    public int add(User user) {
        return userMapper.add(user);
    }

    @Override
    public int delById(int id) {
        return userMapper.delById(id);
    }

    @Override
    public int update(User user) {
        return userMapper.update(user);
    }
}

Controller: 

package com.hua.contrller;


import com.hua.pojo.User;
import com.hua.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@RestController
public class UserController {
    @Autowired
    private UserService userService;

    @RequestMapping("/query")
    public List<User> queryAll(){
        return userService.queryAll();
    }
    @RequestMapping("/update/{name}/{age}/{gender}")
    public String update(@PathVariable("name") String name, @PathVariable("age")
             int age,@PathVariable("gender") String gender, User user){
         user.setName(name);
         user.setAge(age);
         user.setGender(gender);
        userService.update(user);
        return "OK";
    }
    @RequestMapping("/add/{name}/{age}/{gender}")
    public String add(@PathVariable("name") String name, @PathVariable("age")
            int age,@PathVariable("gender") String gender, User user){
        user.setName(name);
        user.setAge(age);
        user.setGender(gender);
        userService.update(user);
        userService.add(user);
       return "OK";
    }
    @RequestMapping("/del/{age}")
    public String del(@PathVariable("age") int age){
        userService.delById(age);
        return "ok";
    }
}

以上就是mybatis集成SpringBoot

13.SpringSecurity (安全)

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

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

●漏洞,隐私泄露

●架构一旦确定

shiro、SpringSecurity: 很像除了类不一样,名字不一样;

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

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

… 拦截器,过滤器:大量的原生代码比较冗余

mvc- spring-springboot-框架思想

AOP :横切~配置类

1、SpringSecurity 简介

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

记住几个类:

  1. WebSecurityConfigurerAdapter: 自定义Security策略
  2. AuthenticationManagerBuilder: 自定义认证策略
  3. @EnableWebSecurity: 开启WebSecurity模式,@Enablexxx 开启某个功能

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

  1. “认证”(Authentication)
  2. "授权”(Authorization

这个概念是通用的,不是只在SpringSecurity中存在。

参考官网: https://spring.io/projects/spring-security.

1、SpringSecurity环境搭建

引入依赖

 <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-test</artifactId>
            <scope>test</scope>
        </dependency>

写一个config类去继成 WebSecurityConfigurerAdapter,实现其两个核心方法

package com.hua.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;

@EnableWebSecurity
public class config extends WebSecurityConfigurerAdapter {
    //链式编程

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //首页所有人都可以访问
        //level1要VIP1 2要 vip2 3要vip3
        http.authorizeRequests()
                .antMatchers("/").permitAll()
                .antMatchers("/level1/**").hasRole("vip1")
                .antMatchers("/level2/**").hasRole("vip2")
                .antMatchers("/level3/**").hasRole("vip3");
        //没有权限会默认跳到登陆页面,需要开启登录的页面
        //login
        //http.formLogin()
        //定制登录页
        //http.formLogin().usernameParameter().passwordParameter().loginPage("/Login"); 设置前端发过来的参数别名
        http.formLogin().loginPage("/Login");
        //定制注销后跳转的页面
        http.logout().logoutSuccessUrl("/");
        //防止跨站攻击 SpringBoot默认帮我们开启 不关闭他会报错
        http.csrf().disable();
//        http.logout();
//        记住账号密码
//        http.rememberMe();
//        自定义记住我
        http.rememberMe().rememberMeParameter("remenber");
    }

    //认证
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        //如果不给他一个解码,他就会报错 spring版本2.2.+
        //而2.1.- 可以完美运行
        auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder())
                .withUser("hua").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1","vip2","vip3")
                .and()
                .withUser("hudonggou").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1","vip2")
                .and()
                .withUser("hongcao").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1");
    }
}

controller

package com.hua.controller;


import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;

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

    @RequestMapping({"/Login"})
    public String toLogin(){
        return "views/login";
    }
//    @RequestMapping("/login")
//    public String login(){
//        return "index";
//    }
    @RequestMapping("/level1/{id}")
    public String toLevel1(@PathVariable("id") int id){
        return "views/level1/"+id;
    }

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

14、Shiro(安全)

1、Shiro简介

​ Apache Shiro是一个强大且易用的Java安全框架,执行身份验证、授权、密码和会话管理。使用Shiro的易于理解的API,您可以快速、轻松地获得任何应用程序,从最小的移动应用程序到最大的网络和企业应用程序。

Shiro可以非常容易的开发出足够好的应用,不仅可以用在JavaSE环境,也可以用在JavaEE环境。

下载地址: http://shiro.apache.org/

2、Shiro有哪些功能?

img

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

3、Shiro架构(外部)

img

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

SpringBoot集成Shiro

导入依赖

<dependencies>
    <!-- https://mvnrepository.com/artifact/org.apache.shiro/shiro-core -->
    <dependency>
        <groupId>org.apache.shiro</groupId>
        <artifactId>shiro-core</artifactId>
        <version>1.8.0</version>
    </dependency>


    <!-- configure logging -->
    <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>
    <!-- https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-core -->
    <dependency>
        <groupId>org.apache.logging.log4j</groupId>
        <artifactId>log4j-core</artifactId>
        <version>2.14.1</version>
    </dependency>

</dependencies>

2、配置文件

resources目录下

log4j.properties

log4j.rootLogger=INFO, stdout

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

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

# Spring
log4j.logger.org.springframework=WARN

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

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

shiro.ini,需要安装ini插件:在idea中可以下载

Quickstart

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

quicklystart代码

/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.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) {

        // The easiest way to create a Shiro SecurityManager with configured
        // realms, users, roles and permissions is to use the simple INI config.
        // We'll do that by using a factory that can ingest a .ini file and
        // return a SecurityManager instance:

        // Use the shiro.ini file at the root of the classpath
        // (file: and url: prefixes load from files and urls respectively):

        Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
        SecurityManager securityManager = factory.getInstance();

        // for this simple example quickstart, make the SecurityManager
        // accessible as a JVM singleton.  Most applications wouldn't do this
        // and instead rely on their container configuration or web.xml for
        // webapps.  That is outside the scope of this simple quickstart, so
        // we'll just do the bare minimum so you can continue to get a feel
        // for things.
        SecurityUtils.setSecurityManager(securityManager);

        // Now that a simple Shiro environment is set up, let's see what you can do:

        // get the currently executing user:
        //获取当前的用户对象  Subject
        Subject currentUser = SecurityUtils.getSubject();

        // Do some stuff with a Session (no need for a web or EJB container!!!)
        //通过当前对象获取当前用户的Session
        Session session = currentUser.getSession();
        session.setAttribute("someKey", "aValue");
        //将aValue的session保存在someKey中
        String value = (String) session.getAttribute("someKey");
        if (value.equals("aValue")) {
            log.info("Subject==》session [" + value + "]");
        }

        // let's login the current user so we can check against roles and permissions:
        //判断当前的用户是否被认证
        if (!currentUser.isAuthenticated()) {
            //Token 令牌
            UsernamePasswordToken token = new UsernamePasswordToken("lonestarr", "vespa");
            token.setRememberMe(true);
            try {
                currentUser.login(token); //执行 登录操作
            } catch (UnknownAccountException uae) {//未知账号
                log.info("There is no user with username of " + token.getPrincipal());
            } catch (IncorrectCredentialsException ice) {//密码错误
                log.info("Password for account " + token.getPrincipal() + " was incorrect!");
            } catch (LockedAccountException lae) {  //锁定账号
                log.info("The account for username " + token.getPrincipal() + " is locked.  " +
                        "Please contact your administrator to unlock it.");
            }
            // ... catch more exceptions here (maybe custom ones specific to your application?
            catch (AuthenticationException ae) { //认证异常
                //unexpected condition?  error?
            }
        }

        //say who they are:
        //print their identifying principal (in this case, a username):
        //获取当前用户信息
        log.info("User [" + currentUser.getPrincipal() + "] logged in successfully.");

        //test a role:
        //测试角色  判断当前用户是什么角色
        if (currentUser.hasRole("schwartz")) {
            log.info("May the Schwartz be with you!");
        } else {
            log.info("Hello, mere mortal.");
        }

        //test a typed permission (not instance-level)
        //简单 粗粒度
        if (currentUser.isPermitted("lightsaber:wield")) {
            log.info("You may use a lightsaber ring.  Use it wisely.");
        } else {
            log.info("Sorry, lightsaber rings are for schwartz masters only.");
        }

        //细粒度
        //a (very powerful) Instance Level permission:
        if (currentUser.isPermitted("winnebago:drive:eagle5")) {
            log.info("You are permitted to 'drive' the winnebago with license plate (id) 'eagle5'.  " +
                    "Here are the keys - have fun!");
        } else {
            log.info("Sorry, you aren't allowed to drive the 'eagle5' winnebago!");
        }

        //all done - log out!
        //注销
        currentUser.logout();

        //结束
        System.exit(0);
    }
}

这些功能在Spring-Secutiry都有,总结:

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

    //通过当前对象获取当前用户的Session
   Session session = currentUser.getSession();
   
   //判断当前的用户是否被认证
   currentUser.isAuthenticated()
   
	//获取当前用户信息
   currentUser.getPrincipal()
   
     //测试角色  判断当前用户是什么角色
   currentUser.hasRole("schwartz")
   
   currentUser.isPermitted("lightsaber:wield")
   
     //注销
currentUser.logout();

6、SpringBoot整合Shiro环境搭建

导入相关依赖

<!-- https://mvnrepository.com/artifact/org.apache.shiro/shiro-core -->
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-core</artifactId>
            <version>1.8.0</version>
        </dependency>


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

        <!--thymeleaf模板,我们都是基于3.x开发-->
        <dependency>
            <groupId>org.thymeleaf</groupId>
            <artifactId>thymeleaf-spring5</artifactId>
        </dependency>
        <dependency>
            <groupId>org.thymeleaf.extras</groupId>
            <artifactId>thymeleaf-extras-java8time</artifactId>
        </dependency>

注意测试SpringBoot环境是否搭建成功

首先写一个config类:

package com.hua.config;

import org.apache.shiro.realm.Realm;
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 org.springframework.web.server.session.DefaultWebSessionManager;

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

@Configuration
public class ShiroConfig {
    //3.ShiroFilterFactoryBean
    @Bean
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("security") DefaultWebSecurityManager securityManager) {
        ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean();
        factoryBean.setSecurityManager(securityManager);
        Map<String, String> map = new LinkedHashMap<>();
        /*
         *  anon:无需认证就可以访问
         * authc:必须认证才可以范文
         * user:必须拥有记住我功能才可以访问
         * perms:拥有对某个资源的权限才可以访问
         * role:拥有某个角色权限才能访问
         * */

//        map.put("*","anon");
        //授权,没有权限会跳转到无法访问页面
        //亲测,这样只会生效下面的
        map.put("/user/add", "perms[user:add]");
//        map.put("/user/add", "perms[user:all]");
        map.put("/user/update", "perms[user:update]");
//        map.put("/user/update", "perms[user:all]");
                map.put("/user/*", "authc");


        factoryBean.setFilterChainDefinitionMap(map);
        factoryBean.setLoginUrl("/toLogin");
        //未授权页面
        factoryBean.setUnauthorizedUrl("/mistake");
        return factoryBean;
    }

    //2.defalutWebSecurityManage
    @Bean
    @Qualifier("security")
    public DefaultWebSecurityManager securityManager(@Qualifier("getRealm") Realm getRealm){
        DefaultWebSecurityManager securityManager =
                new DefaultWebSecurityManager();
        securityManager.setRealm(getRealm);
        return securityManager;
    }


    //1.realm
    @Bean
    public Realm getRealm(){
        return new MyRealm();
    }
}

 自定义Realm类:继承AuthorizingRealm

package com.hua.config;

import com.hua.pojo.User;
import com.hua.service.UserService;
import jdk.nashorn.api.scripting.ScriptUtils;
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;

public class MyRealm extends AuthorizingRealm {
    @Autowired
    UserService userService;
    @Override
//    授权
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        System.out.println("执行了1");
//       有个很像的要注意
        SimpleAuthorizationInfo
                info = new SimpleAuthorizationInfo();

        //拿到当前登录的对象
        Subject subject = SecurityUtils.getSubject();
        User user = (User) subject.getPrincipal();
        //还可以授予权限
        if(user.getName().equals("hua")){
            info.addStringPermission("user:update");

        }
        info.addStringPermission(user.getPermit());
        return info;
    }

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        System.out.println("执行了5");
        UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
        String username = token.getUsername();
        //链接真实数据库
        User user = userService.queryByname(username);
        System.out.println(username);
//        System.out.println(username);
        if(username==null){//没有这个人
            return null;//UnknownAccountException
        }
        if(!username.equals(user.getName())){
//            System.out.println(1);
            return null;
        }
        Subject subject = SecurityUtils.getSubject();
        Session session = subject.getSession();
        session.setAttribute("userName",user.getName());

        //密码可以加密 MD5/MD5盐值(更深)
//        密码认证不用我们做 shiro做
        return new SimpleAuthenticationInfo(user,user.getPsd(),"");


    }
}

静态页面:

index

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org"
      xmlns:shiro="http://www.thymeleaf.org/thymeleaf-extras-shiro">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>首页</h1>
<hr>
<div th:if="${session.userName==null}">
    <a th:href="@{/toLogin}">登录</a>
</div>
<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>

login

<!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">
<form th:action="@{/login}">
    账号:<input type="text" name="username">
    <br>
    密码:<input type="password" name="password">
    <br>
    <input type="submit" value="登录">
</form>
</body>
</html>

add

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
add
</body>
</html>

update

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
update
</body>
</html>
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
未经授权无法访问
</body>
</html>

controller

package com.hua.controller;

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
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.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
public class getRequest {
    @RequestMapping("/")
    public String getIndex(){
        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);
//        System.out.println(password);
        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("/mistake")
    public String getError() {
        return "error";
    }
}

实体类:

package com.hua.pojo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

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

mapper

package com.hua.mapper;

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

@Repository
@Mapper
public interface userMapper {
    User queryByname(String username);
}

mapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.hua.mapper.userMapper">
    <select id="queryByname" resultType="User">
        select * from book.useraccount where name = #{name}
    </select>

</mapper>

Service

package com.hua.service;

import com.hua.pojo.User;

public interface UserService {
    User queryByname(String username);
}

接口

package com.hua.service;

import com.hua.pojo.User;

public interface UserService {
    User queryByname(String username);
}

配置文件

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

    #Spring Boot 默认是不注入这些属性值的,需要自己绑定
    #druid 数据源专有配置
    initialSize: 5
    minIdle: 5
    maxActive: 20
    maxWait: 60000
    timeBetweenEvictionRunsMillis: 60000
    minEvictableIdleTimeMillis: 300000
    validationQuery: SELECT 1 FROM DUAL
    testWhileIdle: true
    testOnBorrow: false
    testOnReturn: false
    poolPreparedStatements: true
    #配置监控统计拦截的filters,stat:监控统计、log4j:日志记录、wall:防御sql注入
    #如果允许时报错  java.lang.ClassNotFoundException: org.apache.log4j.Priority
    #则导入 log4j 依赖即可,Maven 地址: https://mvnrepository.com/artifact/log4j/log4j
    filters: stat,wall,log4j
    maxPoolPreparedStatementPerConnectionSize: 20
    useGlobalDataSourceStat: true
    connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
mybatis:
  mapper-locations: classpath:mapper/*.xml
  type-aliases-package: com.hua.pojo

13、Swagger

1、Swagger的作用和概念

​ 官方地址:https://swagger.io/

​ Swagger 是一个规范且完整的框架,用于生成、描述、调用和可视化 RESTful 风格的 Web 服务以及 集成Swagger自动生成API文档。

Swagger 的目标是对 REST API 定义一个标准且和语言无关的接口,可以让人和计算机拥有无须访问源码、文档或网络流量监测就可以发现和理解服务的能力。当通过 Swagger 进行正确定义,用户可以理解远程服务并使用最少实现逻辑与远程服务进行交互。与为底层编程所实现的接口类似,Swagger 消除了调用服务时可能会有的猜测。

1、Swagger 的优势

  • 支持 API 自动生成同步的在线文档:使用 Swagger 后可以直接通过代码生成文档,不再需要自己手动编写接口文档了,对程序员来说非常方便,可以节约写文档的时间去学习新技术。
  • 提供 Web 页面在线测试 API:光有文档还不够,Swagger 生成的文档还支持在线测试。参数和格式都定好了,直接在界面上输入参数对应的值即可在线测试接口。

2、SwaggerUI 特点

  1. 无依赖 UI可以在任何开发环境中使用,无论是本地还是在Web端中。
  2. 人性化允许最终开发人员轻松地进行交互,并尝试API公开的每个操作,以方便使用。
  3. 易于浏览归类整齐的文档可快速查找并使用资源和端点。
  4. 所有浏览器支持 Swagger UI 在所有主要浏览器中均可使用,以适应各种可能的情况。
  5. 完全可定制 通过完整的源代码访问方式以所需方式设置和调整Swagger UI。
  6. 完整的OAS支持 可视化Swagger 2.0或OAS 3.0中定义的API

2、SpringBoot集成Swagger

引入依赖

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

测试是否运行成功

@RestController
public class HelloController {

   @RequestMapping(value = "/hello")
    public String Hello(){
        return "Hello Swgger!";
    }

}

4、要使用Swagger,需要编写一个配置类SwaggerConfig来配置 Swagger

   @EnableSwagger2// 开启Swagger2的自动配置

@Configuration //配置类
@EnableSwagger2// 开启Swagger2的自动配置
public class SwaggerConfig {
}

 访问测试 :http://localhost:8080/swagger-ui.html

如果用的是2.6.x以上的版本还要在配置文件修改路径

spring.mvc.pathmatch.matching-strategy=ant_path_matcher

3、配置Swagger

1、Swagger实例Bean是Docket,所以通过配置Docket实例来配置Swaggger

package com.hua.config;


import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import org.springframework.core.env.Profiles;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Contact;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

import java.util.ArrayList;

@Configuration //配置类
@EnableSwagger2// 开启Swagger2的自动配置
public class SwaggerConfig {

    //配置了Swagger的Docket的bean实例
    @Bean
    public Docket docket(Environment environment){
//        Environment environment:在配置文件中获取启动环境
        Profiles of = Profiles.of("dev", "test");
        //判断配置环境是否是dev或者test 若是则是true 若不是则为false
        //如果我们只希望在开发的时候开启swagger 而在发布的时候不开启
        //判断是否为开发环境boolean b = environment.acceptsProfiles(of);把b作为enable的实参传进去
        boolean b = environment.acceptsProfiles(of);
        return new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo())
//                enable()是否开启swagger
                 .enable(true)
                .groupName("华")
                .select()
        //        any() 扫描全部
        //        none() 全部不扫描
        //        basePackage() 根据路径扫描
        //        withClassAnnotation() 根据类注解扫描
        //        withMethodAnnotation() 根据方法注解扫描
                .apis(RequestHandlerSelectors.basePackage("com.hua.controller"))
//                .paths(PathSelectors.ant("/hua/**"))//过滤什么路径 只扫描com.hua.controller带有hua的controller
                .build();

    }
    @Bean
    public Docket docket1(Environment environment){
        return new Docket(DocumentationType.SWAGGER_2).groupName("波");
    }
    @Bean
    public Docket docket2(Environment environment){
        return new Docket(DocumentationType.SWAGGER_2).groupName("湖东狗");
    }
    //配置swgger信息Bean
    public ApiInfo apiInfo(){
        return  new ApiInfo("陈俊华的博客","这个人很懒什么都没有留下"
        ,"1.0","www.baidu.com",new Contact("陈俊华","www.baidu.com" ,"407909756@qq.com"),"H工作室","www.HJLove.com",new ArrayList<>());
    }
}

 每一个这个对应一个分组的成员

@Configuration
@EnableSwagger2 // 开启Swagger2的自动配置
public class SwaggerConfig {
    //配置了Swagger的Docket的bean实例
    @Bean
    public Docket docket(Environment environment){
      return new Docket(DocumentationType.SWAGGER_2);
        
}

这个是测试配置文件,可以用这个配合enable,是否开启swagger

//判断配置环境是否是dev或者test 若是则是true 若不是则为false
//如果我们只希望在开发的时候开启swagger 而在发布的时候不开启
//判断是否为开发环境boolean b = environment.acceptsProfiles(of);把b作为enable的实参传进去在我们实际的运行过程中是不会开启这个模式的,毕竟给用户看到接口不好。

 4、实体配置

package com.hua.pojo;


import io.swagger.annotations.Api;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import io.swagger.annotations.ApiOperation;

//@Api(注释)
@Api("我是注释")
@ApiModel("用户实体类")
public class User {

    @ApiModelProperty("用户名")
    private String name;
    @ApiModelProperty("密码")
    private String pas;

    public User() {

    }

    public User(String name, String pas) {
        this.name = name;
        this.pas = pas;
    }

    @ApiOperation("得到名字")
    public String getName() {
        return name;
    }
    @ApiOperation("设置名字")
    public void setName(String name) {
        this.name = name;
    }
    @ApiOperation("得到密码")
    public String getPas() {
        return pas;
    }
    @ApiOperation("设置名字")
    public void setPas(String pas) {
        this.pas = pas;
    }

}

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

注解详解
@Api:用在类上,说明该类的作用。

  • tags:可以使用tags()允许您为操作设置多个标签的属性,而不是使用该属性。
  • description:可描述描述该类作用。


@ApiOperation:注解来给API增加方法说明。

  •  value 接口说明
  • httpMethod 接口请求方式
  • response 接口返回参数类型
  • notes 接口发布说明

@ApiImplicitParams : 用在方法上包含一组参数说明。
可以包含一个或多个@ApiImplicitParam

@ApiImplicitParam:用来注解来给方法入参增加说明。

  • name :参数名。
  • value : 参数的具体意义,作用。
  • required : 参数是否必填。
  • dataType :参数的数据类型。
  • paramType :查询参数类型,这里有几种形式:
类型作用
path以地址的形式提交数据
query直接跟参数完成自动映射赋值
body以流的形式提交 仅支持POST
header参数在request headers 里边提交
form以form表单的形式提交 仅支持POST

@ApiResponses:用于表示一组响应
可以包含一个或多个@ApiResponses

@ApiResponse:用在@ApiResponses中,一般用于表达一个错误的响应信息

  • code:数字,例如400
  • message:信息,例如"请求参数没填好"
  • response:抛出异常的类

@ApiModel:描述一个Model的信息(一般用在请求参数无法使用@ApiImplicitParam注解进行描述的时候)

  • 用于类 ;表示对类进行说明,用于参数用实体类接收
  • value–表示对象名
  • description–描述

@ApiModelProperty:描述一个model的属性
用于方法,字段 ,表示对model属性的说明或者数据操作更改

  • value:字段说明
  • name:重写属性名字
  • dataType:重写属性类型
  • required:是否必填
  • example:举例说明
  • hidden:隐藏

5、其他皮肤

<!--        换肤-->
        <!-- https://mvnrepository.com/artifact/com.github.xiaoymin/swagger-bootstrap-ui -->
        <dependency>
            <groupId>com.github.xiaoymin</groupId>
            <artifactId>swagger-bootstrap-ui</artifactId>
            <version>1.9.6</version>
        </dependency>

 访问 http://localhost:8080/doc.html

14、任务

1、异步任务

服务器响应时间长,客户端体验不好

模拟代码

service层

package com.hua.service;

import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

@Service
public class Asyn{
    @Async//@Async:告诉spring这是一个异步方法
    public void getInfo(){
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("数据处理中");
    }
}
package com.hua.controller;

import com.hua.service.Asyn;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class controller {
    @Autowired
    com.hua.service.Asyn Asyn;
    @RequestMapping("/hello")
    public String hello(){
        Asyn.getInfo();
        return "ok";
    }


}

在主方法中加一个注解:

@EnableAsync://开启异步任务,前台秒刷新,后台处理数据

在被调用的方法中@Async:告诉spring这是一个异步方法

这样就可以实现前台秒刷新后台出数据

2、邮件任务

1.导包

<!--邮件发送-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-mail</artifactId>
</dependency>

2、获取qq邮箱授权码:这个就不演示

3、编写配置文件application.properties

spring.mail.username=407909756@qq.com
spring.mail.password=验证码
spring.mail.host=smtp.qq.com
# 开启加密验证

spring.mail.properties.mail.smtp.ssl.enable=true
package com.hua;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.mail.javamail.JavaMailSenderImpl;
import org.springframework.mail.javamail.MimeMailMessage;
import org.springframework.mail.javamail.MimeMessageHelper;

import javax.mail.MessagingException;
import javax.mail.internet.MimeMessage;
import java.io.File;

@SpringBootTest
class SpringBoot10MailApplicationTests {

    @Autowired
    JavaMailSenderImpl javaMailSender;
    //普通邮件
    @Test
    void contextLoads() {

        SimpleMailMessage simpleMailMessage = new SimpleMailMessage();
        simpleMailMessage.setSubject("陈俊华华华");
        simpleMailMessage.setText("华哥");
        simpleMailMessage.setFrom("407909756@qq.com");
        simpleMailMessage.setTo("1944575687@qq.com");
        javaMailSender.send(simpleMailMessage);
    }
    @Test
    //复杂邮寄
    void contextLoads1() throws Exception {

        MimeMessage mimeMessage = javaMailSender.createMimeMessage();
        MimeMessageHelper mail = new MimeMessageHelper(mimeMessage,true);
        mail.setSubject("陈俊华华华");
        mail.setText("华哥");
        mail.setFrom("407909756@qq.com");
        mail.setTo("407909756@qq.com");
        mail.addAttachment("陈艺元的狗照.jpg",
                new File("C:\\Users\\Dista\\Desktop\\1.jpg"));
        javaMailSender.send(mimeMessage);
    }

}

正常的话是在controller里面的

3.定时任务:有点像linux里面的

Taskscheduler任务调度者

TaskExecutor任务执行者

在main中添加@Enablescheduling://开启定时功能的注解

package com.hua;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;

@SpringBootApplication
@EnableScheduling//开启定时功能的注解
public class SpringBoot10MailApplication {

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

}

写一个定时类:@Scheduled(cron = "0/1 * * * * ?") //每秒钟执行一次

package com.hua;

import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;

@Service
public class Schedule {
    @Scheduled(cron = "0/1 * * * * ?")   //每秒钟执行一次
    public void hello() {
        System.out.println("hello,你被执行了~");
    }
}
字段允许值允许的特殊字符
秒(Seconds)0~59的整数, - * / 四个字符
分(Minutes)0~59的整数, - * / 四个字符
小时(Hours)0~23的整数, - * / 四个字符
日(DayofMonth)1~31的整数(但是你需要考虑你月的天数),- * ? / L W C 八个字符
月份(Month)1~12的整数或者 JAN-DEC- * / 四个字符
周(DayofWeek)1~7的整数或者 SUN-SAT (1=SUN), - * ? / L C # 八个字符
年(可选,留空)(Year)1970~2099, - * / 四个字符


15、分布式 Dubbo+Zookenper+SpringBoot

1、分布式理论
1、什么是分布式系统?


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

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

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

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

2、Dubbo文档

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

在Dubbo的官网文档有这样一张图

img
 

3、单一应用架构

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

 img

适用于小型网站,小型管理系统,将所有功能都部署到一个功能里,简单易用。

缺点:

1、性能扩展比较难

2、协同开发问题

3、不利于升级维护

4、垂直应用架构

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

img

通过切分业务来实现各个模块独立部署,降低了维护和部署的难度,团队各司其职更易管理,性能扩展也更方便,更有针对性。

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

1、分布式服务架构

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

2、流动计算架构


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

img

3、RPC


什么是RPC?

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

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

RPC基本原理

 img

4、Dubbo


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

dubbo官网

1.了解Dubbo的特性

2.查看官方文档

5、dubbo基本概念

img

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

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

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

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

调用关系说明

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

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

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

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

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

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

Dubbo+zookeeper+springboot

首先要下载Dubbo+zookeeper


以管理员的身份打开它,不要关闭,否则找不到zookeeper注册中心会报错,

dubbo 

详细下载参照其他博客

环境搭建:

1.导入依赖

  <!--        导入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>2.7.3</version>
        </dependency>
        <!--        Zkclient-->
        <!-- 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>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>
            <!--排除这个slf4j-log4j12-->
            <exclusions>
                <exclusion>
                    <groupId>org.slf4j</groupId>
                    <artifactId>slf4j-log4j12</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

搭建提供者:

提供者的配置文件:

#提供者的名字
dubbo.application.name=provider-server
server.port=8001
#哪些类要去注册,导入需要注册的包
dubbo.scan.base-packages=com.hua.service
#注册中心 ip地址和端口可以改,可以是外网
dubbo.registry.address=zookeeper://127.0.0.1:2181

 提供者的类:

package com.hua.service;

import org.apache.dubbo.config.annotation.Service;
import org.springframework.stereotype.Component;

@Component
@Service
public class TickteServiceImpl implements TickteServise{

    @Override
    public String getTickte() {
        return "huashen";
    }
}

接口:

package com.hua.service;

public interface TickteServise {
    String getTickte();
}

配置开启服务后:

 

 消费者:

配置文件:


server.port=8002

#消费者名字
dubbo.application.name=comsumer
#注册中心地址
dubbo.registry.address=zookeeper://127.0.0.1:2181
#dubbo.registry.address=zookeeper://127.0.0.1:2181

消费者一样要导入依赖:一样的依赖

package com.hua.service;


import org.apache.dubbo.config.annotation.Reference;
import org.springframework.stereotype.Service;

@Service
public class comsumer {
    //想要拿到provider提供的票 需要去拿去注册中心的服务  @DubboReference
    @Reference
    private TickteServise tickteServise;

    public void buyTicket(){
        String ticket = tickteServise.getTickte();
        System.out.println("在注册中心拿到=》"+ticket);
    }
}
wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==

引入的时候,我们创建和提供者一样的包一样的TickteServise接口

因为用了@Reference所以不是使用这个comsumer同级的TickteService而是会去注册中心中找

测试类:

package com.hua;

import com.hua.service.comsumer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class ComsummerApplication {


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

}
wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==

结果:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Hyong~~

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值