springboot学习笔记

本文为springboot学习笔记,与代码同步进行,主要目的熟悉SpringBoot内部机制,以及通过不断编写demo熟悉常用注解,核心代码参考了很多帖子和书籍,代码通过maven构建,学习真的会上瘾。。

文章目录

一 Spring Boot 之 HelloWorld 详解

本节主要完成springboot及maven等关键工具的描述,同时完成第一个项目HelloWorld。

1、springboot

1.1 概述

Build Anything with Spring Boot:Spring Boot is the starting point for building all Spring-based applications. Spring Boot is designed to get you up and running as quickly as possible, with minimal upfront configuration of Spring.

springboot并不是什么新的框架,而是默认配置了很多框架的使用方式。Spring Boot 的目的是提供一组工具,以便快速构建容易配置的 Spring 应用程序。

Starter

starter 是 Spring Boot 的一个重要组成部分,用于限制您需要执行的手动配置依赖项数量。
starter 实际上是一组依赖项(比如 Maven POM),这些依赖项是 starter 所表示的应用程序类型所独有的。
所有 starter 都使用以下命名约定:spring-boot-starter-XYZ,其中 XYZ 是想要构建的应用程序类型。以下是一些流行的 Spring Boot starter:

  • spring-boot-starter-web 用于构建 RESTful Web 服务,它使用 Spring MVC 和 Tomcat 作为嵌入式应用程序容器。
  • spring-boot-starter-jersey 是 spring-boot-starter-web 的一个替代,它使用 Apache Jersey 而不是 Spring MVC。
  • spring-boot-starter-jdbc 用于建立 JDBC 连接池。它基于 Tomcat 的 JDBC 连接池实现。
 <!-- Spring Boot Web 依赖 -->
 <dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-web</artifactId>
 </dependency>

如上,我们依赖spring-boot-starter-web,进入spring-boot-starter-web中,又有以下依赖:

<dependencies>
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter</artifactId>
	</dependency>
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-tomcat</artifactId>
	</dependency>
	<dependency>
		<groupId>org.hibernate</groupId>
		<artifactId>hibernate-validator</artifactId>
	</dependency>
	<dependency>
		<groupId>com.fasterxml.jackson.core</groupId>
		<artifactId>jackson-databind</artifactId>
	</dependency>
	<dependency>
		<groupId>org.springframework</groupId>
		<artifactId>spring-web</artifactId>
	</dependency>
	<dependency>
		<groupId>org.springframework</groupId>
		<artifactId>spring-webmvc</artifactId>
	</dependency>
</dependencies>

更多更详细starter可参考:Spring Boot starter 参考页面

1.2 springboot 自动配置

自动配置AutoConfiguration是实现spring boot的重要原理,理解AutoConfiguration的运行原理特别重要,Spring Boot的自动配置看起来神奇,其实原理非常简单,背后全依赖于@Conditional注解来实现的。
我们采用自动注入(通过@Autowired或@Resource等)注解前,我们会在类定义上使用@Component、@Configuration等注解,实现类被spring的容器管理。但是对于jar包中的类,要实现自动注入就需要spring boot的自动配置功能。
使用@EnableAutoConfiguration注解,会启用自动配置。

  1. 当SpringBoot应用启动的时候,就从主方法里面进行启动的。
//Spring Boot 应用的标示
@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        // 程序启动入口
        // 启动嵌入式的 Tomcat 并初始化 Spring 环境及其各 Spring 组件
        SpringApplication.run(Application.class, args);
    }

}

@SpringBootApplication注解主配置类里边最主要的功能就是SpringBoot开启了一个@EnableAutoConfiguration注解的自动配置功能。SpringBootApplication源码如下:

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(
    excludeFilters = {@Filter(
    type = FilterType.CUSTOM,
    classes = {TypeExcludeFilter.class}
), @Filter(
    type = FilterType.CUSTOM,
    classes = {AutoConfigurationExcludeFilter.class}
)}
)
public @interface SpringBootApplication {
    @AliasFor(
        annotation = EnableAutoConfiguration.class,
        attribute = "exclude"
    )
    Class<?>[] exclude() default {};

    @AliasFor(
        annotation = EnableAutoConfiguration.class,
        attribute = "excludeName"
    )
    String[] excludeName() default {};

    @AliasFor(
        annotation = ComponentScan.class,
        attribute = "basePackages"
    )
    String[] scanBasePackages() default {};

    @AliasFor(
        annotation = ComponentScan.class,
        attribute = "basePackageClasses"
    )
    Class<?>[] scanBasePackageClasses() default {};
}
  1. @EnableAutoConfiguration

@EnableAutoConfiguration主要利用了一个EnableAutoConfigurationImportSelector选择器给Spring容器中来导入一些组件。EnableAutoConfiguration源码如下:

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import({EnableAutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
    String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

    Class<?>[] exclude() default {};

    String[] excludeName() default {};
}
  1. 组件

通过看EnableAutoConfigurationImportSelector源码来看导入了哪些组件,源码如下:

/** @deprecated */
@Deprecated
public class EnableAutoConfigurationImportSelector extends AutoConfigurationImportSelector {
    public EnableAutoConfigurationImportSelector() {
    }

    protected boolean isEnabled(AnnotationMetadata metadata) {
        return this.getClass().equals(EnableAutoConfigurationImportSelector.class) ? (Boolean)this.getEnvironment().getProperty("spring.boot.enableautoconfiguration", Boolean.class, true) : true;
    }
}

可以看到EnableAutoConfigurationImportSelector继承了类AutoConfigurationImportSelector,下来来看AutoConfigurationImportSelector的源码,由于源码较多,看其中的selectImports:

public String[] selectImports(AnnotationMetadata annotationMetadata) {
    if (!this.isEnabled(annotationMetadata)) {
        return NO_IMPORTS;
    } else {
        try {
            AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
            AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
            List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
            configurations = this.removeDuplicates(configurations);
            configurations = this.sort(configurations, autoConfigurationMetadata);
            Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
            this.checkExcludedClasses(configurations, exclusions);
            configurations.removeAll(exclusions);
            configurations = this.filter(configurations, autoConfigurationMetadata);
            this.fireAutoConfigurationImportEvents(configurations, exclusions);
            return (String[])configurations.toArray(new String[configurations.size()]);
        } catch (IOException var6) {
            throw new IllegalStateException(var6);
        }
    }
}

在selectImports这个方法里面主要有个configurations,并且这个configurations最终会被返回。
configurations是通过this.getCandidateConfigurations方法获取:

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
      List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader());
      Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you are using a custom packaging, make sure that file is correct.");
      return configurations;
  }

又通过loadFactoryNames从类路径下得到一个资源:

public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) {
    String factoryClassName = factoryClass.getName();

    try {
        Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");
        ArrayList result = new ArrayList();

        while(urls.hasMoreElements()) {
            URL url = (URL)urls.nextElement();
            Properties properties = PropertiesLoaderUtils.loadProperties(new UrlResource(url));
            String factoryClassNames = properties.getProperty(factoryClassName);
            result.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(factoryClassNames)));
        }

        return result;
    } catch (IOException var8) {
        throw new IllegalArgumentException("Unable to load [" + factoryClass.getName() + "] factories from location [" + "META-INF/spring.factories" + "]", var8);
    }
}
  1. 资源

那么从类路径下得到了那些资源呢,可以看到是扫描javajar包类路径下的“META-INF/spring.factories”这个文件,是把这个文件的urls拿到之后并把这些urls每一个遍历,最终把这些文件整成一个properties对象;
然后它从properties对象里边获取一些值,把这些获取到的值来加载我们最终要返回的这个结果,这个结果就是我们要交给Spring容器中的所有组件,这相当于这factoryClassName就是我们传过来的Class的这个类名。
而selectImports方法通过调用getAttributes获取属性:

protected AnnotationAttributes getAttributes(AnnotationMetadata metadata) {
    String name = this.getAnnotationClass().getName();
    AnnotationAttributes attributes = AnnotationAttributes.fromMap(metadata.getAnnotationAttributes(name, true));
    Assert.notNull(attributes, "No auto-configuration attributes found. Is " + metadata.getClassName() + " annotated with " + ClassUtils.getShortName(name) + "?");
    return attributes;
}

可以看到getAttributes调用this.getAnnotationClass方法:

protected Class<?> getAnnotationClass() {
    return EnableAutoConfiguration.class;
}

getSpringFactoriesLoaderFactoryClass()这个方法得到从properties中获取到EnableAutoConfiguration.class类名对应的值,再将它们添加在容器中。
5. 加载的类详情

通过查看Springjar包的META-INF下的spring.factories
在这里插入图片描述
可以看到spring.factories文件如下:

# Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\
org.springframework.boot.autoconfigure.logging.AutoConfigurationReportLoggingInitializer

# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.boot.autoconfigure.BackgroundPreinitializer

# Auto Configuration Import Listeners
org.springframework.boot.autoconfigure.AutoConfigurationImportListener=\
org.springframework.boot.autoconfigure.condition.ConditionEvaluationReportAutoConfigurationImportListener

# Auto Configuration Import Filters
org.springframework.boot.autoconfigure.AutoConfigurationImportFilter=\
org.springframework.boot.autoconfigure.condition.OnClassCondition

# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\
org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\
org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration,\
org.springframework.boot.autoconfigure.cloud.CloudAutoConfiguration,\
org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration,\
org.springframework.boot.autoconfigure.context.MessageSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration,\
org.springframework.boot.autoconfigure.couchbase.CouchbaseAutoConfiguration,\
org.springframework.boot.autoconfigure.dao.PersistenceExceptionTranslationAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchAutoConfiguration,\
org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.jpa.JpaRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.ldap.LdapDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.ldap.LdapRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.mongo.MongoRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.neo4j.Neo4jDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.neo4j.Neo4jRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.solr.SolrRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration,\
org.springframework.boot.autoconfigure.data.redis.RedisRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.rest.RepositoryRestMvcAutoConfiguration,\
org.springframework.boot.autoconfigure.data.web.SpringDataWebAutoConfiguration,\
org.springframework.boot.autoconfigure.elasticsearch.jest.JestAutoConfiguration,\
org.springframework.boot.autoconfigure.freemarker.FreeMarkerAutoConfiguration,\
org.springframework.boot.autoconfigure.gson.GsonAutoConfiguration,\
org.springframework.boot.autoconfigure.h2.H2ConsoleAutoConfiguration,\
org.springframework.boot.autoconfigure.hateoas.HypermediaAutoConfiguration,\
org.springframework.boot.autoconfigure.hazelcast.HazelcastAutoConfiguration,\
org.springframework.boot.autoconfigure.hazelcast.HazelcastJpaDependencyAutoConfiguration,\
org.springframework.boot.autoconfigure.info.ProjectInfoAutoConfiguration,\
org.springframework.boot.autoconfigure.integration.IntegrationAutoConfiguration,\
org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.JndiDataSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.XADataSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration,\
org.springframework.boot.autoconfigure.jms.JmsAutoConfiguration,\
org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration,\
org.springframework.boot.autoconfigure.jms.JndiConnectionFactoryAutoConfiguration,\
org.springframework.boot.autoconfigure.jms.activemq.ActiveMQAutoConfiguration,\
org.springframework.boot.autoconfigure.jms.artemis.ArtemisAutoConfiguration,\
org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration,\
org.springframework.boot.autoconfigure.groovy.template.GroovyTemplateAutoConfiguration,\
org.springframework.boot.autoconfigure.jersey.JerseyAutoConfiguration,\
org.springframework.boot.autoconfigure.jooq.JooqAutoConfiguration,\
org.springframework.boot.autoconfigure.kafka.KafkaAutoConfiguration,\
org.springframework.boot.autoconfigure.ldap.embedded.EmbeddedLdapAutoConfiguration,\
org.springframework.boot.autoconfigure.ldap.LdapAutoConfiguration,\
org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration,\
org.springframework.boot.autoconfigure.mail.MailSenderAutoConfiguration,\
org.springframework.boot.autoconfigure.mail.MailSenderValidatorAutoConfiguration,\
org.springframework.boot.autoconfigure.mobile.DeviceResolverAutoConfiguration,\
org.springframework.boot.autoconfigure.mobile.DeviceDelegatingViewResolverAutoConfiguration,\
org.springframework.boot.autoconfigure.mobile.SitePreferenceAutoConfiguration,\
org.springframework.boot.autoconfigure.mongo.embedded.EmbeddedMongoAutoConfiguration,\
org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration,\
org.springframework.boot.autoconfigure.mustache.MustacheAutoConfiguration,\
org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration,\
org.springframework.boot.autoconfigure.reactor.ReactorAutoConfiguration,\
org.springframework.boot.autoconfigure.security.SecurityAutoConfiguration,\
org.springframework.boot.autoconfigure.security.SecurityFilterAutoConfiguration,\
org.springframework.boot.autoconfigure.security.FallbackWebSecurityAutoConfiguration,\
org.springframework.boot.autoconfigure.security.oauth2.OAuth2AutoConfiguration,\
org.springframework.boot.autoconfigure.sendgrid.SendGridAutoConfiguration,\
org.springframework.boot.autoconfigure.session.SessionAutoConfiguration,\
org.springframework.boot.autoconfigure.social.SocialWebAutoConfiguration,\
org.springframework.boot.autoconfigure.social.FacebookAutoConfiguration,\
org.springframework.boot.autoconfigure.social.LinkedInAutoConfiguration,\
org.springframework.boot.autoconfigure.social.TwitterAutoConfiguration,\
org.springframework.boot.autoconfigure.solr.SolrAutoConfiguration,\
org.springframework.boot.autoconfigure.thymeleaf.ThymeleafAutoConfiguration,\
org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration,\
org.springframework.boot.autoconfigure.transaction.jta.JtaAutoConfiguration,\
org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration,\
org.springframework.boot.autoconfigure.web.DispatcherServletAutoConfiguration,\
org.springframework.boot.autoconfigure.web.EmbeddedServletContainerAutoConfiguration,\
org.springframework.boot.autoconfigure.web.ErrorMvcAutoConfiguration,\
org.springframework.boot.autoconfigure.web.HttpEncodingAutoConfiguration,\
org.springframework.boot.autoconfigure.web.HttpMessageConvertersAutoConfiguration,\
org.springframework.boot.autoconfigure.web.MultipartAutoConfiguration,\
org.springframework.boot.autoconfigure.web.ServerPropertiesAutoConfiguration,\
org.springframework.boot.autoconfigure.web.WebClientAutoConfiguration,\
org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration,\
org.springframework.boot.autoconfigure.websocket.WebSocketAutoConfiguration,\
org.springframework.boot.autoconfigure.websocket.WebSocketMessagingAutoConfiguration,\
org.springframework.boot.autoconfigure.webservices.WebServicesAutoConfiguration

