SpringBoot框架3


热部署

  1. 导入依赖

     <!--        热部署-->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-devtools</artifactId>
       <optional>true</optional>
    </dependency>
    
  2. ctrl+shift+alt + /

    Registry

  1. settings

  1. 基本上是针对的是jsp和controller层修改之后,不需要重新启动服务器,实现自动刷新的效果

拦截器

  1. 登录拦截器的类

    @Component
    public class LoginInterceptor implements HandlerInterceptor {
    
        /**
         * 每个控制层的中的每个方法 - 小handler - 处理器方法
         * 
         * 进入handler之前执行
         * @param request
         * @param response
         * @param handler
         * @return
         * @throws Exception
         */
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
            
            //false - 不放行..
            //true - 放行...
            HttpSession session = request.getSession();
            User user = (User) session.getAttribute("user");
            if(null!=user)
                return true;
            return false;
        }
    }
    
  2. springboot拓展了springmvc的功能

    @Configuration
    public class WebMvcConfig implements WebMvcConfigurer {
                
        @Autowired
        private LoginInterceptor loginInterceptor;
                
        @Override
        public void addInterceptors(InterceptorRegistry registry) {
            //注册登录拦截器
            registry.addInterceptor(loginInterceptor)
            .addPathPatterns("/**")
            .excludePathPatterns("/users/send","/users/login","/phone/index","/plugins/**","/imgs/**","/css/**","/js/**");
            //排除哪些路径是需要拦截,哪些路径是不需要拦截
        }
    }
    

SpringBoot整合分页插件

  1. 导入依赖

      <!--        springboot分页插件-->
    <dependency>
      <groupId>com.github.pagehelper</groupId>
      <artifactId>pagehelper-spring-boot-starter</artifactId>
      <version>1.3.1</version>
    </dependency>
    
  1. PageInfo<实体名> pageInfo = new PageInfo<>(mapper查询出来的集合对象);

    此处的实体一定是entity[有表对应的],不能是第三方实体.

jquery-load

异步加载页面.

后台cart/cart -> cart.jsp[单独抽出来的一部分] - 存在核心的分页的数据的那一部分的jsp内容

index_load.jsp - div采用jquery.load方式去异步加载上面的jsp文件/cart/cart

图片上传

图片nginx服务器

限制图片上传的大小

spring:
servlet:
   multipart:
     # 启用
     enabled: true
     # 单个文件的大小
     max-file-size: 50MB
     # 设置总上传数据总大小
     max-request-size: 100MB

服务器端校验

前端校验可以被绕过,所以必须要进行服务器端校验 - 后端校验

  1. 导入依赖

    <!--        后端校验-->
    <dependency>
      <groupId>org.hibernate.validator</groupId>
      <artifactId>hibernate-validator</artifactId>
    </dependency>
    

fastjson

将java对象转换成json字符串.

<dependency>
 <groupId>com.alibaba</groupId>
 <artifactId>fastjson</artifactId>
 <version>1.2.78</version>
</dependency>
package tech.aistar.fastjson;

import com.alibaba.fastjson.JSON;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import tech.aistar.mapper.UsersMapper;
import tech.aistar.model.entity.Users;

/**
 * 本类用来演示:
 *
 * @author: success
 * @date: 2021/10/8 2:53 下午
 */
@SpringBootTest
public class TestFastJSon {

    @Autowired
    private UsersMapper usersMapper;

    @Test
    public void testJavaToJsonString(){
        Users users = usersMapper.findByEmail("849962874@qq.com");

        String jsonStr = JSON.toJSONString(users);
        System.out.println(jsonStr);
    }
}
  //设置文档的输出类型
response.setContentType("application/json;charset=utf-8");
//获取文档输出流
PrintWriter out = response.getWriter();
String jsonResult = JSON.toJSONString(new Result("507","非法的token"));
out.println(jsonResult);

