Spring Boot2.x 开发技巧及实战 第2章:Spring Boot2.x 的基础配置(持续更新中...3月6日更新)

第2章:Spring Boot2.x 的基础配置

本章概要

  • 什么是注解?
  • Spring Boot2.x的核心注解有哪些?
  • 什么是自动配置?
  • 我们怎么用自动配置特性,扩展我们的应用?
  • 来个小例子,讲解一下自动配置吧?

温馨提示:
本文配套代码:https://gitee.com/guduwuhen/springboot2-lesson 下的:lesson2-1

文章目录

  • 第2章:Spring Boot2.x 的基础配置
    • 2.1 我们先来讲注解
      • 2.1.1 注解是什么?
      • 2.1.2 亲手写一个注解
        • 2.1.2.1 编写注解类
        • 2.1.2.2 注解属性
        • 2.1.2.3 注解组成
        • 2.1.2.4 注解使用类
        • 2.1.2.5 注解解释类
      • 2.1.3 关于注解的总结
    • 2.2 Springboot 核心注解
      • 2.2.1 @SpringBootApplication
      • 2.2.2 @EnableAutoConfiguration:自动配置
      • 2.2.3 @Configuration:配置文件
      • 2.2.4 @ComponentScan:自动扫描
      • 2.2.5 @Bean:
    • 2.3 Springboot 自动配置
      • 2.3.1 自动配置原理
    • 2.4 Springboot 自定义配置
      • 2.4.1 自动配置

2.1 我们先来讲注解

2.1.1 注解是什么?

官方定义:注解(Annotation也被称为元数据)是一种应用于类、方法、参数、变量、构造器及包声明中的特殊修饰符。它是一种由JSR-175标准选择用来描述元数据的一种工具。通俗来讲,也就是为我们在代码中添加信息提供了一种形式化的方法,使我们可以在稍后某个时刻非常方便地使用这些数据。

其实呢,注解本身没有任何的作用。简单说和注释没有什么区别,而它有作用的原因是:注解解释类,也就是对注解解释的特定类。这样讲,还是比较抽象,下面我们利用一个实例来讲解一下,让大家亲自动手去写一个注解,也取大家就会深入了解了。

2.1.2 亲手写一个注解

为了更好的理解注解,我们在编写实例时,会对一些代码进行解释,同时也会调试,请大家一定要准备好Springboot开发环境(可以参照上一章最后一节的内容搭建),让我们一起动手吧。

实例功能:通过实体类添加数据库表注解、类中变量添加列注解方式,根据实体类对象,利用反射机制,生成查询sql语句。这个功能类似于hibernate的思想。

2.1.2.1 编写注解类

  1. 创建包:com.binge.springboot.lesson21.annotation
  2. 并在此包下创建MytableMyColumn两个注解类

小技巧:创建注解小技巧。
在创建类时,在弹出的对话框中选择Annatation类型即可,如下图
Spring Boot2.x 开发技巧及实战 第2章:Spring Boot2.x 的基础配置(持续更新中...3月6日更新)_第1张图片
创建接口类、枚举类,也选择响应的类型即可。

生成代码如下:

public @interface Mytable {
}

从生成的代码我们可以看出,注解的类定义其实和接口差不多,区别在与@interface前面多了一个@符号。也许你会问,这样一个注解就定义完毕了吗? 当然不会,下面我们继续讲一下,注解的属性。

2.1.2.2 注解属性

注解的属性也称为成员变量,注解只有成员变量,没有方法。注解的成员变量在注解的定义中以“无形参的方法”形式来声明,其方法名定义了该成员变量的名字,其返回值定义了该成员变量的类型。

注解内的可使用的数据类型是有限制的,类型如下:

所有的基本类型(int,float,boolean 等)
String
Class
enum(@Retention 中属性的类型为枚举)
Annotation
以上类型的数组(@Target 中属性类型为枚举类型的数组)

编译器对属性的默认值也有约束。首先,属性不能有不确定的的值。也就是说,属性要么具有默认值,要么在使用注解时提供属性的值。对于非基本类型的属性,无论是在源代码中声明时,或是在注解接口中定义默认值时,都不能使用 null 为其值。因此,为了绕开这个约束,我们需要自己定义一些特殊的值,例如空字符串或负数,来表示某个属性不存在。

2.1.2.3 注解组成

J2SE5.0版本在java.lang.annotation提供了四种元注解,专门注解其他的注解:

  • @Documented –注解是否将包含在JavaDoc中
  • @Retention –什么时候使用该注解
  • @Target? –注解用于什么地方
  • @Inherited – 是否允许子类继承该注解

