springboot 也是配置地狱,插件太多,配置太多,也要做模板化工程,如果每个项目都从头来做,那
等配置完黄花菜都凉了,突然还是觉得django香了
模板源码:https://gitee.com/jpruby/springboot-template.git
生成目录方法
IDEA
在写文档的时候,想把项目输出成文档树的形式,可以使用以下命令
tree >> D:/tree.txt 只有文件夹
tree /f >> D:/tree.txt 包括文件夹和文件
├─src
│ ├─main
│ │ ├─java
│ │ │ └─com
│ │ │ └─jpruby
│ │ │ │ DemoApplication.java
│ │ │ │
│ │ │ ├─annotation
│ │ │ │ CurrentUserId.java
│ │ │ │ SystemLog.java
│ │ │ │
│ │ │ ├─aspect
│ │ │ │ LogAspect.java
│ │ │ │
│ │ │ ├─config
│ │ │ │ ArgumentResolverConfig.java
│ │ │ │ InterceptorConfig.java
│ │ │ │ SwaggerConfig.java
│ │ │ │ WebConfig.java
│ │ │ │
│ │ │ ├─controller
│ │ │ │ UserController.java
│ │ │ │
│ │ │ ├─domain
│ │ │ │ ├─common
│ │ │ │ │ AppHttpCodeEnum.java
│ │ │ │ │ ResponseResult.java
│ │ │ │ │
│ │ │ │ └─entity
│ │ │ │ User.java
│ │ │ │
│ │ │ ├─exception
│ │ │ │ SystemException.java
│ │ │ │
│ │ │ ├─handler
│ │ │ │ ├─exception
│ │ │ │ │ GlobalExceptionHandler.java
│ │ │ │ │
│ │ │ │ └─resolver
│ │ │ │ UserIdArgumentResolver.java
│ │ │ │
│ │ │ ├─interceptor
│ │ │ │ LoginInterceptor.java
│ │ │ │
│ │ │ ├─mapper
│ │ │ │ UserMapper.java
│ │ │ │
│ │ │ ├─service
│ │ │ │ │ UserService.java
│ │ │ │ │
│ │ │ │ └─impl
│ │ │ │ UserServiceImpl.java
│ │ │ │
│ │ │ └─utils
│ │ │ JwtUtil.java
│ │ │
│ │ └─resources
│ │ application-test.yml
│ │ application.yml
│ │
起步
官网:Spring Boot
目前最新稳定版本 2.6.7
构建项目 创建项目 meven配置阿里云镜像settings.xml (maven的配置文件)
<mirror>
<id>alimavenid>
<name>aliyun mavenname>
<url>http://maven.aliyun.com/nexus/content/groups/public/url>
<mirrorOf>centralmirrorOf>
mirror>
mirrors>
<profile>
<id>jdk-1.8id>
<activation>
<activeByDefault>trueactiveByDefault>
<jdk>1.8jdk>
activation>
<properties>
<maven.compiler.source>1.8maven.compiler.source>
<maven.compiler.target>1.8maven.compiler.target>
<maven.compiler.compilerVersion>1.8maven.compiler.compilerVersion>
properties>
profile>
pom.xml
<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.0modelVersion>
<parent>
<artifactId>spring-boot-starter-parentartifactId>
<groupId>org.springframework.bootgroupId>
<version>2.6.6version>
parent>
<groupId>com.jprubygroupId>
<artifactId>springboot-demoartifactId>
<version>1.0-SNAPSHOTversion>
<properties>
<maven.compiler.source>8maven.compiler.source>
<maven.compiler.target>8maven.compiler.target>
properties>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-devtoolsartifactId>
<optional>trueoptional>
dependency>
整合junit5
注意包的结构和启动器的的包的所在位置要一致
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
使用
package com.jpruby;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class DemoApplicationTests {
@Test
void contextLoads() {
}
}
整合mybatis
pom.xml
<dependency>
<groupId>com.baomidougroupId>
<artifactId>mybatis-plus-boot-starterartifactId>
<version>3.5.1version>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
dependency>
dependencies>
application.yml 配置数据库连接池,默认自带希卡利 HikariPool,如果用德鲁伊自己去引
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://192.168.126.128/test
username: root
password: 123456
mybatis-plus:
configuration:
# 日志
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
global-config:
db-config:
logic-delete-field: delFlag # 逻辑删除, 1 删除 0 不删除
logic-delete-value: 1
logic-not-delete-value: 0
id-type: auto
效果:
统一返回接口格式枚举类定义返回code 和 msg
package com.jpruby.domain.common;
public enum AppHttpCodeEnum {
// 成功
SUCCESS(200," *** 作成功"),
// 登录
NEED_LOGIN(401,"需要登录后 *** 作"),
NO_OPERATOR_AUTH(403,"无权限 *** 作"),
SYSTEM_ERROR(500,"出现错误"),
USERNAME_EXIST(501,"用户名已存在"),
PHONENUMBER_EXIST(502,"手机号已存在"), EMAIL_EXIST(503, "邮箱已存在"),
REQUIRE_USERNAME(504, "必需填写用户名"),
LOGIN_ERROR(505,"用户名或密码错误");
int code;
String msg;
AppHttpCodeEnum(int code, String errorMessage){
this.code = code;
this.msg = errorMessage;
}
public int getCode() {
return code;
}
public String getMsg() {
return msg;
}
}
定义统一返回的实体类
package com.jpruby.domain.common;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.jpruby.domain.common.AppHttpCodeEnum;
import java.io.Serializable;
@JsonInclude(JsonInclude.Include.NON_NULL)
public class ResponseResult<T> implements Serializable {
private Integer code;
private String msg;
private T data;
public ResponseResult() {
this.code = AppHttpCodeEnum.SUCCESS.getCode();
this.msg = AppHttpCodeEnum.SUCCESS.getMsg();
}
public ResponseResult(Integer code, T data) {
this.code = code;
this.data = data;
}
public ResponseResult(Integer code, String msg, T data) {
this.code = code;
this.msg = msg;
this.data = data;
}
public ResponseResult(Integer code, String msg) {
this.code = code;
this.msg = msg;
}
public static ResponseResult errorResult(int code, String msg) {
ResponseResult result = new ResponseResult();
return result.error(code, msg);
}
public static ResponseResult okResult() {
ResponseResult result = new ResponseResult();
return result;
}
public static ResponseResult okResult(int code, String msg) {
ResponseResult result = new ResponseResult();
return result.ok(code, null, msg);
}
public static ResponseResult okResult(Object data) {
ResponseResult result = setAppHttpCodeEnum(AppHttpCodeEnum.SUCCESS, AppHttpCodeEnum.SUCCESS.getMsg());
if (data != null) {
result.setData(data);
}
return result;
}
public static ResponseResult errorResult(AppHttpCodeEnum enums) {
return setAppHttpCodeEnum(enums, enums.getMsg());
}
public static ResponseResult errorResult(AppHttpCodeEnum enums, String msg) {
return setAppHttpCodeEnum(enums, msg);
}
public static ResponseResult setAppHttpCodeEnum(AppHttpCodeEnum enums) {
return okResult(enums.getCode(), enums.getMsg());
}
private static ResponseResult setAppHttpCodeEnum(AppHttpCodeEnum enums, String msg) {
return okResult(enums.getCode(), msg);
}
public ResponseResult<?> error(Integer code, String msg) {
this.code = code;
this.msg = msg;
return this;
}
public ResponseResult<?> ok(Integer code, T data) {
this.code = code;
this.data = data;
return this;
}
public ResponseResult<?> ok(Integer code, T data, String msg) {
this.code = code;
this.data = data;
this.msg = msg;
return this;
}
public ResponseResult<?> ok(T data) {
this.data = data;
return this;
}
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
}
效果
swagger的配置pom.xml
<dependency>
<groupId>io.springfoxgroupId>
<artifactId>springfox-boot-starterartifactId>
<version>3.0.0version>
dependency>
<dependency>
<groupId>com.github.xiaoymingroupId>
<artifactId>swagger-bootstrap-uiartifactId>
<version>1.9.6version>
dependency>
application.yml
spring:
mvc:
pathmatch.matching-strategy: ant_path_matcher
config 配置类
package com.jpruby.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.oas.annotations.EnableOpenApi;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Contact;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
@Configuration
@EnableOpenApi
public class SwaggerConfig {
/**
* 创建API应用
* apiInfo() 增加API相关信息
* 通过select()函数返回一个ApiSelectorBuilder实例,用来控制哪些接口暴露给Swagger来展现,
* 本例采用指定扫描的包路径来定义指定要建立API的目录。
*
* @return
*/
@Bean
public Docket restApi() {
return new Docket(DocumentationType.SWAGGER_2)
.groupName("后台api")
.apiInfo(apiInfo("Spring Boot中使用Swagger 构建RESTful APIs", "1.0"))
.useDefaultResponseMessages(true)
.forCodeGeneration(false)
.select()
.apis(RequestHandlerSelectors.basePackage("com.jpruby.controller"))
.paths(PathSelectors.any())
.build();
}
/**
* 创建该API的基本信息(这些基本信息会展现在文档页面中)
* 访问地址:http://ip:port/swagger-ui.html
* http://ip:port/doc.html (新UI插件的地址)
*
* @return
*/
private ApiInfo apiInfo(String title, String version) {
return new ApiInfoBuilder()
.title(title)
.description("项目后台api")
.termsOfServiceUrl("")
.contact(new Contact("jpruby", "", "japan8364@163.com"))
.version(version)
.build();
}
}
效果 http://localhost:1983/doc.html
跨域cors解决
增加配置类
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
// 设置允许跨域的路径
registry.addMapping("/**")
// 设置允许跨域请求的域名
.allowedOriginPatterns("*")
// 是否允许cookie
.allowCredentials(true)
// 设置允许的请求方式
.allowedMethods("GET", "POST", "DELETE", "PUT")
// 设置允许的header属性
.allowedHeaders("*")
// 跨域允许时间
.maxAge(3600);
}
}
token生成方案 jwt
pom.xml
<dependency>
<groupId>io.jsonwebtokengroupId>
<artifactId>jjwtartifactId>
<version>0.9.1version>
dependency>
jwt 工具类创建
package com.jpruby.utils;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;
import java.util.Date;
import java.util.UUID;
/**
* JWT工具类
*/
public class JwtUtil {
//有效期为
public static final Long JWT_TTL = 24 * 60 * 60 * 1000L;// 60 * 60 *1000 一个小时
//设置秘钥明文
public static final String JWT_KEY = "jpruby";
public static String getUUID() {
String token = UUID.randomUUID().toString().replaceAll("-", "");
return token;
}
/**
* 生成jtw
*
* @param subject token中要存放的数据(json格式)
* @return
*/
public static String createJWT(String subject) {
JwtBuilder builder = getJwtBuilder(subject, null, getUUID());// 设置过期时间
return builder.compact();
}
/**
* 生成jtw
*
* @param subject token中要存放的数据(json格式)
* @param ttlMillis token超时时间
* @return
*/
public static String createJWT(String subject, Long ttlMillis) {
JwtBuilder builder = getJwtBuilder(subject, ttlMillis, getUUID());// 设置过期时间
return builder.compact();
}
private static JwtBuilder getJwtBuilder(String subject, Long ttlMillis, String uuid) {
SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
SecretKey secretKey = generalKey();
long nowMillis = System.currentTimeMillis();
Date now = new Date(nowMillis);
if (ttlMillis == null) {
ttlMillis = JwtUtil.JWT_TTL;
}
long expMillis = nowMillis + ttlMillis;
Date expDate = new Date(expMillis);
return Jwts.builder()
.setId(uuid) //唯一的ID
.setSubject(subject) // 主题 可以是JSON数据
.setIssuer("sg") // 签发者
.setIssuedAt(now) // 签发时间
.signWith(signatureAlgorithm, secretKey) //使用HS256对称加密算法签名, 第二个参数为秘钥
.setExpiration(expDate);
}
/**
* 创建token
*
* @param id
* @param subject
* @param ttlMillis
* @return
*/
public static String createJWT(String id, String subject, Long ttlMillis) {
JwtBuilder builder = getJwtBuilder(subject, ttlMillis, id);// 设置过期时间
return builder.compact();
}
public static void main(String[] args) throws Exception {
String token = "eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiJjYWM2ZDVhZi1mNjVlLTQ0MDAtYjcxMi0zYWEwOGIyOTIwYjQiLCJzdWIiOiJzZyIsImlzcyI6InNnIiwiaWF0IjoxNjM4MTA2NzEyLCJleHAiOjE2MzgxMTAzMTJ9.JVsSbkP94wuczb4QryQbAke3ysBDIL5ou8fWsbt_ebg";
Claims claims = parseJWT(token);
System.out.println(claims);
}
/**
* 生成加密后的秘钥 secretKey
*
* @return
*/
public static SecretKey generalKey() {
byte[] encodedKey = Base64.getDecoder().decode(JwtUtil.JWT_KEY);
SecretKey key = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES");
return key;
}
/**
* 解析
*
* @param jwt
* @return
* @throws Exception
*/
public static Claims parseJWT(String jwt) throws Exception {
SecretKey secretKey = generalKey();
return Jwts.parser()
.setSigningKey(secretKey)
.parseClaimsJws(jwt)
.getBody();
}
}
注意!!JWT报错提示Exception in thread “main“ java.lang.NoClassDefFoundError: javax/xml/bind/DatatypeConverter
解决方案
在jdk8以后,就不再引入javax包了,其实当我们看到
NoClassDefFoundError
就应该意识到JDK出了问题
解决问题的方法有两个
更换JDK版本,我用的是JDK11(本人感觉很香),更换为jdk8以后就OK了
引入相关jar包
pom.xml
<dependency>
<groupId>javax.xml.bindgroupId>
<artifactId>jaxb-apiartifactId>
dependency>
测试效果
// 测试效果
public static void main(String[] args) throws Exception {
//生成token
String token = createJWT(UUID.randomUUID().toString(),"nihao",null);
System.out.println("token = " + token);
//解析token
Claims subject = parseJWT(token);
System.out.println("subject.getSubject() = " + subject.getSubject());
}
拦截器
首先分清楚什么是拦截器,什么是过滤器
- Filter:过滤器,过滤从客户端向服务器发送的请求。
- Interceptor:拦截器,更细粒度化的拦截。(拦截其中的具体的方法)。
定义一个拦截器 自己建立一个文件夹
package com.jpruby.interceptor;
import com.jpruby.utils.JwtUtil;
import io.jsonwebtoken.Claims;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Component
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//获取请求头的token
String token = request.getHeader("token");
if(!StringUtils.hasText(token)){
//token为空 直接拦截
response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
return false;
}
//解析token
try {
Claims claims = JwtUtil.parseJWT(token);
String subject = claims.getSubject();
} catch (Exception e) {
e.printStackTrace();
response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
return false;
}
//如果出现异常未登录 直接返回异常统一处理
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
}
}
配置类走一波
package com.jpruby.config;
import com.jpruby.interceptor.LoginInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
/**
* 加入自己写好的拦截器
* @param registry
*/
@Autowired
private LoginInterceptor loginInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(loginInterceptor) //添加拦截器
.addPathPatterns("/user") //指定一个拦截对象
.excludePathPatterns("/doc.html/**","/webjars/**"); //放行
}
}
测试效果,401成功完美!
异常统一处理优雅的处理统一异常处理与统一结果返回
定义全局异常处理器 GlobalExceptionHandler
package com.jpruby.handler.exception;
import com.jpruby.domain.common.AppHttpCodeEnum;
import com.jpruby.domain.common.ResponseResult;
import com.jpruby.exception.SystemException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
@ExceptionHandler(SystemException.class)
public ResponseResult systemExceptionHandler(SystemException e){
//打印异常信息
log.error("出现了异常! {}",e);
//从异常对象中获取提示信息封装返回
return ResponseResult.errorResult(e.getCode(),e.getMsg());
}
@ExceptionHandler(Exception.class)
public ResponseResult exceptionHandler(Exception e){
//打印异常信息
log.error("出现了异常! {}",e);
//从异常对象中获取提示信息封装返回
return ResponseResult.errorResult(AppHttpCodeEnum.SYSTEM_ERROR.getCode(),e.getMessage());
}
}
自定义一种异常可以表示很多内容的那种SystemException 哇哈哈哈
package com.jpruby.exception;
import com.jpruby.domain.common.AppHttpCodeEnum;
public class SystemException extends RuntimeException{
private int code;
private String msg;
public int getCode() {
return code;
}
public String getMsg() {
return msg;
}
public SystemException(AppHttpCodeEnum httpCodeEnum) {
super(httpCodeEnum.getMsg());
this.code = httpCodeEnum.getCode();
this.msg = httpCodeEnum.getMsg();
}
}
自定义异常需要一个枚举类支持,这个类在别的地方也有大作用
package com.jpruby.domain.common;
public enum AppHttpCodeEnum {
// 成功
SUCCESS(200," *** 作成功"),
// 登录
NEED_LOGIN(401,"需要登录后 *** 作"),
NO_OPERATOR_AUTH(403,"无权限 *** 作"),
SYSTEM_ERROR(500,"出现错误"),
USERNAME_EXIST(501,"用户名已存在"),
PHONENUMBER_EXIST(502,"手机号已存在"), EMAIL_EXIST(503, "邮箱已存在"),
REQUIRE_USERNAME(504, "必需填写用户名"),
LOGIN_ERROR(505,"用户名或密码错误");
int code;
String msg;
AppHttpCodeEnum(int code, String errorMessage){
this.code = code;
this.msg = errorMessage;
}
public int getCode() {
return code;
}
public String getMsg() {
return msg;
}
}
自定义参数解析
反反复复获取header 里的token 信息没意义 自定义即可
HandlerMethodArgumentResolver 了解一下
先定义一个属于自己的注解
package com.jpruby.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface CurrentUserId {
}
再定义一个handler 作为自己的注解解析器
package com.jpruby.handler.resolver;
import com.jpruby.annotation.CurrentUserId;
import com.jpruby.utils.JwtUtil;
import io.jsonwebtoken.Claims;
import org.springframework.core.MethodParameter;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;
/**
* Created with IntelliJ IDEA.
* Project: demo
* Author: jpruby
* Date: 2022/04/25/15:36
* Description: 一看就会,一写就废
* FilePath: com.jpruby.handler.resolver
* HandlerMethodArgumentResolver 是spring的一个处理器 还有设置config
* Copyright (c) 2022, All Rights Reserved.
*/
@Component
public class UserIdArgumentResolver implements HandlerMethodArgumentResolver {
// 判断方法参数能使用当前的参数解析器,进行处理,自己要需要定义一个注解
@Override
public boolean supportsParameter(MethodParameter parameter) {
// 如果方法参数含有@CurrentUserId 就能被我自定义的注解解析器解析
return parameter.hasParameterAnnotation(CurrentUserId.class);
}
// 真正的参数解析的方法,可以在方法中获取对应的数据,然后把数据作为返回值返回,方法的返回值就会赋值给对应的方法参数
@Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
// 获取请求头中的token
String token = webRequest.getHeader("token");
// 解析token
if (StringUtils.hasText(token)) {
Claims claims = JwtUtil.parseJWT(token); // 如果解析不到也会被我的全局异常捕获
return claims.getSubject();
}
return null;
}
}
最后再配置一下自己的解析器否则系统认你谁谁啊
package com.jpruby.config;
import com.jpruby.handler.resolver.UserIdArgumentResolver;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import java.util.List;
@Configuration
public class ArgumentResolverConfig implements WebMvcConfigurer {
@Autowired
UserIdArgumentResolver userIdArgumentResolver;
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
resolvers.add(userIdArgumentResolver);
}
}
测试效果,成功!
声明式事务直接在需要事务控制的方法上加上对应的注解 @Transactional
直接上图
AOP 登场批量的增强
springboot 默认开启Aop
pom.xml
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-aopartifactId>
dependency>
用法举例
先来一个注解
package com.jpruby.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface SystemLog {
String businessName();
}
aop 需要一个JSON的依赖先引进来
<!--fastjson依赖-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.33</version>
</dependency>
创建aop啦!
package com.jpruby.aspect;
import com.alibaba.fastjson.JSON;
import com.jpruby.annotation.SystemLog;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
@Component
@Aspect
@Slf4j
public class LogAspect {
// 确定切点
@Pointcut("@annotation(com.jpruby.annotation.SystemLog)")
public void pt() {
}
// 定义通知方法
@Around("pt()")
public Object printLog(ProceedingJoinPoint joinPoint) throws Throwable {
Object ret;
try {
handleBefore(joinPoint);
ret = joinPoint.proceed();
handleAfter(ret);
} finally {
// 结束后换行
log.info("=======End=======" + System.lineSeparator());
}
return ret;
}
private void handleAfter(Object ret) {
// 打印出参
log.info("Response : {}", JSON.toJSONString(ret));
}
private void handleBefore(ProceedingJoinPoint joinPoint) {
ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = requestAttributes.getRequest();
//获取被增强方法上的注解对象
SystemLog systemLog = getSystemLog(joinPoint);
log.info("=======Start=======");
// 打印请求 URL
log.info("URL : {}", request.getRequestURL());
// 打印描述信息
log.info("BusinessName : {}", systemLog.businessName());
// 打印 Http method
log.info("HTTP Method : {}", request.getMethod());
// 打印调用 controller 的全路径以及执行方法
log.info("Class Method : {}.{}", joinPoint.getSignature().getDeclaringTypeName(), ((MethodSignature) joinPoint.getSignature()).getName());
// 打印请求的 IP
log.info("IP : {}", request.getRemoteHost());
// 打印请求入参
log.info("Request Args : {}", JSON.toJSONString(joinPoint.getArgs()));
}
private SystemLog getSystemLog(ProceedingJoinPoint joinPoint) {
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
return methodSignature.getMethod().getAnnotation(SystemLog.class);
}
}
使用
效果
整合Redispom.xml
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-redisartifactId>
dependency>
application.yml
spring:
redis:
host: 192.168.126.128
port: 6379
测试
效果
多环境配置直接上图
jar包测试环境切换>java -jar .\demo-0.0.1-SNAPSHOT.jar --spring.profiles.active=test
PS D:\后端练习\三更springboot\code\demo\target> java -jar .\demo-0.0.1-SNAPSHOT.jar --spring.profiles.active=test
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.6.6)
2022-04-25 18:20:54.567 INFO 3652 --- [ main] com.jpruby.DemoApplication : Starting DemoApplication v0.0.1-SNAPSHOT using Java 17.0.2 on LAPTOP-jpruby with PID 3652 (D:\后端练习\三更springboot\code\demo\target\demo-0.0.1-SNAPSHOT.jar started by jpruby in D:\后端练习\三更springboot\code\demo\target)
2022-04-25 18:20:54.571 INFO 3652 --- [ main] com.jpruby.DemoApplication : The following 1 profile is active: "test"
2022-04-25 18:20:55.545 INFO 3652 --- [ main] .s.d.r.c.RepositoryConfigurationDelegate : Multiple Spring Data modules found, entering strict repository configuration mode!
2022-04-25 18:20:55.550 INFO 3652 --- [ main] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data Redis repositories in DEFAULT mode.
2022-04-25 18:20:55.579 INFO 3652 --- [ main] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 10 ms. Found 0 Redis repository interfaces.
2022-04-25 18:20:56.955 INFO 3652 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 1999 (http)
2022-04-25 18:20:56.968 INFO 3652 --- [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat]
2022-04-25 18:20:56.968 INFO 3652 --- [ main] org.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/9.0.60]
2022-04-25 18:20:57.065 INFO 3652 --- [ main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
2022-04-25 18:20:57.066 INFO 3652 --- [ main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 2436 ms
Logging initialized using 'class org.apache.ibatis.logging.stdout.StdOutImpl' adapter.
Property 'mapperLocations' was not specified.
_ _ |_ _ _|_. ___ _ | _
| | |\/|_)(_| | |_\ |_)||_|_\
/ |
3.5.1
2022-04-25 18:20:59.694 INFO 3652 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 1999 (http) with context path ''
2022-04-25 18:20:59.959 INFO 3652 --- [ main] com.jpruby.DemoApplication : Started DemoApplication in 5.884 seconds (JVM running for 6.291)
context.getBean(UserController.class).getClass().getName() = com.jpruby.controller.UserController$$EnhancerBySpringCGLIB$$8638c748
2022-04-25 18:21:38.436 INFO 3652 --- [nio-1999-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring DispatcherServlet 'dispatcherServlet'
2022-04-25 18:21:38.437 INFO 3652 --- [nio-1999-exec-1] o.s.web.servlet.DispatcherServlet : Initializing Servlet 'dispatcherServlet'
2022-04-25 18:21:38.441 INFO 3652 --- [nio-1999-exec-1] o.s.web.servlet.DispatcherServlet : Completed initialization in 4 ms
日志
开启日志
application.yaml
debug: true #开启日志
logging:
level:
com.jpruby: debug #设置日志级别
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)