密码加密

  1. 导入依赖 - 权限认证的框架 - SpringSecurity,还有shiro

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

    package tech.aistar.pwd;
          
    import org.junit.jupiter.api.Test;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.test.context.SpringBootTest;
    import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
          
    /**
     * 本类用来演示: 加密技术
     *
     * @author: success
     * @date: 2021/10/8 3:18 下午
     */
    @SpringBootTest
    public class PasswordTest {
        //思考 - 为什么RedisTemplate可以直接使用
          
        //springboot底层的自动化配置原理有关系.
          
        @Autowired
        private BCryptPasswordEncoder encoder;
          
        @Test
        public void testEncoder(){
            //页面上输入的数据.
            String pwd = "admin123";
          
            //加密之后保存到db中
            //db的password的字段的长度修改长一点
          
            //不可逆 - 底层随机盐
            String encoderPwd = encoder.encode(pwd);
          
            //密码需要进行密文的处理的
            //$2a$10$XHCcsh3SWU/gjOzht17CPOX2W4pGFiBogyUCfUVBGyIfuJRY6UX5m
            System.out.println(encoderPwd);
        }
          
        @Test
        public void testGet(){
            //直接进行判断
            boolean flag = encoder.matches("admin123","$2a$10$XHCcsh3SWU/gjOzht17CPOX2W4pGFiBogyUCfUVBGyIfuJRY6UX5m");
            System.out.println(flag);
        }
    }
    
  1. 需要对拦截的请求处理一下 - 放行,否则会自动跳转默认的登录界面里面

    package tech.aistar.config;
          
    import org.springframework.context.annotation.Configuration;
    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;
          
    /**
     * 本类用来演示:
     *
     * @author: success
     * @date: 2021/10/8 3:17 下午
     */
    @Configuration
    @EnableWebSecurity
    public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
          
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http.authorizeRequests()
                    .antMatchers("/**").permitAll()
                    .anyRequest().authenticated()
                    .and().csrf().disable();
        }
    }
    

redis+token如何防止表单重复提交

  1. /user/registerView - server端生成token[令牌] - 存储到redis中,并且将这个token存储到request作用域中.

  2. 转发到WEB-INF/register.jsp文件 - form表单

    <input type='hidden' value='${token}' name='token'>
    
  3. 点击注册按钮 - 都可以将这个隐藏域的token值一起发送给server - /user/register

  4. /user/register后端是可以拿到redis中的token,然后和form表单提交过来的token值进行比较.

    如果一样,则直接顺利执行.执行注册操作之后,将redis中的token删除.

  5. 再次发送,隐藏域中的token会再次和redis中的token进行比较[已经被删除了] - 比较失败! - 重复提交了.

RabbitMQ

ActiveMQ:基于JMS,Apache

RocketMQ:(Rocket,火箭)阿里巴巴的产品,基于JMS,目前由Apache基于会维护

Kafka:分布式消息系统,亮点:吞吐量超级高,没秒中数十万的并发。

RabbitMQ:(Rabbit,兔子)由erlang语言开发,基于AMQP协议,在erlang语言特性的加持下,RabbitMQ稳定性要比其他的MQ产品好一些,而且erlang语言本身是面向高并发的编程的语言,所以RabbitMQ速度也非常快。且它基于AMQP协议,对分布式、微服务更友好。

RabbitMQ是实现了高级消息队列协议(AMQP)的开源消息代理软件(亦称面向消息的中间件)

特点

消息队列中间件是分布式系统中重要的组件,主要解决应用耦合,异步消息,流量削锋等问题实现高性能,高可用,可伸缩和最终一致性[架构]

使用较多的消息队列有ActiveMQ,RabbitMQ,ZeroMQ,Kafka,MetaMQ,RocketMQ。其使用场景为:异步处理,应用解耦,流量削锋和消息通讯四个场景。

安装

  1. Erlang - http://www.erlang.org/downloads
  2. RabbitMQ - https://github.com/rabbitmq/rabbitmq-server/releases
  1. 注意一下两者的版本,需要相互兼容.否则安装运行失败!

RestFul

