1、直接在自己工程中建同包同类名的类进行替换

方式简单粗暴,可以直接覆盖掉jar包中的类,spring项目会优先加载自定义的类。

1.1 替换实现

第三方jar

下面是覆盖 third-bean.jar中的一个类 MyConfig,主要是想修改它里面的getProjectName方法的业务逻辑。

java 重写jar 方法为什么不走 重写jar包方法_java 重写jar 方法为什么不走

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即可。

java 重写jar 方法为什么不走 重写jar包方法_ide_02

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 接口访问

java 重写jar 方法为什么不走 重写jar包方法_java_03

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

访问结果如下,替换成功。

java 重写jar 方法为什么不走 重写jar包方法_java 重写jar 方法为什么不走_04

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 - 添加香蕉