# Failure analyzers
org.springframework.boot.diagnostics.FailureAnalyzer=\
org.springframework.boot.autoconfigure.diagnostics.analyzer.NoSuchBeanDefinitionFailureAnalyzer,\
org.springframework.boot.autoconfigure.jdbc.DataSourceBeanCreationFailureAnalyzer,\
org.springframework.boot.autoconfigure.jdbc.HikariDriverConfigurationFailureAnalyzer

# Template availability providers
org.springframework.boot.autoconfigure.template.TemplateAvailabilityProvider=\
org.springframework.boot.autoconfigure.freemarker.FreeMarkerTemplateAvailabilityProvider,\
org.springframework.boot.autoconfigure.mustache.MustacheTemplateAvailabilityProvider,\
org.springframework.boot.autoconfigure.groovy.template.GroovyTemplateAvailabilityProvider,\
org.springframework.boot.autoconfigure.thymeleaf.ThymeleafTemplateAvailabilityProvider,\
org.springframework.boot.autoconfigure.web.JspTemplateAvailabilityProvider

每一个autoconfigure,都是容器中的一个组件,并都加入到了容器中。接下来就可以对它们来做自动配置。

  1. 从容器中获取

类似于@EnableConfigurationProperties(HttpEncodingProperties.class) 注解,这个@EnableConfigurationProperties注解的作用就是把HttpEncodingProperties.class和配置文件进行绑定起来并把HttpEncodingProperties加入到容器中。
接下来这个自动配置类,通过一个有参构造器把这个属性拿到,而这个属性已经和SpringBoot映射了,接下来要用什么编码,就是拿到HttpEncodingProperties这个类里边的属性。
所以SpringBoot能配置什么,它要设置编码,它是获取properties里边getCharset里边的name值。

filter.setEncoding(this.properties.getCharset().name());

以此类推,配置一个Spring配置,就可以照着HttpEncodingProperties这里边的来配置。
比如在application.properties配置文件下配置一个http.encoding.enabled属性:
spring.http.encoding.enabled=true 能配置这个就相当于是我们之前的判断属性还能配置其他的一些属性。
比如:
spring.http.encoding.charset=UTF-8
所以我们能够配置哪些属性,都是来源于这个功能的properties类。
有了这个自动配置类,自动配置类就给容器中添加这个filter,然后这个filter就会起作用了。

具体几个@Conditon注解的含义
@ConditionalOnBean
仅仅在当前上下文中存在某个对象时,才会实例化一个Bean。
@ConditionalOnClass
某个class位于类路径上,才会实例化一个Bean),该注解的参数对应的类必须存在,否则不解析该注解修饰的配置类。
@ConditionalOnExpression
当表达式为true的时候,才会实例化一个Bean。
@ConditionalOnMissingBean
仅仅在当前上下文中不存在某个对象时,才会实例化一个Bean,该注解表示,如果存在它修饰的类的bean,则不需要再创建这个bean,可以给该注解传入参数例如@ConditionOnMissingBean(name = “example”),这个表示如果name为“example”的bean存在,这该注解修饰的代码块不执行。
@ConditionalOnMissingClass
某个class类路径上不存在的时候,才会实例化一个Bean。
@ConditionalOnNotWebApplication。
不是web应用时,才会执行。

用好SpringBoot只要把握这几点:

  • SpringBoot启动会加载大量的自动配置类
  • 所要做的就是我们需要的功能SpringBoot有没有帮我们写好的自动配置类。
  • 如果有就再来看这个自动配置类中到底配置了哪些组件,Springboot自动配置类里边只要我们要用的组件有,我们就不需要再来配置了,但是如果说没有我们所需要的组件,那么我们就需要自己来写一个配置类来把我们相应的组件配置起来。
  • 给容器中自动配置类添加组件的时候,会从properties类中获取某些属性,而这些属性我们就可以在配置文件指定这些属性的值

2、maven

因为测试项目是通过maven构建,在此简单介绍一下maven。
Apache Maven是一个软件项目管理和综合工具。基于项目对象模型(POM)的概念,Maven可以从一个中心资料片管理项目构建,报告和文件。片面一点说,Maven的核心功能便是合理叙述项目间的依赖关系,说白了就是通过pom.xml文件的配置获取jar包,而不用手动去添加jar包。
在maven中通过groupId、artifactId、version三个属性定位一个jar包(坐标),groupId一般为包名,域名反写;artifactId为项目名称;version所需要jar的版本。
例如:

<dependencies>
     <!-- spring boot web依赖 -->
     <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-web</artifactId>
         <version>1.5.4.RELEASE</version>
     </dependenc
     <!-- Junit -->
     <dependency>
         <groupId>junit</groupId>
         <artifactId>junit</artifactId>
         <version>4.12</version>
     </dependency>
 </dependencies>

Maven安装和配置本文不再描述。

3、HelloWorld

POM文件依赖

<dependencies>
        <!-- spring boot web依赖 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <version>1.5.4.RELEASE</version>
        </dependency>
    </dependencies>

    <!-- 使用 spring-boot-maven-plugin 插件生成该 Spring Boot 应用程序 -->
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

启动文件:

//Spring Boot 应用的标示
@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        // 程序启动入口
        // 启动嵌入式的 Tomcat 并初始化 Spring 环境及其各 Spring 组件
        SpringApplication.run(Application.class, args);
    }
}

Controller文件

@RestController
public class HelloWorldController {

    @RequestMapping("/helloworld")
    public String sayHello () {
        return "Hello World!";
    }

}

@RestController 注解: 该注解是 @Controller 和 @ResponseBody 注解的合体版。

二、Spring Boot 之配置文件详解

Spring Boot通过ConfigurationProperties注解从配置文件中获取属性。ConfigurationProperties注解可以通过设置prefix指定需要批量导入的数据。支持获取字面值,集合,Map,对象等复杂数据。@ConfigurationProperties详解过程:

  1. 导入依赖,若要使用ConfigurationProperties注解,需要导入依赖 spring-boot-configuration-processor ,本项目由于直接依赖于spring-boot-starter-web如下:
<!-- spring boot web依赖 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

而spring-boot-starter-web又依赖于spring-boot-starter:

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter</artifactId>
</dependency>

spring-boot-starter依赖spring-boot-autoconfigure

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-autoconfigure</artifactId>
</dependency>

spring-boot-autoconfigure依赖spring-boot-configuration-processor

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-configuration-processor</artifactId>
	<optional>true</optional>
</dependency>
  1. 配置数据,例如springboot的全局配置文件application.properties,如下:
## 数据源配置
spring.datasource.url=jdbc:mysql://localhost:3306/springbootdb?useUnicode=true&characterEncoding=utf8
spring.datasource.username=root
spring.datasource.password=***
spring.datasource.driver-class-name=com.mysql.jdbc.Driver

可以看到spring.datasource的配置类DataSourceProperties,也有@ConfigurationProperties(prefix = “spring.datasource”)注解,如下:

@ConfigurationProperties(prefix = "spring.datasource")
public class DataSourceProperties implements BeanClassLoaderAware, EnvironmentAware, InitializingBean {
    ...
}
  1. 自定义配置
    增加一个配置类:
/**
 * @Author: dong
 * @Project: springboot
 * @Description: 家乡属性
 * @Date: Created in 10:23 AM 2018/11/10
 * @Modified By:
 */
@Component
@ConfigurationProperties(prefix = "home")
public class HomeProperties {
    /**
     * 省份
     */
    private String province;

    /**
     * 城市
     */
    private String city;

    /**
     * 描述
     */
    private String desc;

    //set,get省略
}

增加两个配置文件,application-home1.properties、application-home2.properties,具体内容如下(home2类似):

## 家乡属性 home1
home1.province=ZheJiang
home1.city=WenLing
home1.desc=home1: I'm living in ${home1.province} ${home1.city}.

上述实例可以看出在application.properties中的各个参数之间也可以直接引用来使用,通过${}方式。
此时application.properties加载多个自定义文件,加载application-home1.properties、application-home2.properties,具体内容如下(home2类似)::

# Spring Profiles Active
spring.profiles.active=dev

三、Spring Boot 之配置注解使用

Spring配置Bean有多种形式,第一种常用的就是通过XML文件配置,另外一种就是通过@Configuration声明类,表明是一个配置文件,他的本质作用和XML是相同的,作为Bean的载体。

Configuration的主要功能是用来注册Bean,然后使用这个Configuration注册/初始化Spring容器,然后就可以通过getBean来获取了。

注意:@Configuration注解的配置类有如下要求:

@Configuration不可以是final类型;
@Configuration不可以是匿名类;
嵌套的configuration必须是静态类。

1 用@Configuration加载spring

1.1 @Configuration配置spring并启动spring容器

  • @Configuration配置spring并启动spring容器

@Configuration作用在类上的主要功能是用来注册Bean。然后使用这个Configuration注册/初始化Spring容器,就可以通过getBean来获取了。

@Configuration
public class MessageConfiguration {

    @Bean
    public String message() {
        System.out.println("message configuration。。。");
        return "message configuration";
    }
}

测试方法:

// Spring Boot 应用的标识
@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();

        context.register(MessageConfiguration.class);
        context.refresh();

        String mybean = (String)context.getBean("message");
        System.out.print(mybean);
    }
}

使用AnnotationConfigApplicationContext可以实现基于Java的配置类加载Spring的应用上下文。避免使用application.xml进行配置。相比XML配置,更加便捷。

public class AnnotationConfigApplicationContext extends GenericApplicationContext implements AnnotationConfigRegistry {
    ...
}

在这里插入图片描述

