1. 第一个springboot程序

springboot:https://start.spring.io/

通过该网站进行项目搭建,下载压缩包,解压后导入IDEA

程序的主入口(该类本身就是spring的一个组件):

@SpringBootApplication
public class HelloworldApplication {

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

springboot可以直接运行,内置了tomcat

我们项目的目录要和程序的主入口类在同一目录下,如

image-20200509134235661

写一个Controller,我们不需要任何配置,就可以直接访问

@RestController
public class TestController {

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

image-20200509134323928

springboot核心原理:自动装配!!

springboot的核心依赖 pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <!--有一个父项目-->
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.7.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>cn.codewei</groupId>
    <artifactId>helloworld</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>helloworld</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        
         <!--启动器-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        
        <!--web依赖:tomcat,dispatcherServlet,xml...-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!--单元测试-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <!--打jar包插件-->
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

springboot所有的依赖都是使用spring-boot-starter开头的

使用tomcat作为默认的嵌入式容器


2. IDEA快速创建springboot项目

IDEA集成了springboot创建项目的网站,我们不再用去官网下载压缩包了

直接用idea创建一个springboot项目(开发一般直接在IDEA中创建)

image-20200509140349696

image-20200509140529406

image-20200509140619808

image-20200509140659724

image-20200509141020466

可以把多余的文件删除

可以在application.properties中修改配置

如,修改端口号

server.port=80

如,修改springboot banner

在resources目录下新建一个banner.txt,将从网上复制的banner粘贴进去

////////////////////////////////////////////////////////////////////
//                          _ooOoo_                               //
//                         o8888888o                              //
//                         88" . "88                              //
//                         (| ^_^ |)                              //
//                         O\  =  /O                              //
//                      ____/`---'\____                           //
//                    .'  \\|     |//  `.                         //
//                   /  \\|||  :  |||//  \                        //
//                  /  _||||| -:- |||||-  \                       //
//                  |   | \\\  -  /// |   |                       //
//                  | \_|  ''\---/''  |   |                       //
//                  \  .-\__  `-`  ___/-. /                       //
//                ___`. .'  /--.--\  `. . ___                     //
//              ."" '<  `.___\_<|>_/___.'  >'"".                  //
//            | | :  `- \`.;`\ _ /`;.`/ - ` : | |                 //
//            \  \ `-.   \_ __\ /__ _/   .-` /  /                 //
//      ========`-.____`-.___\_____/___.-`____.-'========         //
//                           `=---='                              //
//      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^        //
//            佛祖保佑       永不宕机     永无BUG                    //
////////////////////////////////////////////////////////////////////

直接启动springboot项目就会看到springboot banner修改了

image-20200509144343836


3. Springboot自动装配原理

pom.xml中

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

springboot在父工程中也配置了解决资源过滤问题

我们在写或者引入springboot依赖的时候,不再需要指定版本,因为由这些版本仓库在父工程中

3.1 启动器

启动器

<!--启动器-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter</artifactId>
</dependency>
  • 启动器:说白了就是springboot的启动场景(自认为启动器就是依赖jar包的集合)
  • 比如:spring-boot-starter-web就会帮我们自动导入web环境所有的依赖
  • springboot会将所有的功能场景,都变成一个个的启动器
  • 我们要使用什么功能,就只需要找到对应的启动器就可以了starter

3.2 主程序

@SpringBootApplication
public class HelloworldApplication {

    public static void main(String[] args) {
        // 将springboot应用启动
        SpringApplication.run(HelloworldApplication.class, args);
    }
}
  • 注解

@SpringBootAppplication:标注这个类是一个SpringBoot应用:启动类下的所有资源被导入

image-20200509152126482

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

获取候选的配置(AutoConfigurationImportSelector类下)

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

META-INF/spring.factories:自动配置的核心文件image-20200509155803246

自动配置类中核心注解:@ConditionalOnxxx:如果这里的条件都满足,才会生效

......

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

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

    SpringApplication类

    run方法

    • SpringApplication这个类做了一下四件事情:

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

    4. yaml

    springboot官方推荐yaml格式的配置文件,而不推荐使用properties文件

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

    1. application.properties

    语法:key=value

    1. application.yaml

    语法:key:空格 value

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

    • yaml中的语法,如

      server:
          port:8080
    • xml中的语法,如

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

    4.1 yaml语法

    yaml语法对缩进(空格)要求极其严格!!

    1. 普通的key-value

      name: codewei
    2. 对象

      student:
          name: codewei
          age: 12

    对象的行内写法

    student: {name: codewei,age: 12}
    1. 数组

      pets:
      - cat
      - dog

    数组的行内写法

    pets: [cat,dog]

    4.2 给属性赋值的几种方式

    yaml可以直接给实体类赋值,如:

    • 实体类

      @Component
      public class Dog {
          private String name;
          private Integer age;
          ...
          有参无参构造
          Getter Setter方法
      }
    @Component
    public class Person {
        private String name;
        private Integer age;
        private Boolean happy;
        private Date birth;
        private Map<String,Object> map;
        private List<Object> list;
        private Dog dog;
        ...
        有参无参构造
        Getter Setter方法
    }

    曾经我们通过@Value("xxx")或@Autowired的方式给属性赋值,而现在我们可以使用yaml

    • yaml配置

      person:
        name: 唐嫣
        age: 36
        happy: true
        birth: 1986/12/21
        map:
          k1: v1
          k2: v2
        list:
          - l1
          - l2
        dog:
          name: 旺财
          age: 12
    • 在实体类上添加注解 @ConfigurationProperties(prefix="xxx") 注解中的值对应yaml中的对象名