Rest和RestFul

  1. REST(Representational State Transfer) 表现状态转换【架构样式的网络系统】

    REST – Representational State Transfer 直接翻译:表现层状态转移

    通俗理解就是 - URL定位资源,用HTTP动词(GET,POST,DELETE,PUT,PATCH)描述操作。

    put和patch都是进行更新的http动词,区别是

    1. put - 更新全局,比如更新页面,更新用户,username,email.password… - 消耗更多的带宽 .

      在页面中仅仅只更新了一个一个属性,比如username.提交请求的时候,也会将这个user中所有的属性全部

      发送到后端,封装到user对象中.

      @PutMapping
      @ResponseBody
      public Result update(Users user){
        //....
      }
      
    2. patch - 更新局部,如果仅仅更新了username,只会发送username到后端的user对象中.

    通俗来讲就是:资源在网络中以某种表现形式进行状态转移。分解开来:
    Resource:资源,即数据
    Representational:某种表现形式,比如用JSON,XML,JPEG等;
    State Transfer:状态变化。通过HTTP动词实现。

  2. RESTFUL是一种网络应用程序的设计风格和开发方式

  3. REST指的是一组架构约束条件和原则(描述的是在网络中client和server的一种交互形式)。满足这些约束条件和原则的应用程序或设计就是Restful

RestFul特点

1).每一个URI代表1种资源;
2).CRUD(POST GET PUT DELETE)
3).通过操作资源的表现形式来操作资源
4).资源的表现形式是XML或者HTML
5).客户端与服务端之间的交互在请求之间是无状态的,从客户端到服务端的每个请求都必须包含理解请求所必需的信息

RestFul使用

URL设计  
 A).动宾结构(动词+宾语[名词])
     GET        获取                              安全
     POST       创建                              不安全
     PUT[PATCH] 更新【X-HTTP-Method-Override】    不安全
     DELETE     删除【X-HTTP-Method-Override】    不安全
 B).使用
     GET    :    /users       - 获取用户列表
     GET    :    /users/1     - 获取 Id 为 1 的用户
     POST   :    /users       - 创建一个用户
     PUT    :    /users/1     - 替换 Id 为 1 的用户
     PATCH  :    /users/1     - 修改 Id 为 1 的用户
     DELETE :    /users/1     - 删除 Id 为 1 的用户    

@Value和@ConfigurationProperties

@ConfigurationProperties @Value
功能 批量注入配置文件中的属性 一个个指定
松散绑定(松散语法) 支持 不支持
SpEL 不支持 支持
JSR303数据校验 支持 不支持
复杂类型封装 支持 不支持

如果说,我们只是在某个业务逻辑中需要获取一下配置文件中的某项值,使用@Value - 一一绑定

如果说,我们专门编写了一个javaBean来和配置文件进行映射,我们就直接使用@ConfigurationProperties - 统一绑定.

Spring Configuration Proccessor 配置文件处理器
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-configuration-processor</artifactId>
</dependency>

全局异常处理器

什么是全局异常处理器?

就是把错误异常统一处理的方法。

应用场景

1、当你使用jsr303参数校验器,如果参数校验不通过会抛异常,而且无法使用try-catch语句直接捕获,这时可以使用全局异常处理器来捕捉该异常。

2、当你自定义了一个异常类,可以在全局异常处理器中来捕捉该异常。(当然也可以直接在抛出异常处直接捕获,但是这样需要在每一个抛出的地方都写一次捕获代码,看起来不够美观且复用性不强,其他异常同理)。

package tech.aistar.exception;

import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import tech.aistar.model.bo.Result;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * 本类用来演示: 全局异常处理器
 *
 * @author: success
 * @date: 2021/10/12 9:37 上午
 */
@ControllerAdvice
public class GlobalExceptionHandler {

//    @ExceptionHandler(value = Exception.class)
//    @ResponseBody
//    public Result handler(){
//        System.out.println("==============");
//        return new Result("500","sorry,server is update");
//    }