如果想要了解更清楚的,在此推荐一篇博文,对AnnotationConfigApplicationContext分析很透彻:AnnotationConfigApplicationContext的实例化过程

1.2 @Configuration启动容器+@Bean注册Bean,@Bean下管理bean的生命周期

@Bean标注在方法上(返回某个实例的方法),等价于spring的xml配置文件中的,作用为:注册bean对象

四、Spring Boot 实现 Restful 服务,基于 HTTP / JSON 传输

REST全称是Representational State Transfer,中文意思是表述(编者注:通常译为表征)性状态转移。

1 理解Restful

1.1 资源与URI

在Web中资源的唯一标识是URI(Uniform Resource Identifier),URI既可以看成是资源的地址,也可以看成是资源的名称。如果某些信息没有使用URI来表示,那它就不能算是一个资源, 只能算是资源的一些信息而已。

网络上的所有事物都可以被抽象为资源。
每一个资源都有唯一的资源标识,对资源的操作不会改变这些标识。所有的操作都是无状态的。REST 是面向资源的,这个概念非常重要,而资源是通过 URI 进行暴露。
资源:网络上的一个实体,或者说是网络上的一个具体信息。
schema://host[:port]/path [?query-string][#anchor]

  • schema:指定底层使用的协议(例如:http,https,ftp)
  • host:服务器的IP地址或者域名
  • port:服务器端口,默认为80
  • path:访问资源的路径
  • query-string:发送给http服务器的数据
  • anchor:锚

URI设计上的一些技巧:

  • 使用_或-来让URI可读性更好
  • 使用?用来过滤资源
  • 使用/来表示资源的层级关系
  • ,或;可以用来表示同级资源的关系

1.2 统一资源接口

对于资源最常见的操作就是CRUD,RESTful架构应该遵循统一接口原则,统一接口包含了一组受限的预定义的操作。
HTTP协议下RestFul框架的交互格式就像这样:

  • POST /uri 创建
  • DELETE /uri/xxx 删除
  • PUT /uri/xxx 更新或创建
  • GET /uri/xxx 查看
1) GET
  • 安全且幂等
  • 获取表示
  • 变更时获取表示(缓存)
  • 200(OK) - 表示已在响应中发出
  • 204(无内容) - 资源有空表示
  • 301(Moved Permanently) - 资源的URI已被更新
  • 303(See Other) - 其他(如,负载均衡)
  • 304(not modified)- 资源未更改(缓存)
  • 400 (bad request)- 指代坏请求(如,参数错误)
  • 404 (not found)- 资源不存在
  • 406 (not acceptable)- 服务端不支持所需表示
  • 500 (internal server error)- 通用错误响应
  • 503 (Service Unavailable)- 服务端当前无法处理请求
2) POST
  • 不安全且不幂等
  • 使用服务端管理的(自动产生)的实例号创建资源
  • 创建子资源
  • 部分更新资源
  • 如果没有被修改,则不过更新资源(乐观锁)
  • 200(OK)- 如果现有资源已被更改
  • 201(created)- 如果新资源被创建
  • 202(accepted)- 已接受处理请求但尚未完成(异步处理)
  • 301(Moved Permanently)- 资源的URI被更新
  • 303(See Other)- 其他(如,负载均衡)
  • 400(bad request)- 指代坏请求
  • 404 (not found)- 资源不存在
  • 406 (not acceptable)- 服务端不支持所需表示
  • 409 (conflict)- 通用冲突
  • 412 (Precondition Failed)- 前置条件失败(如执行条件更新时的冲突)
  • 415 (unsupported media type)- 接受到的表示不受支持
  • 500 (internal server error)- 通用错误响应
  • 503 (Service Unavailable)- 服务当前无法处理请求
3) PUT
  • 不安全但幂等
  • 用客户端管理的实例号创建一个资源
  • 通过替换的方式更新资源
  • 如果未被修改,则更新资源(乐观锁)
  • 200 (OK)- 如果已存在资源被更改
  • 201 (created)- 如果新资源被创建
  • 301(Moved Permanently)- 资源的URI已更改
  • 303 (See Other)- 其他(如,负载均衡)
  • 400 (bad request)- 指代坏请求
  • 404 (not found)- 资源不存在
  • 406 (not acceptable)- 服务端不支持所需表示
  • 409 (conflict)- 通用冲突
  • 412 (Precondition Failed)- 前置条件失败(如执行条件更新时的冲突)
  • 415 (unsupported media type)- 接受到的表示不受支持
  • 500 (internal server error)- 通用错误响应
  • 503 (Service Unavailable)- 服务当前无法处理请求
4) DELETE
  • 不安全但幂等
  • 删除资源
  • 200 (OK)- 资源已被删除
  • 301 (Moved Permanently)- 资源的URI已更改
  • 303 (See Other)- 其他,如负载均衡
  • 400 (bad request)- 指代坏请求
  • 404 (not found)- 资源不存在
  • 409 (conflict)- 通用冲突
  • 500 (internal server error)- 通用错误响应
  • 503 (Service Unavailable)- 服务端当前无法处理请求
5) http的状态码

http的状态码分成五个类别:

  • 1xx:相关信息
  • 2xx:操作成功
  • 3xx:重定向
  • 4xx:客户端错误
  • 5xx:服务器错误
API 不需要1xx状态码
2xx 状态码

200状态码表示操作成功,但是不同的方法可以返回更精确的状态码。

GET: 200 OK
POST: 201 Created
PUT: 200 OK
PATCH: 200 OK
DELETE: 204 No Content

上面代码中,POST返回201状态码,表示生成了新的资源;DELETE返回204状态码,表示资源已经不存在。
此外,202 Accepted状态码表示服务器已经收到请求,但还未进行处理,会在未来再处理,通常用于异步操作。下面是一个例子。

HTTP/1.1 202 Accepted

{
  "task": {
    "href": "/api/company/job-management/jobs/2130040",
    "id": "2130040"
  }
}
3xx 状态码

API 用不到301状态码(永久重定向)和302状态码(暂时重定向,307也是这个含义),因为它们可以由应用级别返回,浏览器会直接跳转,API 级别可以不考虑这两种情况。
API 用到的3xx状态码,主要是303 See Other,表示参考另一个 URL。它与302和307的含义一样,也是"暂时重定向",区别在于302和307用于GET请求,而303用于POST、PUT和DELETE请求。收到303以后,浏览器不会自动跳转,而会让用户自己决定下一步怎么办。下面是一个例子。

HTTP/1.1 303 See Other
Location: /api/orders/12345
4xx 状态码

4xx状态码表示客户端错误,主要有下面几种:

  • 400 Bad Request:服务器不理解客户端的请求,未做任何处理。
  • 401 Unauthorized:用户未提供身份验证凭据,或者没有通过身份验证。
  • 403 Forbidden:用户通过了身份验证,但是不具有访问资源所需的权限。
  • 404 Not Found:所请求的资源不存在,或不可用。
  • 405 Method Not Allowed:用户已经通过身份验证,但是所用的 HTTP - 方法不在他的权限之内。
  • 410 Gone:所请求的资源已从这个地址转移,不再可用。
  • 415 Unsupported Media Type:客户端要求的返回格式不支持。比如,API 只能返回 - JSON 格式,但是客户端要求返回 XML 格式。
  • 422 Unprocessable Entity :客户端上传的附件无法处理,导致请求失败。
  • 429 Too Many Requests:客户端的请求次数超过限额。
5xx 状态码

5xx状态码表示服务端错误。一般来说,API不会向用户透露服务器的详细信息,所以只要两个状态码就够了:

  • 500 Internal Server Error:客户端请求有效,服务器处理时发生了意外。
  • 503 Service Unavailable:服务器无法处理请求,一般用于网站维护状态。
Requests Header | Http Header
Header 解释 示例
Accept 指定客户端能够接收的内容类型 Accept: text/plain, text/html
Accept-Charset 浏览器可以接受的字符编码集。 Accept-Charset: iso-8859-5
Accept-Encoding 指定浏览器可以支持的web服务器返回内容压缩编码类型。 Accept-Encoding: compress, gzip
Accept-Language 浏览器可接受的语言 Accept-Language: en,zh
Accept-Ranges 可以请求网页实体的一个或者多个子范围字段 Accept-Ranges: bytes
Authorization HTTP授权的授权证书 Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==
Cache-Control 指定请求和响应遵循的缓存机制 Cache-Control: no-cache
Connection 表示是否需要持久连接。(HTTP 1.1默认进行持久连接) Connection: close
Cookie HTTP请求发送时,会把保存在该请求域名下的所有cookie值一起发送给web服务器。 Cookie: $Version=1; Skin=new;
Content-Length 请求的内容长度 Content-Length: 348
Content-Type 请求的与实体对应的MIME信息 Content-Type: application/x-www-form-urlencoded
Date 请求发送的日期和时间 Date: Tue, 15 Nov 2010 08:12:31 GMT
Expect 请求的特定的服务器行为 Expect: 100-continue
From 发出请求的用户的Email From: user@email.com
Host 指定请求的服务器的域名和端口号 Host: www.zcmhi.com
If-Match 只有请求内容与实体相匹配才有效 If-Match: “737060cd8c284d8af7ad3082f209582d”
If-Modified-Since 如果请求的部分在指定时间之后被修改则请求成功,未被修改则返回304代码 If-Modified-Since: Sat, 29 Oct 2010 19:43:31 GMT
If-None-Match 如果内容未改变返回304代码,参数为服务器先前发送的Etag,与服务器回应的Etag比较判断是否改变 If-None-Match: “737060cd8c284d8af7ad3082f209582d”
If-Range 如果实体未改变,服务器发送客户端丢失的部分,否则发送整个实体。参数也为Etag If-Range: “737060cd8c284d8af7ad3082f209582d”
If-Unmodified-Since 只在实体在指定时间之后未被修改才请求成功 If-Unmodified-Since: Sat, 29 Oct 2010 19:43:31 GMT
Max-Forwards 限制信息通过代理和网关传送的时间 Max-Forwards: 10
Pragma 用来包含实现特定的指令 Pragma: no-cache
Proxy-Authorization 连接到代理的授权证书 Proxy-Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==
Range 只请求实体的一部分,指定范围 Range: bytes=500-999
Referer 先前网页的地址,当前请求网页紧随其后,即来路 Referer: http://www.zcmhi.com/archives/71.html
TE 客户端愿意接受的传输编码,并通知服务器接受接受尾加头信息 TE: trailers,deflate;q=0.5
Upgrade 向服务器指定某种传输协议以便服务器进行转换(如果支持) Upgrade: HTTP/2.0, SHTTP/1.3, IRC/6.9, RTA/x11
User-Agent User-Agent的内容包含发出请求的用户信息 User-Agent: Mozilla/5.0 (Linux; X11)
Via 通知中间网关或代理服务器地址,通信协议 Via: 1.0 fred, 1.1 nowhere.com (Apache/1.1)
Warning 关于消息实体的警告信息 Warn: 199 Miscellaneous warning
Responses

服务器回应约束
不要返回纯本文:
API 返回的数据格式,不应该是纯文本,而应该是一个 JSON 对象,因为这样才能返回标准的结构化数据。所以,服务器回应的 HTTP 头的Content-Type属性要设为application/json。
客户端请求时,也要明确告诉服务器,可以接受 JSON 格式,即请求的 HTTP 头的ACCEPT属性也要设成application/json。下面是一个例子。

GET /orders/2 HTTP/1.1 
Accept: application/json

发生错误时,不要返回 200 状态码,有一种不恰当的做法是,即使发生错误,也返回200状态码,把错误信息放在数据体里面,就像下面这样:

HTTP/1.1 200 OK
Content-Type: application/json

{
  "status": "failure",
  "data": {
    "error": "Expected at least two items in list."
  }
}

正确的做法是,状态码反映发生的错误,具体的错误信息放在数据体里面返回。下面是一个例子:

HTTP/1.1 400 Bad Request
Content-Type: application/json

