SpringBoot配置logback
项目的日志配置属于比较常见的case了,之前接触和使用的都是Spring结合xml的方式,引入几个依赖,然后写个 logback.xml
配置文件即可,那么在SpringBoot中可以怎么做?
Guava在实际的Java后端项目中应用的场景还是比较多的,比如限流,缓存,容器操作之类的,有挺多实用的工具类,这里记录一下,在使用GuavaCache,返回null的一个问题
1 | @Test |
上面是一个非常简单的测试case,需要注意的是,cache.get("word")
的执行,并不如逾期的返回的是null,而是会抛一个异常出来
1 | word |
从异常描述能看出,不允许返回null,这一块之前倒是没怎么注意,因此对于null的情况,要么定义一个标记表示不存在,要么在load()
方法中主动抛一个异常出来,在使用的时候注意下,通过异常的使用方式,可以如下
1 | public class NoVlaInGauvaException extends Exception { |
说明:为什么重写fillInStackTrace
方法
其次就是get
与getUnchecked
的区别了
一灰灰的个人博客,记录所有学习和工作中的博文,欢迎大家前去逛逛
尽信书则不如,已上内容,纯属一家之言,因个人能力有限,难免有疏漏和错误之处,如发现bug或者有更好的建议,欢迎批评指正,不吝感激
在resource目录下有一个application.yml文件,希望是通过@PropertySource
注解,将配置文件数据读取到Environment
中,然而调试发现数据始终读取不到,google之后,记录下解决方法
在测试用例中,指定初始化方式 @ContextConfiguration(classes = RedisConf.class, initializers = ConfigFileApplicationContextInitializer.class)
1 | @RunWith(SpringJUnit4ClassRunner.class) |
对应的配置类
1 | @Configuration |
一灰灰的个人博客,记录所有学习和工作中的博文,欢迎大家前去逛逛
尽信书则不如,已上内容,纯属一家之言,因个人能力有限,难免有疏漏和错误之处,如发现bug或者有更好的建议,欢迎批评指正,不吝感激
相关博文,推荐查看:
前面一篇介绍了使用工厂方式创建消费者,其中一个不太友好的地方就在配置都是硬编码的方式,不太灵活,那么是否可以结合前一篇的FactoryBean来实现从配置中来灵活的创建消费者呢?
相关博文,推荐查看:
在前面的一篇演示了如何使用Spring来进行RabbitMQ的消息投递和消费,虽然可以实现基本的需求场景,但是使用起来却并不是特别顺手,首先是不同的消费者,得添加好多不同的配置项,加上有较多的配置(QueueName, ExchangeName, RoutingKey, autoAck…)
那么有没有可能借助工厂方式,来简化消费者这边的大多数配置呢?
去掉xml的配置方式,改成用Java来配置,最常见的就是将xml中的 bean定义, scanner包扫描,属性文件的配置信息读取等
在javaConfig中注解@Configuration
用来代替一个xml文件,可以简单的理解他们的作用是相等的,一般bean的定义也都是放在被这个注解修饰的类中
如一个基本的配置文件如下
1 | @Configuration |
上面的例子中,在方法上添加了@Bean注解,这个就相当于传统的
1 | <bean name="rabbitAdmin" class="org.springframework.amqp.rabbit.core.RabbitAdmin"/> |
因此在需要引入rabbitAdmin实例的地方,可以如下使用
a. 属性字段上添加 @Autowired
注解
1 | public class RConsumer { |
b. 设置方法上添加 @Autowired
注解
1 | public class RConsumer { |
c. 使用构造器的方式
1 | public class RConsumer { |
上面就是Spring容器支持的几种典型的IoC方式
这个类似于xml中的 <context:component-scan"/>
标签
1 | @ComponentScan("com.git.hui.rabbit.spring") |
上面的这个配置,表示自动扫描包 com.git.hui.rabbit.spring
下面的bean (要求类上添加了 @Component, @Repository, @Service)
那么一个问题来了,如果一个类既被自动扫描加载,又显示定义了bean,会怎样?
1 | package com.git.hui.rabbit.spring; |
对应的JavaConfig
1 | @Configuration |
实际测试,发现这个bean只会有一个实例,即输出计数只会有一条,实际查看ApplicationContext
中的内容,TestBean的实例,也确实只有一个,如果改成下面这种场景呢
1 | @Bean(name="testBean2") |
会有两条记录输出,实际查看容器中的Bean对象,会有两个实例如下
这和我们的预期也是一样的,因为一个类我可能需要多个不同的Bean实例来干一些事情
那么出现这种JavaConfig定义的beanName与自动扫描的冲突的情况会怎样呢?
新增一个NewBean对象,
1 | public class NewBean { |
在JavaConfig中新加一个bean定义,但是BeanName与自动扫描的TestBean重复了
1 | @Bean(name="testBean") |
此时发现有意思的事情了,从Spring容器中,将查不到TestBean的实例,但是可以查到NewBean的实例
这个的表现是:
那么跟着来的一个问题就是如果JavaConfig中定义了两个相同的BeanName的bean呢?
1 | @Bean(name = "testBean2") |
因为我们TestBean上加了@Component
注解,因此容器中至少有一个,但是否会有testBean2这个实例呢? 通过实际查看是没有的,testBean2这个名被 NewBean
占领了
so,表现上看,加上实测,将上面的定义换个位置,得出下面的结论
然后就是最后一个问题了,当自动扫描时,两个类包不同,但是类名相同,会怎样?
1 | package com.git.hui.rabbit.spring.demo; |
实测,会抛出一个异常,在使用xml的配置方式时,经常见到的一个BeanName冲突的异常
1 | org.springframework.context.annotation.ConflictingBeanDefinitionException: Annotation-specified bean name 'testBean' for bean class [com.git.hui.rabbit.spring.demo.TestBean] conflicts with existing, non-compatible bean definition of same name and class [com.git.hui.rabbit.spring.TestBean] |
小结:
在xml配置中,另一个常见的case就是引入另一个xml配置,在JavaConfig中代替的就是Import注解
1 | @Configuration |
这个就等同于xml中常见的:
1 | <import resource="service.xml" /> |
上面说了用JavaConfig代替xml配置的方式,另一个关键的地方就是测试用例的写法了,对于之前的xml,有两种常见的使用姿势
case1: 注解方式
1 | @RunWith(SpringJUnit4ClassRunner.class) |
case2: 主动加载容器方式
1 | private ServiceA serviceA; |
那么替换成JavaConfig的用法,也有两种
case1: 注解方式,指定内部classes值
1 | @RunWith(SpringJUnit4ClassRunner.class) |
case2: 主动加载容器,改为AnnotationConfigApplicationContext
1 | @Test |
JavaConfig方式基本上采用的是替换的思路来取代xml,即原xml中的一些东西,可以直接通过注解来代替,如
<bean>
标签作用相同<context:component-scan>
<import>
标签类似,引入其他的配置信息在实际使用中,有一点需要额外注意,对于beanName相同的情况,通过测试的规则如下(没有看源码,不保证完全准确,仅为测试后得出的依据):
最简单的就是修改原来的注解@ContextConfiguration
中的值
1 | @ContextConfiguration(classes = SpringConfig.class) |
一灰灰的个人博客,记录所有学习和工作中的博文,欢迎大家前去逛逛
尽信书则不如,已上内容,纯属一家之言,因个人能力有限,难免有疏漏和错误之处,如发现bug或者有更好的建议,欢迎批评指正,不吝感激
相关博文,推荐查看:
前一篇基本使用篇的博文中,介绍了rabbitmq的三种使用姿势,可以知道如何向RabbitMQ发送消息以及如何消费,但遗留下几个疑问,本篇则主要希望弄清楚这几点
RabbitMQ是一个消息队列,和Kafka以及阿里的ActiveMQ从属性来讲,干的都是一回事。消息队列的主要目的实现消息的生产者和消费者之间的解耦,支持多应用之间的异步协调工作
由于工作原因,接触和使用rabbitmq作为生产环境下的消息队列,因此准备写一些博文,记录下这个过程中的收货;而开篇除了环境搭建之外,就是对于其内部的基本概念进行熟悉和了解了。
基础环境搭建可以参考: 《RabbitMq基础教程之安装与测试》
本文则主要集中在以下几点:
在博文 《Spring学习之事务的使用姿势》 中,演示了基于注解和xml的事务使用姿势,以@Transactional
注解为例,其中很多的参数都没有详细说明
本篇博文,则主要目的是弄懂这些参数有啥用,以及在实际项目中如何选择
主要记录下spring是如何支持事务的,以及在Spring结合mybatis时,可以怎么简单的实现数据库的事务功能
首先准备两张表,一个user表,一个story表,结构如下
1 | CREATE TABLE `user` ( |
我们的事务场景在于用户修改name时,要求两张表的name都需要一起修改,不允许出现不一致的情况
转账,一个用户减钱,另一个用户加钱
1 | CREATE TABLE `money` ( |
相比上面那个case,这个更加简单了,下面的实例则主要根据这个进行说明,至于case1,则留待扩展里面进行
首先是实现对应的dao和entity
1 | @Data |
对应的mapper文件为
1 | <?xml version="1.0" encoding="UTF-8"?> |
对应的mybatis连接数据源的相关配置
1 | <bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"> |
通过网上查询,Spring事务管理总共有四种方式,下面逐一进行演示,每种方式是怎么玩的,然后看实际项目中应该如何抉择
编程式事务管理,既通过TransactionTemplate来实现多个db操作的事务管理
那么,我们的转账case可以如下实现
1 | @Repository |
主要看上面的transfor方法,内部通过 transactionTemplate 来实现事务的封装,内部有三个db操作,一个查询,两个更新,具体分析后面说明
上面的代码比较简单了,唯一需要关注的就是transactionTemplate这个bean如何定义的,xml文件中与前面重复的就不贴了,直接贴上关键代码, 一个是根据DataSource创建的TransactionManager
,一个则是根据TransactionManager
创建的TransactionTemplate
1 | <!--编程式事务--> |
正常演示情况, 演示没有任何异常,不考虑并发的情况
1 | @RunWith(SpringJUnit4ClassRunner.class) |
输出如下,两个账号的钱都没有问题
1 | ---------before---------- |
转账过程中出现异常,特别是转账方钱已扣,收款方还没收到钱时,也就是case中的status为1的场景
1 | // 内部抛异常的情况 |
对此,我们希望把转账方的钱还回去, 输出如下,发现两个的钱都没有变化
1 | ---------before---------- |
当status为2,表示在转账人钱已扣,收款人钱没收到之间,又有人给收款人转了200,此时根据mysql的锁机制,另外人的转账应该是立马到的(因为收款人账号没有被锁住),且金额不应该有问题
输出结果如下:
1 | ---------before---------- |
当status为3, 表示在转账人钱已扣,收款人钱没收到之间,又有人给转账人转了200,这时因为转账人的记录以及被加了写锁,因此只能等待转账的事务提交之后,才有可能+200成功,当然最终的金额也得一致
输出结果如下
1 | ---------before---------- |
至此,编程式事务已经实例演示ok,从上面的过程,给人的感觉就和直接写事务相关的sql一样,
1 | start transaction; |
TransactionProxyFactoryBean
方式接下来的三个就是声明式事务管理,这种用得也比较少,因为需要每个事务管理类,添加一个TransactionProxyFactoryBean
除了将 TransactionTemplate
干掉,并将内部的sql逻辑移除之外,对比前面的,发现基本上没有太多差别
1 | public class FactoryBeanDemo2 { |
重点来了,主要是需要配置一个 TransactionProxyBeanFactory
,我们知道BeanFactory就是我们自己来创建Bean的一种手段,相关的xml配置如下
1 | <!--编程式事务--> |
通过上面的配置,大致可以了解到这个通过TransactionProxyFactoryBean就是创建了一个FactoryBeanDemo2的代理类,这个代理类内部封装好事务相关的逻辑,可以看做是前面编程式的一种简单通用抽象
测试代码与前面基本相同,唯一的区别就是我们使用的应该是上面BeanFactory生成的Bean,而不是直接使用FactoryBeanDemo2
正常演示case:
1 | @RunWith(SpringJUnit4ClassRunner.class) |
输出
1 | ---------before---------- |
status为1,内部异常的情况下,我们希望钱也不会有问题
1 | @Test |
输出为
1 | ---------before---------- |
status为2 时,分析结果与上面应该相同,输出如下
1 | ---------before---------- |
status为3时,输出
1 | ---------before---------- |
TransactionProxyFactoryBean 的思路就是利用代理模式来实现事务管理,生成一个代理类,拦截目标方法,将一组sql的操作封装到事务中进行;相比较于硬编码,无侵入,而且支持灵活的配置方式
缺点也显而易见,每个都要进行配置,比较繁琐
Spring有两大特点,IoC和AOP,对于事务这种情况而言,我们可不可以使用AOP来做呢?
对于需要开启事务的方法,拦截掉,执行前开始事务,执行完毕之后提交事务,出现异常时回滚
这样一看,感觉还是蛮有希望的,而下面两种姿势正是这么玩的,因此需要加上aspect的依赖
1 | <dependency> |
java类与第二种完全一致,变动的只有xml
1 | <!-- 首先添加命名空间 --> |
观察上面的配置,再想想第二种方式,思路都差不多了,但是这种方式明显更加通用,通过切面和切点,可以减少大量的配置
1 | @RunWith(SpringJUnit4ClassRunner.class) |
这个测试起来,和一般的写法就没啥两样了,比第二种的FactoryBean的注入方式简单点
正常输出
1 | ---------before---------- |
status=1 出现异常时,输出
1 | ---------before---------- |
status=2 转账过程中,又存钱的场景,输出,与前面预期一致
1 | ---------before---------- |
status=3 的输出,与前面预期一致
1 | ---------before---------- |
这个就是消灭xml,用注解来做的方式,就是将前面xml中的配置用 @Transactional注解替换
1 | @Repository |
因此需要在xml中配置,开启事务注解
1 |
|
这样一看,就更加清晰了,实际项目中,xml和注解方式也是用得最多的场景了
和第三种测试case完全相同, 输出结果也一样,直接省略
上面说了Spring中四种使用事务的姿势,其中硬编码方式可能是最好理解的,就相当于将我们写sql中,使用事务的方式直接翻译成对应的java代码了;而FactoryBean方式相当于特殊情况特殊对待,为每个事务来一个代理类来增强事务功能;后面的两个则原理差不多都是利用事务通知(AOP)来实现,定义切点及相关信息
编程式:
TransactionTemplate
transactionTemplate#execute
方法内代理BeanFactory:
TransactionProxyFactoryBean
为事务相关类生成代理xml配置:
<tx:advice>
标签定义事务通知,内部可有较多的配置信息<aop:config>
配置切点,切面注解方式:
@Transactional
注解即可<tx:annotation-driven transaction-manager="transactionManager"/>
文档
源码
基于hexo + github pages搭建的个人博客,记录所有学习和工作中的博文,欢迎大家前去逛逛
尽信书则不如,已上内容,纯属一家之言,因本人能力一般,见识有限,如发现bug或者有更好的建议,随时欢迎批评指正
当服务有较多外部依赖时,如果其中某个服务的不可用,导致整个集群会受到影响(比如超时,导致大量的请求被阻塞,从而导致外部请求无法进来),这种情况下采用hystrix就很有用了
出于这个目的,了解了下hystrix框架,下面记录下,框架尝新的历程
通过官网和相关博文,可以简单的说一下这个工作机制,大致流程如下
首先是请求过来 -> 判断熔断器是否开 -> 服务调用 -> 异常则走fallback,失败计数+1 -> 结束
下面是主流程图
1 | graph LR |
熔断机制主要提供了两种,一个是基于线程池的隔离方式来做;还有一个则是根据信号量的抢占来做
线程池方式 : 支持异步,支持超时设置,支持限流
信号量方式 : 本线程执行,无异步,无超时,支持限流,消耗更小
基本上有上面这个简单的概念之后,开始进入我们的使用测试流程
1 | <dependency> |
从官方文档来看,支持两种Command方式,一个是基于观察者模式的ObserverCommand, 一个是基本的Command,先用简单的看以下
1 | public class HystrixConfigTest extends HystrixCommand<String> { |
使用起来还是比较简单的,一般步骤如下:
HsytrixCommand
类写上面的代码比较简单,但是有几个地方不太好处理
根据上面那一段代码的删删改改,貌似理解了以下几个点,不知道对误
run方法是核心执行服务调用,如果需要某些服务不统计到熔断的失败率(比如因为调用姿势不对导致服务内部的异常抛上来了,但是服务本身是正常的),这个时候,就需要包装下调用逻辑,将不需要的异常包装到 HystrixBadRequestException
类里
如
1 | @Override |
当发生失败时,hystrix会把原生的异常包装到 HystrixRuntimeException
这个类里,所以我们可以在调用的地方如下处理
1 | try { |
当定义了fallback逻辑时,异常则不会抛到具体的调用方,所以在 fallback 方法内,则有必要获取对应的异常信息
1 | // 获取异常信息 |
然后下一步就是需要获取对应的异常原因了,通过FailureType来表明失败的根源
1 | ((HystrixRuntimeException) t).getFailureType() |
hystrix自己提供了一套监控插件,基本上公司内都会有自己的监控统计信息,因此需要对这个数据进行和自定义,目前还没看到可以如何优雅的处理这些统计信息
主要是看了下这个东西可以怎么玩,整个用下来的感觉就是,设计的比较有意思,但是配置参数太多,很多都没有完全摸透
其次就是一些特殊的case(如监控,报警,特殊情况过滤)需要处理时,用起来并不是很顺手,主要问题还是没有理解清楚这个框架的内部工作机制的问题
基于hexo + github pages搭建的个人博客,记录所有学习和工作中的博文,欢迎大家前去逛逛
尽信书则不如,已上内容,纯属一家之言,因本人能力一般,见识有限,如发现bug或者有更好的建议,随时欢迎批评指正
Update your browser to view this website correctly. Update my browser now