您现在的位置是:首页 > 文章详情

分享几个 SpringBoot 实用的小技巧

日期:2018-11-29点击:495

前言


这次就分享一点接地气的: SpringBoot 使用中的一些小技巧。


算不上多高大上的东西,但都还挺有用。


屏蔽外部依赖


第一个是屏蔽外部依赖,什么意思呢?


比如大家日常开发时候有没有这样的烦恼:


项目是基于 SpringCloud 或者是 dubbo 这样的分布式服务,你需要依赖许多基础服务。

比如说某个订单号的生成、获取用户信息等。

由于服务拆分,这些功能都是在其他应用中以接口的形式提供,单测还好我还可以利用 Mock 把它屏蔽掉。


但如果自己想把应用启动起来同时把自己相关的代码跑一遍呢?


通常有几种做法:


本地把所有的服务都启动起来。

把注册中心换为开发环境,依赖开发环境的服务。

直接把代码推送到开发环境自测。


看起来三种都可以,以前我也是这么干的。但还是有几个小问题:


本地启动有可能服务很多,全部起来电脑能不能撑住还两说,万一服务有问题就进行不下去了。

依赖开发环境的前提是网络打通,还有一个问题就是开发环境代码很不稳定很大可能会影响你的测试。

推送到开发环境应该是比较靠谱的方案,但如果想调试只有日志大法,没有本地 debug 的效率高效。


那如何解决问题呢?既可以在本地调试也不用启动其他服务。


其实也可以利用单测的做法,把其他外部依赖 Mock 掉就行了。


大致的流程分为以下几步:


SpringBoot 启动之后在 Spring 中找出你需要屏蔽的那个 API 的 bean(通常情况下这个接口都是交给 Spring 管理的)。

手动从 bean 容器中删除该 bean。

重新创建一个该 API 的对象,只不过是通过 Mock 出来的。

再手动注册进 bean 容器中。


以下面这段代码为例:

    @Override     public BaseResponse<OrderNoResVO> getUserByHystrix(@RequestBody UserReqVO userReqVO) {         OrderNoReqVO vo = new OrderNoReqVO();         vo.setAppId(123L);         vo.setReqNo(userReqVO.getReqNo());         BaseResponse<OrderNoResVO> orderNo = orderServiceClient.getOrderNo(vo);        return orderNo;     }

这是一个 SpringCloud 应用。


它依赖于 orderServiceClient 获取一个订单号。


其中的 orderServiceClient 就是一个外部 API,也是被 Spring 所管理。


替换原有的 Bean


下一步就是替换原有的 Bean。