    /**
     * 测试阶段...
     * @param e
     * @return
     */
//    @ExceptionHandler(value = Exception.class)
//    @ResponseBody
//    public Result handler(Exception e){
//        System.out.println("==============");
//        return new Result("500","sorry,server is update",e.getMessage());
//    }

    @ExceptionHandler(value = Exception.class)
    public void  handler(HttpServletResponse response){
       //servlet代码中重定向
        try {
            response.sendRedirect("/boot/error/error.html");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

SpringBoot自动化配置原理

SpringBoot中如何注入bean

  1. 直接在bean上加上@Component注解

  2. 需要新建一个bean的配置类 - 专门用来注入bean

    package tech.aistar.config;
       
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
       
    /**
     * 本类用来演示: 注入第三的bean,让这些bean受spring管理
     *
     * @author: success
     * @date: 2021/10/8 3:21 下午
     */
    @Configuration
    public class BeanConfig {
       
        //<bean id="" class="">
        @Bean
        public BCryptPasswordEncoder encoder(){
            return new BCryptPasswordEncoder();
        }
    }
       
    
  1. 保留了spring的传统,新建了一个spring文件 - 配置了bean标签

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
       
        <bean id="teacher" class="tech.aistar.model.bo.Teacher"></bean>
    </beans>
    

    在springboot的主程序上读取spring文件

    @ImportResource("classpath:applicationContext.xml")
    
  1. 自动化配置 - 自动化注入

场景

  1. part01 - 之前已经在配置类中进行了配置了

    @Autowired
    private BCryptPasswordEncoder encoder;
    
  2. part02 - 并没有手动配置

    @Autowired
    private RedisTemplate redisTemplate;
       
    @Autowired
    private RabbitMqTemplate rrr;
    

剖析源码

@SpringBootApplication - @EnableAutoConfiguration - @Import({AutoConfigurationImportSelector.class})

研究AutoConfigurationImportSelector - 重要的方法 - selectImports

手动实现

  1. 分别写MyRabbitTemplate和MyRedisTemplate

    public class MyRabbitTemplate {
        public MyRabbitTemplate(){
            System.out.println("rabbit...");
        }
    }
    package tech.aistar.auto;
    
    public class MyRedisTemplate {
        public MyRedisTemplate(){
            System.out.println("redis...");
        }
    }
    
  2. AutoRabbitConfig和AutoRedisConfig

    public class AutoRabbitConfig {
    
        @Bean
        public MyRabbitTemplate rabbit(){
            return new MyRabbitTemplate();
        }
    }
    
    package tech.aistar.auto;
    
    import org.springframework.context.annotation.Bean;
    
    /**
     * 本类用来演示:
     *
     * @author: success
     * @date: 2021/10/12 10:36 上午
     */
    public class AutoRedisConfig {
    
        @Bean
        public MyRedisTemplate redis(){
            return new MyRedisTemplate();
        }
    }
    
  3. 读取自动化配置的类

    package tech.aistar.auto;
    
    import org.springframework.context.annotation.ImportSelector;
    import org.springframework.core.type.AnnotationMetadata;
    
    /**
     * 本类用来演示:
     *
     * @author: success
     * @date: 2021/10/12 10:38 上午
     */
    public class MyAutoConfigurationSelector implements ImportSelector {
        @Override
        public String[] selectImports(AnnotationMetadata importingClassMetadata) {
            //源码去读取了spring.factories文件 - 自动化配置类的全限定名
            return new String[]{"tech.aistar.auto.AutoRabbitConfig","tech.aistar.auto.AutoRedisConfig"};
        }
    }
    
  4. 配置类

    package tech.aistar.auto;
       
    import org.springframework.context.annotation.Configuration;
    import org.springframework.context.annotation.Import;
       
    /**
     * 本类用来演示:
     *
     * @author: success
     * @date: 2021/10/12 10:40 上午
     */
    @Configuration
    @Import(MyAutoConfigurationSelector.class)
    public class AllBean {
    }
    

流程图

SpringBoot日志框架配置

  1. 日志门面 - 日志接口 - slfj4-api.jar
  2. 日志实现
  3. 如果日志接口和日志实现不匹配,还需要适配包

推荐使用logback日志框架[slf4j和logback - 作者是同一个人,不需要]

<?xml version="1.0" encoding="UTF-8"?>
<!--
scan:当此属性设置为true时,配置文件如果发生改变,将会被重新加载,默认值为true。
scanPeriod:设置监测配置文件是否有修改的时间间隔,如果没有给出时间单位,默认单位是毫秒当scan为true时,此属性生效。默认的时间间隔为1分钟。
debug:当此属性设置为true时,将打印出logback内部日志信息,实时查看logback运行状态。默认值为false。
-->
<configuration scan="false" scanPeriod="60 seconds" debug="false">
 <!-- 定义日志的根目录 -->
 <property name="LOG_HOME" value="app/log" />
 <!-- 定义日志文件名称 -->
 <property name="appName" value="aistar-springboot"></property>
 <!-- ch.qos.logback.core.ConsoleAppender 表示控制台输出 -->
 <appender name="stdout" class="ch.qos.logback.core.ConsoleAppender">
     <!--
     日志输出格式:
            %d表示日期时间,
            %thread表示线程名,
            %-5level:级别从左显示5个字符宽度
            %logger{50} 表示logger名字最长50个字符,否则按照句点分割。
            %msg:日志消息,
            %n是换行符
     -->
     <!--<layout class="ch.qos.logback.classic.PatternLayout">-->
     <encoder>
         <springProfile name="dev">
             <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} -----> [%thread] ---> %-5level %logger{50} - %msg%n</pattern>
         </springProfile>
         <springProfile name="prod">
             <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} ====> [%thread] ==== %-5level %logger{50} - %msg%n</pattern>
         </springProfile>
     </encoder>
     <!--</layout>-->
 </appender>