{
  "error": "Invalid payoad.",
  "detail": {
     "surname": "This field is required."
  }
}
Header 解释 示例
Accept-Ranges 表明服务器是否支持指定范围请求及哪种类型的分段请求 Accept-Ranges: bytes
Age 从原始服务器到代理缓存形成的估算时间(以秒计,非负) Age: 12
Allow 对某网络资源的有效的请求行为,不允许则返回405 Allow: GET, HEAD
Cache-Control 告诉所有的缓存机制是否可以缓存及哪种类型 Cache-Control: no-cache
Content-Encoding web服务器支持的返回内容压缩编码类型。 Content-Encoding: gzip
Content-Language 响应体的语言 Content-Language: en,zh
Content-Length 响应体的长度 Content-Length: 348
Content-Location 请求资源可替代的备用的另一地址 Content-Location: /index.htm
Content-MD5 返回资源的MD5校验值 Content-MD5: Q2hlY2sgSW50ZWdyaXR5IQ==
Content-Range 在整个返回体中本部分的字节位置 Content-Range: bytes 21010-47021/47022
Content-Type 返回内容的MIME类型 Content-Type: text/html; charset=utf-8
Date 原始服务器消息发出的时间 Date: Tue, 15 Nov 2010 08:12:31 GMT
ETag 请求变量的实体标签的当前值 ETag: “737060cd8c284d8af7ad3082f209582d”
Expires 响应过期的日期和时间 Expires: Thu, 01 Dec 2010 16:00:00 GMT
Last-Modified 请求资源的最后修改时间 Last-Modified: Tue, 15 Nov 2010 12:45:26 GMT
Location 用来重定向接收方到非请求URL的位置来完成请求或标识新的资源 Location: http://www.zcmhi.com/archives/94.html
Pragma 包括实现特定的指令,它可应用到响应链上的任何接收方 Pragma: no-cache
Proxy-Authenticate 它指出认证方案和可应用到代理的该URL上的参数 Proxy-Authenticate: Basic
refresh 应用于重定向或一个新的资源被创造,在5秒之后重定向(由网景提出,被大部分浏览器支持) Refresh: 5; url=http://www.atool.org/httptest.php
Retry-After 如果实体暂时不可取,通知客户端在指定时间之后再次尝试 Retry-After: 120
Server web服务器软件名称 Server: Apache/1.3.27 (Unix) (Red-Hat/Linux)
Set-Cookie 设置Http Cookie Set-Cookie: UserID=JohnDoe; Max-Age=3600; Version=1
Trailer 指出头域在分块传输编码的尾部存在 Trailer: Max-Forwards
Transfer-Encoding 文件传输编码 Transfer-Encoding:chunked
Vary 告诉下游代理是使用缓存响应还是从原始服务器请求 Vary: *
Via 告知代理客户端响应是通过哪里发送的 Via: 1.0 fred, 1.1 nowhere.com (Apache/1.1)
Warning 警告实体可能存在的问题 Warning: 199 Miscellaneous warning
WWW-Authenticate 表明客户端请求实体应该使用的授权方案 WWW-Authenticate: Basic

1.3 REST 系统的特征

1)无状态(Stateless)

所谓无状态的,即所有的资源,都可以通过URI定位,而且这个定位与其他资源无关,也不会因为其他资源的变化而改变。

2)统一接口(Uniform Interface)

RESTful架构风格规定,数据的元操作,即CRUD(create, read, update和delete,即数据的增删查改)操作,分别对应于HTTP方法:GET用来获取资源,POST用来新建资源(也可以用于更新资源),PUT用来更新资源,DELETE用来删除资源,这样就统一了数据操作的接口,仅通过HTTP方法,就可以完成对数据的所有增删查改工作。

3)可缓存(Cachable)

服务器必须让客户知道请求是否可以被缓存。(Ross:更详细解释请参考 理解本真的REST架构风格 以及 StackOverflow 的这个问题 中对缓存的解释。)

4)分层系统(Layered System)

服务器和客户之间的通信必须被这样标准化:允许服务器和客户之间的中间层(Ross:代理,网关等)可以代替服务器对客户的请求进行回应,而且这些对客户来说不需要特别支持。

5)统一接口(Uniform Interface)

客户和服务器之间通信的方法必须是统一化的。(Ross:GET,POST,PUT.DELETE, etc)。

2 RestFul实例

2.1 @RequestMapping

RequestMapping是一个用来处理请求地址映射的注解,可用于类或方法上。用于类上,表示类中的所有响应请求的方法都是以该地址作为父路径。
RequestMapping的属性
value:指定请求的实际地址,指定的地址可以是URI Template 模式;
method:指定请求的method类型, GET、POST、PUT、DELETE等;
consumes:指定处理请求的提交内容类型(Content-Type),例如application/json, text/html;
produces:指定返回的内容类型,仅当request请求头中的(Accept)类型中包含该指定类型才返回;
params:指定request中必须包含某些参数值是,才让该方法处理。
headers:指定request中必须包含某些指定的header值,才能让该方法处理请求。

2.2 实例

value与method实例:

@Api(tags = "1.0", description = "swaggerDemoController相关的api", value = "城市信息管理")
@RestController
public class CityRestController {

    @Autowired
    private CityService cityService;

    @ApiOperation(value = "查询一个城市")
    @RequestMapping(value = "/api/city/{id}", method = RequestMethod.GET)
    public City findOneCity(@PathVariable("id") Long id) {
        return cityService.findCityById(id);
    }

    @ApiOperation(value = "查询多个城市")
    @RequestMapping(value = "/api/city", method = RequestMethod.GET)
    public List<City> findAllCity() {
        return cityService.findAllCity();
    }

    @ApiOperation(value = "增加城市信息")
    @RequestMapping(value = "/api/city", method = RequestMethod.POST)
    public void createCity(@RequestBody City city) {
        cityService.saveCity(city);
    }

    @ApiOperation(value = "更新城市信息")
    @RequestMapping(value = "/api/city", method = RequestMethod.PUT)
    public void modifyCity(@RequestBody City city) {
        cityService.updateCity(city);
    }

    @ApiOperation(value = "删除城市信息")
    @RequestMapping(value = "/api/city/{id}", method = RequestMethod.DELETE)
    public void modifyCity(@PathVariable("id") Long id) {
        cityService.deleteCity(id);
    }
}

value的uri值可以是指定为普通的具体值;也可以指定为含有某变量的一类值(URI Template Patterns with Path Variables);还可以指定为含正则表达式的一类值( URI Template Patterns with Regular Expressions)。


cousumes的样例(方法仅处理request Content-Type为“application/json”类型的请求):

@RequestMapping(value = "/api/city", method = RequestMethod.POST, consumes="application/json")
public void addCity(@RequestBody City city) {
    cityService.addCity(city);
}

produces的样例(方法仅处理request请求中Accept头中包含了"application/json"的请求,同时暗示了返回的内容类型为application/json)

@ApiOperation(value = "查询一个城市2")
@RequestMapping(value = "/api/city/{id}", method = RequestMethod.GET, produces="application/json")
public City findOneCity2(@PathVariable("id") Long id) {
    return cityService.findCityById(id);
}

params的样例(仅处理请求中包含了名为“myParam”,值为“myValue”的请求

@ApiOperation(value = "查询一个城市3")
@RequestMapping(value = "/api/city/{id}", method = RequestMethod.GET, params="myParam=myValue")
public City findOneCity3(@PathVariable("id") Long id) {
    return cityService.findCityById(id);
}

headers的样例(仅处理request的header中包含了指定“Refer”请求头和对应值为“http://127.0.0.01/”的请求

@ApiOperation(value = "查询一个城市4")
@RequestMapping(value = "/api/city/{id}", method = RequestMethod.GET, headers="Referer=http://127.0.0.1/")
public City findOneCity4(@PathVariable("id") Long id) {
 return cityService.findCityById(id);

五、Spring Boot 集成 FreeMarker

1、简述

FreeMarker 是一款 模板引擎:即一种基于模板和要改变的数据,并用来生成输出文本(HTML网页,电子邮件,配置文件,源代码等)的通用工具。它不是面向最终用户的,而是一个Java类库,是一款程序员可以嵌入他们所开发产品的组件。

模板编写语言为FTL(FreeMarker Template Language)。 那就意味着要准备数据在真实编程语言中来显示,比如数据库查询和业务运算,之后模板显示已经准备好的数据。在模板中,你可以专注于如何展现数据,而在模板之外可以专注于要展示什么数据。
FreeMarker的原理就是:模板+数据模型=输出。FreeMarker与Web容器无关,即在Web运行时,它并不知道Servlet或HTTP。它不仅可以用作表现层的实现技术,而且还可以用于生成XML,JSP或Java等。
在这里插入图片描述
这种方式通常被称为MVC(模型、视图、控制器) 模式。

2、优缺点

优点:

  • 可以彻底的分离表现层和业务逻辑
  • 可以提高开发效率
  • 使得开发过程中的人员分工更加明确
    缺点:
  • 应用FreeMarker模板技术,在修改模板后,可能会看到已经过期的数据
  • FreeMarker模板技术在应用过程中,FreeMarker中的变量必须要赋值,如果不赋值,那么就会抛出异常。想避免错误就要应用if/elseif/else指令进行判段,如果对每一个变量都判断的话,那么则反而增加了编程的麻烦。
  • FreeMarker的map限定key必须是string,其他数据类型无法操作。
  • FreeMarker不支持集群应用。为了编成的方便性,把序列化的东西都放到了Session中,如Session,request等,在开发的过程中确实方便,但如果将应用放到集群中,就会出现错误。

3、FTL语法

1)字符处理


4、实例

1)创建一个maven项目导入 FreeMarker jar 包

<!-- Spring Boot Freemarker 依赖 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>

2)创建目录templates,并创建一个 FreeMarker模版文件 *.ftl

<!DOCTYPE html>
<html>
<head>
    <link rel="icon" type="image/x-icon" href="/images/favicon.ico">
    <title>${welcome}</title>
</head>
<body>
${welcome}
</body>
</html>

3)创建一个运行FreeMarker模版引擎的 FreeMarkerDemo.java 文件

@Controller
@RequestMapping("/")
public class TemplateController {

    @GetMapping("index")
    public String index(ModelMap map){
        map.put("welcome","SpringBoot Hello World!");
        return "index";
    }
}

4)启动并验证

在这里插入图片描述

六、Spring Boot 集成 swagger

1 swagger概述

Swagger 是一个规范和完整的框架,用于生成、描述、调用和可视化 RESTful 风格的 Web 服务。总体目标是使客户端和文件系统作为服务器以同样的速度来更新。文件的方法,参数和模型紧密集成到服务器端的代码,允许API来始终保持同步。Swagger 是一个规范和完整的框架,用于生成、描述、调用和可视化 RESTful 风格的 Web 服务。具有语言的无关性。
swagger的优缺点:
优点

  • 前后端分离开发
  • API文档非常明确
  • 测试的时候不需要再使用URL输入浏览器的方式来访问Controller
  • 传统的输入URL的测试方式对于post请求的传参比较麻烦(当然,可以使用postman这样- 的浏览器插件)
  • Swagger与SpringBoot的集成简单

缺点

  • 没有导出的功能

2 swagger集成Spring Boot

2.1 添加依赖

1)在项目的pom.xml添加swagger依赖
<!-- spring boot swagger依赖 -->
<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger2</artifactId>
    <version>2.6.1</version>
</dependency>
<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger-ui</artifactId>
    <version>${swagger-version}</version>
</dependency>
2)swagger 配置类
@Configuration
@EnableSwagger2
public class SwaggerConfig {
    @Bean
    public Docket createRestApi() {
        return new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(apiInfo())
                .select()
                .apis(RequestHandlerSelectors.basePackage("pers.dong.springboot"))
                .paths(PathSelectors.any())
                .build();
    }

    private ApiInfo apiInfo() {
        return new ApiInfoBuilder()
                .title("spring boot swagger test")
                .contact(new Contact("dong", "https://me.csdn.net/coding_dong", "yaodongw@yeah.net"))
                .version("1.0")
                .build();
    }

}