      @Component
      @ConfigurationProperties(prefix="person")
      public class Person {
          private String name;
          private Integer age;
          private Boolean happy;
          private Date birth;
          private Map<String,Object> map;
          private List<Object> list;
          private Dog dog;
          ...
      }

    ==注意:yaml中的键名要和实体类中的属性名一致,否则实体类对应属性的值为null==

    添加注解后,可能会出现一个红条,但是不影响程序正常运行

    image-20200509212431406

    解决方法:

    添加依赖

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-configuration-processor</artifactId>
        <optional>true</optional>
    </dependency>
    • 测试结果

    image-20200509213501400

    也可以使用properties文件给实体类赋值

    • properties文件

      name=唐嫣
      age=23
    • 在实体类上添加注解

    通过注解@PropertySource(value="classpath:xxx") 加载指定properties配置文件

    然后通过@Value进行一一赋值,通过SPEL表达式将配置文件中的值取出(SPEL和E表达式基本相同,${xxx})

    @Component
    @PropertySource(value = "classpath:application.properties")
    public class Person {
        @Value("${name}")
        private String name;
        @Value("${age}")
        private Integer age;
        ...
    }

    image-20200509214728434

    测试结果,因为我们在配置文件中只给name和age赋值,所以后面的都为null

    在yaml文件中,同样支持SPEL语法,如

    person:
      name: 唐嫣${random.uuid}  #随机uuid
      age: 36${random.int}    #随机数
      happy: true
      birth: 1986/12/21
      map:
        k1: v1
        k2: v2
      list:
        - l1
        - l2
      dog:
        name: ${person.hello:hello}旺财  #如果persion.hello值不存在,则会为冒号后面的值,如果存在,则为这个值
        age: 12

    测试结果

    image-20200509215737308

    @ConfigurationProperties@Value
    功能批量注入配置文件中的属性一个个指定
    松散绑定(松散语法)支持不支持
    SPEL不支持支持
    JSR303数据校验支持不支持
    复杂类型封装支持不支持
    • 松散绑定:比如我的yaml中写的last-name,这个和lastName是一样的, - 后面跟着的字母默认是大写的,这就是松散绑定
    • JSR303数据校验:这个就是我们可以在字段是增加一层过滤验证,可以保证数据的合法性
    • 复杂类型封装:yaml中可以封装对象,使用@Value就不支持

    5. JSR303数据校验

    注解@Validate添加到实体类上,如

    @Component
    @ConfigurationProperties(prefix = "person")
    @Validated //数据校验
    public class Person {
    
        @Email  // 指定email属性要符合邮箱格式,可以指定一些值,如@Email(message="邮箱格式错误")
        private String email;
        private String name;
        private Integer age;
        private Boolean happy;
        private Date birth;
        private Map<String,Object> map;
        private List<Object> list;
        private Dog dog;
        ...
    }
    person:
      email: xxx
      name: 唐嫣${random.uuid}  #随机uuid
      age: ${random.int}    #随机数
      happy: true
      birth: 1986/12/21
      ....

    测试结果:

    image-20200510095835395

    还有很多注解:(前提必须在实体类上写上了@Validated)

    Constraint详细信息
    @Null被注释的元素必须为 null
    @NotNull被注释的元素必须不为 null
    @AssertTrue被注释的元素必须为 true
    @AssertFalse被注释的元素必须为 false
    @Min(value)被注释的元素必须是一个数字,其值必须大于等于指定的最小值
    @Max(value)被注释的元素必须是一个数字,其值必须小于等于指定的最大值
    @DecimalMin(value)被注释的元素必须是一个数字,其值必须大于等于指定的最小值
    @DecimalMax(value)被注释的元素必须是一个数字,其值必须小于等于指定的最大值
    @Size(max, min)被注释的元素的大小必须在指定的范围内
    @Digits (integer, fraction)被注释的元素必须是一个数字,其值必须在可接受的范围内
    @Past被注释的元素必须是一个过去的日期
    @Future被注释的元素必须是一个将来的日期
    @Pattern(value)被注释的元素必须符合指定的正则表达式
    @Email被注释的元素必须是电子邮箱地址
    @Length被注释的字符串的大小必须在指定的范围内
    @NotEmpty被注释的字符串的必须非空
    @Range被注释的元素必须在合适的范围内

    6. 多环境配置及配置文件位置

    6.1 配置文件可以存在的位置

    1. 可以在项目根目录下建一个目录:config,和src目录同级,可以将配置文件放在config目录下
    2. 配置文件可以直接放在项目根目录下
    3. 配置文件可以放在main下的resources目录中
    4. 配置文件可以放在rescources目录下的config目录下

    配置文件的优先级(从高到底):

    1. 项目目录下的config目录下的配置文件
    2. 项目目录下的配置文件
    3. main目录下的resources下的config目录下的配置文件
    4. resources下的配置文件

    image-20200510114255599

    6.2 多套配置环境直接快速切换

    方式一:在优先级高的地方新建配置文件,覆盖之前的配置文件

    方式二:通过配置进行选择配置文件

    我们可以在同意目录下拥有多套配置文件,如

    application.yaml(默认)

    application-test.yaml

    application-dev.yaml

    image-20200510114953599

    在没有更高优先级配置文件时,springboot会自动读取application.yaml配置文件,我们可以在application.yaml中进行一些配置来指定使用其他的配置文件,如

    spring:
      profiles:
        active: test

    这样就指定了使用application-test.yaml配置文件

    使用该配置可以选择激活哪一个配置文件,值只写"-"后面的值就可以,如dev,test

    方式三:

    在yaml中,我们可以使用“---”来将一个yaml文件分隔成不同的模块,每一个模块相当于不同的配置文件

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

    我们可以通过

    spring:
        profiles: xxx

    来给模块取名字,如果没有给模块取名字,springboot则会使用最上面没有取名字的模块,我们仍然可以通过

