Spring如何重写内置Bean(Controller、Service等)
作者:catoop
本文介绍了在Spring Boot工程中处理外部JAR包中Controller方法重写的需求,通过PostProcessor方式和自定义注解@ExcludeBean两种方法,解决了在不修改源代码的情况下重写接口的问题
Spring重写内置Bean(Controller、Service等)
场景
svc1.jar中有一个TestController类,当前源代码工程 demo 依赖了这个svc1.jar。- 当前 demo 工程启动正常,TestController 被初始化,
TestController中提供的接口/svc1/test1、/svc1/test2、/svc1/test3均可以正常访问。 - 因接口
/svc1/test2对应的方法中逻辑处理不满足需求,需要对这个方法进行重写处理。 - 该接口在
svc1.jar包,不能直接修改源代码。
处理方式一(简单处理)
主要思路是在 Spring 注册 Bean 之后进行 PostProcessor 时,将已经注册的 svc1.jar 中的 TestController 从上下文中删除。
使我们新创建的继承 TestController 的重写类能被正常加载实例化,而不出现 PathMapping 重复的报错冲突。
主要代码如下:
/**
* 排除容器中的Bean
*
* @author 单红宇
* @since 2024/11/28 13:14
*/
@Slf4j
@Configuration
public class ExcludeComponentConfiguration implements BeanDefinitionRegistryPostProcessor {
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
registry.removeBeanDefinition(getDefaultBeanName(FileController.class));
registry.removeBeanDefinition(getDefaultBeanName(UserController.class));
}
/**
* 根据类 class 获取 spring 默认的 beanName
*
* @param clazz clazz
* @return String
*/
private String getDefaultBeanName(Class<?> clazz) {
// 测试内部类 AutowireUtils.ObjectFactoryDelegatingInvocationHandler 的默认 BeanName
// StringUtils.uncapitalizeAsProperty(ClassUtils.getShortName(AutowireUtils.ObjectFactoryDelegatingInvocationHandler.class.getName()))
// spring 6.0 之前使用 StringUtils.uncapitalize()
return org.springframework.util.StringUtils.uncapitalizeAsProperty(ClassUtils.getShortName(clazz.getName()));
}
}
/**
* 重写FileController类
*
* @author 单红宇
* @since 2024/11/28 12:03
*/
@RestController //该注解必须要有,Mapping那些注解不需要
public class OverrideFileController extends FileController {
@Override
public void fileDownload(@RequestParam("fileId") Long fileId,
@RequestParam("isInline") Integer isInline,
HttpServletResponse response, HttpServletRequest request) {
System.out.println("override filedownload...");
}
}
处理方式二(自定义注解)
我们可以自定义一个注解 @ExcludeBean,然后使用该注解来很方便的在任何地方来排除需要排除的类。
1、自定义注解
/**
* 自定义注解,用来排除原本会在spring上下文中的bean。
* 一般用于删除一个不可以修改的Bean,然后自定义一个类继承原有类然后重写特定的方法取代原来的类
*
* @author 单红宇
* @since 2024/12/5 9:36
*/
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface ExcludeBean {
Class<?>[] clazz() default {};
String[] name() default {};
}2、自动配置类
/**
* 排除 Spring Bean 自动配置类
* @author 单红宇
* @since 2024/12/5 9:13
*/
@AutoConfiguration
public class ExcludeBeanAutoConfiguration implements BeanDefinitionRegistryPostProcessor {
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
// not implemented
}
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
if (beanFactory instanceof BeanDefinitionRegistry bdr) {
Arrays.stream(beanFactory.getBeanNamesForAnnotation(ExcludeBean.class))
.map(item -> beanFactory.findAnnotationOnBean(item, ExcludeBean.class, false)).filter(Objects::nonNull)
.flatMap(item -> Stream.concat(Arrays.stream(item.name()),
Arrays.stream(item.clazz()).map(cls ->
org.springframework.util.StringUtils.uncapitalizeAsProperty(ClassUtils.getShortName(cls.getName())))))
.distinct()
.forEach(bdr::removeBeanDefinition);
}
}
}3、配置自动配置
@AutoConfiguration 自动配置注解启用的方法详见文章: SpringBoot 自动配置 @AutoConfiguration 在 2.7 版本新增
4、注解使用示例
我们来重写一个 Controller 的方法,主要应用场景是:目标 Controller 是通过 jar 包依赖过来的,并且我们需要重写其中的一个方法逻辑。
/**
* 重写 Controller 类 TestApi 中的特定方法
*
* @author 单红宇
* @since 2024/11/28 9:06
*/
@ExcludeBean(clazz = TestApi.class)
@RestController
public class OverrideTestApi extends TestApi {
/**
* 重写 Hello 方法
*
* @return String
*/
@Override
public String hello() {
return "Hello, Override!";
}
}补充
如果是基于一个接口然后有多个实现类的多实例场景,使用的地方是基于接口注入的。
也可以使用 @Primary 注解来修饰特定的类。
但是对于如 Controller 以及其他没有定义并通过接口注入方式的可能就要使用我们本文的方法了。
总结
以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。
