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容器中运行