    spring:
        profiles:
            active: xxx

    来指定想要使用的模块


    7. 自动配置原理再理解

    在application.yaml配置文件中到底能写什么----->spring.factories 这两个之间有一定的联系

    image-20200510121452557

    spring.factories中指定的一些类,点进去就可以看到它们都是一个个的配置类,他们通过@EnableConfigurationProperties(xxx.class)来加载一些类,读取配置文件,在这些被加载的类中的属性就是我们可以配置的,如

    我们随便点进去一个类

    image-20200510123242948

    我们可以看到该类加载读取了ProjectInfoProperties.class类(XxxProperties.class)

    image-20200510123335107

    我们点进这个类

    image-20200510123404826

    就可以看到该类可以通过yaml文件来对该类中的属性进行赋值配置,前缀是spring.info

    所以我们就可以通过配置文件application.yaml来配置这些属性,如

    image-20200510123608723

    @ConditionalOnXxx
    // 该注解是spring底层的注解,根据不用的条件,来判断当前配置或者是类是否生效,如
    @ConditionalOnClass(CharacterEncodingFilter.class)
    // 判断是否有CharacterEncodingFilter这个类,如果有这个类,当前配置才会生效,否则不生效
    @Conditional扩展注解                      作用
        
     @ConditionalOnJava                     系统的java版本是否符合要求
     @ConditionalOnBean                     容器中存在指定Bean
     @ConditionalOnMissingBean              容器中不存在指定Bean
     @ConditionalOnExpression                 满足SpEL表达式指定
     @ConditionalOnClass                    系统中有指定的类
     @ConditionalOnMissingClass              系统中没有指定的类
     @ConditionalOnSingleCandidate             容器中只有一个指定的Bean,或者这个Bean是首选Bean
     @ConditionalOnProperty                 系统中指定的属性是否有指定的值
     @ConditionalOnResource                 路径下是否存在指定资源文件
     @ConditionalOnWebApplication              当前是web环境
     @ConditionalOnNotWebApplication        当前不是web环境
     @ConditionalOnJndi                     JNDI存在指定项

    在我们配置文件中能配置的东西,都存在一个固有的规律:

    存在对应的xxxProperties文件,如

    spring:
        webservices:
            path: xxx

    就对应着WebServicesProperties.class配置类

    这个类一般又会配xxxAutoConfiguration装配,如

    WebServicesAutoConfiguration就可以在spring.factories中找到

    image-20200510130646241

    image-20200510130746151

    image-20200510130848188

    这样就在我们就找到了yaml文件和spring.factories文件之间存在的关系,这就是自动装配的原理

    7.1 精髓

    1. SpringBoot启动会加载大量的自动配置类
    2. 我们看我们需要的功能有没有在Springboot默认写好的自动配置类中
    3. 我们再来看这个自动配置类中到底配置了哪些组件(只要我们要用的组件存在其中,我们就不需要再手动配置了)
    4. 给容器中自动配置类添加组件的时候,会从properties类中读取某些属性,我们只需要再配置文件中指定这些属性的值即可

    xxxAutoConfiguration:自动配置类

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

    7.2 其他

    在配置文件中添加一行

    debug: true

    启动springboot程序,我们就可以看到哪些配置生效,哪些配置没有生效

    测试结果

    image-20200510132744526

    image-20200510132809381

    image-20200510132853097


    8. SpringBoot Web开发

    要解决的问题:

    1. 导入静态资源
    2. 首页
    3. 模板引擎
    4. 装配扩展SpringMVC
    5. 增删改查
    6. 拦截器
    7. 国际化(中英文切换)

    8.1 静态资源导入

    在WebMvcConfiguration类中有相关配置

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        if (!this.resourceProperties.isAddMappings()) {
            logger.debug("Default resource handling disabled");
            return;
        }
        Duration cachePeriod = this.resourceProperties.getCache().getPeriod();
        CacheControl cacheControl = this.resourceProperties.getCache().getCachecontrol().toHttpCacheControl();
        if (!registry.hasMappingForPattern("/webjars/**")) {
            customizeResourceHandlerRegistration(registry.addResourceHandler("/webjars/**")
                                                 .addResourceLocations("classpath:/META-INF/resources/webjars/")
                                                 .setCachePeriod(getSeconds(cachePeriod)).setCacheControl(cacheControl));
        }
        String staticPathPattern = this.mvcProperties.getStaticPathPattern();
        if (!registry.hasMappingForPattern(staticPathPattern)) {
            customizeResourceHandlerRegistration(registry.addResourceHandler(staticPathPattern)
                                                 .addResourceLocations(getResourceLocations(this.resourceProperties.getStaticLocations()))
                                                 .setCachePeriod(getSeconds(cachePeriod)).setCacheControl(cacheControl));
        }
    }

    什么是webjars:

    我们可以通过webjars网站找到需要的资源,然后通过maven导入

    image-20200510143846900

    image-20200510144021751

    然后我们可以通过localhost:8080/webjars/xxx获取到刚才导入的资源,如

    image-20200510144139607

    我们静态资源可以放在

    classpath:(就相当于是resources目录) ==很少使用webjars==

    classpath:/META-INF/resources/ 下,就是webjars
    classpath:/resources/ 下
    classpath:/static/ 下
    classpath:/public/ 下
    classpath:/ 下

    静态资源放在这些文件夹下就可以通过localhost:8080/xxx直接访问到了(除了webjars)

    优先级(从高到底):

    1. classpath:/resources/ 下 优先级最高 一般放上传的文件
    2. classpath:/static/ 下 优先级第二 一般放一些图片等
    3. classpath:/public/ 下 优先级第三 一般放一些公共资源,js等

    8.2 首页订制

