Spring Boot 基本开发手册

一个最简单的 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";
    }
}

 

 
posted @ 2023-05-27 14:08  三黎  阅读(279)  评论(0编辑  收藏  举报