SpringBoot快速实践 --Ⅰ

文章目录

启动一个SpringBoot项目

如果你觉得使用官网来创建太慢了,那你直接把以前项目的依赖粘过来就行了:

一个是父工程的依赖:

xml 复制代码
    <!--指定了一个父工程,父工程中的东西在该工程中可以继承过来使用-->
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.5.0</version>
    </parent>
    <!--JDK 的版本-->
    <properties>
        <java.version>8</java.version>
    </properties>

一个是常用的web依赖和测试依赖:

xml 复制代码
    <dependencies>
        <!--该依赖就是我们在创建 SpringBoot 工程勾选的那个 Spring Web 产生的-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!--这个是单元测试的依赖,我们现在没有进行单元测试,所以这个依赖现在可以没有-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

最后一个是打包的插件:

xml 复制代码
    <build>
        <plugins>
            <!--这个插件是在打包时需要的,而这里暂时还没有用到-->
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

然后写一个启动类就行,这里为了效果,我再写一个Controller:

java 复制代码
@SpringBootApplication
@RestController
public class MyApplication  {
    public static void main(String[] args) {
        SpringApplication.run(MyApplication.class,args);
    }

    @GetMapping("/hello")
    public String test1(){
        System.out.println(123);
        return "123";
    }
}

注意:

  • 类名没有啥要求
  • 该类不能放在默认包中,也就是说你直接放在java包下,它会显示Your ApplicationContext is unlikely to start due to a @ComponentScan of the default package.

至于打包发布,直接maven package就行,然后在对应的目录java -jar即可。前提是要有我们上面那个打包插件,否则会运行不了。

这个插件的作用:

  • 把项目打包成一个可执行的超级JAR(uber-JAR),包括把应用程序的所有依赖打入JAR文件内,并为JAR添加一个描述文件,其中的内容能让你用java -jar来运行应用程序。
  • 搜索public static void main()方法来标记为可运行类。

如何替换内嵌容器

spring-boot-starter-web默认集成了tomcat,假如我们想把它换为jetty,可以在pom.xml中spring-boot-starter-web下排除tomcat依赖,然后手动引入jetty依赖:

xml 复制代码
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
        <exclusions>
            <exclusion>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-tomcat</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
    
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-jetty</artifactId>
    </dependency>
</dependencies>

玩转SpringBoot配置

在SpringBoot中有如下几种配置形式:

  • 全局配置文件(一般名为application.xxx,xxx可以为properties、yml、yaml)
  • 自定义配置文件 + @PropertySource(作用是将指定的属性文件加载到环境中,这样才使@Value、@ConfigurationProperties等注解能使用其中的属性进行值的注入。)
  • 命令行配置
  • xml配置(基本不用)
  • profile多环境配置
  • 外部配置文件

读取配置文件的形式:

  • @Value(${xxx})
  • Environment对象
  • 自定义对象 + @ConfigurationProperties(在bean定义中指定外部化配置的来源)

注意:

  • @EnableConfigurationProperties注解是一个组合注解,它包含@Import注解(一个用来导入其他组件或者配置类到当前Spring容器中的注解),它的作用是将标注了@ConfigurationProperties的配置类导入Spring容器(也就是说如果标注了@ConfigurationProperties的配置类已经在容器中了,那么就不需要这个注解)。这样一来,自定义的配置类就被加载到了Spring上下文中,它的properties就会被绑定对应的ConfigurationProperties类。
  • @ConfigurationProperties允许我们在bean定义中指定外部化配置的来源。

提一嘴:

@ConfigurationProperties(prefix="xxx")和@ConfigurationProperties("xxx")是一个效果,我们可以看看源码:

@AliasFor注解的作用就是可以给一个属性创建一个别名,从而绑定到同一个外部属性上。
application全局配置文件能配置的属性:
application.properties中可配置所有官方属性

接下来我们一个个演示一下:

全局配置文件

这里我们使用@Value以及自定义对象的形式去拿到属性值:

java 复制代码
server.port=8888

属性类:

java 复制代码
@Component
@ConfigurationProperties(prefix="server")
public class PortConfig {
    private int port;

    public int getPort() {
        return port;
    }

    public void setPort(int port) {
        this.port = port;
    }
}
java 复制代码
@SpringBootApplication
@RestController
public class MyApplication  {

    public static void main(String[] args) {
        SpringApplication springApplication = new SpringApplication();

        SpringApplication.run(MyApplication.class,args);
    }