    首页index.html可以放在静态资源目录下,resources下的resources中,或static中,或public中

    访问localhost:8080/ 我们就可以直接访问到主页

    image-20200511084539460

    image-20200511084624688

    8.3 图标订制

    在springboot2.1.7前,可以进行图标订制

    把ico图标放在public或resources或static下

    然后在配置文件中关闭默认图标

    spring:
        mvc:
            favicon:
                enabled: false

    访问我们的网页就可以看到ico小图标了

    8.4 模板引擎

    之前我们用的jsp就是一种模板引擎

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

    Thymeleaf的GitHub主页:https://github.com/thymeleaf/thymeleaf

    springboot官网对应的starter:https://docs.spring.io/spring-boot/docs/2.2.7.RELEASE/reference/html/using-spring-boot.html#using-boot-starter

    image-20200511101456167

    首先我们要导入依赖 启动器

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

    模板引擎的作用就是我们来写一个页面模板,比如有些值是动态的,我们写一些表达式,来获取从后台传过来的数据

    这样我们在resources目录下的templates目录下写的html就可以通过controller来跳转访问了

    templates目录下的资源像之前的/WEB-INF下的资源一样,不能够直接访问到,要通过controller跳转访问

    public static final String DEFAULT_PREFIX = "classpath:/templates/";
    public static final String DEFAULT_SUFFIX = ".html";

    使用Thymeleaf

    首先我们要导入约束

    <html xmlns:th="http://www.thymeleaf.org">

    image-20200511112406001

    8.5 Thymeleaf语法

    所有的html元素都可以被thymeleaf替换接管,th:元素名,如

    <div th:text="${msg}"></div>   // 这样就可以取出后台传过来的msg数据了
    • 简单表达式:

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

    image-20200511114501424

    th:text th:utext

    th:text 不转义

    th:utext 转义

    如果使用th:text,后台如果传"<h1>xxx</h1>",那么在页面上就会原样输出

    如果使用th:utext,就会对后台传入的数据进行转义,如传入<h1>xxx</h1>,那么就会在页面上显示出<h1>的格式

    <div th:utext="${msg}"></div>

    还有另外一种写法

    <div>[[${msg}]]</div>

    通过这种写法也可以取出msg的值

    th:each 遍历

    <h3 th:each="user:${users}" th:text="${user}"></h3>

    image-20200511120534807

    8.6 扩展装配SpringMVC

    要拓展SpringMVC,实现一些拦截器,格式化,视图解析器等,我们要自己定义一个类,然后添加注解@Configuration,实现接口WebMvcCongigurar,不能添加注解@EnableWebMvc,重写里面的方法来实现想要的功能,如

    // 全面扩展SpringMVC
    @Configuration
    public class MyMvcConfig implements WebMvcConfigurer {
        @Override
        public void addInterceptors(InterceptorRegistry registry) {
            ...
        }
        ...
    }

    如果想diy一些定制化的功能,只要写这个组件,然后将他交给springboot,springboot就会帮我们自动装配,如

    @Configuration
    public class MyMvcConfig implements WebMvcConfigurer {
    
        @Bean
        public ViewResolver myViewResolver(){
            return new MyViewResolver();
        }
    
    
        public static class MyViewResolver implements ViewResolver{
            @Override
            public View resolveViewName(String s, Locale locale) throws Exception {
                return null;
            }
        }
    }

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


    9. 员工管理系统

    9.1 首页配置

    注意点:所有页面静态资源都需要使用thymeleaf接管,@{},如

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

    首页放在了templates目录下,不能够直接被访问,我们可以配置,直接访问

    @Configuration
    public class MyMvcConfig implements WebMvcConfigurer {
        @Override
        public void addViewControllers(ViewControllerRegistry registry) {
            registry.addViewController("/").setViewName("index");
            registry.addViewController("/index.html").setViewName("index");
        }
    }

    可以通过localhost:8080/localhost:8080/index.html直接访问

    9.2 国际化

    首先IDEA的fileencoding要全部改为UTF-8

    在resources目录下建一个i18n(国际化单词的缩写)目录

    在i18n目录下新建一个配置文件login.properties

    再建一个配置文件login_zh_CN.properties,会发现两个配置晚间自动合并到了一个目录下

    image-20200511161536117

    然后右键自动生成的目录,点加号,输入en_US,点击OK,就又添加了一个配置文件

    image-20200511162003531

    image-20200511162143173

    在idea中,在编写配置文件时,可以点击下面的Resource Bundle进行可视化配置

    image-20200511162504243

    image-20200511162739560

    image-20200511162923469

    这样用可是化配置,在一个里面就可以配置三个配置文件

    这样就会在login.properties配置文件中存在login.tip=请登录

    在login_en_US.properties配置文件中存在login.tip=Please sign in

    在login_zh_CN.properties配置文件中存在login.tip=请登录

    image-20200511183202016

    image-20200511183210955

    image-20200511183218992

    然后在application配置文件中进行配置(指定国际化自动生成的那个包),如

    spring:
      messages:
        basename: i18n.login

    thymeleaf中国际化的表达式为#{...}

    然后在页面html中进行修改对应的值

    那么怎么实现国际之间的切换呢??

    如果实现了LocaleResolver接口,那么这个类就是一个国际化解析的类,如

    public class MyLocaleResolver implements LocaleResolver {
        @Override
        public Locale resolveLocale(HttpServletRequest request) {
            // 获取请求中的语言参数
            String language = request.getParameter("l");
            Locale locale = Locale.getDefault(); // 如果没有就使用默认的
            // 如果请求的连接携带了国际化的参数
            if (!StringUtils.isEmpty(language)){
                // zh_CN  国家,地区
                String[] s = language.split("_");
                locale = new Locale(s[0], s[1]);
            }
            return locale;
        }
    