创建Swagger配置类时,类名随意创建,但是类上面必须添加@Configuration、@EnableSwagger2这两个注解,通过@Configuration注解,让Spring来加载该类配置,@EnableSwagger2注解来启用Swagger2。再通过被@Bean注解的函数创建Docket的Bean之后,apiInfo()用来创建该Api的基本信息(这些基本信息会展现在文档页面中)。select()函数返回一个ApiSelectorBuilder实例用来控制哪些接口暴露给Swagger来展现,本例采用指定扫描的包路径来定义,Swagger会扫描该包下所有Controller定义的API,并产生文档内容(除了被@ApiIgnore注解的API)。这里如果函数上面不配置@Bean,那么Swagger只是启动了,没有创建API信息。

3)编写Controller

首先看一下git上的描述:Quick Annotation Overview

NameDescription
@ApiMarks a class as a Swagger resource.
@ApiImplicitParamRepresents a single parameter in an API Operation.
@ApiImplicitParamsA wrapper to allow a list of multiple ApiImplicitParam objects.
@ApiModelProvides additional information about Swagger models.
@ApiModelPropertyAdds and manipulates data of a model property.
@ApiOperationDescribes an operation or typically a HTTP method against a specific path.
@ApiParamAdds additional meta-data for operation parameters.
@ApiResponseDescribes a possible response of an operation.
@ApiResponsesA wrapper to allow a list of multiple ApiResponse objects.
@AuthorizationDeclares an authorization scheme to be used on a resource or an operation.
@AuthorizationScopeDescribes an OAuth2 authorization scope.
@ResponseHeaderRepresents a header that can be provided as part of the response.
最新版本还添加了许多用于在Swagger Definition级别添加扩展和元数据的注释:
NameDescription
@SwaggerDefinitionDefinition-level properties to be added to the generated Swagger definition
@InfoGeneral metadata for a Swagger definition
@ContactProperties to describe the contact person for a Swagger definition
@LicenseProperties to describe the license for a Swagger definition
@ExtensionAdds an extension with contained properties
@ExtensionPropertyAdds custom properties to an extension

简要描述:

  • @Api: 描述Controller
  • @ApiIgnore: 忽略该Controller,指不对当前类做扫描
  • @ApiOperation: 描述Controller类中的method接口
  • @ApiParam: 单个参数描述,与@ApiImplicitParam不同的是,他是写在参数左侧的。- [x] 如(@ApiParam(name = “username”,value = “用户名”) String username)
  • @ApiModel: 描述POJO对象
  • @ApiProperty: 描述POJO对象中的属性值
  • @ApiImplicitParam: 描述单个入参信息
  • @ApiImplicitParams: 描述多个入参信息
  • @ApiResponse: 描述单个出参信息
  • @ApiResponses: 描述多个出参信息
  • @ApiError: 接口错误所返回的信息
2)swagger 的 Controller
@Api(tags = "1.0", description = "swaggerDemoController相关的api", value = "城市信息管理")
@RestController
public class CityRestController {

    @Autowired
    private CityService cityService;

    @ApiOperation(value = "查询一个城市")
    @RequestMapping(value = "/api/city/{id}", method = RequestMethod.GET)
    public City findOneCity(@PathVariable("id") Long id) {
        return cityService.findCityById(id);
    }

    @ApiOperation(value = "查询多个城市")
    @RequestMapping(value = "/api/city", method = RequestMethod.GET)
    public List<City> findAllCity() {
        return cityService.findAllCity();
    }

    @ApiOperation(value = "增加城市信息")
    @RequestMapping(value = "/api/city", method = RequestMethod.POST)
    public void createCity(@RequestBody City city) {
        cityService.saveCity(city);
    }

    @ApiOperation(value = "更新城市信息")
    @RequestMapping(value = "/api/city", method = RequestMethod.PUT)
    public void modifyCity(@RequestBody City city) {
        cityService.updateCity(city);
    }

    @ApiOperation(value = "删除城市信息")
    @RequestMapping(value = "/api/city/{id}", method = RequestMethod.DELETE)
    public void modifyCity(@PathVariable("id") Long id) {
        cityService.deleteCity(id);
    }
}

如上所示,增加了5个接口

3) 启动并查看

启动后的截图如下:
在这里插入图片描述

七、Spring Boot HTTP over JSON 的错误码异常处理

八、Spring Boot 整合 Mybatis 的完整 Web 案例

1、概述

MyBatis 是一款优秀的持久层框架,它支持定制化 SQL、存储过程以及高级映射。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。MyBatis 可以使用简单的 XML 或注解来配置和映射原生信息,将接口和 Java 的 POJOs(Plain Old Java Objects,普通的 Java对象)映射成数据库中的记录。
要使用 MyBatis, 只需将 mybatis-x.x.x.jar 文件置于 classpath 中即可。
如果使用 Maven 来构建项目,则需将下面的 dependency 代码置于 pom.xml 文件中

2、Spring Boot之Mybatis实例

2.1 Mybatis配置

2.2 Mybatis映射文件

Mapper XML 文件:
MyBatis 的真正强大在于它的映射语句,MyBatis是针对SQL构建的,其强大的映射器使得XML文件就显得相对简单。
SQL 映射顶级元素:

  • cache:给定命名空间的缓存配置。
  • cache-ref:其他命名空间缓存配置的引用。
  • resultMap:是最复杂也是最强大的元素,用来描述如何从数据库结果集中来加载对象。查询时,我们需要返回类型,即用resultMap。
  • parameterMap:通常应用于mapper中有多个参数要传进来时,表示将查询结果集中列值的类型一一映射到java对象属性的类型上,在开发过程中不推荐这种方式,或者增改删时,我们需要参数类型,也可用parameterMap。
  • sql:可被其他语句引用的可重用语句块。
  • insert:映射插入语句。
  • update:映射更新语句。
  • delete:映射删除语句。
  • select:映射查询语句。

下面将对各个顶级元素进行详细讨论:

select
<resultMap id="BaseResultMap" type="pers.dong.springboot.entity.City">
	<result column="id" property="id" />
	<result column="province_id" property="provinceId" />
	<result column="city_name" property="cityName" />
	<result column="description" property="description" />
</resultMap>

<select id="findByName" resultMap="BaseResultMap" parameterType="java.lang.String">
	select
	<include refid="Base_Column_List" />
	from city
	where city_name = #{cityName}
</select>

这个语句被称作 findByName,接受一个 String 类型的参数,并返回一个 City 的实体对象,其中的键是列名,值便是结果行中的对应值。
select 元素有很多属性,来决定每条语句的作用细节:

<select
 id="findByName"
 parameterType="String"
 parameterMap="deprecated"
 resultType="hashmap"
 resultMap="personResultMap"
 flushCache="false"
 useCache="true"
 timeout="10000"
 fetchSize="256"
 statementType="PREPARED"
 resultSetType="FORWARD_ONLY">  
属性描述
id在命名空间中唯一的标识符,可以被用来引用这条语句。
parameterType将会传入这条语句的参数类的完全限定名或别名。这个属性是可选的,因为 MyBatis 可以通过 TypeHandler 推断出具体传入语句的参数,默认值为 unset。
resultType从这条语句中返回的期望类型的类的完全限定名或别名。注意如果是集合情形,那应该是集合可以包含的类型,而不能是集合本身。使用 resultType 或 resultMap,但不能同时使用。
resultMap外部resultMap的命名引用。结果集的映射是MyBatis最强大的特性,对其有一个很好的理解的话,许多复杂映射的情形都能迎刃而解。使用resultMap或resultType,但不能同时使用
flushCache将其设置为true,任何时候只要语句被调用,都会导致本地缓存和二级缓存都会被清空,默认值:false。
useCache将其设置为true,将会导致本条语句的结果被二级缓存,默认值:对select元素为true。
timeout这个设置是在抛出异常之前,驱动程序等待数据库返回请求结果的秒数。默认值为unset依赖驱动)。
fetchSize这是尝试影响驱动程序每次批量返回的结果行数和这个设置值相等。默认值为unset(依赖驱动)。
statementTypeSTATEMENT,PREPARED或CALLABLE的一个。这会让MyBatis分别使用Statement,PreparedStatement 或 CallableStatement,默认值:PREPARED。
resultSetTypeFORWARD_ONLY,SCROLL_SENSITIVE 或 SCROLL_INSENSITIVE中的一个,默认值为unset(依赖驱动)。
databaseId如果配置了databaseIdProvider,MyBatis会加载所有的不带databaseId或匹配当前databaseId的语句;如果带或者不带的语句都有,则不带的会被忽略。
resultOrdered这个设置仅针对嵌套结果select语句适用:如果为true,就是假设包含了嵌套结果集或是分组了,这样的话当返回一个主结果行的时候,就不会发生有对前面结果集的引用的情况。这就使得在获取嵌套的结果集的时候不至于导致内存不够用。默认值:false。
resultSets这个设置仅对多结果集的情况适用,它将列出语句执行后返回的结果集并每个结果集给一个名称,名称是逗号分隔的。
insert, update 和 delete
<insert
  id="insertAuthor"
  parameterType="domain.blog.Author"
  flushCache="true"
  statementType="PREPARED"
  keyProperty=""
  keyColumn=""
  useGeneratedKeys=""
  timeout="20">

<update
  id="updateAuthor"
  parameterType="domain.blog.Author"
  flushCache="true"
  statementType="PREPARED"
  timeout="20">

<delete
  id="deleteAuthor"
  parameterType="domain.blog.Author"
  flushCache="true"
  statementType="PREPARED"
  timeout="20">

nsert, Update 和 Delete 的属性:

属性描述
id命名空间中的唯一标识符,可被用来代表这条语句。
parameterType将要传入语句的参数的完全限定类名或别名。这个属性是可选的,因为 MyBatis 可以通过 TypeHandler 推断出具体传入语句的参数,默认值为 unset。
flushCache将其设置为 true,任何时候只要语句被调用,都会导致本地缓存和二级缓存都会被清空,默认值:true(对应插入、更新和删除语句)。
timeout这个设置是在抛出异常之前,驱动程序等待数据库返回请求结果的秒数。默认值为 unset(依赖驱动)。
statementTypeSTATEMENT,PREPARED 或 CALLABLE 的一个。这会让 MyBatis 分别使用 Statement,PreparedStatement 或 CallableStatement,默认值:PREPARED。
useGeneratedKeys(仅对 insert 和 update 有用)这会令 MyBatis 使用 JDBC 的 getGeneratedKeys 方法来取出由数据库内部生成的主键(比如:像 MySQL 和 SQL Server 这样的关系数据库管理系统的自动递增字段),默认值:false。
keyProperty(仅对 insert 和 update 有用)唯一标记一个属性,MyBatis 会通过 getGeneratedKeys 的返回值或者通过 insert 语句的 selectKey 子元素设置它的键值,默认:unset。如果希望得到多个生成的列,也可以是逗号分隔的属性名称列表。
keyColumn(仅对 insert 和 update 有用)通过生成的键值设置表中的列名,这个设置仅在某些数据库(像 PostgreSQL)是必须的,当主键列不是表中的第一列的时候需要设置。如果希望得到多个生成的列,也可以是逗号分隔的属性名称列表。
databaseId如果配置了 databaseIdProvider,MyBatis 会加载所有的不带 databaseId 或匹配当前 databaseId 的语句;如果带或者不带的语句都有,则不带的会被忽略。

insert,update 和 delete 语句的示例:

<insert id="insertAuthor">
  insert into Author (id,username,password,email,bio)
  values (#{id},#{username},#{password},#{email},#{bio})
</insert>

<update id="updateAuthor">
  update Author set
    username = #{username},
    password = #{password},
    email = #{email},
    bio = #{bio}
  where id = #{id}
</update>

<delete id="deleteAuthor">
  delete from Author where id = #{id}
</delete>
sql

定义可重用的 SQL 代码段,可以包含在其他语句中。例如:

<sql id="Base_Column_List">
	id, province_id, city_name, description
</sql>

这个sql片段可以被其他语句使用,例如:

<select id="findByName" resultMap="BaseResultMap" parameterType="java.lang.String">
	select
	<include refid="Base_Column_List" />
	from city
	where city_name = #{cityName}
</select>
参数(Parameters)

例如,以下为一个简单的命名参数映射:

<select id="findByName" resultMap="BaseResultMap" parameterType="java.lang.String">
	select
	<include refid="Base_Column_List" />
	from city
	where city_name = #{cityName}
</select>

参数也可以指定一个特殊的数据类型:

#{property,javaType=int,jdbcType=NUMERIC}

可以指定一个特殊的类型处理器类(或别名),比如:

#{age,javaType=int,jdbcType=NUMERIC,typeHandler=MyTypeHandler}

对于数值类型,还有一个小数保留位数的设置,来确定小数点后保留的位数:

#{height,javaType=double,jdbcType=NUMERIC,numericScale=2}

mode 属性允许你指定 IN,OUT 或 INOUT 参数。如果参数为 OUT 或 INOUT,参数对象属性的真实值将会被改变,就像你在获取输出参数时所期望的那样。如果 mode 为 OUT(或 INOUT),而且 jdbcType 为 CURSOR(也就是 Oracle 的 REFCURSOR),你必须指定一个 resultMap 来映射结果集到参数类型。要注意这里的 javaType 属性是可选的,如果左边的空白是 jdbcType 的 CURSOR 类型,它会自动地被设置为结果集。

#{department, mode=OUT, jdbcType=CURSOR, javaType=ResultSet, resultMap=departmentResultMap}
缓存

MyBatis 包含一个非常强大的查询缓存特性,默认情况下是没有开启缓存的,除了局部的 session 缓存,可以增强变现而且处理循环依赖也是必须的。要开启二级缓存,你需要在你的 SQL 映射文件中添加一行:

<cache/>

这个简单语句的效果如下:

  • 映射语句文件中的所有 select 语句将会被缓存。
  • 映射语句文件中的所有 insert,update 和 delete 语句会刷新缓存。
  • 缓存会使用 Least Recently Used(LRU,最近最少使用的)算法来收回。
  • 根据时间表(比如 no Flush Interval,没有刷新间隔), 缓存不会以任何时间顺序 来刷新。
  • 缓存会存储列表集合或对象(无论查询方法返回什么)的 1024 个引用。
  • 缓存会被视为是 read/write(可读/可写)的缓存,意味着对象检索不是共享的,而 且可以安全地被调用者修改,而不干扰其他调用者或线程所做的潜在修改。

所有的这些属性都可以通过缓存元素的属性来修改。比如:

<cache
  eviction="FIFO"
  flushInterval="60000"
  size="512"
  readOnly="true"/>

这个更高级的配置创建了一个 FIFO 缓存,并每隔 60 秒刷新,存数结果对象或列表的 512个引用,而且返回的对象被认为是只读的,因此在不同线程中的调用者之间修改它们会导致冲突。

可用的收回策略有(默认的是 LRU):

  • LRU 最近最少使用的:移除最长时间不被使用的对象。
  • FIFO 先进先出:按对象进入缓存的顺序来移除它们。
  • SOFT软引用:移除基于垃圾回收器状态和软引用规则的对象。
  • WEAK 弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象。

flushInterval(刷新间隔)可以被设置为任意的正整数,而且它们代表一个合理的毫秒形式的时间段。默认情况是不设置,也就是没有刷新间隔,缓存仅仅调用语句时刷新。

size(引用数目)可以被设置为任意正整数,要记住你缓存的对象数目和你运行环境的 可用内存资源数目。默认值是 1024。
readOnly(只读)属性可以被设置为 true 或 false。只读的缓存会给所有调用者返回缓 存对象的相同实例。因此这些对象不能被修改。这提供了很重要的性能优势。可读写的缓存 会返回缓存对象的拷贝(通过序列化) 。这会慢一些,但是安全,因此默认是 false。

自定义缓存
可以通过实现你自己的缓存或为其他第三方缓存方案创建适配器来完全覆盖缓存行为。

<cache type="com.domain.something.MyCustomCache"/>

这个示例展示了如何使用一个自定义的缓存实现。type属性指定的类必须实现org.mybatis.cache.Cache接口。这个接口是MyBatis框架中很多复杂的接口之一,但是简单给定它做什么就行。

public interface Cache {
  String getId();
  int getSize();
  void putObject(Object key, Object value);
  Object getObject(Object key);
  boolean hasKey(Object key);
  Object removeObject(Object key);
  void clear();
}

要配置你的缓存, 简单和公有的 JavaBeans 属性来配置你的缓存实现, 而且是通过 cache 元素来传递属性, 比如, 下面代码会在你的缓存实现中调用一个称为 “setCacheFile(String file)” 的方法:

<cache type="com.domain.something.MyCustomCache">
  <property name="cacheFile" value="/tmp/my-custom-cache.tmp"/>
</cache>

你可以使用所有简单类型作为JavaBeans的属性,MyBatis会进行转换。
记得缓存配置和缓存实例是绑定在 SQL 映射文件的命名空间是很重要的。因此,所有 在相同命名空间的语句正如绑定的缓存一样。 语句可以修改和缓存交互的方式, 或在语句的 语句的基础上使用两种简单的属性来完全排除它们。默认情况下,语句可以这样来配置:

<select ... flushCache="false" useCache="true"/>
<insert ... flushCache="true"/>
<update ... flushCache="true"/>
<delete ... flushCache="true"/>

因为那些是默认的,你明显不能明确地以这种方式来配置一条语句。相反,如果你想改 变默认的行为,只能设置 flushCache 和 useCache 属性。比如,在一些情况下你也许想排除 从缓存中查询特定语句结果,或者你也许想要一个查询语句来刷新缓存。相似地,你也许有 一些更新语句依靠执行而不需要刷新缓存。
参照缓存:在命名空间中共享相同的缓存配置和实例。可以使用cache-ref元素来引用另外一个缓存:

<cache-ref namespace="com.someone.application.data.SomeMapper"/>

2.3 Mybatis动态SQL

MyBatis使用一种强大的动态SQL语言,这种语言可以被用在任意的SQL映射语句中,MyBatis采用功能强大的基于OGNL的表达式来消除其他元素。

  • if
  • choose (when, otherwise)
  • trim (where, set)
  • foreach
if

动态 SQL 通常要做的事情是有条件地包含 where 子句的一部分。比如:

<select id="findActiveBlogWithTitleLike" resultType="Blog">
  SELECT * FROM BLOG 
  WHERE state = ‘ACTIVE’ 
  <if test="title != null">
    AND title like #{title}
  </if>
</select>

这条语句提供了一个可选的文本查找类型的功能。如果没有传入"title",那么所有处于"ACTIVE"状态的BLOG都会返回;反之若传入了"title",那么就会把模糊查找"title"内容的BLOG结果返回(就这个例子而言,细心的读者会发现其中的参数值是可以包含一些掩码或通配符的)。

如果想可选地通过"title"和"author"两个条件搜索该怎么办呢?首先,改变语句的名称让它更具实际意义;然后只要加入另一个条件即可。

<select id="findActiveBlogLike" resultType="Blog">
  SELECT * FROM BLOG WHERE state = ‘ACTIVE’ 
  <if test="title != null">
    AND title like #{title}
  </if>
  <if test="author != null and author.name != null">
    AND author_name like #{author.name}
  </if>
</select>
choose, when, otherwise

choose元素类似于Java中的switch语句。
还是上面的例子,但是这次变为提供了"title"就按"title"查找,提供了"author"就按"author"查找,若两者都没有提供,就返回所有符合条件的BLOG(实际情况可能是由管理员按一定策略选出BLOG列表,而不是返回大量无意义的随机结果)。

<select id="findActiveBlogLike" resultType="Blog">
  SELECT * FROM BLOG WHERE state = ‘ACTIVE’
  <choose>
    <when test="title != null">
      AND title like #{title}
    </when>
    <when test="author != null and author.name != null">
      AND author_name like #{author.name}
    </when>
    <otherwise>
      AND featured = 1
    </otherwise>
  </choose>
</select>
trim, where, set

这次我们将"ACTIVE = 1"也设置成动态的条件:

<select id="findActiveBlogLike" resultType="Blog">
  SELECT * FROM BLOG 
  WHERE 
  <if test="state != null">
    state = #{state}
  </if> 
  <if test="title != null">
    AND title like #{title}
  </if>
  <if test="author != null and author.name != null">
    AND author_name like #{author.name}
  </if>
</select>

如果这些条件没有一个能匹配上将会怎样?最终这条 SQL 会变成这样:

SELECT * FROM BLOG
WHERE

这会导致查询失败。如果仅仅第二个条件匹配又会怎样?这条 SQL 最终会是这样:

SELECT * FROM BLOG
WHERE
AND title like 'someTitle'

同样也会失败,MyBatis有一个简单的处理,可以自定义处理方式来令其正常工作。一处简单的修改就能得到想要的效果:

<select id="findActiveBlogLike" resultType="Blog">
  SELECT * FROM BLOG 
  <where> 
    <if test="state != null">
         state = #{state}
    </if> 
    <if test="title != null">
        AND title like #{title}
    </if>
    <if test="author != null and author.name != null">
        AND author_name like #{author.name}
    </if>
  </where>
</select>

where元素知道只有在一个以上的if条件有值的情况下才去插入"WHERE"子句。而且,若最后的内容是"AND"或"OR"开头的,where元素也知道如何将他们去除。
如果where元素没有按正常套路出牌,我们还是可以通过自定义trim元素来定制我们想要的功能。比如,和where元素等价的自定义trim元素为:

<trim prefix="WHERE" prefixOverrides="AND |OR ">
  ... 
</trim>

prefixOverrides属性会忽略通过管道分隔的文本序列(注意此例中的空格也是必要的)。它带来的结果就是所有在prefixOverrides属性中指定的内容将被移除,并且插入prefix属性中指定的内容。
通常做更新操作时,可以通过trim与if实现动态配置:

<!-- 更新城市信息 -->
<update id="updateCity" parameterType="pers.dong.springboot.entity.City">
	UPDATE CITY SET
	<trim suffixOverrides="," >
		<if test="id != null" >id = #{id, jdbcType=VARCHAR},</if>
		<if test="provinceId != null" >province_Id=#{provinceId},</if>
		<if test="cityName != null" >city_Name=#{cityName},</if>
		<if test="description != null" >description=#{description},</if>
	</trim>
	WHERE id = #{id, jdbcType=VARCHAR}
</update>

<!-- 插入记录 -->
<insert id="insertCity" parameterType="pers.dong.springboot.entity.City" >
	insert into CITY
	<trim prefix="(" suffix=")" suffixOverrides="," >
		<if test="id != null" >ID,</if>
		<if test="provinceId != null" >province_Id,</if>
		<if test="cityName != null" >city_Name,</if>
		<if test="description != null" >description,</if>
	</trim>
	values
	<trim prefix="(" suffix=")" suffixOverrides="," >
		<if test="id != null" >#{id},</if>
		<if test="provinceId != null" > #{provinceId},</if>
		<if test="cityName != null" >#{cityName},</if>
		<if test="description != null" >#{description},</if>
	</trim>
</insert>

类似的用于动态更新语句的解决方案叫做set。set元素可以被用于动态包含需要更新的列,而舍去其他的。比如:

<!-- 更新城市信息 -->
<update id="updateCity1">
	update CITY
	<set>
		<if test="id != null" >id = #{id, jdbcType=VARCHAR},</if>
		<if test="provinceId != null" >province_Id=#{provinceId},</if>
		<if test="cityName != null" >city_Name=#{cityName},</if>
		<if test="description != null" >description=#{description},</if>
	</set>
	where id=#{id}
</update>

这里,set元素会动态前置SET关键字,同时也会消除无关的逗号,因为用了条件语句之后很可能就会在生成的赋值语句的后面留下这些逗号。等价与:

<trim prefix="SET" suffixOverrides=",">
  ...
</trim>
foreach

动态 SQL 的另外一个常用的必要操作是需要对一个集合进行遍历,通常是在构建 IN 条件语句的时候。比如:

<select id="selectPostIn" resultType="domain.blog.Post">
  SELECT *
  FROM POST P
  WHERE ID in
  <foreach item="item" index="index" collection="list"
      open="(" separator="," close=")">
        #{item}
  </foreach>
</select>

foreach 元素的功能是非常强大的,它允许你指定一个集合,声明可以用在元素体内的集合项和索引变量。它也允许你指定开闭匹配的字符串以及在迭代中间放置分隔符。这个元素是很智能的,因此它不会偶然地附加多余的分隔符。

注意 你可以将一个List实例或者数组作为参数对象传给MyBatis,当你这么做的时候,MyBatis 会自动将它包装在一个Map中并以名称为键。List实例将会以"list"作为键,而数组实例的键将是"array"。

bind

bind元素可以从OGNL表达式中创建一个变量并将其绑定到上下文。比如:

<select id="selectBlogsLike" resultType="Blog">
  <bind name="pattern" value="'%' + _parameter.getTitle() + '%'" />
  SELECT * FROM BLOG
  WHERE title LIKE #{pattern}
</select>
Multi-db vendor support

一个配置了"_databaseId"变量的databaseIdProvider对于动态代码来说是可用的,这样就可以根据不同的数据库厂商构建特定的语句。比如下面的例子:

<insert id="insert">
  <selectKey keyProperty="id" resultType="int" order="BEFORE">
    <if test="_databaseId == 'oracle'">
      select seq_users.nextval from dual
    </if>
    <if test="_databaseId == 'db2'">
      select nextval for seq_users from sysibm.sysdummy1"
    </if>
  </selectKey>
  insert into users values (#{id}, #{name})
</insert>
动态 SQL 中可插拔的脚本语言

MyBatis 从 3.2 开始支持可插拔的脚本语言,因此你可以在插入一种语言的驱动(language driver)之后来写基于这种语言的动态 SQL 查询。
可以通过实现下面接口的方式来插入一种语言:

public interface LanguageDriver {
  ParameterHandler createParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql);
  SqlSource createSqlSource(Configuration configuration, XNode script, Class parameterType);
  SqlSource createSqlSource(Configuration configuration, String script, Class parameterType);
}

一旦有了自定义的语言驱动,你就可以在mybatis-config.xml文件中将它设置为默认语言:

<typeAliases>
  <typeAlias type="org.sample.MyLanguageDriver" alias="myLanguage"/>
</typeAliases>
<settings>
  <setting name="defaultScriptingLanguage" value="myLanguage"/>
</settings>

除了设置默认语言,你也可以针对特殊的语句指定特定语言,这可以通过如下的lang属性来完成:

<select id="selectBlog" lang="myLanguage">
  SELECT * FROM BLOG
</select>

或者在你正在使用的映射中加上注解@Lang来完成:

public interface Mapper {
  @Lang(MyLanguageDriver.class)
  @Select("SELECT * FROM BLOG")
  List selectBlog();
}

2.4 MyBatis Java API

2.5 MyBatis SQL语句构建器

2.6 MyBatis日志

2.7 MyBatis知识点

在MyBatis映射文件中Map表示映射,Type表示Java类型。
resultMap与resultType
两者都是表示查询结果集java对象之间的一种关系,处理查询结果集,映射到java对象。
resultMap表示将查询结果集中的列一一映射到bean对象的各个属性,映射的查询结果集中的列标签可以根据需要灵活变化,并且,在映射关系中,还可以通过typeHandler设置实现查询结果值的类型转换:

<resultMap id="BaseResultMap" type="pers.dong.springboot.entity.City">
	<result column="id" property="id" />
	<result column="province_id" property="provinceId" />
	<result column="city_name" property="cityName" />
	<result column="description" property="description" />
</resultMap>
<sql id="Base_Column_List">
	id, province_id, city_name, description
</sql>

<select id="findByName" resultMap="BaseResultMap" parameterType="java.lang.String">
	select
	<include refid="Base_Column_List" />
	from city
	where city_name = #{cityName}
</select>

resultType 表示的是bean中的对象类。个人理解有两种情况可用resultType,第一种是搜索只是返回一个值,比如说String,或者是int,那么直接用resultType就行了;第二种是查询结果集中的属性和bean对象类中的属性是一一对应的,此时大小写不敏感,但是有限制。
第一种:

<!-- 查询城市数量 -->
<select id="queryCityCount" resultType="java.lang.Integer" >
select count(1) from city
</select>

第二种:

<sql id="Base_Column_List">
	id, province_id, city_name, description
</sql>

<select id="findByName" resultType="pers.dong.springboot.entity.City" parameterType="jpers.dong.springboot.entity.City">
	select
	<include refid="Base_Column_List" />
	from city
	where city_name = #{cityname}
</select>

ParameterMap(不推荐) 与 parameterType
ParameterMap:与resultMap方法类似,表示将查询结果集中列值的类型一一映射到java对象属性的类型上。
parameterType直接将查询结果列值类型自动对应到java对象属性类型上,不再配置映射关系一一对应。
总之:当实体类中的属性和数据库中的字段对应是,我们使用resultType和parameterType就可以完成CRUD;当实体类中的属性和数据库中的字段不对应时,就要用resultMap和parameterMap了。

**#{}和KaTeX parse error: Expected 'EOF', got '#' at position 19: …使用**</font> #̲{}具有预编译效果,{}不具有预编译效果。
通常是能用#不用$,但是如果需要动态拼接表名(select * from ${tablename})或者动态拼接排序字段( select * from table order by u s e r n a m e ) 使 用 {username} )使用 username使

2.8 MyBatis实例

POM文件中增加Mybatis依赖:

<!-- Spring Boot Mybatis 依赖 -->
<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>${mybatis-spring-boot}</version>
</dependency>

由于需要操作数据库,因此需要增加MySQL驱动依赖:

<!-- MySQL 连接驱动依赖 -->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>${mysql-connector}</version>
</dependency>

在application.properties文件中配置数据源以及MyBatis:

## 数据源配置
spring.datasource.url=jdbc:mysql://localhost:3306/springbootdb?useUnicode=true&characterEncoding=utf8
spring.datasource.username=root
spring.datasource.password=***
spring.datasource.driver-class-name=com.mysql.jdbc.Driver

## MyBatis 配置
mybatis.typeAliasesPackagemybatis.typeAliasesPackage=org.spring.springboot.entity
mybatis.mapperLocationrsdcs=classpath:mapper/*.xml

编写代码及启动类,主要介绍启动类:

@MapperScan("pers.dong.springboot.dao")
public class Application {
    public static void main(String[] args) {
        // 程序启动入口
        // 启动嵌入式的 Tomcat 并初始化 Spring 环境及其各 Spring 组件
        SpringApplication.run(Application.class,args);
    }
}

@MapperScan注解
当我们定义一些类时,并没有在该类上定义类似@Service或者@Controller之类的注解,需要被Spring管理需要两种方式:
第一种方法是使用@Mapper注解:

@Mapper
public class Demo{
    ...
}

上述方法直接在Mapper类上面添加注解@Mapper,这种方式要求每一个mapper类都需要添加此注解,麻烦。
第二种方法是使用@MapperScan注解:
通过使用@MapperScan可以指定要扫描的Mapper类的包的路径,比如:

@MapperScan("org.spring.springboot.dao")

如果由多个需要扫描的Mapper类的包时:

@MapperScan({"org.spring.springboot.dao.demo","org.spring.springboot.dao.demo1"})

也可用如下方式:

@MapperScan("org.spring.springboot.dao.*")

九 Mybatis Annotation

1、 简述

注解(Annotation)相当于一种标记,在程序中加入注解就等于为程序打上某种标记,没有加,则等于没有任何标记,以后,javac编译器、开发工具和其他程序可以通过反射来了解你的类及各种元素上有无何种标记,看你的程序有什么标记,就去干相应的事,标记可以加在包、类、属性、方法,方法的参数以及局部变量上。
在这里插入图片描述

2、Annotation详解

2.1 Annotation架构:

在这里插入图片描述

1个Annotation 和 1个RetentionPolicy关联。可以理解为:每1个Annotation对象,都会有唯一的RetentionPolicy属性。
1个Annotation 和 1~n个ElementType关联。可以理解为:对于每1个Annotation对象,可以有若干个ElementType属性。
Annotation 有许多实现类,包括:Deprecated, Documented, Inherited, Override等等。
Annotation 的每一个实现类,都“和1个RetentionPolicy关联”并且“和1~n个ElementType关联”。

2.2 Annotation组成:

java annotation 的组成中,有3个非常重要的主干类:Annotation、ElementType和RetentionPolicy。

1)Annotation
package java.lang.annotation;
public interface Annotation {

    boolean equals(Object obj);

    int hashCode();

    String toString();

    Class<? extends Annotation> annotationType();
}

Annotation是个接口。 “每1个Annotation” 都与 “1个RetentionPolicy”关联,并且与 “1~n个ElementType”关联。可以通俗的理解为:每1个Annotation对象,都会有唯一的RetentionPolicy属性;至于ElementType属性,则有1~n个。

2)ElementType
package java.lang.annotation;
public enum ElementType {
    TYPE,               /* 类、接口(包括注释类型)或枚举声明  */
    FIELD,              /* 字段声明(包括枚举常量)  */
    METHOD,             /* 方法声明  */
    PARAMETER,          /* 参数声明  */
    CONSTRUCTOR,        /* 构造方法声明  */
    LOCAL_VARIABLE,     /* 局部变量声明  */
    ANNOTATION_TYPE,    /* 注释类型声明  */
    PACKAGE             /* 包声明  */
}