@Componentpublic class OrderMockServiceConfig implements CommandLineRunner {    private final static Logger logger = LoggerFactory.getLogger(OrderMockServiceConfig.class);    @Autowired     private ApplicationContext applicationContext;    @Value("${excute.env}")    private String env;    @Override     public void run(String... strings) throws Exception {        // 非本地环境不做处理         if ("dev".equals(env) || "test".equals(env) || "pro".equals(env)) {            return;         }         DefaultListableBeanFactory defaultListableBeanFactory = (DefaultListableBeanFactory) applicationContext.getAutowireCapableBeanFactory();         OrderServiceClient orderServiceClient = defaultListableBeanFactory.getBean(OrderServiceClient.class);         logger.info("======orderServiceClient {}=====", orderServiceClient.getClass());         defaultListableBeanFactory.removeBeanDefinition(OrderServiceClient.class.getCanonicalName());         OrderServiceClient mockOrderApi = PowerMockito.mock(OrderServiceClient.class,                 invocationOnMock -> BaseResponse.createSuccess(DateUtil.getLongTime() + "", "mock orderNo success"));         defaultListableBeanFactory.registerSingleton(OrderServiceClient.class.getCanonicalName(), mockOrderApi);         logger.info("======mockOrderApi {}=====", mockOrderApi.getClass());     } }

其中实现了 CommandLineRunner 接口,可以在 Spring 容器初始化完成之后调用 run() 方法。


代码非常简单,简单来说首先判断下是什么环境,毕竟除开本地环境其余的都是需要真正调用远程服务的。


之后就是获取 bean 然后手动删除掉。


关键的一步:

OrderServiceClient mockOrderApi = PowerMockito.mock(OrderServiceClient.class,                 invocationOnMock -> BaseResponse.createSuccess(DateUtil.getLongTime() + "", "mock orderNo success")); defaultListableBeanFactory.registerSingleton(OrderServiceClient.class.getCanonicalName(), mockOrderApi);

创建了一个新的 OrderServiceClient 对象并手动注册进了 Spring 容器中。


第一段代码使用的是 PowerMockito.mock 的 API,他可以创建一个代理对象,让所有调用 OrderServiceClient 的方法都会做默认的返回。

BaseResponse.createSuccess(DateUtil.getLongTime() + "", "mock orderNo success"))

测试一下,当我们没有替换时调用刚才那个接口并且本地也没有启动 OrderService:

images/zHanM7sKaztMxiRAPynzW5KyMcrECsXk.jpg


因为没有配置 fallback 所以会报错,表示找不到这个服务。


替换掉 bean 时:


images/b5GjQSHRhk3exDDFKaTSKTjBFfKmhkED.jpg


再次请求没有报错,并且获得了我们默认的返回。

images/HY6jcJeenKeWb8myeTpAQ86fMsSxeKjh.jpg


通过日志也会发现 OrderServiceClient 最后已经被 Mock 代理了,并不会去调用真正的方法。


配置加密


下一个则是配置加密,这应该算是一个基本功能。


比如我们配置文件中的一些账号和密码,都应该是密文保存的。


因此这次使用了一个开源组件来实现加密与解密,并且对 SpringBoot 非常友好只需要几段代码即可完成。


首先根据加密密码将需要加密的配置加密为密文。

替换原本明文保存的配置。

再使用时进行解密。


使用该包也只需要引入一个依赖即可:

<dependency>     <groupId>com.github.ulisesbocchio</groupId>     <artifactId>jasypt-spring-boot-starter</artifactId>     <version>1.14</version> </dependency>

同时写一个单测根据密码生成密文,密码也可保存在配置文件中:

jasypt.encryptor.password=123456

接着在单测中生成密文。

    @Autowired     private StringEncryptor encryptor;    @Test     public void getPass() {         String name = encryptor.encrypt("userName");         String password = encryptor.encrypt("password");         System.out.println(name + "----------------");         System.out.println(password + "----------------");     }

之后只需要使用密文就行。


由于我这里是对数据库用户名和密码加密,所以还得有一个解密的过程。


利用 Spring Bean 的一个增强接口即可实现:

@Componentpublic class DataSourceProcess implements BeanPostProcessor {    @Autowired     private StringEncryptor encryptor;    @Override     public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {        return bean;     }    @Override     public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {        if (bean instanceof DataSourceProperties){             DataSourceProperties dataSourceProperties = (DataSourceProperties) bean;             dataSourceProperties.setUsername(encryptor.decrypt(dataSourceProperties.getUsername())) ;             dataSourceProperties.setPassword(encryptor.decrypt(dataSourceProperties.getPassword()));            return dataSourceProperties ;         }        return bean;     } }

这样就可以在真正使用时还原为明文。


同时也可以在启动命令中配置刚才的密码:

java -Djasypt.encryptor.password=password -jar target/jasypt-spring-boot-demo-0.0.1-SNAPSHOT.jar


总结


这样两个小技巧就讲完了,大家有 SpringBoot 的更多使用技巧欢迎留言讨论。


上文的一些实例代码可以在这里找到:


https://github.com/crossoverJie/springboot-cloud


原文链接:https://blog.roncoo.com/article/134176
关注公众号

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。

持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。

转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。

文章评论

共有0条评论来说两句吧...

文章二维码

扫描即可查看该文章

点击排行

推荐阅读

最新文章