        @Override
        public void setLocale(HttpServletRequest request, HttpServletResponse response, Locale locale) {
    
        }
    }

    写完国际化类后,我们要将其放入IOC中,这样我们自定义的国际化组件就生效了

    @Bean
    public LocaleResolver localeResolver(){
        return new MyLocaleResolver();
    }

    由前端进行传递参数

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

    这样我们就实现了国际化的切换了

    核心:

    1. 配置i18n文件
    2. 如果要实现点击按钮自动切换,我们就需要自定义一个国际化组件LocaleResolver
    3. 将自己写的国际化组件放入IOC中
    4. 前端传递参数 th:href="@{/index.html(xx='xxxx')}"

    9.3 登录功能实现

    在做提示信息回显是,在前端要判断回显的信息是否为空,如

    <p style="color: red" th:if="${not #strings.isEmpty(msg)}" th:text="${msg}"></p>

    #strings.xxx是thymeleaf中封装的工具类,themeleaf中封装了很多的工具类

    测试登录成功后,url会暴露出用户名和密码,如

    image-20200512092505792

    解决这个问题,我们可以在配置类中添加视图映射,就是在视图解析器配置类中添加

    registry.addViewController("/main.html").setViewName("dashboard");

    在访问/main.html时,显示的是dashboard的页面

    然后再我们的LoginController中,返回时重定向到main.html,这样显示的页面就为dashboard.html,且url为localhost:8080/mian.html

    image-20200512092808287

    9.4 登录拦截器

    首先写一个类,实现HandlerIntercepter接口,那么这个类,就称为了一个拦截器(和SpringMVC中一样)

