当前位置 : 首页 » 文章分类 :  开发  »  Spring Boot 学习笔记

Spring Boot 学习笔记

Spring Boot 学习笔记


spring boot 2.x 中jedis改为lettuce

随着Spring Boot2.x的到来,支持的组件越来越丰富,也越来越成熟,其中对Redis的支持不仅仅是丰富了它的API,更是替换掉底层Jedis的依赖,取而代之换成了Lettuce(生菜)

Lettuce
Lettuce 和 Jedis 的都是连接Redis Server的客户端程序。Jedis在实现上是直连redis server,多线程环境下非线程安全,除非使用连接池,为每个Jedis实例增加物理连接。Lettuce基于Netty的连接实例(StatefulRedisConnection),可以在多个线程间并发访问,且线程安全,满足多线程环境下的并发访问,同时它是可伸缩的设计,一个连接实例不够的情况也可以按需增加连接实例。

一起来学SpringBoot | 第九篇:整合Lettuce Redis
https://blog.battcn.com/2018/05/11/springboot/v2-nosql-redis/

RedisTemplate中的executePipelined

// 创建结果集
Map<Long, Long> result = Maps.newLinkedHashMap();

// 加入从redis中没取到
// values为空时从数据库查
userIds.forEach(u -> result.put(u, getAccountIdByUserId(u)));

// 查完批量放入redis(其实直接用mset一条命令即可,不用管道)
executorService.execute(() -> {
    try {
        // 批量放入redis
        redisClient.executePipelined(redisConnection -> {
            RedisSerializer<String> serializer = new StringRedisSerializer();
            result.forEach((userId, accountId) -> {
                if (accountId != null) {
                    String key = Joiner.on("-").join(USER_ID_PRE, userId);
                    redisConnection.set(serializer.serialize(key), serializer.serialize(String.valueOf(accountId)));
                }
            });
            return null;
        });
    } catch (RedisConnectionFailureException e) {
        logger.error("[Redis] Exception catched when set mapping of user id and account id", e);
    }
});

redis pipeline简介
https://www.jianshu.com/p/a8e33e058518


NamedThreadLocal

org.springframework.core.NamedThreadLocal<T>
Spring提供的一个命名的ThreadLocal实现。

package org.springframework.core;

import org.springframework.util.Assert;

public class NamedThreadLocal<T> extends ThreadLocal<T> {
    private final String name;

    public NamedThreadLocal(String name) {
        Assert.hasText(name, "Name must not be empty");
        this.name = name;
    }

    public String toString() {
        return this.name;
    }
}

记录请求处理时间

实现分析:
1、在进入处理器之前记录开始时间,即在拦截器的preHandle记录开始时间;
2、在结束请求处理之后记录结束时间,即在拦截器的afterCompletion记录结束实现,并用结束时间-开始时间得到这次请求的处理时间。

问题:
我们的拦截器是单例,因此不管用户请求多少次都只有一个拦截器实现,即线程不安全,那我们应该怎么记录时间呢?
解决方案是使用ThreadLocal,它是线程绑定的变量,提供线程局部变量(一个线程一个ThreadLocal,A线程的ThreadLocal只能看到A线程的ThreadLocal,不能看到B线程的ThreadLocal)

public class StopWatchHandlerInterceptor extends HandlerInterceptorAdapter {
    private NamedThreadLocal<Long>  startTimeThreadLocal = new NamedThreadLocal<Long>("StopWatch-StartTime");

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        long beginTime = System.currentTimeMillis();//1、开始时间
        startTimeThreadLocal.set(beginTime);//线程绑定变量(该数据只有当前请求的线程可见)
        return true;//继续流程
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        long endTime = System.currentTimeMillis();//2、结束时间
        long beginTime = startTimeThreadLocal.get();//得到线程绑定的局部变量(开始时间)
        long consumeTime = endTime - beginTime;//3、消耗的时间
        if(consumeTime > 500) {//此处认为处理时间超过500毫秒的请求为慢请求
            //TODO 记录到日志文件
            System.out.println(String.format("%s consume %d millis", request.getRequestURI(), consumeTime));
        }
    }
}

Spring MVC学习(五)——-处理器拦截器详解
http://blog.51cto.com/3001448/1205771


@Value注解默认值

变量名后加冒号加默认值,语法为:

@Value("${some.key:my default value}")
private String stringWithDefaultValue;

例如:

// int默认值
@Value("${datasource.maximum-pool-size:100}")
private int mysqlMaximumPoolSize;

// string默认值
@Value("${some.key:my default value}")
private String stringWithDefaultValue;

