SpringBoot

简介

SprintBoot是一款快速开发框架,能够帮助我们快速整合第三方框架

  • 不同于SSM项目繁琐的xml配置,SpintBoot去除了xml配置
  • 全部采用注解化的方式配置
  • 内嵌Tomcat,运行就会启动Tomcat。不需要打包成war包再启动Tomcat
  • SpringBoot框架默认的情况下spring-boot-starter-web已经帮助我们整合好SpingMVC框架

快速入门

创建Maven工程


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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.mayikt</groupId>
    <artifactId>springboot-helloworld</artifactId>
    <version>1.0-SNAPSHOT</version>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.8.RELEASE</version>
    </parent>
    <dependencies>
    <!--Spring SpringMVC  -->
   <dependency>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter-web</artifactId>
   </dependency>
</dependencies>

<!--
spring-boot-starter-parent
在pom.xml中引入spring-boot-start-parent,spring官方的解释叫什么stater poms,它可以提供dependency management,也就是说依赖管理,
引入以后在申明其它dependency的时候就不需要version了,后面可以看到。
-->

    <!--
    spring-boot-starter-web
    -->

</project>

@RestController的作用

在main的java目录下新建package,com.benson.service

不写controller是因为它还有视图层,规范写service

SpringBoot的启动方式



如果用的是@Controller,要记得在getUser方法上@ResponseBody

再新建一个service
MemberService.java

package com.benson.service;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class MemberService {
    @RequestMapping
    public String getMember(){
        return "member";
    }
}

此刻回到测试类执行程序,肯定是访问不到/getMember的,因为只设置了HelloworldService
可以在HelloworldService.java那设置扫包注解

@ComponentScan("com.benson.service")//扫描service这个包下所有类

最常用的启动方式

在Service类所在的包下新建个启动类,就是Application.java也可以加APP.java,名字不重要
而Service类就不需要写@EnableAutoConfiguration和@ComponentScan了
HelloworldService.java

package com.benson.service;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HelloworldService {
    /**
     * @RestController 与@Controller之间区别
     * 如果在类上加上@RestController,该类中所有SpringMVCUrl接口映射都是返回json格式
     * 它的效果就是在每个方法上加上@ResponseBody注解 返回json格式
     * @RestController 是我们SpringMVC提供 而不是Springboot提供
     * <p>
     * Rest 微服务接口开发中 Rest风格 数据传输格式json格式 协议http协议
     * <p>
     * Controller 控制层注解 SpringMVCUrl接口映射 默认的情况下返回页面跳转
     * @Controller 如果需要返回json格式的情况下需要使用@ResponseBody注解
     */
    @RequestMapping("/getUser")
    public String getUser(){
        return "benson";
    }
}

MemberService.java

package com.benson.service;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class MemberService {
    @RequestMapping("/getMember")
    public String getMember(){
        return "member";
    }
}

Application.java

package com.benson.service;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
/* @SpringBootApplication
* 将@EnableAutoConfiguration和@ComponentScan组合在一起
* @ComponentScan的范围是当前启动类所在的包或子包
* */
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class);
    }
}

整合静态资源访问

我们在开发Web应用的时候,需要引用大量的js、css、图片等静态资源。

直接往resources里放一张图片,然后访问,发现会404

在resources下新建static目录,再往里面放一张图片

yml和properties配置

properties


实际开发中绝大多数时候使用的是yml配置,配置文件放在resources下
application.properties(默认名字一定要是application)

benson.name=benson
benson.age=18

benson是团体(公司)名

yml

properties繁琐在每次都要写benson
application.yml

benson:
  name: benson
  age: 18

属性:后要加空格,编译器好识别 name:空格benson

整合模板引擎thymeleaf

<!--引入thymeleaf的依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>

application.yml

