1 快速使用
1.1 创建入门项目
- 创建一个新项目(File->new->Project->Spring initalizr)
- 填写项目信息
- 选择初始化的组件(后期可以通过Pom添加)
-
1.2 项目目录结构
1.2.1 项目目录结构
主启动类(XxxApplication)
- 项目配置文件(application.properties)
- 项目构建文件(pom.xml)
- 测试类(XxxApplicationTests)
1.2.2 主启动类
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class,args);
}
}
main方法的参数args为运行时参数,可以在程序启动时通过program arguments
来修改启动的参数。
1.2.3 项目配置文件
配置文件默认生成为application.properties,也可以使用application.yml
(1) YAML
基本语法:
- 格式-key:value(空格必须有)
- 属性和值是大小写敏感的
- 使用缩进表示层级关系
- 缩进时不允许使用Tab键,只允许使用空格。
- 缩进的空格数目不重要,只要相同层级的元素左侧对齐即可。
- 字符串默认不用加上单引号或者双引号,使用单引号会对转义字符进行转义 ```yaml
person: lastName: hello age: 18 boss: false birth: 2017/12/12 maps: {k1: v1,k2: 12} lists:
- lisi
- zhaoliu
dog:
name: 小狗
age: 12
**(2) 配置文件值注入**<br /> 使用@ConfigurationProperties 或 @Value 注解将配置文件的值注入对象
- @ConfigurationProperties: SpringBoot将本类中的所有属性和配置文件中相关的配置进行绑定;prefix = "person":配置文件中哪个下面的所有属性进行一一映射; 需要@Component注册组件;
- @Value({"person.lastName"})在单个属性上注入
```java
@Component
@ConfigurationProperties(prefix = "person")
public class Person {
private String lastName;
private Integer age;
private Boolean boss;
private Date birth;
private Map<String,Object> maps;
private List<Object> lists;
private Dog dog;
自定义的类和配置文件绑定一般没有提示,在pom引入
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
(3) 配置文件的拆分
默认使用application.yml 主配置文件,可以通过来文件名指定多个环境版本( application-{profile}.properties/yml )
# 用来指定使用哪个环境
spring:
profiles:
active: dev
(4) 配置文件的加载顺序
Spring Boot启动会扫描以下位置的application.properties或者application.yml文件作为Spring Boot的默认配置文件,优先级由高到底,高优先级的配置会覆盖低优先级的配置,并互补配置。
- file:/config/
- file:/
- classpath:/config/
- classpath:/
在使用SpringCloud Config配置中心时,会使用到bootstrap.yml(bootstrap.properties)文件,bootstrap优先级比application更高
1.2.4 项目构建文件
<!--springboot项目的父项目,管控依赖 -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.5.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<!--springboot项目启动器,如web启动器 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
Pom文件对项目依赖进行管理
- 通过配置的maven仓库进行依赖的获取(默认中央仓库),中央仓库没有的依赖需要从私库获取或者通过手动引入
- 默认无需关注版本号,自动版本仲裁
- 约定俗成的 spring-boot-starter-* 为官方提供的场景依赖,第三方的使用 *-spring-boot-starter
自动配置原理(跳转)
1.2.5 测试类
单元测试基本操作(跳转)
2 WEB开发
2.1 静态资源访问
2.1.1 默认静态资源文件夹
- resources/static (新项目只有 /static)
- resources/public
- resources/resources
- resources/META-INF/resources
SpringBoot放置静态资源的目录,只要把静态资源放到这几个目录下,就能直接通过当前项目根路径 + / + 静态资源名访问到。
spring:
mvc:
static-path-pattern: /res/**
resources:
static-locations: [classpath:/haha/]
默认情况下SpringBoot是帮你映射的路径是 /**,可通过修改配置添加前缀。
2.1.2 自定义样式
自定义样式放在把对应的文件放在静态资源文件夹即可(修改默认访问路径会导致失效),欢迎页(index.html)、横幅(banner.txt)、网站图标(favicon.ico)
2.1.3 静态资源配置原理
WebMvcAutoConfiguration里的addResourceHandler进行了配置,
即请求进来,先去找Controller看能不能处理。不能处理的所有请求又都交给静态资源处理器。静态资源也找不到则响应404页面。
2.2 模板引擎
Spring Boot支持Thymeleaf,freemarker等模板引擎,通过模板引擎来展示页面
前后端分离开发不再使用这种模板引擎,模板引擎现在用来生产代码等,如果用到请参考官方文档。
2.3 请求处理
2.3.1 请求映射
使用注解@RequestMapping、@xxxMapping(RESTFUL风格)
@Controller
public class TestController {
@RequestMapping("/t1")
public String test1(){
//默认访问classpath:/templates/test.html
return "test";
}
}
2.3.2 请求参数
使用注解@PathVariable、@RequestParam、@RequestBody等
@GetMapping("/save")
public Map getMethod(@RequestParam String userName){
Map<String,Object> map = new HashMap<>();
map.put("content",content);
return map;
}
@PostMapping("/save")
public Map postMethod(@RequestBody String content){
Map<String,Object> map = new HashMap<>();
map.put("content",content);
return map;
}
@GetMapping("/save/{id}")
public Map varMethod(@PathVariable String id){
Map<String,Object> map = new HashMap<>();
map.put("content",content);
return map;
}
2.3.3 请求原理
原理:即springmvc的原理
2.3.4 扩展使用SpringMVC
//应为类型要求为WebMvcConfigurer,所以我们实现其接口
//可以使用自定义类扩展MVC的功能
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
// 浏览器发送/test , 就会跳转到test页面;
registry.addViewController("/test").setViewName("test");
}
}
2.3.5 优雅的参数校验
(1)引入JSR-303验证规范的Validator校验框架
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
(2)实体类
@Data
public class User {
@NotBlank(message = "名字为必填项")
private String name;
@Length(min = 8, max = 16, message = "password长度必须位于8到12之间")
private String password;
@Email(message = "请填写正确的邮箱地址")
private String email;
//自定义注解
@EnumCheck(clazz = SexEnum.class)
private String sex;
//使用@Valid支持嵌套
@Valid
private Address address;
}
(3)接口调用
@RestController
@Slf4j
@RequestMapping("/valid")
public class UserController {
@PostMapping("/testJson")
public String testJson(@Valid @RequestBody User user) {
return "testJson valid success";
}
}
(4)自定义注解校验
@Documented
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD})
@Constraint(validatedBy = EnumValidator.class)
public @interface EnumCheck {
/**
* 枚举的类型
*/
Class<? extends Enum<?>> clazz();
String message() default "";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
public class EnumValidator implements ConstraintValidator<EnumCheck, String> {
Class<? extends Enum<?>> clazz;
@Override
public void initialize(EnumCheck constraintAnnotation) {
clazz = constraintAnnotation.clazz();
}
@Override
public boolean isValid(String s, ConstraintValidatorContext constraintValidatorContext) {
List<Object> types = EnumUtil.getFieldValues(clazz, "type");
return StrUtil.isBlank(s) || types.contains(s);
}
}
2.3.6 优雅的结果响应
@RestControllerAdvice
public class GlobalResponseHandler<T> implements ResponseBodyAdvice<Object> {
@Resource
private ObjectMapper objectMapper;
@Override
public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
return true;
}
@SneakyThrows
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
if (body instanceof String) {
return objectMapper.writeValueAsString(Result.success(body));
}
if (body instanceof Result) {
return body;
}
return Result.success(body);
}
}
@Data
@ToString
public class Result<T> implements Serializable {
private static final long serialVersionUID = 1L;
private Integer code;
private String msg;
private T data;
public Result(Integer code, String msg) {
this.code = code;
this.msg = msg;
}
public Result(Integer code, String msg, T data) {
this.code = code;
this.msg = msg;
this.data = data;
}
public static <T> Result<T> success() {
return new Result<>(HttpStatus.OK.value(), HttpStatus.OK.getReasonPhrase(), null);
}
public static <T> Result<T> success(T data) {
return new Result<>(HttpStatus.OK.value(), HttpStatus.OK.getReasonPhrase(), data);
}
public static <T> Result<T> success(String msg, T data) {
return new Result<T>(HttpStatus.OK.value(), msg, data);
}
public static<T> Result<T> fail() {
return new Result<>(HttpStatus.INTERNAL_SERVER_ERROR.value(), HttpStatus.INTERNAL_SERVER_ERROR.getReasonPhrase(),null);
}
public static Result<String> fail(String msg) {
return new Result<>(HttpStatus.INTERNAL_SERVER_ERROR.value(), msg);
}
public static Result<String> fail(Integer code ,String msg) {
return new Result<>(HttpStatus.INTERNAL_SERVER_ERROR.value(), msg);
}
}
2.4 异常处理
2.4.1 默认的异常处理
程序中出现了异常 SpringBoot 就会请求 /error
的 url ;
SpringBoot 提供BasicExceptionController 来处理 /error
请求,然后跳转到默认显示异常的页面来展示异常信息。
接下来就是自定义异常错误页面了,方法很简单,就是在目录 src/main/resources/templates/
下定义一个叫 error
的文件,可以是 jsp 也可以是 html 。
2.4.2 优雅的异常处理
@RestControllerAdvice
public class GlobalExceptionHandler {
//自定义的异常进行处理
@ExceptionHandler(BusinessException.class)
public Result businessExceptionHandler(BusinessException e){
return Result.error().code(e.getErrorCode()).message(e.getErrorMsg());
}
//其他异常统一处理
@ExceptionHandler(Exception.class)
public Result zeroException(Exception e){
return Result.error().message(e.getMessage());
}
//Validator校验异常
@ExceptionHandler(value = {BindException.class, ConstraintViolationException.class, MethodArgumentNotValidException.class})
public ResponseEntity<Result<String>> exceptionHandler(Exception e) {
Result<String> resp = null;
if (e instanceof MethodArgumentNotValidException) {
MethodArgumentNotValidException ex = (MethodArgumentNotValidException) e;
resp = Result.fail(HttpStatus.BAD_REQUEST.value(),
ex.getBindingResult().getAllErrors().stream()
.map(ObjectError::getDefaultMessage)
.collect(Collectors.joining("; "))
);
} else if (e instanceof ConstraintViolationException) {
ConstraintViolationException ex = (ConstraintViolationException) e;
resp = Result.fail(HttpStatus.BAD_REQUEST.value(),
ex.getConstraintViolations().stream()
.map(ConstraintViolation::getMessage)
.collect(Collectors.joining("; "))
);
} else if (e instanceof BindException) {
BindException ex = (BindException) e;
resp = Result.fail(HttpStatus.BAD_REQUEST.value(),
ex.getAllErrors().stream()
.map(ObjectError::getDefaultMessage)
.collect(Collectors.joining("; "))
);
}
return new ResponseEntity<>(resp,HttpStatus.BAD_REQUEST);
}
}
@Data
public class BusinessException extends RuntimeException{
private Integer errorCode;
private String errorMsg;
public BusinessException(String code) {
this.code = code;
this.errMsg = getMessage(code);
}
//异常代码编写在国际化文件中
private String getMessage(String errCode) {
ResourceBundle bundle = ResourceBundle.getBundle("i18n/messages", Locale.CHINA);
return bundle.getString(errCode);
}
}
2.5 切面编程
2.5.1 springboot使用切面
(1)引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
(2)编写切面类
@Aspect
@Component
public class MyAspect {
@Before("execution(* com.demo.service.*.*(..))")
public void before(JoinPoint joinPoint){
System.out.println("前置通知");
joinPoint.getTarget();//目标对象
joinPoint.getSignature();//方法签名
joinPoint.getArgs();//方法参数
}
}
2.6 日志处理
2.6.1 日志
springboot默认的日志门面是SLF4J ,日志框架是Logback
如果在springboot的resources目录下配置 logback-spring.xml, logback-spring.groovy, logback.xml or logback.groovy,则spring就不在使用默认的日志配置
Logback 日志级别 TRACE < DEBUG < INFO < WARN < ERROR
<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="false">
<!--定义日志文件的存储地址 勿在 LogBack 的配置中使用相对路径-->
<property name="LOG_HOME" value="./logs/"/>
<!-- 彩色日志依赖的渲染类 -->
<conversionRule conversionWord="clr" converterClass="org.springframework.boot.logging.logback.ColorConverter"/>
<conversionRule conversionWord="wex"
converterClass="org.springframework.boot.logging.logback.WhitespaceThrowableProxyConverter"/>
<conversionRule conversionWord="wEx"
converterClass="org.springframework.boot.logging.logback.ExtendedWhitespaceThrowableProxyConverter"/>
<!-- 日志格式 -->
<property name="LOG_PATTERN"
value="%boldWhite(%date{yyyy-MM-dd HH:mm:ss}) | %highlight(%-5level) | %boldYellow(%thread) | %boldMagenta(traceId:%X{traceId}) | %boldGreen(%logger) ---> %boldCyan(%msg%n)"/>
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<layout class="ch.qos.logback.classic.PatternLayout">
<pattern>${LOG_PATTERN}</pattern>
</layout>
</appender>
<!-- 按照每天生成日志文件 INFO_FILE-->
<appender name="INFO_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- 正在记录的日志文件的路径及文件名 -->
<file>${LOG_HOME}/info.log</file>
<!--日志文件最大的大小-->
<triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
<MaxFileSize>10MB</MaxFileSize>
</triggeringPolicy>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>INFO</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!--日志文件输出的文件名-->
<FileNamePattern>${LOG_HOME}/info-%d{yyyy-MM-dd}.%i.log</FileNamePattern>
<!--日志文件保留天数-->
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>10MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
<!--日志文件保留天数-->
<maxHistory>30</maxHistory>
</rollingPolicy>
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>${LOG_PATTERN}</pattern>
<!-- 设置字符集 -->
<charset>UTF-8</charset>
</encoder>
</appender>
<!-- 按照每天生成日志文件 WARN_FILE-->
<appender name="WARN_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- 正在记录的日志文件的路径及文件名 -->
<file>${LOG_HOME}/warn.log</file>
<!--日志文件最大的大小-->
<triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
<MaxFileSize>10MB</MaxFileSize>
</triggeringPolicy>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>WARN</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!--日志文件输出的文件名-->
<FileNamePattern>${LOG_HOME}/warn-%d{yyyy-MM-dd}.%i.log</FileNamePattern>
<!--日志文件保留天数-->
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>10MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
<!--日志文件保留天数-->
<maxHistory>30</maxHistory>
</rollingPolicy>
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>${LOG_PATTERN}</pattern>
<!-- 设置字符集 -->
<charset>UTF-8</charset>
</encoder>
</appender>
<!-- 按照每天生成日志文件 ERROR_FILE-->
<appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- 正在记录的日志文件的路径及文件名 -->
<file>${LOG_HOME}/error.log</file>
<!--日志文件最大的大小-->
<triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
<MaxFileSize>10MB</MaxFileSize>
</triggeringPolicy>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>ERROR</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!--日志文件输出的文件名-->
<FileNamePattern>${LOG_HOME}/error-%d{yyyy-MM-dd}.%i.log</FileNamePattern>
<!--日志文件保留天数-->
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>10MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
<!--日志文件保留天数-->
<maxHistory>30</maxHistory>
</rollingPolicy>
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>${LOG_PATTERN}</pattern>
<!-- 设置字符集 -->
<charset>UTF-8</charset>
</encoder>
</appender>
<!--控制台打印SQL语句,去重additivity="false"-->
<logger name="com.firstlinkgroup.paycenter.dao" level="debug" additivity="false">
<appender-ref ref="CONSOLE"/>
</logger>
<root level="INFO">
<appender-ref ref="CONSOLE"/>
<appender-ref ref="INFO_FILE"/>
<appender-ref ref="WARN_FILE"/>
<appender-ref ref="ERROR_FILE"/>
</root>
</configuration>
2.6.2 切面日志
@Aspect
@Component
@Slf4j
public class IcbcLogAspect {
@Pointcut("execution(public * com.demo.service.impl.DemoServiceImpl.*(..))")
public void pointcut() {
}
@Before("pointcut()")
public void before(JoinPoint jp) {
MDC.put("traceId", IdUtil.simpleUUID());
log.info("方法{},参数{}", jp.getSignature().getName(), JSONUtil.toJsonStr(jp.getArgs()));
}
@AfterReturning(value = "pointcut()", returning = "returnValue")
public void after(JoinPoint jp, Object returnValue) {
log.info("方法{},返回值{}", jp.getSignature().getName(), JSONUtil.toJsonStr(returnValue));
MDC.clear();
}
}
MDC作用:方便日志追踪
2.7 拦截器
2.7.1拦截器作用
可以对URL路径进行拦截,可以用于权限验证、解决乱码、操作日志记录、性能监控、异常处理等
2.7.2 使用拦截器
(1)配置拦截器
创建拦截器类,实现HandlerInterceptor接口,重写preHandle方法,在该方法中编写业务拦截的规则
public class MyInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object o) throws Exception {
System.out.println("======1=====");
return true;//返回true 放行 返回false阻止
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object o, ModelAndView modelAndView) throws Exception {
System.out.println("=====2=====");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object o, Exception e) throws Exception {
System.out.println("=====3=====");
}
}
(2)注册拦截器
@Configuration
public class InterceptorConfig extends WebMvcConfigurationSupport {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new MyInterceptor())//添加拦截器
.addPathPatterns("/hello/**")//添加拦截的请求路径
.excludePathPatterns("/hello/world");//添加排除那些请求路径不经过拦截器
}
}
//springboot2.x 静态资源在自定义拦截器之后无法访问的解决方案
@Override
protected void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/**") //代表以什么样的请求路径访问静态资源
.addResourceLocations("classpath:/static/")
.addResourceLocations("classpath:/templates/");
}
}
2.8 自定义starter
(1)新建工程
命名为xxx-spring-boot-starter
(2)pom依赖
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
(3)自定义一个实体类映射配置信息
@ConfigurationProperties(prefix = "demo.kafka" )
@Data
public class KafkaProperty {
private String topics;
private String groupId;
}
(4)创建自动配置类XXXXAutoConfiguration
@ConditionalOnProperty:指定的属性是否有指定的值
@ConditionalOnBean:当SpringIoc容器内存在指定Bean的条件,使用这个可以完成
@Configuration
@EnableConfigurationProperties(KafkaProperty.class)
@ConditionalOnProperty(prefix = "oiltour.consumer",name = "isopen",havingValue = "true")
public class AutoConfiguration {
@Bean(name = "kafkaProperty")
public KafkaProperty kafkaProperty() {
return new KafkaProperty();
}
}
(5)编写spring.factories文件
在resources文件夹META-INF/spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.example.springbootstarter.config.AutoConfiguration
(6)生成自定义的starter
执行mvn clean install
2.9 其他功能
2.9.1 ApplicationRunner的使用
SpringBoot中提供两个接口CommandLineRunner和ApplicationRunner进行一些额外的操作,比如读取配置文件、数据库操作等自定义的内容。
@Component
@Slf4j
@Order(1)
public class TestApplicationRunner implements ApplicationRunner {
@Override
public void run(ApplicationArguments args) throws Exception {
log.info("=======项目启动,ApplicationRunner执行一次=======");
}
}
- ApplicationRunner中run方法的参数为ApplicationArguments,而CommandLineRunner接口中run方法的参数为String数组。
- ApplicationRunner默认优先级比CommandLineRunner高
3 原理
3.1 自动装配
3.1.1 @SpringBootApplication作用
@SpringBootApplication是一个复合注解,主要包括以下三个注解
@SpringBootConfiguration:配置Spring容器,也即 JavaConfig 形式的 Spring IoC 容器的配置类所使用
@ComponentScan:指定组件扫描的类型和范围,默认为入口类所在子包以及子包
@EnableAutoConfiguration:注解启用自动配置,其可以帮助 SpringBoot 应用将所有符合条件的@Configuration 配置都加载到当前 IoC 容器之中
3.1.2 流程解析
- @SpringBootConfiguration标明启动类是一个配置类
- 当启动类启动的时候SpringBoot会通过@EnableAutoConfiguration中的@AutoConfigurationPackage自动装配包
- @AutoConfigurationPackage中的@ImportRegistrar.class)标注包及其子包要被扫描
- @ComponentScan去扫描这些包下的添加@component的类
- 然后@Import(AutoConfigurationImportSelector.class)用于向容器中导入自动配置组件
- AutoConfigurationImportSelector继承了DeferredImportSelector实现了 ImportSelector,重写selectImports()(SPI技术),
- 调用getCandidateConfigurations(),然后通过
- SpringFactoriesLoader.loadFactoryNames()获取资源文件
- META-INF/spring.factories中的XXXXAutoConfiguration(默认的配置位于springboot-autoconfigure下)
3.1.2 SPI
SPI的英文名称是Service Provider Interface,是Java 内置的服务发现机制。
- 定义接口
- 提供方’META-INF/services’目录下新建一个名称为接口全限定名的文件,内容为接口实现类全限定名
- 调用方通过ServiceLoader.load方法加载接口的实现类实例
//1.定义一个接口
public interface UploadCDN {
void upload(String url);
}
//2.定义两个接口实现类
public class Qiyi implements UploadCDN { //上传爱奇艺
@Override
public void upload(String url) {
System.out.println("upload to qiyi cdn");
}
}
public class ChinaNet implements UploadCDN {//上传网宿
@Override
public void upload(String url) {
System.out.println("upload to chinaNet cdn");
}
}
//3.在resources下的‘META-INF/services’目录下新建一个名称为接口全限定名(Upload)的文件,内容为接口实现类全限定名。(Qiyi/ChinaNet)
//4.通过ServiceLoader.load方法加载接口的实现类实例
public static void main(String[] args) {
ServiceLoader<UploadCDN> uploadCDN = ServiceLoader.load(UploadCDN.class);
for (UploadCDN u : uploadCDN) {
u.upload("filePath");
}
}
4 整合
4.1 整合ORM框架
4.1 MyBatis和MyBatisPlus
(1) 引入依赖
<!-- Mybatis-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>${mybatis.version}</version>
</dependency>
<!-- Mybatis-Plus-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>
(2) 修改配置文件
mybatis:
type-aliases-package: com.demo.pojo # 所有POJO类所在包路径
mapper-locations: classpath:mapper/*.xml # mapper映射文件
(3)新增配置类
@Configuration
public class MybatisPlusConfig {
/**
* 新的分页插件,一缓和二缓遵循mybatis的规则,需要设置 MybatisConfiguration#useDeprecatedExecutor = false 避免缓存出现问题
*/
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
}
4.2 整合缓存
4.2.1 SpringCache
SpringCache是基于注解的缓存技术,本质上不是一种缓存的实现,而是一种缓存的抽象。
实际使用的时候还要引入缓存实现框架(如redis等)的依赖
(1)引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
(2)使用
@Cacheable | 没有缓存就调用方法返回,并缓存。有缓存则直接返回 |
@CachePut | 会把方法结果缓存下来,与@Cacheable的区别是每次方法都被调用 |
@CacheEvict | 会清空指定缓存 |
@Caching | 缓存注解组合 |
@CacheConfig | 以上都是方法级别的注解,这个是类级别的注解用于配置缓存 |
4.2.2 Redis和Redisson
(1)引入依赖
<!--redis-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!--redisson-->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>${redisson.version}</version>
</dependency>
(2) 修改配置文件
redis:
database: 0
host: 127.0.0.1
port: 6379
(3)新增redis配置类
@Data
@Configuration
@ConfigurationProperties(prefix = "spring.redis")
@EnableCaching //开启缓存
public class RedisConfig {
private String host;
private String port;
private Integer database;
private String password;
public static final String REDISSON_CACHE_MANAGER = "redissonCacheManager";
public static final String REDIS_CACHE_MANAGER = "redisCacheManager";
/**
* 定义Redisson,指定连接、序列化等配置
*
* @return RedissonClient
*/
@Bean(destroyMethod = "shutdown")
public RedissonClient redissonClient() {
Config config = new Config();
config.useSingleServer().setAddress("redis://" + host + ":" + port).setDatabase(database).setPassword(password);
config.setCodec(new JsonJacksonCodec());
return Redisson.create(config);
}
/**
* Redisson的CacheManager,@Primary指定为默认
*
* @param redissonClient redissonClient
* @return CacheManager
*/
@Bean(RedisConfig.REDISSON_CACHE_MANAGER)
@Primary
CacheManager cacheManager(RedissonClient redissonClient) {
Map<String, CacheConfig> config = new HashMap<>(16);
return new RedissonSpringCacheManager(redissonClient, config);
}
/**
* 定义StringRedisTemplate ,指定序列号和反序列化的处理类
*
* @param redisConnectionFactory redisConnectionFactory
* @return RedisTemplate
*/
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
//json序列化配置
Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
//string的序列化
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
//key采用String的序列化方式
template.setKeySerializer(stringRedisSerializer);
//hash的key采用String的序列化方式
template.setHashKeySerializer(stringRedisSerializer);
//value序列化方式采用jackson
template.setValueSerializer(jackson2JsonRedisSerializer);
//hash的value也采用jackson
template.setHashValueSerializer(jackson2JsonRedisSerializer);
//将所有配置set进配置文件中
template.afterPropertiesSet();
return template;
}
/**
* RedisTemplate的CacheManager
*
* @param template RedisTemplate
* @return CacheManager
*/
@Bean(RedisConfig.REDIS_CACHE_MANAGER)
public CacheManager cacheManager(RedisTemplate<String, Object> template) {
RedisCacheConfiguration defaultCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(template.getStringSerializer()))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(template.getValueSerializer()));
return RedisCacheManager.RedisCacheManagerBuilder
.fromConnectionFactory(Objects.requireNonNull(template.getConnectionFactory()))
.cacheDefaults(defaultCacheConfiguration)
.transactionAware()
.build();
}
}