 <!-- 滚动记录文件,先将日志记录到指定文件,当符合某个条件时,将日志记录到其他文件 -->
 <appender name="appLogAppender" class="ch.qos.logback.core.rolling.RollingFileAppender">
     <!-- 指定日志文件的名称 -->
     <file>${LOG_HOME}/${appName}.log</file>
     <!--
     当发生滚动时,决定 RollingFileAppender 的行为,涉及文件移动和重命名
     TimeBasedRollingPolicy: 最常用的滚动策略,它根据时间来制定滚动策略,既负责滚动也负责出发滚动。
     -->
     <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
         <fileNamePattern>logFile.%d{yyyy-MM-dd}.log</fileNamePattern>
         <maxHistory>30</maxHistory>
         <totalSizeCap>3GB</totalSizeCap>
     </rollingPolicy>
     <!-- 日志输出格式: -->
     <encoder>
         <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [ %thread ] - [ %-5level ] [ %logger{50} : %line ] - %msg%n</pattern>
     </encoder>
 </appender>

 <!--
        logger主要用于存放日志对象,也可以定义日志类型、级别
        name:表示匹配的logger类型前缀,也就是包的前半部分
        level:要记录的日志级别,包括 TRACE < DEBUG < INFO < WARN < ERROR
        additivity:作用在于children-logger是否使用 rootLogger配置的appender进行输出,
        false:表示只用当前logger的appender-ref,true:
        表示当前logger的appender-ref和rootLogger的appender-ref都有效
 -->
 <!-- hibernate logger -->
 <logger name="tech.aistar" level="info" />
 <!-- Spring framework logger -->
 <logger name="org.springframework" level="debug" additivity="false"></logger>

 <!--
 root与logger是父子关系,没有特别定义则默认为root,任何一个类只会和一个logger对应,
 要么是定义的logger,要么是root,判断的关键在于找到这个logger,然后判断这个logger的appender和level。
 -->
 <root level="info">
     <appender-ref ref="stdout" />
     <appender-ref ref="appLogAppender" />

 </root>
</configuration>

文章作者: 码农耕地人
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 码农耕地人 !
  目录