前言
全局异常处理,AOP,SpringBoot配置优先级,Spring Bean管理,SpringBoot的自动装配
一、全局异常处理
定义类加上@RestControllerAdvice
注解,需要对异常进行处理,并返回JSON数据
异常处理方法上加上@ExceptionHandler(Exception.class)
注解,可以设定处理的异常,以下设置的Exception.class
import com.qinjie.tliaswebmanagement.domain.Result;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
@Slf4j
@RestControllerAdvice
public class GlobalExceptionAdvice {
@ExceptionHandler(Exception.class)
public Result handleException(Exception e){
log.error("服务器异常", e);
return Result.fail("操作失败");
}
}
二、AOP
2.1快速入门
2.1.1引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
2.1.2定义AOP类以及切入点
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
/**
* AOP类记录方法执行时间
* @Aspect定义该类为切面类
*/
@Aspect
@Component
@Slf4j
public class TimeAspect {
/**
* 定义切点,方法执行后,日志打印方法的执行时间
* org.aspectj.lang.ProceedingJoinPoint可以执行方法,以及获取到方法名称
*/
@Around("execution(* com.qinjie.aop.service.*.*(..))")
public Object doTimeAspect(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
long start = System.currentTimeMillis();
//真正方法执行
Object result = proceedingJoinPoint.proceed();
log.info(proceedingJoinPoint.getSignature() + "方法执行完成,执行时间:{}ms", System.currentTimeMillis() - start);
return result;
}
}
2.2 其他AOP注解
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
@Aspect
@Slf4j
@Component
public class AllAspect {
/**
* 定义切点方法信息,进行复用
*/
@Pointcut("execution(* com.qinjie.aop.service.impl.DeptServiceImpl.*(..))")
public void pointCut(){}
/**
* 方法执行前调用
*/
@Before("pointCut()")
public void before(){
log.info("before");
}
/**
* 方法执行前后调用
* @param joinPoint 切点,包含方法执行的方法的信息
* @return 方法执行返回值
* @throws Throwable 方法执行抛出异常
*/
@Around("pointCut()")
public Object around(org.aspectj.lang.ProceedingJoinPoint joinPoint) throws Throwable {
log.info("around");
Object proceed = joinPoint.proceed();
log.info("around end");
return proceed;
}
/**
* 方法执行后执行,抛出异常也会
*/
@After("pointCut()")
public void after(){
log.info("after");
}
/**
* 方法返回后执行
*/
@AfterReturning("pointCut()")
public void afterReturning(){
log.info("afterReturning");
}
/**
* 方法抛出异常后执行
*/
@AfterThrowing("pointCut()")
public void afterThrowing(){
log.info("afterThrowing");
}
}
2.3 在使用了注解的方法进行AOP
2.3.1自定义注解
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 MyLog {
}
2.3.2需要使用切面的方法加上注解
@MyLog
@Override
public Dept getDeptById(Long id) {
return deptMapper.selectByPrimaryKey(id);
}
2.3.3切面类切点设定注解
@Around("@annotation(com.qinjie.aop.aop.MyLog)")
public Object doTimeAspect(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
long start = System.currentTimeMillis();
//真正方法执行
Object result = proceedingJoinPoint.proceed();
log.info(proceedingJoinPoint.getSignature() + "方法执行完成,执行时间:{}ms", System.currentTimeMillis() - start);
return result;
}
三 SpringBoot配置优先级
3.1 Java系统属性和命令行参数
3.1.1Idea设置
Edit Configurations
如下图,通过 -Dxxx
指定Java系统属性(vm options
),通过--xxx
指定命令行参数(Program arguments
)。通过修改启动查看端口,可以确定,命令行参数优先级更高,其次是Java系统属性,然后才是propertis,yml,yaml。
在Modify options
可以选择需要展示的配置:
3.1.2 项目打包配置
首先确保pom.xml
有springboot的maven打包插件依赖,骨架创建默认自带
spring-boot-maven-plugin
即为该依赖
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
然后Idea的maven->Lifecycle
->package
进行打包
项目的target
目录下有xxx.jar
使用命令行执行java -Dxxx -jar xxx.jar --xxx
运行,xxx换成Java系统属性,jar包名称和命令行属性
通过上图可以看到使用的端口为9001,证明命令行参数优先级高于Java系统属性。
四. Spring Bean的管理
4.1获取Bean
从Spring容器中获取的Bean,默认单例,名称不同,就不是同一个对象了
import com.qinjie.tliaswebmanagement.controller.DeptController;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.ApplicationContext;
@SpringBootTest
class ApplicationTests {
@Autowired
private ApplicationContext applicationContext;
@Test
void testGetBean() {
//根据名称获取Bean
Object deptController = applicationContext.getBean("deptController");
System.out.println(deptController);
//根据类型获取Bean
Object deptControllerByType = applicationContext.getBean(DeptController.class);
System.out.println(deptControllerByType);
//根据名称和类型获取Bean
DeptController deptControllerByNameAndType = applicationContext.getBean("deptController", DeptController.class);
System.out.println(deptControllerByNameAndType);
}
}
com.qinjie.tliaswebmanagement.controller.DeptController@24fc2c80
com.qinjie.tliaswebmanagement.controller.DeptController@24fc2c80
com.qinjie.tliaswebmanagement.controller.DeptController@24fc2c80
4.2 bean在启动时创建
定义构造器,打印日志
@Slf4j
@RequestMapping("/depts")
@RestController
public class DeptController {
public DeptController() {
log.info("DeptController被创建了=============================");
}
}
Debug运行获取Bean时打断点
在断点时,已经打印
2024-04-11 16:21:58.195 INFO 44982 — [ main] c.q.t.controller.DeptController : DeptController被创建了============================
证明Bean启动被创建
修改Controller类,增加@Lazy
注解
...
@Lazy
public class DeptController {
...
}
同样打断点运行,到断点时,未打印构造器的日志,断点走过后打印了,且仅打印了一次,证明 @Lazy
注解使得类延迟到需要时创建,同时,默认创建的是单例对象。
Controllerl类上加注解@Scope("prototype")
,使其变为非单例,运行,以下结果证明,是三个不同的对象
2024-04-11 16:32:55.161 INFO 45505 — [ main] c.q.t.controller.DeptController : DeptController被创建了=============================
com.qinjie.tliaswebmanagement.controller.DeptController@b32e983
2024-04-11 16:32:55.175 INFO 45505 — [ main] c.q.t.controller.DeptController : DeptController被创建了=============================
com.qinjie.tliaswebmanagement.controller.DeptController@3c346c45
2024-04-11 16:32:55.176 INFO 45505 — [ main] c.q.t.controller.DeptController : DeptController被创建了=============================
com.qinjie.tliaswebmanagement.controller.DeptController@16df9bde
4.3 第三方bean使用@Bean
演示使得SaxReader为Spring管理的单例Bean
4.3.1 引入依赖
<!-- DOM解析XML -->
<dependency>
<groupId>org.dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>2.1.3</version>
</dependency>
4.3.2 定义配置类,@Bean创建Bean
这里@Bean
未指定bean名称,默认为方法名
import org.dom4j.io.SAXReader;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
@Component
public class BeanConfig {
@Bean
public SAXReader saxReader(){
return new SAXReader();
}
}
4.3.3 注入使用
import org.dom4j.io.SAXReader;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class ApplicationTests {
@Autowired
private SAXReader saxReader;
@Test
void testThirdBean() {
System.out.println(saxReader);
}
}
运行结果:
org.dom4j.io.SAXReader@377e573a
如果第三方Bean需要依赖其他Bean,方法形参自动注入
以下注入的DeptController
import com.qinjie.tliaswebmanagement.controller.DeptController;
import org.dom4j.io.SAXReader;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
@Component
public class BeanConfig {
@Bean
public SAXReader saxReader(DeptController deptController){
System.out.println(deptController);
return new SAXReader();
}
}
五. SpringBoot自动装配
5.1自动装配的类,可以直接使用
import com.google.gson.Gson;
import com.qinjie.tliaswebmanagement.domain.Result;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class ApplicationTests {
@Autowired
private Gson gson;
/**
* GSON是第三方自动装配的类,容器启动已经有了,可以直接使用
*/
@Test
public void testAutoConfigBean(){
String json = gson.toJson(Result.ok());
System.out.println(json);
}
}
打印:{“code”:1,“msg”:“成功”}
5.2 SpringBoot自动装配原理
5.2.1 外部类不在启动类所在包及以下,不可访问
创建maven项目,仅依赖spring-boot-starter
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>depend-utils</artifactId>
<version>0.0.1-SNAPSHOT</version>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<version>2.7.7</version>
</dependency>
</dependencies>
</project>
结构如下:
定义Spring管理的类TokenParser
package com.example;
import org.springframework.stereotype.Component;
@Component
public class TokenParser {
public void parse(){
System.out.println("TokenParser ... parse ...");
}
}
定义普通类HeaderParser
package com.example;
public class HeaderParser {
public void parse(){
System.out.println("HeaderParser ... parse ...");
}
}
定义普通类HeaderGenerator
package com.example;
public class HeaderGenerator {
public void generate(){
System.out.println("HeaderGenerator ... generate ...");
}
}
定义配置类HeaderConfig
,创建headerParser
和headerGenerator
package com.example;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class HeaderConfig {
@Bean
public HeaderParser headerParser(){
return new HeaderParser();
}
@Bean
public HeaderGenerator headerGenerator(){
return new HeaderGenerator();
}
}
创建另外的maven项目,pom.xml引入刚刚包的依赖
<dependency>
<groupId>com.example</groupId>
<artifactId>depend-utils</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
创建测试类,测试获取Bean
import org.junit.jupiter.api.Test;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.ApplicationContext;
@SpringBootTest
class ApplicationTests {
@Autowired
private ApplicationContext applicationContext;
@Test
public void testOutBean(){
try {
Object tokenParser = applicationContext.getBean("tokenParser");
System.out.println(tokenParser);
} catch (BeansException e) {
e.printStackTrace();
}
try {
Object headerParser = applicationContext.getBean("headerParser");
System.out.println(headerParser);
} catch (Exception e) {
e.printStackTrace();
}
Object headerGenerator = applicationContext.getBean("headerGenerator");
System.out.println(headerGenerator);
}
}
以上三个Bean都获取不到,因为不在启动类(包含@ComponentScan注解)的包及子包下,不会ComponentScan。
启动类上加上@ComponentScan({"com.qinjie", "com.example"})
注解,可以获取到Bean,这种方式性能不高
5.2.2 @Import引入普通类,配置类实现ImportSelector的类
启动类上加上注解@Import({TokenParser.class, HeaderConfig.class})
,引入有@Component
注解的类,@Configuration
的配置类,可以将类交给Spring管理
ImportSelecttor的类,需要在depend-utils下创建
实现ImportSelector
接口,实现selectImports
方法,返回值为类的全限定名的字符串数组,这里为HeaderConfig
配置类
package com.example;
import org.springframework.context.annotation.ImportSelector;
import org.springframework.core.type.AnnotationMetadata;
public class MyImportSelector implements ImportSelector {
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[]{"com.example.HeaderConfig"};
}
}
启动类上导入@Import({MyImportSelector.class})
,即可获取到HeaderConfig
中配置的bean
5.2.3 @Enablexxx注解
除了 @Import
,还有定义@Enablexx注解,里面包含 @Import 注解,
depend-utils增加EnableHeaderConfig
注解,引入MyImportSelector
package com.example;
import org.springframework.context.annotation.Import;
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.TYPE)
@Import(MyImportSelector.class)
public @interface EnableHeaderConfig {
}
启动类使用该注解:EnableHeaderConfig
即可获取到MyImportSelector
中定义的类
5.2.4 SpringBoot自动装配原理
SpringBootApplication
包含EnableAutoConfiguration
,这个注解又包含了@Import
注解,里面使用AutoConfugirationImportSelector
,这个类实现了ImportSelector接口的selectImports方法,该方法读取META-INF/spring.factories
以及META-INF/spring
包下的xxx.AutoConfiguration.imports文件,里面记录了很多AutoConfiguration的类的全限定名,将这些类的名称返回到数组中,进行自动装配。
5.2.5 @Conditional注解的派生注解
@ConditionalOnClass
表明满足条件的字节码文件存在,才注入到IOC容器中
@Bean
//存在该类,才会将下面的类注入到容器中
@ConditionalOnClass(name = "org.dom4j.io.SAXReader")
public HeaderParser headerParser(){
return new HeaderParser();
}
@ConditionalOnMissingBean
不存在该类型的Bean,才会将该类型的Bean注入到IOC容器中
@Bean
//不存在该类型的Bean,才会将该类型的Bean注入到IOC容器中。可以通过name和value进行名称或类型的匹配。用在没有定义该Bean,默认创建Bean的情况。
@ConditionalOnMissingBean
public HeaderParser headerParser(){
return new HeaderParser();
}
@ConditionalOnProperty
配置文件有名称为header.enabled,值为true的配置,才将该Bean注入容器
@Bean
//配置文件有名称为header.enabled,值为true的配置,才将该Bean注入容器
@ConditionalOnProperty(name = "header.enabled", havingValue = "true")
public HeaderParser headerParser(){
return new HeaderParser();
}
5.2.6 自定义starter(阿里云OSS)
- 创建自动配置模块
aliyun-oss-autoconfiguration
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>aliyun.oss</groupId>
<artifactId>autoconfiguration</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<!-- 引入 Spring Boot Starter -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<version>2.7.7</version>
</dependency>
<!-- 引入 Spring Boot Web Starter 上传文件需要-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.7.7</version>
</dependency>
<!-- 阿里云OSS -->
<dependency>
<groupId>com.aliyun.oss</groupId>
<artifactId>aliyun-sdk-oss</artifactId>
<version>3.15.1</version>
</dependency>
</dependencies>
</project>
ConfigurationProperty
配置oss配置前缀,以及参数
package aliyun.oss.autoconfiguration.property;
import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties(prefix = "aliyun.oss")
public class ConfigurationProperty {
/**
* 阿里云OSS的endpoint,在OSS控制台上获取。在某个bucket点进去的概览下面的访问端口中的外网地址。
*/
private String endpoint;
/**
* 阿里云OSS的Bucket名称,在OSS控制台上获取。
*/
private String bucketName;
public void setBucketName(String bucketName) {
this.bucketName = bucketName;
}
public String getBucketName() {
return bucketName;
}
public String getEndpoint() {
return endpoint;
}
public void setEndpoint(String endpoint) {
this.endpoint = endpoint;
}
}
OSS上传核心工具类AliyunOssUtil
,使用了ConfigurationProperty
获取OSS的参数
package aliyun.oss.autoconfiguration.util;
import aliyun.oss.autoconfiguration.property.ConfigurationProperty;
import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import com.aliyun.oss.common.auth.CredentialsProviderFactory;
import com.aliyun.oss.common.auth.EnvironmentVariableCredentialsProvider;
import org.springframework.web.multipart.MultipartFile;
import java.util.UUID;
/**
* 阿里云对象存储工具类
*/
public class AliyunOssUtil {
private ConfigurationProperty configurationProperty;
public ConfigurationProperty getConfigurationProperty() {
return configurationProperty;
}
public void setConfigurationProperty(ConfigurationProperty configurationProperty) {
this.configurationProperty = configurationProperty;
}
/**
* 上传文件到OSS。
* @return 文件上传后的访问地址
*/
public String putObject(MultipartFile image) {
// 从环境变量中获取访问凭证。运行本代码前,请确保已设置环境变量OSS_ACCESS_KEY_ID和OSS_ACCESS_KEY_SECRET。
EnvironmentVariableCredentialsProvider credentialsProvider = null;
try {
credentialsProvider = CredentialsProviderFactory.newEnvironmentVariableCredentialsProvider();
} catch (com.aliyuncs.exceptions.ClientException e) {
throw new RuntimeException(e);
}
String endpoint = configurationProperty.getEndpoint();
String bucketName = configurationProperty.getBucketName();
// 创建OSSClient实例。
OSS ossClient = new OSSClientBuilder().build(endpoint, credentialsProvider);
String originalFilename = image.getOriginalFilename();
assert originalFilename != null;
//文件名后缀
int extIndex = originalFilename.lastIndexOf(".");
String extName = originalFilename.substring(extIndex);
//使用UUID生成不重复的文件名,防止文件被覆盖掉
String newFileName = UUID.randomUUID() + extName;
try {
//上传文件到OSS
ossClient.putObject(bucketName, newFileName, image.getInputStream());
return "https://" + bucketName + "." + endpoint + "/" + newFileName;
} catch (Exception oe) {
return null;
} finally {
if (ossClient != null) {
ossClient.shutdown();
}
}
}
}
定义自动配置类AliyunSsoAutoConfiguration
,创建util的Bean
这里EnableConfigurationProperties
注解中有@Import
,引入了ConfigurationProperty
,然后赋值给AliyunOssUtil
。
package aliyun.oss.autoconfiguration.config;
import aliyun.oss.autoconfiguration.property.ConfigurationProperty;
import aliyun.oss.autoconfiguration.util.AliyunOssUtil;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* ConfigurationProperty使用EnableConfigurationProperties进行Import,然后赋值
*/
@Configuration
@EnableConfigurationProperties(ConfigurationProperty.class)
public class AliyunSsoAutoConfiguration {
@Bean
public AliyunOssUtil aliyunOssUtil(ConfigurationProperty configurationProperty){
AliyunOssUtil aliyunOssUtil = new AliyunOssUtil();
aliyunOssUtil.setConfigurationProperty(configurationProperty);
return aliyunOssUtil;
}
}
resources
下创建META-INF
文件夹,下面再创建spring
文件夹,里面创建org.springframework.boot.autoconfigure.AutoConfiguration.imports
文件,写入自动配置类的全限定名,这里为aliyun.oss.autoconfiguration.config.AliyunSsoAutoConfiguration
这里配置了,SpringBoot会找到自动配置类,然后对其中的@Bean进行创建。
- 定义starter,仅有pom.xml,依赖autoconfiguration
<?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>aliyun.oss</groupId>
<artifactId>spring-boot-starter</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<!--引入自动配置依赖-->
<dependencies>
<dependency>
<groupId>aliyun.oss</groupId>
<artifactId>autoconfiguration</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
</project>
- 使用的项目依赖starter
<dependency>
<groupId>aliyun.oss</groupId>
<artifactId>spring-boot-starter</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
- 使用的项目配置oss配置
aliyun:
oss:
# 配置阿里云OSS的endpoint
endpoint: oss-cn-xx.aliyuncs.com
# 配置阿里云OSS的桶名称
bucketName: xx-bucket
- 上传使用:
@Autowired
private AliyunOssUtil aliyunOssUtil;
@PostMapping("/upload")
public Result upload(MultipartFile image) throws IOException {
//存储文件到阿里云OSS
String url = aliyunOssUtil.putObject(image);
return Result.ok(url);
}