    @Value("${server.port}")
    private int port;

    @Resource
    private PortConfig portConfig;

    @GetMapping("/hello")
    public String test1(){
        System.out.println(port);  //结果8888
        System.out.println(portConfig.getPort());  //结果8888
        return "123";
    }
}

自定义配置文件

这里我创建一个自定义的配置文件myapplication.yaml:

java 复制代码
server:
  port: 8888

我们要读取他,首先就要将这个配置文件加入到上下文中,那么就要使用到@PropertySource这个注解。这里有两个注意点:

第一个就是@PropertySource搜索区间问题

@PropertySource 默认是在以下三个地方搜索配置文件:

  1. FileSystem : 先搜索文件系统里指定的路径。
  2. ClassPath : 如果文件系统里没找到,那么搜索类路径里指定的路径。
  3. Environment : 如果类路径里没找到,那么搜索系统当前环境变量里指定的。

注意你必须标明是哪一类中去寻找,他并没有找不到就切换区间尝试的机制。

这也就是为什么我们一般在使用这个注解的时候会在前面加上classpath:

java 复制代码
@PropertySource("classpath:myapplication.yaml")

当然这里我们这样还是读取不到配置文件的,接下来我们再看第二点:SpringBoot中@PropertySource的默认实现是properties类型文件的解析

我们自定义的配置文件使用的是yaml类型的,所以这里是没办法解析的,解决办法就是:实现PropertySourceFactory接口,实现一个解析yaml文件的工具类。

java 复制代码
public class YamlPropertySourceFactory implements PropertySourceFactory {
    @Override
    public PropertySource<?> createPropertySource(String s, EncodedResource encodedResource) throws IOException {
        return new YamlPropertySourceLoader().load(s,encodedResource.getResource()).get(0);
    }
}

接下来我们使用Environment的方法去读取:

java 复制代码
@SpringBootApplication
@RestController
@PropertySource(value = "classpath:myapplication.yaml",factory = YamlPropertySourceFactory.class)
public class MyApplication  {

    public static void main(String[] args) {
        SpringApplication.run(MyApplication.class,args);
    }

    @Value("${server.port}")
    private int port;

    @Resource
    private PortConfig portConfig;

    @Resource
    private Environment environment;

    @GetMapping("/hello")
    public String test1(){
        System.out.println(port);  //结果8888
        System.out.println(portConfig.getPort());  //结果8888
        System.out.println(environment.getProperty("server.port"));  //结果8888
        return "123";
    }
}

命令行配置

在运行Spring Boot jar文件时,可以使用命令java -jar xxx.jar --server.port=8081来改变端口的值。这条命令等价于我们手动到application.properties中修改(如果没有这条属性的话就添加)server.port属性的值为8081

如果不想项目的配置被命令行修改,可以在入口文件的main方法中进行如下设置:

java 复制代码
public static void main(String[] args) {
    SpringApplication app = new SpringApplication(Application.class);
    app.setAddCommandLineProperties(false);
    app.run(args);
}

profile多环境配置

Profile用来针对不同的环境下使用不同的配置文件,多环境配置文件必须以application-{profile}.{properties|yml|yaml}的格式命名,其中{profile}为环境标识。

至于哪个具体的配置文件会被加载,需要在application.properties文件中通过spring.profiles.active属性来设置,其值对应{profile}值。

如:spring.profiles.active=dev就会加载application-dev.properties配置文件内容。可以在运行jar文件的时候使用命令java -jar xxx.jar --spring.profiles.active={profile}切换不同的环境配置。

我们来演示一下:

application-dev.properties:

java 复制代码
server.port=8888

application-prod.properties:

java 复制代码
server.port=9999

外部配置文件

在前一种情况的基础上,我们在当前jar包的同一目录下放一个配置文件application.properties:

内容如下:

java 复制代码
spring.profiles.active=prod

然后我们运行jar包,这次我们不添加命令行参数:

全局异常处理

这里我们就介绍一种最常用的方式 @ControllerAdvice+@ExceptionHandler处理全局异常:

java 复制代码
@RestController
public class ExceptionTestController {

    @GetMapping("/ex")
    public String testException(){
    	//抛出一个自定义的异常
        throw new MyException();
    }
}
java 复制代码
//assignableTypes指定处理哪个Controller中的异常,不加代表处理所有Controller
@ControllerAdvice(assignableTypes = {ExceptionTestController.class}) 
public class MyExceptionHandler {
	
