背景
在spring boot工程中,@FeignClient和@RequestMapping可能被错误得加到同一个方法上,当用户这么使用的情况下,如何能够在程序启动的过程中警告用户并抛出异常呢?本文将给出这个问题的解决方法。
思路
- 通过提供一个jar包,任何引入该jar包的应用在启动过程中自动执行注解使用检查
- jar包需要添加一个Listener,监听应用启动的过程,执行检查工作
步骤
- 实现注解检查Listener
- 启动spring boot自动配置
- 目标应用启动@EnableAutoCongiguration自动配置
代码
Listener定义
@Component
@ConditionalOnClass(FeignClient.class)
public class AnnotationCheckListener implements ApplicationListener<ContextRefreshedEvent> {
@Autowired
public ApplicationContext applicationContext;
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
String[] beanNamesForAnnotation = applicationContext.getBeanNamesForAnnotation(RequestMapping.class);
Arrays.stream(beanNamesForAnnotation)
.filter(beanName -> applicationContext.findAnnotationOnBean(beanName, FeignClient.class) != null)
.forEach(beanName -> throwAnnotationConfigurationException(beanName));
}
static void throwAnnotationConfigurationException(String beanName) {
throw new AnnotationConfigurationException("Cannot have both @RequestMapping and @FeignClient on " + beanName);
}
}
配置spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.swagger.starter.AnnotationCheckListener
总结
SpringFactoriesLoader
loads and instantiates factories of a given type from "META-INF/spring.factories" files which may be present in multiple JAR files in the classpath. The
SpringFactoriesLoaderspring.factories
file must be inProperties
format, where the key is the fully qualified name of the interface or abstract class, and the value is a comma-separated list of implementation class names.- @EnableAutoCongiguration Spring Boot提供的
@EnableAutoCongiguration
功能很强大,所有的配置似乎都被包含进来而无需开发者显式声明。EnableAutoConfigurationImportSelector
使用的是spring-core
模块中的SpringFactoriesLoader#loadFactoryNames()
方法,它的作用是在类路径上扫描并加载META-INF/spring.factories
文件中定义的类。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import({ EnableAutoConfigurationImportSelector.class,
AutoConfigurationPackages.Registrar.class })
public @interface EnableAutoConfiguration {
/**
* Exclude specific auto-configuration classes such that they will never be applied.
*/
Class<?>[] exclude() default {};
}
备选
@Component
@ConditionalOnClass(FeignClient.class)
@Slf4j
public class FeignClientBeanProcessor implements BeanPostProcessor{
@Override
public Object postProcessBeforeInitialization(Object o, String s) throws BeansException {
return null;
}
@Override
public Object postProcessAfterInitialization(Object bean, String s) throws BeansException {
final Class<?> beanClass = bean.getClass();
if(beanClass.isAnnotationPresent(RequestMapping.class) && beanClass.isAnnotationPresent(FeignClient.class)) {
log.error("RequestMapping and FeignClient annotation could not be added to the same class.");
return new BeanCreationException("RequestMapping and FeignClient annotation could not be added to the same " +
"class");
}
return null;
}
}