SpringBoot我是这么用的

2022-12-08 13:43:30 浏览数 (1)

从Spring的配置文件注册Java Bean在到Spring2.5之后支持注解配置Java Bean,现在SpringBoot也使用Spring6.0来到了3.0版本,当然虽说是来了,但是SpringBoot3.0放弃了Java8所以姜同学还没法再生产环境验证它,所以本文是基于Spring官网的GA版本讲解了。

下面我会结合自己的日常工作介绍我对SpringBoot的理解。也算是我个人的沉淀。

快速开发一个Restful风格的接口

pom文件加入依赖

代码语言:javascript复制
<dependencies>
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
  </dependency>
</dependencies>
xml

Writing the Code

代码语言:javascript复制
@RequestMapping("boot")
@RestController
public class BootController {

    @RequestMapping(value = "hello/{name}",method = RequestMethod.GET)
    Body home(@PathVariable("name") String name,
                @RequestHeader(value = "Auth",required = false) String token,
                @RequestParam(value = "param",required = false) String param,
                @RequestBody Body body) {
        Body response = new Body();
        return response.setName(name)
                .setToken(token)
                .setParam(param);
    }

    @Data
    @Accessors(chain = true)
    static class Body{
        String name;
        String token;
        String param;
    }
}
java

发起一个Http请求测试一下这个接口 查看配置文件中的端口的应用上下文

我以思维导图的方式讲解一些这些注解的作用

修改请求体中的参数风格

在日常开发中会发现可爱的同事们都习惯于自己的开发风格,有的人喜欢用下划线,有的人喜欢传驼峰风格的参数,所以我们可以在配置文件中添加Boot使用jackson反序列化的风格,这样接口的入参和返回的参数风格就统一啦。

代码语言:javascript复制
spring:
	jackson:
		property-naming-strategy: SNAKE_CASE #这是下划线风格,默认是驼峰的命名风格
		default-property-inclusion: non_null
		date-format: yyyy-MM-dd HH:mm:ss
		serialization:
			WRITE_DATES_AS_TIMESTAMPS: false
		time-zone: GMT 8
yaml

SpringBoot使用拦截器

实现HandlerInterceptor接口实现自定义拦截器

代码语言:javascript复制
@Component
    @Slf4j
    public class AuthInterceptor implements HandlerInterceptor {
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
            log.info("目标方法执行前执行,返回true则执行下一个拦截器,没有拦截器直接执行目标方法");
            return true;
        }

        @Override
        public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
            log.info("目标方法执行完毕才会执行");
        }

        @Override
        public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
            log.info("渲染完啦,只要执行过preHandle,及时后面的拦截器失败了也会执行");
        }
    }
java

注册拦截器

代码语言:javascript复制
/**
 * 1、编写一个拦截器实现HandlerInterceptor接口
 * 2、拦截器注册到容器中(实现WebMvcConfigurer的addInterceptors)
 * 3、指定拦截规则【如果是拦截所有,静态资源也会被拦截】
 */
@Configuration
public class adminWebConfig implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LoginInterceptor())
                .addPathPatterns("/**")  //所有请求都被拦截包括静态资源
                .excludePathPatterns("/","/login","/css/**","/fonts/**","/images/**","/js/**"); //放行的请求
    }
}
java

如果有多个拦截器,你addInterceptor的顺序就是拦截器执行的顺序。

拦截器原理

1、根据当前请求,找到HandlerExecutionChain【可以处理请求的handler以及handler的所有 拦截器】 2、先来顺序执行 所有拦截器的 preHandle方法 ● 1、如果当前拦截器prehandler返回为true。则执行下一个拦截器的preHandle ● 2、如果当前拦截器返回为false。直接 倒序执行所有已经执行了的拦截器的 afterCompletion; 3、如果任何一个拦截器返回false。直接跳出不执行目标方法 4、所有拦截器都返回True。执行目标方法 5、倒序执行所有拦截器的postHandle方法。 6、前面的步骤有任何异常都会直接倒序触发 afterCompletion 7、页面成功渲染完成以后,也会倒序触发 afterCompletion

全局异常处理

日常开发中有很多异常是开发人员可以预知的,比如说接口需要的参数格式不对,少了必要参数而引发的NPE,这些异常都是需要捕获返回给前端的,但是大量重复的try catch会使本来很干净的代码变得不那么清爽。所以SpringBoot为我们提供了@ControllerAdvice和@RestControllerAdvice为我们提供捕获全局异常的能力。