// 默认空字符串
@Value("${some.key:})"
private String stringWithBlankDefaultValue;

// boolean默认值
@Value("${some.key:true}")
private boolean booleanWithDefaultValue;

数组默认值,可自动解析逗号分隔字符串为Array数组

@Value("${some.key:one,two,three}")
private String[] stringArrayWithDefaults;

@Value("${some.key:1,2,3}")
private int[] intArrayWithDefaults;

Using Spring @Value with Defaults
https://www.baeldung.com/spring-value-defaults


SpringBoot国际化

spring boot默认就支持国际化的,而且不需要你过多的做什么配置,只需要在resources/下定义国际化配置文件即可,注意名称必须以messages开头。

定义如下几个文件:
messages.properties (默认,当找不到语言的配置的时候,使用该文件进行展示)。
messages_zh_CN.properties(中文)
messages_en_US.properties(英文)

不同语言环境下看到不同内容如何实现的?

为了让web应用程序支持国际化,必须识别每个用户的首选区域,并根据这个区域显示内容。在Spring MVC应用程序中,用户的区域是通过区域解析器来识别的,它必须是实现LocaleResolver接口。Spring MVC提供了几个LocaleResolver实现,让你可以按照不同的条件来解析区域。除此之外,你还可以实现这个接口创建自己的区域解析器。如果没有做特殊的处理的话,Spring 采用的默认区域解析器是AcceptHeaderLocaleResolver。它通过检验HTTP请求的头部信息accept-language来解析区域。这个头部是由用户的wb浏览器底层根据底层操作系统的区域设置进行设定的。请注意,这个区域解析器无法改变用户的区域,因为它无法修改用户操作系统的区域设置。

58 Spring Boot国际化(i18n)【从零开始学Spring Boot】
http://412887952-qq-com.iteye.com/blog/2312274


@RequestMapping默认method

缺省 method 的值

@RequestMapping("/enter")
public String enter(){
    return "example_enter_page";
}

method 若是缺省没指定,并不是说它默认只处理 GET 方式的请求,而是它可以处理任何方式的 http method 类型的请求。
指定 method 是为了细化映射 ( 缩小处理方法的映射范围 ),在 method 没有指定的情况下,它的映射范围是最大的。

@RequestParam和@PathVariable

@RequestParam 和 @PathVariable 注解是用于从request中接收请求的,两个都可以接收参数,关键点不同的是@RequestParam 是从request里面拿取值,而 @PathVariable 是从一个URI模板里面来填充
http://localhost:8080/springmvc/hello/101?param1=10&param2=20
根据上面的这个URL,你可以用这样的方式来进行获取

public String getDetails(
    @RequestParam(value="param1", required=true) String param1,
        @RequestParam(value="param2", required=false) String param2){
...
}

@RequestParam默认值

public UdsHttpResponse queryLeadsFollowDetailsAPPV1(@PathVariable("userId") Long userId,
        @RequestParam(name = "offset", required = false, defaultValue = "0") Long offset,
        @RequestParam(name = "count", required = false, defaultValue = "10") Integer count) {
  ... ...
}

@RequestParam 支持下面四种参数
defaultValue 如果本次请求没有携带这个参数,或者参数为空,那么就会启用默认值
name 绑定本次参数的名称,要跟URL上面的一样
required 这个参数是不是必须的
value 跟name一样的作用,是name属性的一个别名

@PathVariable
这个注解能够识别URL里面的一个模板,我们看下面的一个URL
http://localhost:8080/springmvc/hello/101?param1=10&param2=20
上面的一个url你可以这样写:

@RequestMapping("/hello/{id}")
    public String getDetails(@PathVariable(value="id") String id,
    @RequestParam(value="param1", required=true) String param1,
    @RequestParam(value="param2", required=false) String param2){
.......
}

@RequestParam,@PathParam,@PathVariable等注解区别
https://blog.csdn.net/u011410529/article/details/66974974


自定义注解

@AliasFor给注解参数起别名

在Spring的众多注解中,经常会发现很多注解的不同属性起着相同的作用,比如@RequestMapping的value属性和path属性,这就需要做一些基本的限制,比如value和path的值不能冲突,比如任意设置value或者设置path属性的值,都能够通过另一个属性来获取值等等。为了统一处理这些情况,Spring创建了@AliasFor标签。

Spring中的@AliasFor标签
https://www.jianshu.com/p/869ed7037833

自定义注解数组参数

package com.nio.uds.common.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
 * 监控回调方法异常并通过approach推送
 */
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface CallbackMonitor {
    String[] emails() default {"si.ma@nio.com"};
    String[] mobiles() default {"86-13671151121"};
}

使用:
@CallbackMonitor(emails = {“si.ma@nio.com“,”450649025qq.com”}, mobiles = {“86-13671151121”})

注解参数不能设置为变量

@CallbackMonitor(emails = @Value(“${uds.callback.emails}”))
这样会报错,注解参数中只能赋值常量
kafka中实现的注解参数读变量其实时发现有${}符时再从配置文件中读取

How to supply value to an annotation from a Constant java
https://stackoverflow.com/questions/2065937/how-to-supply-value-to-an-annotation-from-a-constant-java?utm_medium=organic&utm_source=google_rich_qa&utm_campaign=google_rich_qa


@Conditional条件Bean

除了自己自定义Condition之外,Spring还提供了很多Condition给我们用
@ConditionalOnBean(仅仅在当前上下文中存在某个对象时,才会实例化一个Bean)
@ConditionalOnClass(某个class位于类路径上,才会实例化一个Bean)
@ConditionalOnExpression(当表达式为true的时候,才会实例化一个Bean)
@ConditionalOnMissingBean(仅仅在当前上下文中不存在某个对象时,才会实例化一个Bean)
@ConditionalOnMissingClass(某个class类路径上不存在的时候,才会实例化一个Bean)
@ConditionalOnNotWebApplication(不是web应用)

Spring4.x高级话题(四):条件注解@Conditional
https://juejin.im/entry/59f67c01518825603a37e400

匹配上下文中是否有某个注解

只能判断@Conditional注解所在的类/方法上是否存在其他注解,不能在整个spring上下文中判断

Spring Annotation based Condition Example(这篇文字特别好,简介清晰)
http://www.javarticles.com/2016/01/spring-annotation-based-condition-example.html

Spring高级装配之条件化创建Bean
https://www.jianshu.com/p/0761ba179625

@ConditionalOnProperty根据配置变量决定是否创建bean

Spring boot中有个注解@ConditionalOnProperty,这个注解能够控制某个configuration是否生效。具体操作是通过其两个属性name以及havingValue来实现的,其中name用来从application.properties中读取某个属性值,如果该值为空,则返回false;如果值不为空,则将该值与havingValue指定的值进行比较,如果一样则返回true;否则返回false。如果返回值为false,则该configuration不生效;为true则生效。

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
@Documented
@Conditional({OnPropertyCondition.class})
public @interface ConditionalOnProperty {
    String[] value() default {}; //数组,获取对应property名称的值,与name不可同时使用
    String prefix() default "";//property名称的前缀,可有可无
    String[] name() default {};//数组,property完整名称或部分名称(可与prefix组合使用,组成完整的property名称),与value不可同时使用
    String havingValue() default "";//可与name组合使用,比较获取到的属性值与havingValue给定的值是否相同,相同才加载配置
    boolean matchIfMissing() default false;//缺少该property时是否可以加载。如果为true,没有该property也会正常加载;反之报错
    boolean relaxedNames() default true;//是否可以松散匹配,至今不知道怎么使用的
}

配置Spring Boot通过@ConditionalOnProperty来控制Configuration是否生效
https://blog.csdn.net/dalangzhonghangxing/article/details/78420057

ConditionalOnProperty的使用
https://blog.csdn.net/u010002184/article/details/79353696


@Around比@ExceptionHandler先拦截到异常

@ControllerAdvice加@ExceptionHandler进行异常统一处理

@ControllerAdvice,是spring3.2提供的新注解,从名字上可以看出大体意思是控制器增强。
该注解使用@Component注解,这样的话当我们使用context:component-scan扫描时也能扫描到

即把@ControllerAdvice注解内部使用@ExceptionHandler、@InitBinder、@ModelAttribute注解的方法应用到所有的 @RequestMapping注解的方法。非常简单,不过只有当使用@ExceptionHandler最有用,另外两个用处不大。

@ExceptionHandler指明要捕获的异常
@ExceptionHandler注解的方法就当做一个处理异常的Controller,可以返回自定义的错误response

/**
 * 处理UdsException
 * @param e
 * @param httpServletRequest
 * @return
 */
@ResponseBody
@ExceptionHandler({UdsRuntimeException.class})
@ResponseStatus(HttpStatus.OK)
public UdsHttpResponse handleServerError(UdsRuntimeException e, HttpServletRequest httpServletRequest) {
    logger.warn("[Handled] Uds exception", e);
    falconClient.avgByTime("uds_exception_count", 1, 30);
    return constructResponse(UdsHttpResponse.createFailedResponse(e.getErrorCode(), e.getMessage(), e.getData()),
            httpServletRequest);
}

Spring3.2新注解@ControllerAdvice
http://jinnianshilongnian.iteye.com/blog/1866350

Spring MVC中@ControllerAdvice注解实现全局异常拦截
https://www.cnblogs.com/EasonJim/p/7887646.html

Spring MVC 中 HandlerInterceptorAdapter的使用

一般情况下,对来自浏览器的请求的拦截,是利用Filter实现的,这种方式可以实现Bean预处理、后处理。
Spring MVC的拦截器不仅可实现Filter的所有功能,还可以更精确的控制拦截精度。
Spring为我们提供了org.springframework.web.servlet.handler.HandlerInterceptorAdapter这个适配器,继承此类,可以非常方便的实现自己的拦截器。他有三个方法:
分别实现预处理、后处理(调用了Service并返回ModelAndView,但未进行页面渲染)、返回处理(已经渲染了页面)
在preHandle中,可以进行编码、安全控制等处理;
在postHandle中,有机会修改ModelAndView;
在afterCompletion中,可以根据ex是否为null判断是否发生了异常,进行日志记录。

Spring MVC 中 HandlerInterceptorAdapter的使用
https://blog.csdn.net/liuwenbo0920/article/details/7283757


IDEA spring boot打包

View -> Tool Windows -> Maven Projects 调出Maven Projects窗口,打开“项目名”下的Lifecycle,双击package,开始自动打包

跳过测试

再Maven Projects边栏中,点击闪电图标 Toggle ‘Skip Tests’ Mode,选中后即打开跳过测试模式。
或者
mvn clean package -DskipTests
或mvn clean package -Dmaven.test.skip=true
或者在pom中配置:

<plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-surefire-plugin</artifactId>
        <configuration>
          <skip>true</skip>
        </configuration>
      </plugin>
</plugins>

spring boot参数配置

实际上,Spring Boot应用程序有多种设置途径,Spring Boot能从多重属性源获得属性,包括如下几种:
根目录下的开发工具全局设置属性(当开发工具激活时为~/.spring-boot-devtools.properties)。
测试中的@TestPropertySource注解。
测试中的@SpringBootTest#properties注解特性。
命令行参数
SPRING_APPLICATION_JSON中的属性(环境变量或系统属性中的内联JSON嵌入)。
ServletConfig初始化参数。
ServletContext初始化参数。
java:comp/env里的JNDI属性
JVM系统属性
操作系统环境变量
随机生成的带random.* 前缀的属性(在设置其他属性时,可以应用他们,比如${random.long})
应用程序以外的application.properties或者appliaction.yml文件
打包在应用程序内的application.properties或者appliaction.yml文件
通过@PropertySource标注的属性源
默认属性(通过SpringApplication.setDefaultProperties指定).

这里列表按组优先级排序,也就是说,任何在高优先级属性源里设置的属性都会覆盖低优先级的相同属性,列如我们上面提到的命令行属性就覆盖了application.properties的属性。


创建Spring boot项目

通过IntelliJ IDEA使用(个人推荐)
IntelliJ IDEA是非常流行的IDE,IntelliJ IDEA 14.1已经支持Spring Boot了。
创建Spring Boot操作步骤如下:
1.在File菜单里面选择 New > Project,然后选择Spring Initializr


Spring Boot父级依赖

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>1.5.1.RELEASE</version>
    <relativePath/> <!-- lookup parent from repository -->
</parent>

这块配置就是Spring Boot父级依赖,有了这个,当前的项目就是Spring Boot项目了,spring-boot-starter-parent是一个特殊的starter,它用来提供相关的Maven默认依赖,使用它之后,常用的包依赖可以省去version标签。关于Spring Boot提供了哪些jar包的依赖,可查看C:\Users\用户.m2\repository\org\springframework\boot\spring-boot-dependencies\1.5.1.RELEASE\spring-boot-dependencies-1.5.1.RELEASE.pom

如果你不想使用某个依赖默认的版本,您还可以通过覆盖自己的项目中的属性来覆盖各个依赖项,例如,要升级到另一个Spring Data版本系列,您可以将以下内容添加到pom.xml中。


Spring Boot Maven插件

<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
        </plugin>
    </plugins>
</build>

上面的配置就是Spring Boot Maven插件,Spring Boot Maven插件提供了许多方便的功能:
1、把项目打包成一个可执行的超级JAR,包括把应用程序的所有依赖打入JAR文件内,并为JAR添加一个描述文件,其中的内容能让你用java -jar来运行应用程序。
2、搜索public static void main()方法来标记为可运行类。


spring boot自定义属性乱码问题

#设置spring-boot 编码格式
server.tomcat.uri-encoding=UTF-8
spring.http.encoding.charset=UTF-8
spring.http.encoding.enabled=true
spring.http.encoding.force=true
spring.messages.encoding=UTF-8

设置 File Encodings的Transparent native-to-ascii conversion为true,具体步骤如下:
依次点击
File -> Settings -> Editor -> File Encodings
将Properties Files (*.properties)下的Default encoding for properties files设置为UTF-8,将Transparent native-to-ascii conversion前的勾选上。

Spring Boot 自定义属性 以及 乱码问题
https://blog.csdn.net/m0_37995707/article/details/77506184

Spring Boot干货系列:(一)优雅的入门篇
http://tengj.top/2017/02/26/springboot1/

Spring Boot干货系列总纲
http://tengj.top/2017/04/24/springboot0/


上一篇 DataGrip使用笔记

下一篇 Phabricator使用笔记