    public class LoginHandlerInterceptor implements HandlerInterceptor {
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
            // 登录成功后,应该存在用户的session
            HttpSession session = request.getSession();
            String loginUser = (String)session.getAttribute("loginUser");
            System.out.println(loginUser);
            System.out.println(loginUser==null);
            System.out.println("".equals(loginUser));
            if (!StringUtils.isEmpty(loginUser)){
                return true;
            }else {
                request.setAttribute("msg","没有权限,请先登录");
                request.getRequestDispatcher("/index.html").forward(request,response);
                return false;
            }
        }
    }

    然后,在视图解析器配置类WebMvcConfiguter中将拦截器放入IOC中,并指定需要拦截的内容

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

    这样,我们的登录验证过滤器就实现了

    9.5 展示员工列表

    抽取页面的公共部分

    一般我们会抽取出公共的组件放入commons.html中,然后在需要的地方进行引用

    抽取公共部分:

    <div th:fragment="topbar">
        ...
    </div>

    在需要使用的地方,进行引用

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

    使用th:insert和使用th:replace基本相同

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

    为了实现点击时高亮的切换

    在调用组件的时候传递一些参数

    <div th:replace="~{commons::topbar(active='mian.html')}"></div>

    在commons.html中在需要参数的地方进行接收参数

    <a th:class=${acive='main.html'?'样式一','样式二'}

    运用了三元表达式,如果参数等于main.html,则显示样式一,否则显示样式二

    ==在thymeleaf中,使用restful风格传递参数:==

    <a th:href="@{/toUpdate/{empId(empId=${employee.getId()})}">编辑</a>

    <a th:href="@{/toUpdate/}+${employee.getId()}">编辑</a>

    第二种方式,加号下面会出现红线,但是不影响正常运行

    springboot中默认日期格式yyyy\MM\dd

    如果我们要修改,在application.yaml中指定格式,如

    spring:
        mvc:
            date-format: yyyy-MM-dd

    9.6 404页面处理

    在template目录下建立一个目录error,放一个404页面,就可以了


    10. 整合JDBC

    对于数据访问层,无论是SQL还是NOSQL,springboot底层都是用spring data的方式进行统一处理

    Spring Data官网:https://spring.io/projects/spring-data

    数据库相关的启动器:https://docs.spring.io/spring-boot/docs/2.2.7.RELEASE/reference/html/using-spring-boot.html#using-boot-starter

    创建一个新项目,勾选JDBC ApiMySQL Driver

    image-20200513090721618

    首先,在yaml中进行配置

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

    测试

    只要maven中存在jdbc启动器和mysql驱动,那么就会存在一个对象DataSource

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

    在Springboot中有很多的xxxTemplate:SpringBoot已经配置好的模板Bean,拿来即用,如JdbcTemplate

    @Autowired
    private DataSource dataSource;
    
    @Autowired
    private JdbcTemplate jdbcTemplate;
    
    @RequestMapping("/userList")
    public List<Map<String,Object>> userList(){
        String sql = "select * from user";
        List<Map<String, Object>> maps = jdbcTemplate.queryForList(sql);
        return maps;
    }

    11. 整合Druid数据源

    首先导入Druid的依赖

    <!-- https://mvnrepository.com/artifact/com.alibaba/druid -->
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid</artifactId>
        <version>1.1.22</version>
    </dependency>

    在application.yaml中配置修改数据源(spring中默认的数据源是HikariCP)

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

    另外在application.yaml中我们可以配置一些最大连接数,最大等待数,日志记录等。。

    druid比其他数据源强势的一点在于:拥有监控功能,配置

        filters: stat,wall,log4j

    因为有log4j,所以我们要导入log4j的依赖

    配置Druid的后台监控功能

    @Configuration
    public class DruidConfig {
    
        @Bean
        @ConfigurationProperties(prefix = "spring.datasource")
        public DataSource druidDataSource(){
            return new DruidDataSource();
        }
    
        // 后台监控功能
        @Bean
        public ServletRegistrationBean servletRegistrationBean(){
            ServletRegistrationBean<StatViewServlet> bean = new ServletRegistrationBean<>(new StatViewServlet(), "/druid/*");
    
            // 后台需要有人登录,账号密码配置
            HashMap<String, String> initParameters = new HashMap<>();
            // 增加配置
            initParameters.put("loginUsername","admin");  // 登录key是固定的 loginUsername loginPassword
            initParameters.put("loginPassword","123456");
    
            // 允许谁能访问
            // 值为空,代表所有人可以访问
            initParameters.put("allow","");
    
            // 禁止谁能访问
            // initParameters.put("codewei","192.168.1.1");  // 指定名字和ip
            bean.setInitParameters(initParameters);  // 设置初始化参数
            return bean;
        }
    }

    这样通过localhost:8080/druid就可以访问到后台页面了

    配置filter

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

    12. 整合Mybatis

    首先导入依赖 mybatis-spring-boot-starter

    然后进行配置数据源

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

    在Mapper接口上添加注解@Mapper,表示本类一个mybatis的接口

    或者在启动类上添加注解@MapperScan("..."),开启包扫描,就是扫描该包下所有Mapper

    然后要在接口上添加注解@Repository,让入IOC中

    然后可以在application.yaml中整合mybatis,如

    mybatis:
      type-aliases-package: cn.codewei.pojo   # 取别名,指定包
      mapper-locations: classpath:mybatis/mapper/*.xml   #指定mapper.xml路径

    这样就和正常写mybatis一样了


    13. SpringSecurity(安全)

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

    利用了AOP横切的思想,不用改动原来的代码

    shiro和SpringSecurity很像,只是类不一样,名字不一样

    功能:认证(账号密码)和授权(vip1,vip2...)

    权限:

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

    之前我们是用过滤器,拦截器

    记住这几个类:

    • WebSecurityConfigurerAdapter:自定义Security策略
    • AuthenticationManagerBuilder:自定义认证策略
    • @EnableWebSecurity:开启WebSecurity模式 @Enablexxxx 开启某个功能

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

    认证:Authentication

    授权:Authorization

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

    13.1 用户认证和授权

    首先要导入security的启动器

    然后我们要写一个类,继承WebSecurityConfigurerAdapter,并且在类上添加注解@EnableWebSecurity

    @EnableWebSecurity
    public class SecurityConfig extends WebSecurityConfigurerAdapter {
        
    }

    然后我们要重写里面的方法,如configure(HttpSecurity http)

    然后我们比对这父类中重写的方法进行配置定义

    @EnableWebSecurity
    public class SecurityConfig extends WebSecurityConfigurerAdapter {
        // 链式编程
        // 定制授权规则
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            // 首页所有人可以访问,功能页只有对应有权限的人才能访问
            // 请求授权的规则
            http.authorizeRequests()
                    .antMatchers("/").permitAll()
                    .antMatchers("/level1/**").hasRole("vip1")   // level1下的所有页面,vip1才能访问
                    .antMatchers("/level2/**").hasRole("vip2")
                    .antMatchers("/level3/**").hasRole("vip3");
            // 没有权限默认会到登录页面(Security自带的登录页),需要开启登录的页面
            http.formLogin();
        }
    
    
        // 定制认证规则
        @Override
        protected void configure(AuthenticationManagerBuilder auth) throws Exception {
            // 读源码,看方法应该这么重写
            // 数据正常从数据库中都,我们这里从内存中读取数据,进行模拟
            // 如果报错:PasswordEncoder mapped for the id "null"
            // 则是需要对密码进行加密,在Spring Security5.0+新增了很多加密方法,如BCryptPasswordEncoder
            auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder())
                    .withUser("root").password(new BCryptPasswordEncoder().encode("shw123zxc")).roles("vip1","vip2","vip3")
                    .and()
                    .withUser("codewei").password(new BCryptPasswordEncoder().encode("shw123zxc")).roles("vip1","vip2")
                    .and()
                    .withUser("guest").password(new BCryptPasswordEncoder().encode("shw123zxc")).roles("vip1");
    
    
        }
    }

    13.2 注销及权限控制

    注销

    在刚才重写的方法中,写上http.logout();,开启注销功能

    在前端页面中,注销访问的链接应为/logout

    我们可以指定注销后跳转到哪个页面:http.logout().logouSuccesstUrl("/"),注销后跳转到主页

    http.logout().logoutSuccessUrl("/");

    权限控制

    登录后,根据不同的权限展示出不同的内容,就需要thymeleaf与spring scurity整合

    首先,我们要导入thymeleaf和spring scurity的整合包Thymeleaf Extras Springsecurity4

    在页面中添加命名空间xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity4"

    ==springboot 2.0.9之前才支持thymeleaf与spring security整合,更高版本的springboot则不支持!!==

    <div sec:authorize="!isAuthenticated()">
        <!--如果未认证(未登录),则显示div中的内容-->
        ...
    </div>
    
    <div sec:authorize="isAuthenticated()">
        <!--如果已认证(已登录),则显示div中的内容-->
        ...
    </div>
    
    <!--如果已登录:显示用户名和角色-->
    <div sec:authorize="isAuthenticated()">
           用户名:<span sec:authentication="name"></span>
           角色:<span sec:authentication="principal.authorities"></span>
    </div>

    当我们点击注销时,可能会出现404,这是因为springboot默认帮我们开启了CSRF(跨站请求伪造),防止网站攻击的,我们需要关闭CSRF

    http.csrf().disable();

    根据不同的权限显示不同的内容,菜单的动态实现,如

    <div sec:authorize="hasRole('vip1')">
        ...
        <!--有vip1的权限时,才会显示该div-->
    </div>

    13.3 记住我及登录页定制

    开启记住我功能

    http.rememberMe();

    定制登录页

    http.formLogin().loginPage("/toLogin");  // 跳转到跳转自己的登录页面的controller
    // 前端登录表单的提交action也要和这个路径相同
    <form th:action="@{/toLogin}" method="post">

    如果想前端表单提交的路径和指定的controller路径不一致,使用/login,可以进行指定

    http.formLogin().loginPage("/toLogin").loginProcessingUrl("/login");

    这样我们前端表单的action就可以写为/login

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

    ==注意:==

    我们自己登录页面的表单属性名默认要和默认的属性名相同,username,password

    如果我们写的属性参数名和默认的不同,如name,pwd

    那么,我们可以进行自定义指定参数名,如

    http.formLogin().usernameParameter("name").passwordParameter("pwd");

    这样我们表单中的属性名就可以自定义了

    在我们自己的登录页面中添加记住我功能

    <input type="checkbox" name="remember"> 记住我

    要自定义接收前端的参数

    http.rememberMe().rememberMeParameter("remember");

    14. 跨域问题

    增加一个拦截器

    @Configuration
    public class GlobalCorsConfig {
        @Bean
        public WebMvcConfigurer corsConfigurer() {
            return new WebMvcConfigurer() {
                @Override
                //重写父类提供的跨域请求处理的接口
                public void addCorsMappings(CorsRegistry registry) {
                    //添加映射路径
                    registry.addMapping("/**")
                            //放行哪些原始域
                            .allowedOrigins("*")
                            //是否发送Cookie信息
                            .allowCredentials(true)
                            //放行哪些原始域(请求方式)
                            .allowedMethods("GET","POST", "PUT", "DELETE")
                            //放行哪些原始域(头部信息)
                            .allowedHeaders("*")
                            //暴露哪些头部信息(因为跨域访问默认不能获取全部头部信息)
                            .exposedHeaders("Header1", "Header2");
                }
            };
        }
    }

    15. 前后端分离shiro

    由于我们在前后端分离中集成了shiro,因此需要在headers中自定义一个'Authorization'字段,此时普通的GET、POST等请求会变成preflighted request,即在GET、POST请求之前会预先发一个OPTIONS请求

    15.1 OPTIONS请求不带'Authorization'请求头字段

    前后端分离项目中,由于跨域,会导致复杂请求,即会发送preflighted request,这样会导致在GET/POST等请求之前会先发一个OPTIONS请求,但OPTIONS请求并不带shiro的'Authorization'字段(shiro的Session),即OPTIONS请求不能通过shiro验证,会返回未认证的信息。

    解决方法:给shiro增加一个过滤器,过滤OPTIONS请求

    public class CORSAuthenticationFilter extends FormAuthenticationFilter {
    
        private static final Logger logger = LoggerFactory.getLogger(CORSAuthenticationFilter.class);
    
        public CORSAuthenticationFilter() {
            super();
        }
    
        @Override
        public boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
            //Always return true if the request's method is OPTIONSif (request instanceof HttpServletRequest) {
                if (((HttpServletRequest) request).getMethod().toUpperCase().equals("OPTIONS")) {
                    return true;
                }
            }
    return super.isAccessAllowed(request, response, mappedValue);
        }
    
        @Override
        protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
            HttpServletResponse res = (HttpServletResponse)response;
            res.setHeader("Access-Control-Allow-Origin", "*");
            res.setStatus(HttpServletResponse.SC_OK);
            res.setCharacterEncoding("UTF-8");
            PrintWriter writer = res.getWriter();
            Map<String, Object> map= new HashMap<>();
            map.put("code", 702);
            map.put("msg", "未登录");
            writer.write(JSON.toJSONString(map));
            writer.close();
            return false;
        }
    }

    15.2 shiro配置类

    package com.xxx.shiro.config;
     
    import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
    import org.apache.shiro.mgt.SecurityManager;
    import org.apache.shiro.session.mgt.SessionManager;
    import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
    import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
    import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
    import org.crazycake.shiro.RedisCacheManager;
    import org.crazycake.shiro.RedisManager;
    import org.crazycake.shiro.RedisSessionDAO;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.web.servlet.HandlerExceptionResolver;
    import java.util.LinkedHashMap;
    import java.util.Map;
     
    /**
     * Created by Administrator on 2017/12/11.
     */
    @Configuration
    public class ShiroConfig {
     
        @Value("${spring.redis.shiro.host}")
        private String host;
        @Value("${spring.redis.shiro.port}")
        private int port;
        @Value("${spring.redis.shiro.timeout}")
        private int timeout;
        @Value("${spring.redis.shiro.password}")
        private String password;
     
        @Bean
        public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) {
            System.out.println("ShiroConfiguration.shirFilter()");
            ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
            shiroFilterFactoryBean.setSecurityManager(securityManager);
     
            Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();
            //注意过滤器配置顺序 不能颠倒
            //配置退出 过滤器,其中的具体的退出代码Shiro已经替我们实现了,登出后跳转配置的loginUrl
            filterChainDefinitionMap.put("/logout", "logout");
            // 配置不会被拦截的链接 顺序判断
            filterChainDefinitionMap.put("/static/**", "anon");
            filterChainDefinitionMap.put("/ajaxLogin", "anon");
            filterChainDefinitionMap.put("/login", "anon");
            filterChainDefinitionMap.put("/**", "authc");
            //配置shiro默认登录界面地址,前后端分离中登录界面跳转应由前端路由控制,后台仅返回json数据
            shiroFilterFactoryBean.setLoginUrl("/unauth");
            // 登录成功后要跳转的链接
    //        shiroFilterFactoryBean.setSuccessUrl("/index");
            //未授权界面;
    //        shiroFilterFactoryBean.setUnauthorizedUrl("/403");
            shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
            return shiroFilterFactoryBean;
        }
     
        /**
         * 凭证匹配器
         * (由于我们的密码校验交给Shiro的SimpleAuthenticationInfo进行处理了
         * )
         *
         * @return
         */
        @Bean
        public HashedCredentialsMatcher hashedCredentialsMatcher() {
            HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
            hashedCredentialsMatcher.setHashAlgorithmName("md5");//散列算法:这里使用MD5算法;
            hashedCredentialsMatcher.setHashIterations(2);//散列的次数,比如散列两次,相当于 md5(md5(""));
            return hashedCredentialsMatcher;
        }
     
        @Bean
        public MyShiroRealm myShiroRealm() {
            MyShiroRealm myShiroRealm = new MyShiroRealm();
            myShiroRealm.setCredentialsMatcher(hashedCredentialsMatcher());
            return myShiroRealm;
        }
     
     
        @Bean
        public SecurityManager securityManager() {
            DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
            securityManager.setRealm(myShiroRealm());
            // 自定义session管理 使用redis
            securityManager.setSessionManager(sessionManager());
            // 自定义缓存实现 使用redis
            securityManager.setCacheManager(cacheManager());
            return securityManager;
        }
     
        //自定义sessionManager
        @Bean
        public SessionManager sessionManager() {
            MySessionManager mySessionManager = new MySessionManager();
            mySessionManager.setSessionDAO(redisSessionDAO());
            return mySessionManager;
        }
     
        /**
         * 配置shiro redisManager
         * <p>
         * 使用的是shiro-redis开源插件
         *
         * @return
         */
        public RedisManager redisManager() {
            RedisManager redisManager = new RedisManager();
            redisManager.setHost(host);
            redisManager.setPort(port);
            redisManager.setExpire(1800);// 配置缓存过期时间
            redisManager.setTimeout(timeout);
            redisManager.setPassword(password);
            return redisManager;
        }
     
        /**
         * cacheManager 缓存 redis实现
         * <p>
         * 使用的是shiro-redis开源插件
         *
         * @return
         */
        @Bean
        public RedisCacheManager cacheManager() {
            RedisCacheManager redisCacheManager = new RedisCacheManager();
            redisCacheManager.setRedisManager(redisManager());
            return redisCacheManager;
        }
     
        /**
         * RedisSessionDAO shiro sessionDao层的实现 通过redis
         * <p>
         * 使用的是shiro-redis开源插件
         */
        @Bean
        public RedisSessionDAO redisSessionDAO() {
            RedisSessionDAO redisSessionDAO = new RedisSessionDAO();
            redisSessionDAO.setRedisManager(redisManager());
            return redisSessionDAO;
        }
     
        /**
         * 开启shiro aop注解支持.
         * 使用代理方式;所以需要开启代码支持;
         *
         * @param securityManager
         * @return
         */
        @Bean
        public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
            AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
            authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
            return authorizationAttributeSourceAdvisor;
        }
     
        /**
         * 注册全局异常处理
         * @return
         */
        @Bean(name = "exceptionHandler")
        public HandlerExceptionResolver handlerExceptionResolver() {
            return new MyExceptionHandler();
        }
    }

    15.3 MyShiroRealm的代码

    package com.xxx.shiro.config;
     
    import com.xxx.shiro.entity.SysPermission;
    import com.xxx.shiro.entity.SysRole;
    import com.xxx.shiro.entity.UserInfo;
    import com.xxx.shiro.service.UserInfoService;
    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.subject.PrincipalCollection;
    import org.apache.shiro.util.ByteSource;
     
    import javax.annotation.Resource;
     
    /**
     * Created by Administrator on 2017/12/11.
     * 自定义权限匹配和账号密码匹配
     */
    public class MyShiroRealm extends AuthorizingRealm {
        @Resource
        private UserInfoService userInfoService;
     
        @Override
        protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
    //        System.out.println("权限配置-->MyShiroRealm.doGetAuthorizationInfo()");
            SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
            UserInfo userInfo = (UserInfo) principals.getPrimaryPrincipal();
            for (SysRole role : userInfo.getRoleList()) {
                authorizationInfo.addRole(role.getRole());
                for (SysPermission p : role.getPermissions()) {
                    authorizationInfo.addStringPermission(p.getPermission());
                }
            }
            return authorizationInfo;
        }
     
        /*主要是用来进行身份认证的,也就是说验证用户输入的账号和密码是否正确。*/
        @Override
        protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token)
                throws AuthenticationException {
    //        System.out.println("MyShiroRealm.doGetAuthenticationInfo()");
            //获取用户的输入的账号.
            String username = (String) token.getPrincipal();
    //        System.out.println(token.getCredentials());
            //通过username从数据库中查找 User对象,如果找到,没找到.
            //实际项目中,这里可以根据实际情况做缓存,如果不做,Shiro自己也是有时间间隔机制,2分钟内不会重复执行该方法
            UserInfo userInfo = userInfoService.findByUsername(username);
    //        System.out.println("----->>userInfo="+userInfo);
            if (userInfo == null) {
                return null;
            }
            if (userInfo.getState() == 1) { //账户冻结
                throw new LockedAccountException();
            }
            SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(
                    userInfo, //用户名
                    userInfo.getPassword(), //密码
                    ByteSource.Util.bytes(userInfo.getCredentialsSalt()),//salt=username+salt
                    getName()  //realm name
            );
            return authenticationInfo;
        }
     
    }
    本文作者: Author:     文章标题: SpringBoot!走上大神之路!
    本文地址: https://codewei.cn/archives/169/      
    版权说明:若无注明,本文皆为“阿伟的小屋”原创,转载请保留文章出处
    Last modification:June 12th, 2020 at 02:37 pm
    贫困山区儿童,谢谢打赏