###ThymeLeaf配置
spring:
  thymeleaf:
    #prefix:指定模板所在的目录
    prefix: classpath:/templates/
    #check-tempate-location: 检查模板路径是否存在
    check-template-location: true
    #cache: 是否缓存,开发模式下设置为false,避免改了模板还要重启服务器,线上设置为true,可以提高性能。
    cache: true
    suffix:  .html
    encoding: UTF-8
    mode: HTML5

渲染是控制器层做的事,所以新建个controller包,controller层负责页面的跳转,service层负责业务逻辑

要完成通过渲染来将对象的值扔到页面上,所以再建个实体类entity包。把启动类APP扔到benson包下
再在resources建个templates目录用来存放模板文件

ThymeleafController.java

package com.benson.controller;

import com.benson.entity.User;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

import javax.servlet.http.HttpServletRequest;

@Controller
public class ThymeleafController {
    @RequestMapping("/MyThymeleaf")
    public String MyThymeleaf(HttpServletRequest request){
        request.setAttribute("user",new User("benson",18));
        return "MyThymeleaf";
    }
}

User.java

package com.benson.entity;

 public class User {
    private String userName;
    private Integer age;

    public User(String userName, Integer age) {
        this.userName = userName;
        this.age = age;
    }

    public String getUserName() {
        return userName;
    }

    public Integer getAge() {
        return age;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    public void setAge(Integer age) {
        this.age = age;
    }
}

myThymeleaf.html

<!DOCTYPE html>
<!--需要在HTML文件中加入以下语句: -->
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Show User</title>
</head>
<body>
<table>
    姓名:<span th:text="${user.userName}"></span>
    年龄:<span th:text="${user.age}"></span>
</table>
</body>
</html>


原生的HttpServletRequest会导致idea在html会误报,可以把参数从对象扔到Map里

循环、判断语句


整合JdbcTemplate

建表

CREATE TABLE `users` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(32) NOT NULL COMMENT '用户名称',
  `age` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;

引入pom

<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.21</version>
        </dependency>

配置yml

    datasource:
      url: jdbc:mysql://localhost:3306/test
      username: root
      password: 123456
      driver-class-name: com.mysql.jdbc.Driver

UserService.java

package com.benson.service;

import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class UserService {

    @Autowired
    private JdbcTemplate jdbcTemplate;//拿到jdbc的模板

    @RequestMapping("/insertUser")
    public String insertUser(String userName, Integer age) {
        int update = jdbcTemplate.update("insert into users values (null,?,?)", userName, age);
        /*null是因为直接insert into要写全所有字段,id自增所以null来表示
         * ?,?来防止sql注入*/
        return update > 0 ? "success" : "failed";
    }
}

注意!mysql驱动要改到8.0版本,不然识别不了密码
yml那在mysql.后面加个cj

整合mybatis

pom依赖

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

yml跟jdbc的一样
在entity包的实体类User中加个id属性,构造器也要加上
新建mapper包,新建UserMapper接口

package com.benson.mapper;

import com.benson.entity.User;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;

@Mapper
//因为类和接口的接口不同,所以APP.java扫不到UserMapper,要加上@Mapper
public interface UserMapper {
    @Insert("insert into users values (null,#{userName},#{age})")
    int insertUser(@Param("userName") String userName, @Param("age") Integer age);

    @Select("select id,name as userName,age from users where  id=#{id}")
    User selectById(@Param("id") Integer id);
}

UserService.java

package com.benson.service;

import com.benson.entity.User;
import com.benson.mapper.UserMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class UserService {

    @Autowired
    private JdbcTemplate jdbcTemplate;//拿到jdbc的模板
    @Autowired
    private UserMapper userMapper;

    @RequestMapping("/insertUser")
    public String insertUser(String userName, Integer age) {
        int update = jdbcTemplate.update("insert into users values (null,?,?)", userName, age);
        /*null是因为直接insert into要写全所有字段,id自增所以null来表示
         * ?,?来防止sql注入*/
        return update > 0 ? "success" : "failed";
    }

    @RequestMapping("/selectById")
    public User selectById(Integer id) {
        return userMapper.selectById(id);
    }

    @RequestMapping("/mybatisInsertUser")
    public String mybatisInsertUser(String userName, Integer age) {
        int insert = userMapper.insertUser(userName, age);
        return insert > 0 ? "success" : "failed";
    }
}

整合热部署框架devtools

热部署就是java类或html页面或者静态文件有变化就会实时更新
热部署能提高开发效率,不用每次做些修改后要手动重启一次。但不适合生产环境,因为不安全
原理:类加载器

pom.xml

<!--SpringBoot热部署配置 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-devtools</artifactId>
    <scope>runtime</scope>
    <optional>true</optional>
</dependency>




重新启动项目,之后就成了热部署了

集成lombok

该插件能让代码变得更简洁
bean类的规范是要给每个属性都写get和set方法,属性多了就显得冗余。
而lombok提供了@Data注解能让bean类直接get和set属性的值,不需要写get和set方法
idea安装插件lombok,不过一般是捆绑好的
再添加依赖(@Data注解实际是依赖提供的)

<dependency>
	<groupId>org.projectlombok</groupId>
	<artifactId>lombok</artifactId>
</dependency>
package com.benson.entity;

import lombok.Data;
import lombok.extern.slf4j.Slf4j;

@Data
@Slf4j
public class User {
    private String userName;
    private Integer age;
    private Integer id;

    public  User(){}
    public User(Integer id, String userName, Integer age) {
        this.userName = userName;
        this.age = age;
        this.id = id;
    }

    public User(String userName, Integer age) {
        this.userName = userName;
        this.age = age;
    }

//    public String getUserName() {
//        return userName;
//    }
//
//    public Integer getAge() {
//        return age;
//    }
//
//    public void setUserName(String userName) {
//        this.userName = userName;
//    }
//
//    public void setAge(Integer age) {
//        this.age = age;
//    }
//
//    public Integer getId() {
//        return id;
//    }
//
//    public void setId(Integer id) {
//        this.id = id;
//    }
//
//    @Override
//    public String toString() {
//        return "User{" +
//                "userName='" + userName + '\'' +
//                ", age=" + age +
//                ", id=" + id +
//                '}';
//    }
public static void main(String[] args) {
    User user = new User();
    user.setUserName("tommy");
    user.setAge(18);
    System.out.println("name: " + user.getUserName() + " age: " + user.getAge());
    //name: tommy age: 18
    log.info("username: " + user.getUserName());
    //23:16:18.719 [main] INFO com.benson.entity.User - username: tommy
}
}

你也可以不在类上@Data,在属性上@Getter和@Setter,不过这样每个属性都要写一次就很麻烦

还提供了@Slf4j注解,简化日志
不需要定义log这样的成员变量了,没有这个注解一百个类如果要看日志,就要定义100个log


  • 原理:
    实际上在开发写代码的时候是不需要写get和set方法,但是在编译class文件中,会帮你自动生成好这样get和set方法放入到class文件中。

整合配置文件

1.yml比properties更简洁,企业开发大多用yml

可以理解成bootstrap是电脑开机时操作系统的配置,而application是应用软件的配置

properties转yml

https://www.toyaml.com/index.html

@ConfigurationProperties


而@ConfigurationProperties能解决该问题,用该注解把配置读取到一个类中
取配置就到那个类去取,实现复用。后续配置又增多了再把多的部分写到类里
先添加依赖,类似于mybatisX的功能,方便yml配置与实体类对应变量的跳转

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

在entity下新建MihoyoEntity类

package com.benson.entity;

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

@Component //@Component标记为bean类
//加上后@ConfigurationProperties就不会报错了
@ConfigurationProperties(prefix = "mihoyo")//prefix指定要取的配置名
public class MihoyoEntity {
    private String addr;
    private String name;
    private Integer age;

    public String getAddr() {
        return addr;
    }

    public void setAddr(String addr) {
        this.addr = addr;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "MihoyoEntity{" +
                "addr='" + addr + '\'' +
                ", name='" + name + '\'' +
                ", age='" + age + '\'' +
                '}';
    }
}

ymlService.java

package com.benson.service;

import com.benson.entity.MihoyoEntity;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class ymlService {
    //获取配置的常规方法@Value
    @Value("${mihoyo.addr}")
    private String addr;
    @Value("${mihoyo.name}")
    private String name;
    @Value("${mihoyo.age}")
    private Integer age;

    @RequestMapping("/getInfo1")
    public String getInfo1() {
        return addr + " , " + name + " , " + age;
    }

    //获取配置的常用方法@ConfigurationProperties
    @Autowired //对类成员变量、方法及构造函数进行标注,完成自动装配
    private MihoyoEntity mihoyoEntity;

    @RequestMapping("/getInfo2")
    public String getInfo2() {
        return mihoyoEntity.toString();//想取什么配置就调用类的什么成员
    }
}

配置文件占位符

配置中也可以用占位符来表示变量

age用占位符使用了随机变量

整合多环境不同配置


当前有3个环境的配置

通过application.yml的spring中profiles的active来指定当前的选择哪个环境的配置

修改端口和上下文路径


idea中server.就有提示了

yml的配置改成了端口8081,默认的访问路径/mayikt
所以直接访问/myThymeleaf会404,得重新写对默认路径

日志管理

使用logback记录日志

Springboot已经默认帮你整合好了logback,日志输出文件在当前项目路径log文件夹下
idea默认自带logback,所以不需要依赖,但需要lombok的依赖来提供注解
在resources新建log目录,在里面新建log.xml

<configuration>
    <!--本文主要输出日志为控制台日志,系统日志,sql日志,异常日志-->
    <!-- %m输出的信息,%p日志级别,%t线程名,%d日期,%c类的全名,,,, -->
    <!--控制台-->
    <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>%d %p (%file:%line\)- %m%n</pattern>
            <charset>UTF-8</charset>
        </encoder>
    </appender>

    <!--系统info级别日志-->
    <!--<File> 日志目录,没有会自动创建-->
    <!--<rollingPolicy>日志策略,每天简历一个日志文件,或者当天日志文件超过64MB时-->
    <!--encoder 日志编码及输出格式-->
    <appender name="fileLog"
              class="ch.qos.logback.core.rolling.RollingFileAppender">
        <File>log/file/fileLog.log</File>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>log/file/fileLog.log.%d.%i</fileNamePattern>
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <!-- or whenever the file size reaches 64 MB -->
                <maxFileSize>64 MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
        </rollingPolicy>
        <encoder>
            <pattern>
                %d %p (%file:%line\)- %m%n
            </pattern>
            <charset>UTF-8</charset>
            <!-- 此处设置字符集 -->
        </encoder>
    </appender>

    <!--sql日志-->
    <appender name="sqlFile"
              class="ch.qos.logback.core.rolling.RollingFileAppender">
        <File>log/sql/sqlFile.log</File>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>log/sql/sqlFile.log.%d.%i</fileNamePattern>
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <!-- or whenever the file size reaches 64 MB -->
                <maxFileSize>64 MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
        </rollingPolicy>
        <!--对记录事件进行格式化。负责两件事,一是把日志信息转换成字节数组,二是把字节数组写入到输出流。-->
        <encoder>
            <!--用来设置日志的输入格式-->
            <pattern>
                %d %p (%file:%line\)- %m%n
            </pattern>
            <charset>UTF-8</charset>
            <!-- 此处设置字符集 -->
        </encoder>
    </appender>


    <!--异常日志-->
    <appender name="errorFile"
              class="ch.qos.logback.core.rolling.RollingFileAppender">
        <File>log/error/errorFile.log</File>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>log/error/errorFile.%d.log.%i</fileNamePattern>
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <!-- or whenever the file size reaches 64 MB -->
                <maxFileSize>64 MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
        </rollingPolicy>
        <!--对记录事件进行格式化。负责两件事,一是把日志信息转换成字节数组,二是把字节数组写入到输出流。-->
        <encoder>
            <!--用来设置日志的输入格式-->
            <pattern>
                %d %p (%file:%line\)- %m%n
            </pattern>
            <charset>UTF-8</charset>
            <!-- 此处设置字符集 -->
        </encoder>
        <!--
            日志都在这里 过滤出 error
            使用 try {}catch (Exception e){} 的话异常无法写入日志,可以在catch里用logger.error()方法手动写入日志
            -->
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>ERROR</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>
    <!--  日志输出级别 -->
    <!--All\DEBUG\INFO\WARN\ERROR\FATAL\OFF-->
    <!--打印info级别日志,分别在控制台,fileLog,errorFile输出
        异常日志在上面由过滤器过滤出ERROR日志打印
    -->
    <root level="INFO">
        <appender-ref ref="fileLog" />
        <appender-ref ref="console" />
        <appender-ref ref="errorFile" />
    </root>

    <!--打印sql至sqlFile文件日志-->
    <logger name="com.dolphin.mapper" level="DEBUG" additivity="false">
        <appender-ref ref="console" />
        <appender-ref ref="sqlFile" />
    </logger>
</configuration>


整合logback配置

要想项目找到logback.xml的配置,还要在yml配置logback的路径
yml中

  • 打印日志

整合log4j日志

添加依赖,先移出自带的logback依赖,以免冲突

<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
</dependency>
<!-- spring boot start -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter</artifactId>
    <exclusions>
        <!-- 排除自带的logback依赖 -->
        <exclusion>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-logging</artifactId>
        </exclusion>
    </exclusions>
</dependency>

<!-- springboot-log4j -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-log4j</artifactId>
    <version>1.3.8.RELEASE</version>
</dependency>

在resources新建log4j.properties

#log4j.rootLogger=CONSOLE,info,error,DEBUG
log4j.rootLogger=DEBUG,error,CONSOLE,info
log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender     
log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout     
log4j.appender.CONSOLE.layout.ConversionPattern=%d{yyyy-MM-dd-HH-mm} [%t] [%c] [%p] - %m%n    

log4j.logger.info=info
log4j.appender.info=org.apache.log4j.DailyRollingFileAppender
log4j.appender.info.layout=org.apache.log4j.PatternLayout     
log4j.appender.info.layout.ConversionPattern=%d{yyyy-MM-dd-HH-mm} [%t] [%c] [%p] - %m%n  
log4j.appender.info.datePattern='.'yyyy-MM-dd
log4j.appender.info.Threshold = info   
log4j.appender.info.append=true
log4j.appender.info.File=D:/Programme/code/log/info.log

log4j.logger.error=error  
log4j.appender.error=org.apache.log4j.DailyRollingFileAppender
log4j.appender.error.layout=org.apache.log4j.PatternLayout     
log4j.appender.error.layout.ConversionPattern=%d{yyyy-MM-dd-HH-mm} [%t] [%c] [%p] - %m%n  
log4j.appender.error.datePattern='.'yyyy-MM-dd
log4j.appender.error.Threshold = error   
log4j.appender.error.append=true
log4j.appender.error.File=D:/Programme/code/log/error.log

log4j.logger.DEBUG=DEBUG
log4j.appender.DEBUG=org.apache.log4j.DailyRollingFileAppender
log4j.appender.DEBUG.layout=org.apache.log4j.PatternLayout     
log4j.appender.DEBUG.layout.ConversionPattern=%d{yyyy-MM-dd-HH-mm} [%t] [%c] [%p] - %m%n  
log4j.appender.DEBUG.datePattern='.'yyyy-MM-dd
log4j.appender.DEBUG.Threshold = DEBUG   
log4j.appender.DEBUG.append=true
log4j.appender.DEBUG.File=D:/Programme/code/log/dubug.log

application.yml

###指定log4j.properties配置文件路径
logging:
  config: classpath:log4j.properties

重启项目后

使用日志的代码和logback完全一样

使用AOP统一打印web请求日志

如果每个方法都要写记录日志的代码,方法多了记录日志的冗余性就高了

为了减少冗余度,可以使用AOP来统一打印日志。每个方法执行前后都会被拦截,打印日志后才放行。

依赖

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

新建aop包,新建WebLogAspect类

package com.benson.aop;

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.util.Enumeration;

@Aspect
@Component
@Slf4j
public class WebLogAspect {//定义切面来拦截包

    /*
    * 切入点
    * com.benson.service.* 拦截service包下所有的类
    * service.*.* 第二个.*是拦截类中的所有方法
    * .*(..)的*(..)是拦截方法中所有的参数
    * */
    @Pointcut("execution(public * com.benson.service.*.*(..))")
    public void webLog() {
    }

    /*
    * 前置通知
    * 请求方法之前做拦截
    * 打印请求的访问地址、协议、ip和参数信息等
    * */
    @Before("webLog()")
    public void doBefore(JoinPoint joinPoint) throws Throwable {
        // 接收到请求,记录请求内容
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        // 记录下请求内容
        log.info("URL : " + request.getRequestURL().toString());
        log.info("HTTP_METHOD : " + request.getMethod());
        log.info("IP : " + request.getRemoteAddr());
        Enumeration<String> enu = request.getParameterNames();
        while (enu.hasMoreElements()) {
            String name = (String) enu.nextElement();
            log.info("name:{},value:{}", name, request.getParameter(name));
        }
    }

    /*
    * 方法执行完后再拦截
    * 打印响应信息
    * */
    @AfterReturning(returning = "ret", pointcut = "webLog()")
    public void doAfterReturning(Object ret) throws Throwable {
        // 处理完请求,返回内容
        log.info("RESPONSE : " + ret);
    }
}

现在类中就可以去掉所有的记录日志的代码了

整合定时任务注解

在Spring Boot的主类中加入@EnableScheduling注解,启用定时任务的配置

整合定时任务Quartz表达式

https://www.bejson.com/othertools/cron/

整合异步的线程

启动加上@EnableAsync ,需要执行异步方法上加上@Async
项目会单独加上一个Spring线程来执行异步方案
用一段伪代码来模拟用户注册时接短信的情景

单线程需要4秒

从日志上看,单线程同步执行了代码

直观体现,页面转了几秒圈圈

现在采用多线程

采用多线程,在用户看来发出请求1s后就响应了

页面一加载就完成了响应


从日志中看出代码也不在按顺序执行,业务逻辑有些问题,但道理就是这个道理

@Async实际就是多线程封装的,异步线程执行方法有可能会非常消耗cpu的资源,所以大的项目建议使用
Mq异步实现。

@Async失效问题


使用@Async标注sms是异步的
启动类开始异步@EnableAsync

启动项目后,问题出现了,程序是同步执行的。

  • 原因
    注意:如果异步注解写当前自己类,有可能aop会失效,无法拦截@Async注解,最终导致异步失效
    原理是aop拦截方法时会判断有没有异步注解,有的话它会单独开个Thead线程处理
    需要经过代理类调用接口,所以需要将异步的代码单独抽取成一个类调用接口。
    新建async包

    回到原来的类,通过把异步的方法单独封装到类里来调用

    这样注解会经过AOP代理就不会失效了
    但还是不要随便创建线程,可能会无限创建线程(因为没有线程池)消耗资源

@Async整合线程池

新建config包,ThreadPoolConfig类

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.task.TaskExecutor;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

import java.util.concurrent.ThreadPoolExecutor;

@Configuration
@EnableAsync
public class ThreadPoolConfig {

    /**
     * 每秒需要多少个线程处理?
     * tasks/(1/taskcost)
     */
    private int corePoolSize = 3;

    /**
     * 线程池维护线程的最大数量
     * (max(tasks)- queueCapacity)/(1/taskcost)
     */
    private int maxPoolSize = 3;

    /**
     * 缓存队列
     * (coreSizePool/taskcost)*responsetime
     */
    private int queueCapacity = 10;

    /**
     * 允许的空闲时间
     * 默认为60
     */
    private int keepAlive = 100;

    @Bean
    public TaskExecutor taskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        // 设置核心线程数
        executor.setCorePoolSize(corePoolSize);
        // 设置最大线程数
        executor.setMaxPoolSize(maxPoolSize);
        // 设置队列容量
        executor.setQueueCapacity(queueCapacity);
        // 设置允许的空闲时间(秒)
        //executor.setKeepAliveSeconds(keepAlive);
        // 设置默认线程名称
        executor.setThreadNamePrefix("thread-");
        // 设置拒绝策略rejection-policy:当pool已经达到max size的时候,如何处理新任务
        // CALLER_RUNS:不在新线程中执行任务,而是有调用者所在的线程来执行
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        // 等待所有任务结束后再关闭线程池
        executor.setWaitForTasksToCompleteOnShutdown(true);
        return executor;
    }

}