@Documented–一个简单的Annotations标记注解,表示是否将注解信息添加在java文档中。

@Retention– 定义该注解的生命周期。

  • RetentionPolicy.SOURCE :在编译阶段丢弃。这些注解在编译结束之后就不再有任何意义,所以它们不会写入字节码。@Override,@SuppressWarnings都属于这类注解。
  • RetentionPolicy.CLASS :在类加载的时候丢弃。在字节码文件的处理中有用。注解默认使用这种方式。
  • RetentionPolicy.RUNTIME:始终不会丢弃,运行期也保留该注解,因此可以使用反射机制读取该注解的信息。我们自定义的注解通常使用这种方式。

@Target – 表示该注解用于什么地方。如果不明确指出,该注解可以放在任何地方。注解(annotation)可被用于 packages(包)、types(类、接口、枚举、Annotation类型)、类型成员(方法、构造方法、成员变量、枚举值)、方法参数和本地变量(如循环变量、catch参数)。在注解类型的声明中使用了target可更加明晰其修饰的目标。取值(ElementType)有:

  • ElementType.ANNOTATION_TYPE 可以应用于注释类型。
  • ElementType.CONSTRUCTOR 可以应用于构造函数。
  • ElementType.FIELD 可以应用于字段或属性。
  • ElementType.LOCAL_VARIABLE 可以应用于局部变量。
  • ElementType.METHOD 可以应用于方法级注释。
  • ElementType.PACKAGE 可以应用于包声明。
  • ElementType.PARAMETER 可以应用于方法的参数。
  • ElementType.TYPE 可以应用于类的任何元素。

为我们的注解类添加如下元注解:Mytable 注解是作用于上的,并且一直生效,MyColumn注解是作用于成员变量上的,并且一直生效

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Mytable {
    String name() default "";
}

@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyColumn {
    String name() default "";
}

2.1.2.4 注解使用类

紧接着我们创建注解的使用类:User.java,就是一个使用注解的Java bean,这里使用我们的自定义注解@Mytable

package com.binge.springboot.lesson21.entity;

import com.binge.springboot.lesson21.annotation.MyColumn;
import com.binge.springboot.lesson21.annotation.Mytable;

/**
 * @Title: User.java
 * @Package com.binge.springboot.lesson21.entity
 * @Description: 用户实体类
 * @author: binge
 * @date 2020/2/27
 * @version V1.0
 */

@Mytable("user")
public class User {
    @MyColumn("user_id")
    String userId;  	//用户ID

    @MyColumn("user_name")
    String userName;	//用户名

    @MyColumn("password")
    String password;	//用户密码
    public String getUserId() {
        return userId;
    }
    public void setUserId(String userId) {
        this.userId = userId;
    }
    public String getUserName() {
        return userName;
    }
    public void setUserName(String userName) {
        this.userName = userName;
    }
    public String getPassword() {
        return password;
    }
    public void setPassword(String password) {
        this.password = password;
    }
}

2.1.2.5 注解解释类

有了下面这个类,我们创建的两个注解才有实际的意义。这个类的思路就是:

  1. 我们会判断一个类是否已经添加我们自定义的两个注解
  2. 然后获取到注解的参数值,加以使用

代码中的注释,非常清晰的注明了注解的使用,代码如下:

package com.binge.springboot.lesson21.service;

import com.binge.springboot.lesson21.annotation.MyColumn;
import com.binge.springboot.lesson21.annotation.Mytable;
import com.binge.springboot.lesson21.entity.User;
import java.lang.reflect.Field;
import java.lang.reflect.Method;

/**
 * @Title: UserService.java
 * @Package com.binge.springboot.lesson21.service
 * @Description: 用户业务类
 * @author: binge
 * @date 2020/2/27
 * @version V1.0
 */

