• springboot入门05 – 包装SpringBoot Controller返回值

    一个项目使用了SpringBoot,需要对Controller的返回值进行二次包装。包装类结构大致如下: 通过查找资料,找到了两种封装方式。 方法一 第一种方式是替换掉RequestResponseBodyMethodProcessor,这需要使用一个MethodReturnValueHandler的装饰类: 在装饰类中使用一个Result类的实例替换了returnValue。而后在InitializingBean中基于原来的RequestResponseBodyMethodProcessor的实例创建一个ResponseBodyWrapHandler的实例来完成替换: 使用ResponseBodyWrapFactoryBean,完成afterProperties方法的调用,只需要创建一个ResponseBodyWrapFactoryBean的实例即可: 这行代码可以放在启动类中。 方法二 第二种方式基于ControllerAdvice和HttpMessageConverter实现。 首先用一个ResponseBodyAdvice类的实现包装Controller的返回值: 如果Controller类的返回值没有String类型的,仅有上面这个类就够了。如果有String类型的返回值,就有可能遇到类型不匹配的问题。HttpMessageConverter是根据Controller的原始返回值类型进行处理的,而我们在ResponseAdvisor中改变了返回值的类型。如果HttpMessageConverter处理的目标类型是Object还好说,如果是其它类型就会出现问题,其中最容易出现问题的就是String类型,因为在所有的HttpMessageConverter实例集合中,StringHttpMessageConverter要比其它的Converter排得靠前一些。我们需要尝试将处理Object类型的HttpMessageConverter放得靠前一些,这可以在一个Configuration类中完成: 在这个方案中,如需要对异常做些特别处理,还可以创建一个ExceptionAdvisor类来完成: 这样还可以根据异常类型来设置返回时的HttpStatus。 就这样。 有朋友在评论区指出问题了,做了些调整,也写了一份示例程序上传到CSDN。有兴趣的可以下载来看看。下载地址如下:点击此处下载。 这是许久之前刚用springboot时写的,现在适应springboot的最新版本存在一些问题。所以稍稍重新调整了下,并将之加入到了最近正在尝试进行的一个系列里面。 新版本的代码可以在GitHub / zhyea上看到。 ##########

    [阅读更多...]
  • SpringBoot探索01 – @Import注解

    Overview Spring中@Import注解最初主要是在配置类中使用,目的是引入其他的配置类(@Configuration)并实现自动注入。 目前Import并不只是支持引入@Configuration注解的类,也支持引入ImportSelector和ImportBeanDefinitionRegistrar接口的实现类,甚至可以引入普通的Java Bean并完成注入。 写了一个简单的应用来进行测试:spring-boot-import。 做些说明。在应用中定义了一个Worker类,应用做的事情就是结合@Import注解用不同的方式注入Worker类的多个Bean实例。 每个Worker Bean的实例通过name进行区分。 代码的一个核心是MyConfig类,代码如下: 这个类中包含@Configuration注解,说明是一个配置类,Spring会自动注入这个类的实例。此外这个类还通过@Bean注解注入了一个Worker Bean实例“tom”,又通过@Import接口引用三个其他类,目的是尝试注入其他的Worker Bean实例。最后在WorkerService中尝试获取并逐行打印注入的Worker实例: 接下来详细介绍下这个过程中是如何使用@Import接口的。 引入普通的Java Bean MyConfig类中使用@Import注解注入的MyAnotherConfig类没有继承任何超类或实现任何接口: 可以看到,如果不是内部的一个方法使用了@Bean注解,它就是一个普通的Java Bean了。也是通过这个@Bean注解,实现了另一个Worker Bean的注入。 引入ImportSelector实现类 根据Spring的文档,ImportSelector的作用是根据一些注解的属性来决定使用哪些@Configuration类。也就是配置类的选择器。通常在spring的引用包中会看到ImportSelector的实现。 因此这里定义了另一个配置类MySelectConfig,不过为了避免当前应用下Spring的自动注入,没有在这个类中添加@Configuration注解。 看起来和前面的MyAnotherConfig是一样的。不过和前例不一样的是:MySelectConfig类的注入是通过MyImportSelector来实现的。 MyImportSelector的实现如下: 这里没有基于AnnotationMetadata进行判定就直接返回了配置类的名称,在实际工作中不是一个好的实践。不过我们这里只是做一个演示,不需纠结太多。 引入ImportBeanDefinitionRegistrar实现类 ImportBeanDefinitionRegistrar与ImportSelector的作用是有着根本上的不同的:ImportSelector的作用是提供配置类;而ImportBeanDefinitionRegistrar的作用则是根据类定义完成相应Bean实例的创建。 通常ImportBeanDefinitionRegistrar多与ClassPathMapperScanner配合使用。ClassPathMapperScanner可以用来扫描指定的package,获取目标类并完成相应实例的创建。具体应用如MyBatis的@Mapper注解的解释。 看下在我们示例中的使用: 在这里通过Worker类的定义创建了一个名为“jerry”的实例。需要注意:这里虽然完成了Worker实例的创建,但是并没有配置任何属性。等在输出注入的Worker Bean的时候我们会看到这个实例的属性都是默认值。 引入Spring Component 使用@Import注解不仅可以引入普通的Java Bean,也可以引入Spring组件类,即需要使用@Component或者@Service等注解标记的类。组建类中通过@Autowired注解引用的其他组件也会被递归引用并注入。 示例应用中的WorkerService类并没有使用任何注解标记,而是在使用的时候通过@Import注解进行的引入。 这样虽然也可以使用,但并不建议这么做。 引入@Configuration注解的类 这个留到最后是因为一开始比较困惑:既然已经有@Configuration注解了,Spring就一定会自动引入这个类的,应该就没必要再使用@Import注解进行引用并注入了。 后来意识到我的想法是有漏洞的:比如一些第三方spring组件包中的配置类,既没有配置packageScan,也没有配置starter,直接使用肯定是不行的。此时使用@Import注解来导入相关的配置类及组件是一个很好地解决方案。 测试 执行WorkerControllerTest类的测试方法all(),观察测试结果,期间会输出我们创建的几个Worker Bean实例: 可以看到一个Worker实例的属性都是默认值,这个实例即是通过ImportBeanDefinitionRegistrar创建的Worker Bean “jerry”。 其他 关于@Import注解的实现原理可以参考 AbstractApplicationContext.refresh -> BeanFactoryPostProcessor -> ConfigurationClassPostProcessor -> ConfigurationClassParser.processImports()。具体就不展开了。 此外,还有另外一个注解@ImportResource主要用来引入xml或groovy配置文件。

    [阅读更多...]
  • Spring Controller层测试 – 01 概述

    Spring Controller层(Web层或API层)的测试有多种方案。有人倾向于使用纯单元测试,有人则倾向于使用集成测试。 单元测试和集成测试 先来看一下单元测试和集成测试的概念。 单元测试 单元测试是对软件中的最小可验证单元进行检查和验证。比如对Java中的类和方法的测试。 测试原则: 尽可能保证测试用例相互独立(测试用例中不能直接调用其他类的方法,而应在测试用例中重写模拟方法); 此阶段一般由软件的开发人员来实施,用以检验所开发的代码功能是否符合自己的设计要求 单元测试的作用: 可以尽早的发现缺陷; 利于重构; 简化集成; 辅助文档; 辅助设计。 单元测试的不足: 每个测试单元至少需要3~5行代码,存在投入与产出的平衡。 集成测试 集成测试是根据集成测试计划,一边将模块或其他软件单位组合成越来越大的系统,一边运行该系统,以分析所组成的系统是否正确,各组成部分是否合拍。集成测试的策略主要有自顶向下和自底向上两种。 集成测试的作用: 目的是覆盖更多的执行路径; 发现单元之间接口的错误; 发现集成后的软件同软件需求不一致的地方。 不足在于: 关注的粒度较大,控制起来比较困难 比较来说单元测试更加重视过程,而集成测试更加重视结果。 Spring提供的Controller层测试方案 我们可以在不运行web server的情况下直接对Controller的逻辑进行测试。要执行这种测试,就需要mock服务器的行为,因此也有可能在测试中漏掉一些内容。不过无需担心,我们可以在集成测试中覆盖可能会漏掉的那部分内容。 当然也可以启动一个WebServer来执行Controller层的测试。这样就可以加载整个应用的Context,因此这种方案也可以被视为一种较重的方案。 在这里介绍三种Spring Controller层的测试方案: 在standalone模式下使用MockMVC方式; 结合SpringRunner使用MockMVC方式; 结合SpringBootTest和RestTemplate进行测试。 通常在执行单元测试的时候使用MockMVC方案,在进行集成测试的时候则建议使用RestTemplate方案。原因是使用MockMVC方式可以对Controller进行细粒度的断言,而RestTemplate则是能够使用Spring的WebApplicationContext(部分或全部)。 示例程序 为了便于说明,写了一个简单的SpringBoot Application来进行演示。 在应用中有如下几个类: WorkerController:WEB接口类; WorkerControllerAdvisor:Controller异常统一处理类,这里捕获了NonExistException并修改返回的status为404; WorkerFilter:过滤器类,在response中添加了header值X-CHOBIT-APP=chobit-header IWorkerService:服务接口类; WorkerServiceImpl:服务实现类,模拟了数据库操作。 示例代码可在CSDN下载,地址:https://download.csdn.net/download/tianxiexingyun/11065824 接下来分几篇文章来介绍几种测试方案。 Spring Controller测试 – 01 概述 Spring Controller测试 – 02 Standalone MockMVC Spring Controller测试 – 03 WebContext & MockMVC Spring Controller测试 – 04 SpringBootTest & MockMVC Spring Controller测试 – 05 SpringBootTest & WebServer

    [阅读更多...]