代码语言:javascript复制
@ControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
    /**
     * 统一处理参数校验的异常
     */
    @ExceptionHandler(value = {MethodArgumentNotValidException.class})
    @ResponseBody
    @ResponseStatus(code = HttpStatus.OK)
    public Result handleValidException(MethodArgumentNotValidException exception) {
        log.error("数据校验出现问题:{},异常类型为:{}", exception.getMessage(),  exception.getClass());
        BindingResult bindingResult = exception.getBindingResult();

        HashMap<String, String> map = new HashMap<>();
        // 1.获取校验的错误结果
        List<FieldError> fieldErrors = bindingResult.getFieldErrors();
        fieldErrors.forEach(item -> {
            // 2.获取校验错误的Filed
            String field = StrUtil.toUnderlineCase(item.getField());
            // 3.获取错误提示
            String message = item.getDefaultMessage();
            map.put(field, message);
        });

        return Result.result(CODE_MSG.PARAM_EXCEPTION,map);
    }

    /**
     * 捕获自定义异常
     */
    @ExceptionHandler({PinsRuntimeException.class})
    @ResponseStatus(code = HttpStatus.OK)
    public Result<Void> handleMyException(Exception ex) {
        log.error("PinsRuntimeException error:{}", ex.getMessage());
        return Result.fail(ex.getMessage());
    }
}
java

这样我们只需要在代码中抛出异常就ok啦。

SpringBoot多环境配置

Spring多环境的配置也是非常简单的,你只需要准备下面四个文件放在resources文件夹下即可。

然后在不同的文件中使用下面的配置指定自己的环境即可,以dev为例。

代码语言:javascript复制
spring:
  config:
    activate:
      on-profile: dev
yaml

最后在application.yml声明使用哪个环境即可。

代码语言:javascript复制
spring:
  profiles:
    active: "dev"
yaml

Bean的自动装配

基于Spring的IOC原理,Bean的自动注入让我们完全感受不要创建对象的过程,就感觉bean不需要创建一样,那么我们怎么将我们自定义的对象注册到IOC容器呢,当然也很简单,不过我最常用的还是下面这两种。

下面演示一下我最常用的@Bean方式。

代码语言:javascript复制
@Configuration
public class ThreadPoolConfig {

    @Bean
    public ExecutorService pinsTaskThreadPool(){
        BlockingQueue<Runnable> workQueue = new ArrayBlockingQueue<>(1000);
        ThreadFactory namedThreadFactory = new ThreadFactoryBuilder()
                .setNameFormat("pins-thread-%s").build();

        int availableProcessors = Runtime.getRuntime().availableProcessors();
        // IO密集型线程池
        int maximumPoolSize = 2*availableProcessors;

        return new ThreadPoolExecutor(maximumPoolSize-1, maximumPoolSize,
                10, TimeUnit.SECONDS, workQueue,
                namedThreadFactory,
                new RejectedExecutionHandler() {
                    @Override
                    public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
                        throw new RejectedExecutionException("Queue full...");
                    }
                });
    }
}
java

这样我们自定义的线程池就交给Spring的容器管理啦。

Bean的条件装配

有一些Bean是要互相依赖滴,比如SpringBoot的DataSourceHealthContributorAutoConfiguration就需要依赖一些其他的Bean,让我们来看看SpringBoot是怎么做的。

上面注解的意思是加载到JdbcTemplate,AbstractRoutingDataSource,两个类并且容器中有DataSource类型的Bean这个配置类才会生效。下面我继续以思维导图的方式介绍一些条件装配常用的注解。

控制Bean到加载顺序

姜同学之前写过一个redis的SDK,过段时间一起分享出来,这里并不是要强调这个SDK,而是要描述一下项目中引用这个SDK发生的问题,业务系统使用的认证授权框架是开源的sa-token,里面集成了redis,我进去改人家的源代码就有点用大炮打蚊子的感觉了,那怎么才能让这些第三方框架都使用姜同学开发的SDK的配置呢,请听我娓娓道来。 首先姜同学发现了sa-token的redis配置类。

类上面的@Component,暴露的它是IOC容器中的一个Bean,而且这个框架很讲道理呀RedisTemplate,声名成了public,怎么让它使用我的SDK这个问题就很好解决了,我们只需要在创建一个Bean让他在sa-token的redis配置bean之后加载,并在加载之前把RedisTemplate替换为我们redis SDK的RedisTemplate就好啦,这时我们的主角@DependsOn,就登场了,这个注解约束指定Bean存在才会创建注解标注的Bean。

代码语言:javascript复制
@Component
@DependsOn(value = {"cn.dev33.satoken.dao.SaTokenDao"})
public class SaTokenConfigure{

    private final AuthProperties authProperties;
    private final PinsStandaloneRedis pinsStandaloneRedis;
    private final ApplicationContext applicationContext;