	//指定处理哪一类异常
    @ExceptionHandler(Exception.class)
    //返回Json数据
    @ResponseBody
    //指定异常处理方法返回的HTTP状态码
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    public Map<String, Object> handleUserNotExistsException(Exception e) {
        Map<String, Object> map = new HashMap<>();
        map.put("message", e.getMessage());
        return map;
    }
}

过滤器

首先我们用最简单的话弄清楚过滤器和拦截器的区别:

  • 归属不同:Filter(过滤器)属于Servlet技术,Interceptor(拦截器)属于SpringMVC技术
  • 拦截内容不同:Filter对所有访问进行增强,Interceptor仅针对SpringMVC的访问进行增强
  • 使用场景不同:

这里我们先说说过滤器怎么实现,两种方式:

  • 手动配置
  • @WebFilter

第一种

我们首先实现一个自己的Filter:

java 复制代码
public class MyFilter implements Filter {
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        System.out.println("当前请求的ip地址为" + servletRequest.getLocalAddr());
        //对请求进行放行
        filterChain.doFilter(servletRequest,servletResponse);
    }
}

然后再配置类中进行Filter注册:

java 复制代码
@Configuration
public class ApplicationConfig {

    @Bean
    public FilterRegistrationBean<MyFilter> filterFilterRegistrationBean(){
        FilterRegistrationBean<MyFilter> filterRegistrationBean = new FilterRegistrationBean<>();
        filterRegistrationBean.setFilter(new MyFilter());
        //可以使用filterRegistrationBean.setUrlPatterns配置要拦截的路径
        return filterRegistrationBean;
    }
}

如果有多个Filter,想定义顺序:

第二种

在自己的过滤器的类上加上@WebFilter 然后在这个注解中通过它提供好的一些参数进行配置

java 复制代码
@WebFilter(filterName = "MyFilter",urlPatterns = "/*")
//这里的urlPatterns支持的是Ant风格的路径
public class MyFilter implements Filter {
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        System.out.println("当前请求的ip地址为" + servletRequest.getLocalAddr());
        //对请求进行放行
        filterChain.doFilter(servletRequest,servletResponse);
    }
}

另外,为了能让 Spring 找到它,你需要在启动类上加上 @ServletComponentScan 注解

拦截器

如果你需要自定义 Interceptor 的话必须实现 org.springframework.web.servlet.HandlerInterceptor接口或继承 org.springframework.web.servlet.handler.HandlerInterceptorAdapter类,并且需要重写下面下面3个方法:

java 复制代码
public boolean preHandle(HttpServletRequest request,
                         HttpServletResponse response,
                         Object handler)
 
 
public void postHandle(HttpServletRequest request,
                       HttpServletResponse response,
                       Object handler,  //就是控制器方法HandlerMethod
                       ModelAndView modelAndView)
 
 
public void afterCompletion(HttpServletRequest request,
                            HttpServletResponse response,
                            Object handler,
                            Exception ex)

注意: preHandle方法返回 true或 false。如果返回 true,则意味着请求将继续到达 Controller 被处理。

处理顺序如下:

注意:postHandle只有当被拦截的方法没有抛出异常成功时才会处理,afterCompletion方法无论被拦截的方法抛出异常与否都会执行。

我们演示一下:

先自定义一个拦截器

java 复制代码
public class MyInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println(handler.toString());
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {

    }
}

然后再配置类中添加拦截器:

java 复制代码
@Configuration
public class ApplicationConfig extends WebMvcConfigurerAdapter {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new MyInterceptor());
    }
}

使用Lombok简洁代码

Lombok是一个Java库,它通过注解的方式来简化Java代码的编写。它的实现原理主要是通过在编译阶段的注解处理器来处理被Lombok注解标记的代码,并在编译过程中生成相应的Java代码。

具体来说,当使用Lombok注解标记一个类、字段或方法时,编译器会在编译阶段将这些被注解标记的代码传递给Lombok的注解处理器。注解处理器会根据注解的类型和参数,生成相应的Java代码,并将生成的代码插入到编译后的Java文件中。

需要注意的是,Lombok生成的代码是在编译阶段完成的,所以在源代码中看不到生成的代码。但在编译后的.class文件中,可以看到Lombok生成的代码已经被插入到相应的位置。这样,生成的代码就可以在运行时被正常调用和使用。