ElementType 是Enum枚举类型,它用来指定Annotation的类型。“每1个Annotation” 都与 “1~n个ElementType”关联。当Annotation与某个ElementType关联时,就意味着:Annotation有了某种用途。
例如,若一个Annotation对象是METHOD类型,则该Annotation只能用来修饰方法。

3)RetentionPolicy
package java.lang.annotation;
public enum RetentionPolicy {
    SOURCE,            /* Annotation信息仅存在于编译器处理期间,编译器处理完之后就没有该Annotation信息了  */
    CLASS,             /* 编译器将Annotation存储于类对应的.class文件中。默认行为  */
    RUNTIME            /* 编译器将Annotation存储于class文件中,并且可由JVM读入 */
}

RetentionPolicy 是Enum枚举类型,它用来指定Annotation的策略。通俗点说,就是不同RetentionPolicy类型的Annotation的作用域不同。“每1个Annotation” 都与 “1个RetentionPolicy”关联。
a) 若Annotation的类型为SOURCE,则意味着:Annotation仅存在于编译器处理期间,编译器处理完之后,该Annotation就没用了。
例如,“ @Override ”标志就是一个Annotation。当它修饰一个方法的时候,就意味着该方法覆盖父类的方法;并且在编译期间会进行语法检查!编译器处理完后,“@Override”就没有任何作用了。
b) 若Annotation的类型为CLASS,则意味着:编译器将Annotation存储于类对应的.class文件中,它是Annotation的默认行为。
c) 若Annotation的类型为RUNTIME,则意味着:编译器将Annotation存储于class文件中,并且可由JVM读入。

2.3 java自带的Annotation

1)Annotation通用定义
@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation1 {
}

上文定义一个Annotation,它的名字是MyAnnotation1。定义了MyAnnotation1之后,我们可以在代码中通过“@MyAnnotation1”来使用它。其它的,@Documented, @Target, @Retention, @interface都是来修饰MyAnnotation1。
a)@interface
使用@interface定义注解时,意味着它实现了java.lang.annotation.Annotation接口,即该注解就是一个Annotation。定义Annotation时,@interface是必须的。
注意:它和我们通常的implemented实现接口的方法不同。Annotation接口的实现细节都由编译器完成。通过@interface定义注解后,该注解不能继承其他的注解或接口。

b)@Documented
类和方法的Annotation在缺省情况下是不出现在javadoc中的。如果使用@Documented修饰该Annotation,则表示它可以出现在javadoc中。定义Annotation时,@Documented可有可无;若没有定义,则Annotation不会出现在javadoc中。

c)@Target(ElementType.TYPE)
前面我们说过,ElementType是Annotation的类型属性。而@Target的作用,就是来指定Annotation的类型属性。@Target(ElementType.TYPE)的意思就是指定该Annotation的类型是ElementType.TYPE。这就意味着,MyAnnotation1是来修饰“类、接口(包括注释类型)或枚举声明”的注解。定义Annotation时,@Target可有可无。若有@Target,则该Annotation只能用于它所指定的地方;若没有@Target,则该Annotation可以用于任何地方。