    public SaTokenConfigure(AuthProperties authProperties, PinsStandaloneRedis pinsStandaloneRedis, ApplicationContext applicationContext) {
        this.authProperties = authProperties;
        this.pinsStandaloneRedis = pinsStandaloneRedis;
        this.applicationContext = applicationContext;
    }
    
    @PostConstruct
    public void init() {
        RedisConnectionFactory connectionFactory = pinsStandaloneRedis.getRedisTemplate().getConnectionFactory();
        String[] beanNamesForType = applicationContext.getBeanNamesForType(SaTokenDaoRedisJackson.class);
        SaTokenDaoRedisJackson saTokenDaoRedisJackson = (SaTokenDaoRedisJackson) applicationContext.getBean(beanNamesForType[0]);
        assert connectionFactory != null;
        saTokenDaoRedisJackson.objectRedisTemplate.setConnectionFactory(connectionFactory);
        saTokenDaoRedisJackson.stringRedisTemplate.setConnectionFactory(connectionFactory);
    }

}

java

在init方法中替换了sa-token的redis配置,这都多亏了DependsOn注解呢。

修改已经装配的Bean

其实在上一步我们已经演示了如何修改已经加载的Bean。

  1. 自动注入ApplicationContext类
  2. getBean获取已经加载的Bean
  3. 拿到Bean修改属性即可 当然修改方法不止我这一种哦。

当然还有其他一些好用的API,都很简单,姜同学就留给大家自己探索了。

开发自定义的boot-start

打开项目的pom文件你会看到很多类似的boot-starter,它们到底是什么东西呢?姜同学以自己写过的一个阿里云RocketMQ的SDK为例讲解下这个boot-starter究竟是个什么东西。 先揭晓一下答案,其实boot-starter里面就是定义了一些我们可以直接使用的JavaBean.

创建boot项目,定义MQ组件.

代码语言:javascript复制
@Component
@Qualifier("pinsNormalRocketMQ")
public class PinsNormalRocketMQ implements PinsMQ{

    private final ProducerBean pinsNormalProducer;
    private final MqConfig mqConfig;
    private final ExecutorService executorService;

    private static HashSet<ConsumerBean> consumerSet = new HashSet<ConsumerBean>();

    public PinsNormalRocketMQ(ProducerBean pinsNormalProducer, MqConfig mqConfig) {
        this.pinsNormalProducer = pinsNormalProducer;
        this.mqConfig = mqConfig;
        this.executorService = ThreadPoolFactory.buildThreadPool();
    }

    @Override
    public void send(String tag, String key,@NotNull String msg) {
        Message message = new Message(
                mqConfig.getTopic(),
                tag,
                msg.getBytes(StandardCharsets.UTF_8));
        message.setKey(key);
        pinsNormalProducer.send(message);
        executorService.execute(() ->
            PinsMessageUtils.saveRecord(mqConfig.getEnv(), RocketMqRecordBuilder.buildProductRecord(message)));
    }
}
java

可以看到我使用了@Component这个注解将PinsNormalRocketMQ声明为IOC容器管理的JavaBean,并用@Qualifier为这个Bean起了一个名字。

创建spring.factories文件

SpringBoot为我们提供了一种机制,那就是在resources/META-INF/目录下创建一个spring.factories文件,并在里面写上我们想要自动装配的Bean,SpringBoot在启动时就会加载到这个文件,并把里面的Bean注册到容器中。

这就是他的目录结构。 里面的内容是这样滴。

代码语言:javascript复制
org.springframework.boot.autoconfigure.EnableAutoConfiguration=
com.pinsmedical.mq.core.PinsNormalRocketMQ,
com.pinsmedical.mq.normal.ConsumerClient,
com.pinsmedical.mq.normal.ProducerClient,
com.pinsmedical.mq.normal.DemoMessageListener,
com.pinsmedical.mq.core.PinsTransactionRocketMQ,
com.pinsmedical.mq.transaction.TransactionProducerClient,
com.pinsmedical.mq.transaction.PinsLocalTransactionChecker
java

可以看到我们上面的com.pinsmedical.mq.core.PinsNormalRocketMQ就在其中,那么下面那些东西是什么鬼呢,姜同学在这个工程中创建了很多Bean,为了演示只是选取了PinsNormalRocketMQ这个类复制了出来,大家知道一下就好啦。

打包传入maven私服

先看一下我们这个工程的gav,如果你没有在maven的配置文件中配置私服地址,那么你以可以直接mvn install将jar直接安装到你的本地仓库。

引入依赖

代码语言:javascript复制
<dependency>
   <groupId>com.pinsmedical</groupId>
   <artifactId>pins-mq</artifactId>
   <version>2.1-RELEASE</version>
</dependency>
xml

Quick start

0 人点赞