Spring Boot 基本开发手册
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class DemoSpringBootApplication {
public static void main(String[] args) {
SpringApplication.run(DemoSpringBootApplication.class, args);
}
}
● 用 @SpringBootApplication 注解启动类
● 调用 SpringApplication.run()
使用 IOC 容器
SpringApplication.run() 会返回一个上下文对象,那个就是容器的引用。
ConfigurableApplicationContext ctx = SpringApplication.run(DemoSpringBootApplication.class, args);
使用 ctx.getBean() 就可以从容器里取 bean 出来。但在这之前,怎么把对象放到容器里面去?
答案是有两种方式:使用配置类、使用 @Component 注解。
配置类
配置类即用 @Configuration (org.springframework.context.annotation.Configuration) 注解修饰的类,其中会定义若干个用 @Bean (org.springframework.context.annotation.Bean) 注解修饰的方法。每个 @Bean 方法都会返回一个 bean。
@Configuration
class DemoCfg {
@Bean
public Demo getDemo() {
return new Demo();
}
}
每个 @Bean 方法都会在服务启动的时候执行仅一次,返回的对象都会被放入容器中成为 bean。由于只调用一次,因此这种方式生成的 bean 都是单例,而且不会懒加载。
配置类可定义多个。但问题来了,服务启动时,框架怎么知道去哪里找到所有的配置类呢?
答案就是扫描。框架会扫描 @SpringBootApplication 注解的类所在的包的所有的子包。只要把配置类放在这个包的子包下,就会被扫描到。扫描到了之后会被加载、实例化,并执行其中的 @Bean 方法。同时,配置类本身也会成为一个 bean。
那么,如果要编写一个配置类,但放在 main 方法所在的包以外的地方,怎么办呢?
第一个方法是采用 @ComponentScan 注解。该注解会告诉框架,需要额外扫描哪些包。需要注意的是,@ComponentScan 不能加在启动类上,因为启动类本身已经带了扫描规则,如果再加上自定义的规则,会覆盖掉原来的规则。
第二个方法是在 resources 目录下新建一个目录叫 META-INF,并在它里面新建一个文件叫 spring.factories。里面写入一行:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.example.abc.DemoCfg
如果有多个配置类,用逗号分隔。
第二种方法也被用来作为第三方包的自动配置。因为框架不仅会扫描当前项目的 resources/spring.factories,也会扫描 class path 上其他 jar 包下的 resources/spring.factories。
@Compoent 注解
用 @Component 注解(org.springframework.stereotype.Component):
@Component
class Demo {
}
@ComponentScan 同样也会扫描被 @Component 修饰的类。实际上,@Configuration 注解本身也是被 @Component 修饰的。
以上是把对象放进容器变成 bean 的方法。那怎么从容器里取 bean 出来装配呢?
依赖注入
Spring 支持三种依赖注入的方式:构造器注入、setter方法注入、属性注入。
1,构造器注入
@Component
public class Demo2 {
private Utils utils;
public Demo2(Utils utils) {
this.utils = utils;
}
public void f() {
this.utils.f();
}
}
@Component
class Utils {
public void f() {
System.out.println("ok");
}
}
构造器注入通常是最推荐的做法。但是有一个缺点:构造器注入无法实现循环依赖。可是问题又来了,为啥要循环依赖?禁掉循环依赖不是更好吗?所以实际上这是一个优点。
2,setter 方法注入
@Component
public class Demo2 {
private Utils utils;
@Autowired
public void setUtils(Utils utils) {
this.utils = utils;
}
public void f() {
this.utils.f();
}
}
@Component
class Utils {
public void f() {
System.out.println("ok");
}
}
需要在 setter 方法上添加 @Autowired 注解。
3,属性注入
@Component
public class Demo2 {
@Autowired
private Utils utils;
public void f() {
this.utils.f();
}
}
@Component
class Utils {
public void f() {
System.out.println("ok");
}
}
这是官方不推荐的方式。因为这种写法的类只能在容器中使用,无法在容器外通过 new 生成。
两个特点
Spring 的容器有两个特点,一是无懒加载。所有的 bean 都在服务启动的时候就生成。二是单例。所有的 bean 默认都是单例模式。
参数与配置
应用要运行,需要一些参数或者配置的输入,而且通常数量还不少。Spring Boot 怎么处理这些东西呢?Spring Boot 统一了概念,不管从命令行来,还是从配置文件中来,这种 key -> value 形式的值都叫做 property。
在 Spring 中,用 @value 注解可以获取到 property 的值:
@Component
class Demo {
@Value("${properties_name}")
public String v;
}
需要注意的是,Demo 必须成为 bean,@Value 才会生效。
properties 的来源有几个:
● 命令行通过 --= 指定
● 通过 properties 文件指定
● 通过配置中心自动写入
properties 文件
prpperties 文件有两种格式: .propertes 和 .yml。
properties 的路径:src/main/resources/application.yml
这里有一个 profile 的概念。properties 文件可以每个环境一个,例如 application.yml、application-dev.yml、application-prod.yml。那么:
● 不指定 --spring.profiles.active,则只加载 application.yml
● 指定 --spring.profiles.active=dev,则先加载 application.yml,再加载 application-dev.yml,且后者的值覆盖前者的值。
property 之间是可以通过 ${property_name} 互相引用的。
默认值
默认情况下,读取的 property 不存在会抛异常。使用冒号可以指定一个配置的默认值:${property_name:default_value}
MVC
现在大多数应用都是前后端分离,所以 V 这一层已经用的很少了。主要是 C 和 M。
MVC 不在 Spring Boot 的核心包中,需要引入依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
这个依赖包主要包括两部分:内置 Tomcat 和封装 Spring MVC。引入之后,就可以这样定义 Controller:
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/test")
public class MyController {
@GetMapping("/abc")
public String f() {
return "haha";
}
}
那么,怎么获取参数、header 这些东西呢?
获取 Query String 中的参数
@GetMapping("/abc")
public String f(String name) {
return "hello, " + name;
}
获取 Path 中的参数
@GetMapping("/abc/{name}")
public String f(@PathVariable("name") String name) {
return "hello, " + name;
}
获取 Body
@RestController
@RequestMapping("/test")
public class MyController {
@PostMapping("/abc")
public String f(@RequestBody Param param) {
return "a=" + param.getA() + " b=" + param.getB();
}
}
class Param {
private String a;
private String b;
public String getA() {
return a;
}
public void setA(String a) {
this.a = a;
}
public String getB() {
return b;
}
public void setB(String b) {
this.b = b;
}
}
获取 header
@PostMapping("/abc")
public String f(@RequestHeader("X-VALUE") String value) {
return "X-VALUE=" + value;
}
filter
filter 是拦截器,也就是在 controller 之前对请求进行拦截的东西。filter 是管道模式。
filter 相关的注解有三个:@Order, @WebFilter, @ServletComponentScan。其中 @Order 用来定义拦截器的顺序,@WebFilter 用来定义拦截器。定义完毕后,需要在启动类加上 @ServletComponentScan,才能启动自动发现拦截器。
import org.springframework.core.annotation.Order;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;
@Order(1)
@WebFilter(urlPatterns = "/*")
public class MyFilter implements Filter {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
filterChain.doFilter(servletRequest, servletResponse);
System.out.println("filter called");
}
}
@SpringBootApplication
@ServletComponentScan
public class DemoSpringBootApplication {
public static void main(String[] args) {
ConfigurableApplicationContext ctx = SpringApplication.run(DemoSpringBootApplication.class, args);
}
}
server 配置
web 服务器的相关配置参考 https://cloud.tencent.com/developer/article/1498028
MyBatis-Plus
用 MyBatis-Plus 来访问数据库,包括几个步骤:1,引入依赖;2,配置连接;3,定义 entity;4,定义 mapper;5,添加 @MapperScan 6,访问数据库。
引入依赖:
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.24</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.1</version>
</dependency>
<dependency>
<groupId>org.mariadb.jdbc</groupId>
<artifactId>mariadb-java-client</artifactId>
<version>2.7.3</version>
<scope>runtime</scope>
</dependency>
配置连接:
spring:
datasource:
driver-class-name: org.mariadb.jdbc.Driver
url: jdbc:mysql://localhost:3306/tt?characterEncoding=utf8
username: root
password: root
定义 entity:
@Data
public class Role {
private Integer id;
private String name;
}
定义 mapper:
public interface RoleMapper extends BaseMapper {
}
该 interface 不需要定义实现,框架会自动生成。
添加 @MapperScan 注解:
@SpringBootApplication
@MapperScan("com.example.mapper")
public class DemoSpringBootApplication {
public static void main(String[] args) {
ConfigurableApplicationContext ctx = SpringApplication.run(DemoSpringBootApplication.class, args);
}
}
访问数据库:
List roleList = roleMapper.selectList(null);
roleList.forEach(System.out::println);
return roleList.get(0).getName();
如果一个项目需要访问多个数据源,则参考 mybatisplus 官方文档:
https://baomidou.com/pages/a61e1b/#dynamic-datasource
接下来就是连接池。参考这两篇文章:
https://blog.csdn.net/xiaodujava/article/details/115611880
https://cloud.tencent.com/developer/article/1575670
而关于连接池的相关背景,可以看这篇文章:
https://blog.csdn.net/qq_37781649/article/details/120252496
AOP
要使用 AOP,首先引入依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
然后,定义切面:
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
@Component
@Aspect
public class LoggingAspect {
@Around("execution(public * com.example.demospringboot.controller.MyController.*(..))")
public Object doLog(ProceedingJoinPoint jp) throws Throwable {
System.out.println("before joinpoint");
Object ret = jp.proceed();
System.out.println("after joinpoint");
return ret;
}
}
就这么简单。但是,复杂的地方在于切面的范围定义。其语法比较复杂,参照:https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#aop-pointcuts-examples
这种方式通常比较危险,因为是基于类似正则的规则去匹配方法,容易出错。所以通常用基于注解的方式去定义切面范围:
@Around("@annotation(com.example.Anotation1) || @annotation(com.example.Anotation2)")
全局异常处理
可以用 @ExceptionHandler 和 @RestControllerAdvice 定义全局异常处理器:
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import javax.servlet.http.HttpServletRequest;
@RestControllerAdvice
public class ExcepthonHandler {
@ExceptionHandler(Exception.class)
public String h1(HttpServletRequest ret, Exception e) {
return "exception catched";
}
}