public class UserService {
    /**
     * 通过用户对象生成查询SQL语句的方法
     * @param user  用户对象
     * @return 生成的sql语句
     */
    public static String createSql(User user){
        StringBuffer sb = new StringBuffer();
        //通过反射,获取用户的class对象
        Class cla = user.getClass();
        //判断是否添加了某种注解,这里判断是否添加了Mytable注解
        boolean hasAnnotation = cla.isAnnotationPresent(Mytable.class);
        //如果没有添加注解,则直接返回,因为没有添加注解,无法生成sql语句
        if(!hasAnnotation){
            return null;
        }
        //如果添加了注解,把此注解类型强制转换为Mytable
        Mytable mytable = (Mytable)cla.getAnnotation(Mytable.class);
        //这个就是给User类上添加注解时,传入的参数
        String tableName = mytable.value();
        //接下来便是,利用反射机制,遍历类的变量进行生成拼接sql语句
        sb.append("select * from ").append(tableName).append(" where 1=1 ");
        Field[] fields = cla.getDeclaredFields();
        for (Field field : fields){
            //判断字段是否添加了MyColumn注解
            hasAnnotation = field.isAnnotationPresent(MyColumn.class);
            if(!hasAnnotation){
                return null;
            }

            //此处将生成getXXX方法,然后利用反射机制,获取字段的值
            String fieldName = field.getName();
            String methodName = "get" + fieldName.substring(0,1).toUpperCase()
                    + fieldName.substring(1);

            //这个变量用户存储每个变量的数值
            //这部分代码就是利用反射机制获取每个变量的数值
            Object fieldValueObj = null;
            try{
                Method method = cla.getMethod(methodName);
                fieldValueObj = method.invoke(user);
            }catch (Exception e){
                e.printStackTrace();
            }

            //获取列上配置的参数名,这个一般是数据库的字段名
            MyColumn myColumn = (MyColumn)field.getAnnotation(MyColumn.class);
            String colName = myColumn.value();

            //拼接数据库的字段及值
            sb.append(" and ").append(colName).append("=").append(fieldValueObj);
        }

        //返回拼接好的sql
        return sb.toString();
    }

    public static void main(String[] args) {
        //创建一个user对象,只设置部分变量的值
        User user = new User();
        user.setUserId("12");
        user.setUserName("binge");
        String sql = UserService.createSql(user);
        System.out.println(sql);
    }
}

运行这个类,可以在控制台中看到如下效果:
Spring Boot2.x 开发技巧及实战 第2章:Spring Boot2.x 的基础配置(持续更新中...3月6日更新)_第2张图片

小技巧:注解配合反射使用
1)只有定义为RetentionPolicy.RUNTIME时,我们才能通过注解反射获取到注解。
2)通过反射获取cla及field对象,然后通过cla.getAnnotation(Mytable.class)和field.getAnnotation(MyColumn.class) 方法可以获取到注解值
3)然后根据注解的值,去实现一些业务逻辑

2.1.3 关于注解的总结

用处:

  1. 生成文档。这是最常见的,也是java 最早提供的注解。常用的有@param @return 等(我们注释里面用到的)
  2. 跟踪代码依赖性,实现替代配置文件功能。(spring注解的大量使用,可以简化配置文件)
  3. 在编译时进行格式检查。如@override 放在方法前,如果你这个方法并不是覆盖了超类方法,则编译时就能检查出

优点 :

  1. 保存在 class 文件中,降低维护成本。
  2. 无需工具支持,无需解析。
  3. 编译期即可验证正确性,查错变得容易。
  4. 提升开发效率。

缺点:

  1. 若要对配置项进行修改,不得不修改 Java 文件,重新编译打包应用。
  2. 配置项编码在 Java 文件中,可扩展性差。

通过这个章节,大家是否对注解有了一定的了解呢?概括一点来说就是不同的注解,作用不同。在SpringBoot框架下,增加了一些特性注解,用于方便开发、部署,后续我们会讲解一些Springboot的核心注解。

2.2 Springboot 核心注解

2.2.1 @SpringBootApplication

SpringBoot的核心注解,主要目的是开启自动配置。它也是一个组合注解,主要组合了@Configuration,@EnableAutoConfiguration(核心)和@ComponentScan。可以通过@SpringBootApplication(exclude={想要关闭的自动配置的类名.class})来关闭特定的自动配置,其中@ComponentScanspring Boot扫描到Configuration类并把它加入到程序上下文。

小技巧:是否开启web功能
如果Maven中没有添加web的依赖

2.2.2 @EnableAutoConfiguration:自动配置

此注释自动载入应用程序所需的所有Bean——这依赖于Spring Boot在类路径中的查找。该注解组合了@Import注解,@Import注解导入了EnableAutoCofigurationImportSelector类,它使用SpringFactoriesLoader.loaderFactoryNames方法来扫描具有META-INF/spring.factories文件的jar包。而spring.factories里声明了有哪些自动配置.

2.2.3 @Configuration:配置文件

@Configuration底层是含有@Component ,所以@Configuration 具有和 @Component 的作用。

@Configuration可理解为用spring的时候xml里面的标签。

@Configuration注解可以达到在Spring中使用xml配置文件的作用。

@Bean可理解为用spring的时候xml里面的标签

总结起来就是有三个重要用法:

  1. 通过配置文件注入类
  2. 使用@Configuration+@Bean方式注册Bean
  3. 使用@Configuration启动容器+@ComponentScan+@Component注册Bean

