1、直接在自己工程中建同包同类名的类进行替换
方式简单粗暴,可以直接覆盖掉jar包中的类,spring项目会优先加载自定义的类。
1.1 替换实现
第三方jar
下面是覆盖 third-bean.jar中的一个类 MyConfig,主要是想修改它里面的getProjectName方法的业务逻辑。
package com.ybw.config;
import org.springframework.context.annotation.Configuration;
/**
* @program: spring-replace-bean
* @description:
* @author: geoffrey
* @create: 2022-06-15 23:14
*/
@Configuration
public class MyConfig {
/**
* @methodName: getProjectName
* @return: java.lang.String
* @author: ybwei
* @date: 2022/6/15
**/
public String getProjectName() {
return "third-bean";
}
}
替换
包路径为 com.ybw.config, 直接工程里面新建一样路径一样类名MyConfig即可。
package com.ybw.config;
/**
* @program: spring-replace-bean
* @description:
* @author: geoffrey
* @create: 2022-06-15 23:14
*/
public class MyConfig {
/**
* @methodName: getProjectName
* @return: java.lang.String
* @author: ybwei
* @date: 2022/6/15
**/
public String getProjectName() {
return "current-bean";
}
}
1.2 接口访问
package com.ybw.controller;
import com.ybw.config.MyConfig;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
/**
* @program: spring-replace-bean
* @description:
* @author: geoffrey
* @create: 2022-06-15 23:20
*/
@RestController
@Slf4j
public class TestController {
@Resource
private MyConfig myConfig;
@GetMapping("/getProjectName")
public String getProjectName() {
return myConfig.getProjectName();
}
}
访问地址:http://localhost:8080/getProjectName
访问结果如下,替换成功。
2、采用@PRIMARY注解
该方法适用于接口实现类,自己创建一个原jar包接口的实现类,然后类上加上@Primary注解,spring则默认加载该类实例化出的Bean。
下面的例子: 一个接口 SportService,原先jar包中只有一个实现类 FootballServiceImpl,现在自己工程里面创建一个 BasketballServiceImpl 继承SportService接口,然后发现所有调用SportService接口里面的方法实际调用走的是BasketballServiceImpl 里面的方法。
package com.ybw.service;
/**
* @program: spring-replace-bean
* @description:
* @author: geoffrey
* @create: 2022-06-18 18:01
*/
public interface SportService {
/**
* 打印运动名称
*
* @methodName: printName
* @return: void
* @author: ybwei
* @date: 2022/6/18
**/
void printName();
}
package com.ybw.service.impl;
import com.ybw.service.SportService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Service;
/**
* @program: spring-replace-bean
* @description:
* @author: geoffrey
* @create: 2022-06-18 18:05
*/
@Service
@Slf4j
@Primary
public class BasketballServiceImpl implements SportService {
@Override
public void printName() {
log.info("运动项目名称:篮球");
}
}
3、 排除需要替换的JAR包中的类
使用 @ComponentScan 里面的 excludeFilters 功能排除调用要替换的类,然后自己写个类继承替换的类即可。
下面的例子是替换掉 jar包中的MyServiceImpl类
package com.ybw;
import com.ybw.service.impl.MyServiceImpl;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.FilterType;
@SpringBootApplication
@ComponentScan(excludeFilters = {@ComponentScan.Filter(type =
FilterType.ASSIGNABLE_TYPE, classes = {MyServiceImpl.class})})
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
package com.ybw.service.impl;
import com.ybw.service.MyService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
@Service
@Slf4j
public class MyTestServiceImpl implements MyService {
@Override
public String getProjectName() {
return "current-bean";
}
}
4、@BEAN 覆盖
该场景针对,框架jar包中有@ConditionalOnMissingBean注解,这种注解是说明如果你也创建了一个一样的Bean则框架就不自己再次创建这个bean了,这样你可以覆写自己的bean。
package com.ybw.config;
import com.ybw.dto.UserDTO;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class MyBeanConfig {
@Bean
@ConditionalOnMissingBean
public UserDTO userDTO(){
return new UserDTO(1L,"张三");
}
}
直接继承要覆盖的类,自己重写里面方法,使用@Component注入到spring中去
package com.ybw.dto;
import com.alibaba.fastjson2.JSON;
import lombok.Data;
import org.springframework.stereotype.Component;
/**
* @author ybw
* @version V1.0
* @className MyUserDTO
* @date 2022/6/16
**/
@Component
@Data
public class MyUserDTO extends UserDTO {
private Long no;
@Override
public String toJson() {
super.id = 2L;
super.name = "李四";
this.no = 50L;
return JSON.toJSONString(this);
}
}
spring注解扩展
- @ConditionalOnBean // 当给定的在bean存在时,则实例化当前Bean
- @ConditionalOnMissingBean // 当给定的在bean不存在时,则实例化当前Bean
- @ConditionalOnClass // 当给定的类名在类路径上存在,则实例化当前Bean
- @ConditionalOnMissingClass // 当给定的类名在类路径上不存在,则实例化当前Bean
5、使用BEANDEFINITIONREGISTRYPOSTPROCESSOR
BeanDefinitionRegistryPostProcessor 说白了就是可以在初始化Bean之前修改Bean的属性,甚至替换原先准备要实例化的bean。
5.1 示例
假设jar包中有一个类 AppleProductServiceImpl,正常情况下它会被spring自动扫描到注入IOC容器中去。
package com.ybw.service.impl;
import com.ybw.service.ProductService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
/**
* @author ybw
* @version V1.0
* @className AppleProductServiceImpl
* @date 2022/6/17
**/
@Service("productService")
@Slf4j
public class AppleProductServiceImpl implements ProductService {
@Override
public void addProduct() {
log.info("添加苹果");
}
}
自己工程中实现ProductService,并且覆写里面的addProduct中的方法,注意不需要@Service
package com.ybw.service.impl;
import com.ybw.service.ProductService;
import lombok.extern.slf4j.Slf4j;
import javax.annotation.PostConstruct;
@Slf4j
public class BananaProductServiceImpl implements ProductService {
@PostConstruct
public void init() {
log.info("我替换了默认实现");
}
@Override
public void addProduct() {
log.info("添加香蕉");
}
}
然后 实现 BeanDefinitionRegistryPostProcessor 接口,修改原来bean定义,主要查看postProcessBeanDefinitionRegistry方法的实现,先清空原bean定义,注册我们自己的bean定义来达到替换的目的。
package com.ybw.config;
import com.alibaba.fastjson2.JSON;
import com.ybw.service.impl.BananaProductServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
import org.springframework.stereotype.Component;
/**
* 替换bean
*
* @author ybw
* @version V1.0
* @className MyBeanDefinitionRegistryPostProcessor
* @date 2022/6/16
**/
@Component
@Slf4j
public class MyBeanDefinitionRegistryPostProcessor implements BeanDefinitionRegistryPostProcessor {
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanDefinitionRegistry) throws BeansException {
// 注册Bean定义,容器根据定义返回bean
log.info("所有bean:{}", JSON.toJSONString(beanDefinitionRegistry.getBeanDefinitionNames()));
//要被替换的bean
String beanName = "productService";
if (beanDefinitionRegistry.containsBeanDefinition(beanName)) {
// 先移除原来的bean定义
beanDefinitionRegistry.removeBeanDefinition(beanName);
// 注册我们自己的bean定义
BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.rootBeanDefinition(BananaProductServiceImpl.class);
// // 如果有构造函数参数, 有几个构造函数的参数就设置几个 没有就不用设置
// beanDefinitionBuilder.addConstructorArgValue("构造参数1");
// beanDefinitionBuilder.addConstructorArgValue("构造参数2");
// beanDefinitionBuilder.addConstructorArgValue("构造参数3");
// // 设置 init方法 没有就不用设置
// beanDefinitionBuilder.setInitMethodName("init");
// // 设置 destory方法 没有就不用设置
// beanDefinitionBuilder.setDestroyMethodName("destory");
// 将Bean 的定义注册到Spring环境
beanDefinitionRegistry.registerBeanDefinition("myTestRegistryConfig", beanDefinitionBuilder.getBeanDefinition());
}
}
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
// bean的名字为key, bean的实例为value
// Map<String, Object> beanMap = configurableListableBeanFactory.getBeansWithAnnotation(RestController.class);
// log.info("所有 RestController 的bean {}", beanMap);
}
}
测试接口
@Resource
private ProductService productService;
/**
*
* @methodName: getUser2
* @return: java.lang.String
* @author: ybw
* @date: 2022/6/16
**/
@GetMapping("/addProduct")
public String addProduct() {
productService.addProduct();
return "OK";
}
访问接口返回值
[INFO ] 2022-06-17 15:20:46.280 [http-nio-8080-exec-4] c.y.s.impl.BananaProductServiceImpl - 添加香蕉