Lombok的注解处理器是以插件的形式集成到Java编译器中的。当使用Lombok库时,IDE或构建工具会将Lombok的注解处理器添加到编译器的处理器列表中。这样,在编译Java代码时,编译器会自动调用Lombok的注解处理器来处理被Lombok注解标记的代码。

需要注意的是,为了使Lombok的注解处理器正常工作,需要在使用Lombok的项目中添加Lombok库的依赖。这样,编译器才能正确识别和处理Lombok的注解。

所以要使用Lombok我们除了添加依赖之外,还要在自己的IDEA中安装lombok的插件:

xml 复制代码
<!-- 引入 Lombok 依赖 -->
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <optional>true</optional>
</dependency>

接下来我们看看这个lombok具体怎么用:

  • @Getter 注解,添加在类或属性上,生成对应的 get 方法。

  • @Setter 注解,添加在类或属性上,生成对应的 set 方法。

  • @ToString 注解,添加在类上,生成 toString 方法。

  • @EqualsAndHashCode 注解,添加在类上,生成 equals 和 hashCode 方法。

  • @AllArgsConstructor、@RequiredArgsConstructor、@NoArgsConstructor 注解,添加在类上,为类自动生成对应参数的构造方法。

  • @Data 注解,添加在类上,是 5 个 Lombok 注解的组合。

    • 为所有属性,添加 @Getter、@ToString、@EqualsAndHashCode 注解的效果
    • 为非 final 修饰的属性,添加 @Setter 注解的效果
    • 为 final 修改的属性,添加 @RequiredArgsConstructor 注解的效果
  • @Value 注解,添加在类上,和 @Data 注解类似,区别在于它会把所有属性默认定义为 private final 修饰,所以不会生成 set 方法。

  • @CommonsLog、@Flogger、@Log、@JBossLog、@Log4j、@Log4j2、@Slf4j、@Slf4jX 注解,添加在类上,自动为类添加对应的日志支持。

  • @NonNull 注解,添加在方法参数、类属性上,用于自动生成 null 参数检查。若确实是 null 时,抛出 NullPointerException 异常。

  • @Cleanup 注解,添加在方法中的局部变量上,在作用域结束时会自动调用 #close() 方法,来释放资源。例如说,使用在 Java IO 流操作的时候。

  • @Builder 注解,添加在类上,给该类加个构造者模式 Builder 内部类。

  • @Synchronized 注解,添加在方法上,添加同步锁。

  • @SneakyThrows 注解,添加在方法上,给该方法添加 try catch 代码块。

  • @Accessors 注解,添加在方法或属性上,并设置 chain = true,实现链式编程。

我们来看看其中三个比较常用的: @Data 和 @Slf4j、@NonNull

@Data

java 复制代码
@Data
public class Person {
    private String name;
    private int age;

}
java 复制代码
@EqualsAndHashCode(callSuper = true)
@Data
@ToString(callSuper = true)
public class Student extends Person{
    private int studyId;
    private int className;
}

反编译后的Student类:

java 复制代码
public class Student extends Person {
    private int studyId;
    private int className;

    public boolean equals(final Object o) {
        if (o == this) {
            return true;
        } else if (!(o instanceof Student)) {
            return false;
        } else {
            Student other = (Student)o;
            if (!other.canEqual(this)) {
                return false;
            } else if (!super.equals(o)) {
                return false;
            } else if (this.getStudyId() != other.getStudyId()) {
                return false;
            } else {
                return this.getClassName() == other.getClassName();
            }
        }
    }

    protected boolean canEqual(final Object other) {
        return other instanceof Student;
    }

    public int hashCode() {
        int PRIME = true;
        int result = super.hashCode();
        result = result * 59 + this.getStudyId();
        result = result * 59 + this.getClassName();
        return result;
    }

    public Student() {
    }

    public int getStudyId() {
        return this.studyId;
    }

    public int getClassName() {
        return this.className;
    }

    public void setStudyId(final int studyId) {
        this.studyId = studyId;
    }

    public void setClassName(final int className) {
        this.className = className;
    }

    public String toString() {
        return "Student(super=" + super.toString() + ", studyId=" + this.getStudyId() + ", className=" + this.getClassName() + ")";
    }
}

如果使用 @Data 注解的类,继承成了其它父类的属性,最好额外添加 @ToString(callSuper = true) 和 @EqualsAndHashCode(callSuper = true) 注解。因为默认情况下,@Data 注解不会处理父类的属性。所以需要我们通过 callSuper = true 属性,声明需要调用父类对应的方法。