后面讲解自定义配置时会详细讲解。

2.2.4 @ComponentScan:自动扫描

表示将该类自动发现扫描组件。个人理解相当于,如果扫描到有@Component、@Controller、@Service等这些注解的类,并注册为Bean,可以自动收集所有的Spring组件,包括@Configuration类。

2.2.5 @Bean:

在Springboot 中,@Bean注解的用法常常结合@Configuration来使用,下面用一个实例来讲解一下:

package com.dsx.demo;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;

@Configuration
public class TestConfiguration {
    public TestConfiguration() {
        System.out.println("TestConfiguration容器启动初始化。。。");
    }

    // @Bean注解注册bean,同时可以指定初始化和销毁方法
    @Bean
    @Scope("prototype")
    public TestBean testBean() {
        return new TestBean();
    }
}

上述操作相当于实例化TestBean ,并交给spring管理。


(1)、@Bean注解在返回实例的方法上,如果未通过@Bean指定bean的名称,则默认与标注的方法名相同;
(2)、@Bean注解默认作用域为单例singleton作用域,可通过@Scope(“prototype”)设置为原型作用域;
(3)、既然@Bean的作用是注册bean对象,那么完全可以使用@Component、@Controller、@Service、@Repository等注解注册bean(在需要注册的类上加注解),当然需要配置@ComponentScan注解进行自动扫描。

在Springboot的开发过程中,我们基本上都是采取第三种方法进行Bean的注册的。

2.3 Springboot 自动配置

2.3.1 自动配置原理

自动配置意思就是框架帮我们把需要配置的参数进行了默认的配置,比如tomcat的端口号,默认是8080,但是这些默认配置在哪里配置的呢?自动配置的原理是什么呢?下面讲解一下:

@SpringBootApplication包含了@EnableAutoConfiguration,这个注解又包含@Import(AutoConfigurationImportSelector.class)springboot启动时,会调用AutoConfigurationImportSelector的回调函数,让其解析所有包下的"META-INF/spring.factories"并把key为"...EnableAutoConfiguration"对应的value里的类都加载为Bean,而那些类基本都是注解了@Configuration的配置类。这便是springboot能自动配置的原因。

Spring Boot2.x 开发技巧及实战 第2章:Spring Boot2.x 的基础配置(持续更新中...3月6日更新)_第3张图片
如上图所示,自动配置中,有以下4个配置文件参与(spring-boot-autoconfigure-2.2.4.RELEASE.jar包下):

  1. spring.factories
  2. spring-configuration-metadata.json和additional-spring-configuration-metadata.json
  3. spring-autoconfigure-metadata.properties

1) spring.factories:

spring.factories文件,里面写了自动配置(AutoConfiguration)相关的类名,因此产生了一个疑问:“明明自动配置的类已经打上了@Configuration的注解,为什么还要写spring.factories文件?”

通过查看源码可以得出结论(查看源码过程省略):
spring.factories文件起到了帮助本项目包以外的bean(即在pom文件中添加依赖中的bean)注册到spring-boot项目的spring容器的作用:由于@ComponentScan注解只能扫描spring-boot项目包内的bean并注册到spring容器中,因此需要@EnableAutoConfiguration注解来注册项目包外的bean。而spring.factories文件,则是用来记录项目包外需要注册的bean类名。

2) additional-spring-configuration-metadata.json 为手动添加, spring-configuration-metadata.json自动生成。additional-spring-configuration-metadata.json将会合并到spring-configuration-metadata.json中,并覆盖掉相同的说明。这两个文件中,定义了默认配置项的相关信息。
比如:tomcat的默认端口就是在spring-configuration-metadata.json文件中:
Spring Boot2.x 开发技巧及实战 第2章:Spring Boot2.x 的基础配置(持续更新中...3月6日更新)_第4张图片

在自定义启动器时,要具备springboot的自动配置功能,就要填写上面的文件(视需要而定,填写其中的几个)

附上:
官方网站上:https://docs.spring.io/spring-boot/docs/2.2.4.RELEASE/reference/html/appendix-application-properties.html#common-application-properties 有全部的默认配置项信息。

比如我们项查看redis相关的配置项,打开此网站,ctrl+F进行搜索redis,会显示相关的配置项及默认值,如下图:

Spring Boot2.x 开发技巧及实战 第2章:Spring Boot2.x 的基础配置(持续更新中...3月6日更新)_第5张图片

疑问: spring-configuration-metadata.json是怎么自动生成的?

2.4 Springboot 自定义配置

2.4.1 自动配置

你可能感兴趣的:(Sprint,boot)