怎么“访问”Spring容器管理的bean?

时间:2024-04-12 10:20:50

目录

  • 一、前言
  • 二、使用场景
    • 1、场景demo
    • 2、在上述场景demo中,直接注入依赖并不是最佳实践
  • 三、获取Spring容器的方法
    • 1、覆写ApplicationContextAware的setApplicationContext方法
    • 2、直接通过@Autowired注入ApplicationContext

一、前言

  • 一种方式:注入依赖【告知容器,我需要某个bean】
    • 正确使用@Resource
    • 正确使用@Autowired
  • 另一种方式:注入容器,直接“访问”容器中的bean。

二、使用场景

1、场景demo

public interface IModel {
    String acquireModelName();

    void sayHello();
}

@Component
public class GeminiModel implements IModel {
    @Override
    public String acquireModelName() {
        return ModelEnum.GEMINI.name();
    }

    @Override
    public void sayHello() {
        System.out.println("Hello, I am GeminiModel");
    }
}

@Component
public class GptModel implements IModel {
    @Override
    public String acquireModelName() {
        return ModelEnum.GPT.name();
    }

    @Override
    public void sayHello() {
        System.out.println("Hello, I am GptModel");
    }
}

@Component
public class ModelManager {
    @Autowired
    private List<IModel> models;

    public IModel acquireModel(String modelName) {
        if (CollectionUtils.isNotEmpty(models)) {
            return models.stream()
                    .filter(model -> StringUtils.equals(modelName, model.acquireModelName()))
                    .findFirst()
                    .orElseThrow(() -> new RuntimeException("no model"));
        } else {
            throw new RuntimeException("no model");
        }
    }
}

public enum ModelEnum {
    GPT, GEMINI;
}

@ComponentScan
public class Application {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(Application.class);
        ModelManager modelManager = applicationContext.getBean(ModelManager.class);
        IModel model = modelManager.acquireModel(ModelEnum.GPT.name());
        model.sayHello();
    }
}

2、在上述场景demo中,直接注入依赖并不是最佳实践

  • 我实际需要的是:
Map<String, IModel> modelMap;

key: modelName
  • 虽然Spring可以注入Map,但是,key为bean的默认名字。【详见:正确使用@Autowired
  • 因此,我更好的做法是:先从Spring容器中获取IModel的实现类的bean,然后,建立modelMap。
  • 代码:
@Component
public class ModelManager implements ApplicationContextAware {
    private static final Map<String, IModel> MODEL_MAP = new HashMap<>();
    private ApplicationContext applicationContext;

    @PostConstruct
    private void buildModelMap() {
        Map<String, IModel> beansOfType = applicationContext.getBeansOfType(IModel.class);
        beansOfType.forEach((k, v) -> MODEL_MAP.put(v.acquireModelName(), v));
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    public IModel acquireModel(String modelName) {
        return Optional.ofNullable(MODEL_MAP.get(modelName))
                .orElseThrow(() -> new RuntimeException("no model"));
    }
}

三、获取Spring容器的方法

1、覆写ApplicationContextAware的setApplicationContext方法

  • 如上文所述,通过该方法可以获取到Spring容器(applicationContext)
  • 但有个难受的问题:
setApplicationContext方法必须是public。覆写方法,访问修饰符不能变“小”。
接口定义的setApplicationContext方法的访问修饰符是public,那么覆写时,只能用public。

这就意味着,使用modelManager时,可以把applicationContext设置为null。
虽然对上文没影响(因为获取modelManager时,applicationContext已经被使用过了,后续不会再用了),但是,一旦后续还会使用,那么就会有npe风险。

public IModel acquireModel(String modelName) {
	System.out.println(applicationContext.getDisplayName()); // 这里会npe
	...
}
@PostConstruct注解就很香,作用的方法[buildModelMap()]的访问修饰符可以是private

2、直接通过@Autowired注入ApplicationContext

@Component
public class ModelManager {
    private static final Map<String, IModel> MODEL_MAP = new HashMap<>();

    @Autowired
    private ApplicationContext applicationContext;

    @PostConstruct
    private void buildModelMap() {
        Map<String, IModel> beansOfType = applicationContext.getBeansOfType(IModel.class);
        beansOfType.forEach((k, v) -> MODEL_MAP.put(v.acquireModelName(), v));
    }

    public IModel acquireModel(String modelName) {
        return Optional.ofNullable(MODEL_MAP.get(modelName))
                .orElseThrow(() -> new RuntimeException("no model"));
    }
}