@Slf4j

你使用的日志门面以及日志实现的依赖依旧照常导入,不能省略。

@Slf4j 注解,添加在类上,给该类创建 Slf4j Logger 静态属性。

@NonNull 注解

@NonNull 注解,添加在方法参数、类属性上,用于自动生成 null 参数检查。若确实是 null 时,抛出 NullPointerException 异常。

使用IDEA HTTP Client进行接口调试

其好处就是: IDEA HTTP Client 采用后缀为 .http 的文本文件,可以和 Java 代码一起使用 Git 进行版本管理,从而实现团队协作的共享。同时因为内置在了IDEA中,不需要切换软件去进行接口测试,比较方便。

.http文件我们在Idea中右键就可以创建,也可以直接在Controller中自动生成:

进去之后可以看到右上角有几个选项,示意如下:

首先我们来说明三种比较常见的情况:

  • GET 请求
  • POST 请求 + Form
  • POST 请求 + JSON

POST 请求 + Form

java 复制代码
@PostMapping("/user/login")
public Map<String, Object> login(@RequestParam("username") String username,
                                 @RequestParam("password") String password) {
    if ("yudaoyuanma".equals(username) && "123456".equals(password)) {
        Map<String, Object> tokenMap = new HashMap<>();
        tokenMap.put("userId", 1);
        tokenMap.put("token", "token001");
        return tokenMap;
    }
    throw new RuntimeException("小朋友,你的账号密码不正确哟!");
}

对应的 IDEA HTTP Client 的代码如下:

java 复制代码
### 测试 /user/login:登陆成功
POST http://127.0.0.1:8080/user/login
Content-Type: application/x-www-form-urlencoded

username=yudaoyuanma&password=123456

POST 请求 + JSON

java 复制代码
@PostMapping("/user/update")
public Boolean update(@RequestBody UserUpdateVO updateVO) {
    logger.info("[update][收到更新请求:{}]", updateVO.toString());
    return true;
}

对应的 IDEA HTTP Client 的代码如下:

java 复制代码
### 测试 /user/update:更新成功
POST http://127.0.0.1:8080/user/update
Content-Type: application/json

{
  "nickname": "我是昵称",
  "gender": 1
}

GET 请求

java 复制代码
@GetMapping("/user/get-current")
public Map<String, Object> getCurrentUser(@RequestHeader("Authorization") String authorization,
                                          @RequestParam("full") boolean full) {
    if ("token001".equals(authorization)) {
        Map<String, Object> userInfo = new HashMap<>();
        userInfo.put("id", 1);
        // full 为 true 时,获得完整信息
        if (full) {
            userInfo.put("nickname", "芋道源码");
            userInfo.put("gender", 1);
        }
        return userInfo;
    }
    throw new RuntimeException("小朋友,你没有登录哟!");
}

对应的 IDEA HTTP Client 的代码如下:

java 复制代码
### 测试 /user/get-current:获取成功
GET http://127.0.0.1:8080/user/get-current?full=true
Authorization: token001

如果想要了解更多,可以参考下面的文章:
芋道 Spring Boot API 接口调试 IDEA HTTP Client

相关推荐
敲代码的小王!2 小时前
MD5加密算法和BCrypt密码加密算法
java·算法·安全
魔尔助理顾问3 小时前
一个简洁高效的Flask用户管理示例
后端·python·flask
李长渊哦6 小时前
使用Druid连接池优化Spring Boot应用中的数据库连接
数据库·spring boot·后端
web135085886356 小时前
【Spring Boot】Spring AOP动态代理,以及静态代理
spring boot·后端·spring
罗政7 小时前
冒险岛079 V8 整合版源码搭建教程+IDEA启动
java·ide·intellij-idea
nbsaas-boot7 小时前
Go 自动升级依赖版本
开发语言·后端·golang
架构默片7 小时前
【JAVA工程师从0开始学AI】,第五步:Python类的“七十二变“——当Java的铠甲遇见Python的液态金属
java·开发语言·python
zzyh1234568 小时前
springcloud的组件及作用
后端·spring·spring cloud
不只会拍照的程序猿8 小时前
从插入排序到希尔排序
java·开发语言·数据结构·算法·排序算法
尚学教辅学习资料8 小时前
基于SpringBoot的图书借阅小程序+LW参考示例
spring boot·后端·小程序·java毕设·图书借阅