前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >SpringBoot我是这么用的

SpringBoot我是这么用的

作者头像
姜同学
发布2022-12-08 13:43:30
6490
发布2022-12-08 13:43:30
举报
文章被收录于专栏:姜同学姜同学

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

image.png
image.png

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

image.png
image.png

快速开发一个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请求测试一下这个接口 查看配置文件中的端口的应用上下文

image.png
image.png
image.png
image.png

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

image.png
image.png

修改请求体中的参数风格

在日常开发中会发现可爱的同事们都习惯于自己的开发风格,有的人喜欢用下划线,有的人喜欢传驼峰风格的参数,所以我们可以在配置文件中添加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文件夹下即可。

image.png
image.png

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

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

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

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

Bean的自动装配

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

image.png
image.png

下面演示一下我最常用的@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是怎么做的。

image.png
image.png

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

image.png
image.png

控制Bean到加载顺序

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

image.png
image.png

类上面的@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修改属性即可 当然修改方法不止我这一种哦。
image.png
image.png

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

开发自定义的boot-start

image.png
image.png

打开项目的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注册到容器中。

image.png
image.png

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

代码语言: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私服

image.png
image.png

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

引入依赖

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

Quick start

image.png
image.png
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2022-10-19,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 快速开发一个Restful风格的接口
    • pom文件加入依赖
      • Writing the Code
        • 修改请求体中的参数风格
        • SpringBoot使用拦截器
          • 实现HandlerInterceptor接口实现自定义拦截器
            • 注册拦截器
              • 拦截器原理
              • 全局异常处理
              • SpringBoot多环境配置
              • Bean的自动装配
              • Bean的条件装配
                • 控制Bean到加载顺序
                  • 修改已经装配的Bean
                  • 开发自定义的boot-start
                    • 创建boot项目,定义MQ组件.
                      • 创建spring.factories文件
                        • 打包传入maven私服
                          • 引入依赖
                            • Quick start
                            相关产品与服务
                            云数据库 Redis
                            腾讯云数据库 Redis(TencentDB for Redis)是腾讯云打造的兼容 Redis 协议的缓存和存储服务。丰富的数据结构能帮助您完成不同类型的业务场景开发。支持主从热备,提供自动容灾切换、数据备份、故障迁移、实例监控、在线扩容、数据回档等全套的数据库服务。
                            领券
                            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档