d)@Retention(RetentionPolicy.RUNTIME)
前面我们说过,RetentionPolicy是Annotation的策略属性,而@Retention的作用,就是指定Annotation的策略属性。@Retention(RetentionPolicy.RUNTIME)的意思就是指定该Annotation的策略是RetentionPolicy.RUNTIME。这就意味着,编译器会将该Annotation信息保留在.class文件中,并且能被虚拟机读取。定义Annotation时,@Retention可有可无。若没有@Retention,则默认是RetentionPolicy.CLASS。
综上:@interface用来声明Annotation,@Documented用来表示该Annotation是否会出现在javadoc中, @Target用来指定Annotation的类型,@Retention用来指定Annotation的策略。

2)java自带的Annotation

java 常用的Annotation:
@Deprecated所标注内容,不再被建议使用。
@Override只能标注方法,表示该方法覆盖父类中的方法。
@Documented所标注内容,可以出现在javadoc中。
@Inherited只能被用来标注“Annotation类型”,它所标注的Annotation具有继承性。
@Retention只能被用来标注“Annotation类型”,而且它被用来指定Annotation的RetentionPolicy属性。
@Target只能被用来标注“Annotation类型”,而且它被用来指定Annotation的ElementType属性。
@SuppressWarnings所标注内容产生的警告,编译器会对这些警告保持静默。

由于“@Deprecated和@Override”类似,“@Documented, @Inherited, @Retention, @Target”类似;下面,我们只对@Deprecated, @Inherited, @SuppressWarnings 这3个Annotation进行说明。

(a)@Deprecated

定义如下:

@Documented
@Retention(RetentionPolicy.RUNTIME)
public @interface Deprecated {
}

说明:
@interface它的用来修饰Deprecated,意味着Deprecated实现了java.lang.annotation.Annotation接口;即Deprecated就是一个注解。
@Documented它的作用是说明该注解能出现在javadoc中。
@Retention(RetentionPolicy.RUNTIME) 它的作用是指定Deprecated的策略是RetentionPolicy.RUNTIME。这就意味着,编译器会将Deprecated的信息保留在.class文件中,并且能被虚拟机读取。
@Deprecated所标注内容,不再被建议使用。

例如,若某个方法被 @Deprecated标注,则该方法不再被建议使用。如果有开发人员试图使用或重写被@Deprecated标示的方法,编译器会给相应的提示信息。示例如下:

public class DeprecatedTest {
    // @Deprecated 修饰 getString1(),表示 它是建议不被使用的函数
    @Deprecated
    private static void getString1(){
        System.out.println("Deprecated Method");
    }
    private static void getString2(){
        System.out.println("Normal Method");
    }
    // Date是日期/时间类。java已经不建议使用该类了
    private static void testDate() {
        Date date = new Date(113, 8, 25);
        System.out.println(date.getYear());
    }
    // Calendar是日期/时间类。java建议使用Calendar取代Date表示“日期/时间”
    private static void testCalendar() {
        Calendar cal = Calendar.getInstance();
        System.out.println(cal.get(Calendar.YEAR));
    }
    public static void main(String[] args) {
        getString1();
        getString2();
        testDate();
        testCalendar();
    }
}
(b)@Inherited

@Inherited 的定义如下:

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Inherited {
}

@interface它的用来修饰Inherited,意味着Inherited实现了java.lang.annotation.Annotation接口;即Inherited就是一个注解。
@Documented它的作用是说明该注解能出现在javadoc中。
@Retention(RetentionPolicy.RUNTIME)它的作用是指定Inherited的策略是RetentionPolicy.RUNTIME。这就意味着,编译器会将Inherited的信息保留在.class文件中,并且能被虚拟机读取。
@Target(ElementType.ANNOTATION_TYPE)它的作用是指定Inherited的类型是ANNOTATION_TYPE。这就意味着,@Inherited只能被用来标注“Annotation类型”。
@Inherited的含义是,它所标注的Annotation将具有继承性。
假设,我们定义了某个Annotaion,它的名称是MyAnnotation,并且MyAnnotation被标注为@Inherited。现在,某个类Base使用了MyAnnotation,则Base具有了“具有了注解MyAnnotation”;现在,Sub继承了Base,由于MyAnnotation是@Inherited的(具有继承性),所以,Sub也“具有了注解MyAnnotation”。
@Inherited的使用示例:

/**
 * 自定义的Annotation。
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@interface Inheritable
{
}

@Inheritable
class InheritableFather
{
    public InheritableFather() {
        // InheritableBase是否具有 Inheritable Annotation
        System.out.println("InheritableFather:"+InheritableFather.class.isAnnotationPresent(Inheritable.class));
    }
}

/**
 * @Author: dong
 * @Project: springboot
 * @Description: InheritableSon 类只是继承于 InheritableFather
 * @Date: Created in 13:21 2018-12-25
 * @Modified By:
 */
public class InheritableSon  extends InheritableFather {
    public InheritableSon() {
        super();    // 调用父类的构造函数
        // InheritableSon类是否具有 Inheritable Annotation
        System.out.println("InheritableSon:"+InheritableSon.class.isAnnotationPresent(Inheritable.class));
    }

    public static void main(String[] args)
    {
        InheritableSon is = new InheritableSon();
    }
}

结果:

InheritableFather:true
InheritableSon:true

综上对比得出:当注解Inheritable被@Inherited标注时,它具有继承性。否则,没有继承性。

(c)@SuppressWarnings

@SuppressWarnings 的定义如下:

@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.SOURCE)
public @interface SuppressWarnings {

    String[] value();

}

@interface它的用来修饰SuppressWarnings,意味着SuppressWarnings实现了java.lang.annotation.Annotation接口;即SuppressWarnings就是一个注解。
@Retention(RetentionPolicy.SOURCE)它的作用是指定SuppressWarnings的策略是RetentionPolicy.SOURCE。这就意味着,SuppressWarnings信息仅存在于编译器处理期间,编译器处理完之后SuppressWarnings就没有作用了。
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})它的作用是指定SuppressWarnings的类型同时包括TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE。
TYPE意味着,它能标注“类、接口(包括注释类型)或枚举声明”。
FIELD意味着,它能标注“字段声明”。
METHOD意味着,它能标注“方法”。
PARAMETER意味着,它能标注“参数”。
CONSTRUCTOR意味着,它能标注“构造方法”。
LOCAL_VARIABLE意味着,它能标注“局部变量”。
String[] value()意味着,SuppressWarnings能指定参数
SuppressWarnings的作用是,让编译器对“它所标注的内容”的某些警告保持静默。例如,"@SuppressWarnings(value={“deprecation”, “unchecked”})" 表示对“它所标注的内容”中的“SuppressWarnings不再建议使用警告”和“未检查的转换时的警告”保持沉默。示例如下:

public class SuppressWarningTest {
    //@SuppressWarnings(value={"deprecation"})
    public static void doSomething(){
        Date date = new Date(113, 8, 26);
        System.out.println(date);
    }

    public static void main(String[] args) {
        doSomething();
    }
}

因为Date属于java不再建议使用的类。如果不存在@SuppressWarnings(value={“deprecation”}),当调用Date的API时,会产生警告。如果存在编译器对“调用Date的API产生的警告”保持沉默。
SuppressWarnings 常用的关键字的表格:

deprecation  -- 使用了不赞成使用的类或方法时的警告
unchecked    -- 执行了未检查的转换时的警告,例如当使用集合时没有用泛型 (Generics) 来指定集合保存的类型。
fallthrough  -- 当 Switch 程序块直接通往下一种情况而没有 Break 时的警告。
path         -- 在类路径、源文件路径等中有不存在的路径时的警告。
serial       -- 当在可序列化的类上缺少 serialVersionUID 定义时的警告。
finally      -- 任何 finally 子句不能正常完成时的警告。
all          -- 关于以上所有情况的警告。

2.4 Annotation作用:

1) 编译检查

Annotation具有“让编译器进行编译检查的作用”。
@SuppressWarnings, @Deprecated和@Override都具有编译检查作用,@SuppressWarnings和@Deprecated上文中有描述,若某个方法被@Override的标注,则意味着该方法会覆盖父类中的同名方法。如果有方法被@Override标示,但父类中却没有“被@Override标注”的同名方法,则编译器会报错。

public class OverrideTest {
    /**
     * toString() 在java.lang.Object中定义;
     * 因此,这里用 @Override 标注是对的。
     */
    @Override
    public String toString(){
        return "Override toString";
    }

    /**
     * getString() 没有在OverrideTest的任何父类中定义;
     * 但是,这里却用 @Override 标注,因此会产生编译错误!
     */
    @Override
    public String getString(){
        return "get toString";
    }

    public static void main(String[] args) {
    }
}

上述代码可以发现“getString()”函数会报错。这是因为“getString()被@Override所标注,但在OverrideTest的任何父类中都没有定义getString1()函数”。“将getString() 上面的@Override注释掉”,即可解决该错误。

2) 在反射中使用Annotation

在反射的Class, Method, Field等函数中,有许多于Annotation相关的接口。

public class AnnotationTest {
    public static void main(String[] args) throws Exception {

        // 新建Person
        Person person = new Person();
        // 获取Person的Class实例
        Class<Person> c = Person.class;
        // 获取 somebody() 方法的Method实例
        Method mSomebody = c.getMethod("somebody", new Class[]{String.class, int.class});
        // 执行该方法
        mSomebody.invoke(person, new Object[]{"lily", 18});
        iteratorAnnotations(mSomebody);


        // 获取 somebody() 方法的Method实例
        Method mEmpty = c.getMethod("empty", new Class[]{});
        // 执行该方法
        mEmpty.invoke(person, new Object[]{});
        iteratorAnnotations(mEmpty);
    }

    public static void iteratorAnnotations(Method method) {

        // 判断 somebody() 方法是否包含MyAnnotation注解
        if(method.isAnnotationPresent(MyAnnotation.class)){
            // 获取该方法的MyAnnotation注解实例
            MyAnnotation myAnnotation = method.getAnnotation(MyAnnotation.class);
            // 获取 myAnnotation的值,并打印出来
            String[] values = myAnnotation.value();
            for (String str:values)
                System.out.printf(str+", ");
            System.out.println();
        }

        // 获取方法上的所有注解,并打印出来
        Annotation[] annotations = method.getAnnotations();
        for(Annotation annotation : annotations){
            System.out.println(annotation);
        }
    }
}


/**
 * Annotation在反射函数中的使用示例
 */
@Retention(RetentionPolicy.RUNTIME)
@interface MyAnnotation {
    String[] value() default "unknown";
}

/**
 * Person类。它会使用MyAnnotation注解。
 */
class Person {

    /**
     * empty()方法同时被 "@Deprecated" 和 “@MyAnnotation(value={"a","b"})”所标注
     * (01) @Deprecated,意味着empty()方法,不再被建议使用
     * (02) @MyAnnotation, 意味着empty() 方法对应的MyAnnotation的value值是默认值"unknown"
     */
    @MyAnnotation
    @Deprecated
    public void empty(){
        System.out.println("\nempty");
    }

    /**
     * sombody() 被 @MyAnnotation(value={"girl","boy"}) 所标注,
     * @MyAnnotation(value={"girl","boy"}), 意味着MyAnnotation的value值是{"girl","boy"}
     */
    @MyAnnotation(value={"girl","boy"})
    public void somebody(String name, int age){
        System.out.println("\nsomebody: "+name+", "+age);
    }
}

结果如下:

somebody: lily, 18
girl, boy, 
@pers.dong.springboot.MyAnnotation(value=[girl, boy])

empty
unknown, 
@pers.dong.springboot.MyAnnotation(value=[unknown])
@java.lang.Deprecated()
3)根据Annotation生成帮助文档

通过给Annotation注解加上@Documented标签,能使该Annotation标签出现在javadoc中。

十 Spring Boot 整合 Mybatis 实现 Druid 多数据源配置

1、 简述

更新日志

参考:

https://www.w3cschool.cn/mybatis/f4uw1ilx.html
http://www.ruanyifeng.com/blog/2018/10/restful-api-best-practices.html
https://blog.florimondmanca.com/restful-api-design-13-best-practices-to-make-your-users-happy
https://www.cnblogs.com/kuoAT/p/7121753.html
http://www.cnblogs.com/skywang12345/p/3344137.html

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值