异步抽象类MemberServiceAsync.java

import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;

@Slf4j
@Component
public class MemberServiceAsync {
    @Async("taskExecutor")//指定线程池
    public String smsAsync() {
        log.info(">02<");
        try {
            log.info(">正在发送短信..<");
            Thread.sleep(3000);
        } catch (Exception e) {

        }
        log.info(">03<");
        return "短信发送完成!";
    }
}


无论访问多少次都不会出现创建的线程多于3条的情况

整合全局捕获异常

实现项目运行中一报错就会执行某个方法的功能

模拟报错

get请求传个值为0的参数就会报错

但不应该直接把系统默认的报错页面展示出来,一方面很low,
另一方面可能别人会根据报错信息找到漏洞从而攻击服务器
service包下
MayiktExceptionHandler.java

@ControllerAdvice
public class MayiktExceptionHandler {

    /**
     * 拦截运行异常出现的错误~~~
     *
     * @return
     */
    @ExceptionHandler(RuntimeException.class)//捕获运行时异常
    @ResponseBody//转为json格式
    public Map<Object, Object> exceptionHandler() {
        Map<Object, Object> map = new HashMap<>();
        map.put("code", "500");
        map.put("msg", "系统出现错误~");
        return map;
    }
}

报错就执行

打包运行发布

先删掉target目录

然后在此路径(项目模块的路径)打开cmd

mvn clean package

将项目打包成jar包

要打开项目可以在cmd中

java –jar 包名

可能会报错,可能是没有找到哪个类作为启动入口
在pom中添加build

<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
            <executions>
                <execution>
                    <goals>
                        <goal>repackage</goal>
                    </goals>
                </execution>
            </executions>
            <configuration>
                <mainClass>com.mayikt.App</mainClass>
                <excludes>
                    <exclude>
                        <groupId>junit</groupId>
                        <artifactId>junit</artifactId>
                    </exclude>
                    <exclude>
                        <groupId>org.springframework.boot</groupId>
                        <artifactId>spring-boot-starter-test</artifactId>
                    </exclude>
                </excludes>
            </configuration>
        </plugin>
    </plugins>
</build>

重新打包再运行
打包的意义在于把项目放到服务器(如微服务器)、docker容器中运行

posted @ 2023-03-01 18:30  ben10044  阅读(40)  评论(0编辑  收藏  举报