Spring @Autowired注入太坤肋了 我们自己写一个

news2024/11/29 8:52:44

1、 背景

众所周知该注解是Spring中用于依赖注入的注解,但是该注解只是简单根据类型的注入, 并且如果该类型存在多个实现类的情况下无法抉择具体哪一个实现类就会抛出错误,除非搭配@Qualifier注解然后使用硬编码的方式去指定Bean的名字去抉择哪个实现类。但是很明显这很不合理, 因为常常我们写代码写着写着我们的类图可能就会变成如下面的的结构,这时我们想注入AService 就必须再加上 @Qualifier("AService") 才能注入成功。

在这里插入图片描述

2、扩展特性

为了实现我们自己的@Autowired 我们先想一下可以扩展哪些好玩的功能

  • 1、当要注入的类不是抽象类和接口则直接注入该类
  • 2、当要注入的类是抽象类或者接口,则增加一个type参数去指定具体注入哪个实现类
  • 3、支持根据从配置文件获取要注入的实现类。 并且可以是全路径类名或者是beanName。
  • 4、增加自定义注入策略用该策略的结果进行注入

最终我们的自定义注解@SuperAutowired如下:

@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface SuperAutowired {

    /**
     * 从类型注入Bean
     */
    Class<?> type() default Void.class;

    /**
     * 从配置文件等中获取该属性值作为Bean注入
     */
    String propValue() default "";

    /**
     * 自定义注入Bean
     */
    Class<? extends SuperAutowiredProxy>[] proxy() default {};

    String extData() default "";
}

3、 与Spring集成

那么如何像 @Autowired注解一样,可以在Spring创建Bean对象后也对标记了@SuperAutowired进行依赖注入? 这时候就轮到我们的Spring扩展点BeanPostProcessor, 它可以在Bean实例化之后做一些对Bean的处理,我们拿到这个Bean后就可以自行对其属性值进行赋值或者修改了。 Spring每个Bean被实例化之后就获取所有BeanPostProcessor然后回调其中的方法把Bean给到我们。

其实@Autowired 也是用的自定义BeanPostProcessor去实现的 就是 AutowiredAnnotationBeanPostProcessor#postProcessProperties

这里我们直继承BeanPostProcessor, 然后实现postProcessAfterInitialization方法即可, 这个方法会在bean实例化之后并且执行对象的初始化方法之后被回调。 然后整体逻辑也很简单,无非就是获取这个bean的所有属性判断是否包含我们的注解,如果包含则执行我们的注入逻辑即可。这里写的业务逻辑与我上面写的特性相同。

@Component
public class SuperAutowiredBeanPostProcessor implements BeanPostProcessor, ApplicationContextAware {

    private ApplicationContext applicationContext;
    
    /**
     * @param bean          容器当前实例化的Bean
     * @param beanName      该Bean的名字
     */
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        ReflectionUtils.doWithFields(bean.getClass(),field -> {
            injectBean(bean, field);
        });
        return bean;
    }

    private void injectBean(Object bean, Field field) {
        SuperAutowired myAutowiredAnnotation = field.getAnnotation(SuperAutowired.class);
        if (myAutowiredAnnotation == null){
            return;
        }

        Object beanValue = null;
        ReflectionUtils.makeAccessible(field);

        // 自定义注入逻辑
        if (beanValue == null){
            beanValue = proxyGetBean(field, myAutowiredAnnotation);
        }

        // 配置属性注入
        if (beanValue == null){
            beanValue = propertiesGetBean(field, myAutowiredAnnotation);
        }

        // 类型注入
        if (beanValue == null){
            beanValue = autowireGetBean(field, myAutowiredAnnotation);
        }

        if (beanValue == null){
            throw new RuntimeException("无法找到对应的实现类");
        }

        ReflectionUtils.setField(field, bean,beanValue);
    }

    private Object proxyGetBean(Field field, SuperAutowired myAutowiredAnnotation) {
        if (myAutowiredAnnotation.proxy().length <= 0) {
            return null;
        }
        Class<? extends SuperAutowiredProxy> proxyClass = myAutowiredAnnotation.proxy()[0];
        SuperAutowiredProxy proxy = applicationContext.getBean(proxyClass);
        return proxy.getBean(myAutowiredAnnotation, applicationContext,field.getType());
    }

    private Object propertiesGetBean(Field field, SuperAutowired myAutowiredAnnotation) {
        if ("".equals(myAutowiredAnnotation.propValue())){
            return null;
        }

        Environment environment = applicationContext.getEnvironment();
        String className = environment.getProperty(myAutowiredAnnotation.propValue());
        if (className == null || Objects.equals(className, "")){
           throw new RuntimeException("该属性值" + myAutowiredAnnotation.propValue() + "为空无法找不到对应的实现类");
        }

        if (className.contains(".")){
            // 当类名处理
            Class<?> beanClass = loadClass(className);
            return getSameBean(beanClass);
        }else {
            // 当bean类处理
            return applicationContext.getBean(className);
        }
    }

    private Class<?> loadClass(String className) {
        try {
            Class<?> beanClass = ClassUtils.getDefaultClassLoader().loadClass(className);
            return beanClass;
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        }
    }


    private Object autowireGetBean(Field field, SuperAutowired myAutowiredAnnotation) {
        Object beanValue;
        Class<?> fieldType = field.getType();
        if (fieldType.isInterface() || Modifier.isAbstract(fieldType.getModifiers())){
            // 如果是接口按 传统autowired注入. 但是可能会有多个实现类. 一般只有一个实现类就不用指定 type了
            try {
                beanValue = applicationContext.getBean(fieldType);
            } catch (NoUniqueBeanDefinitionException e) {
                Class<?> type = myAutowiredAnnotation.type();
                if (type != Void.class){
                    beanValue = getSameBean(type);
                }else{
                    throw e;
                }
            }
        }else{
            // 如果是具体类则具体注入
            beanValue = getSameBean(fieldType);
        }
        return beanValue;
    }

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


    private Object getSameBean(Class<?> fieldType) {
        Map<String, ?> beansOfType = applicationContext.getBeansOfType(fieldType);
        if (beansOfType.size() <= 0){
            throw new RuntimeException("没有找到实现类: " + fieldType.getName());
        }

        for (Object value : beansOfType.values()) {
            if (isSameClass(value.getClass(), fieldType)) {
                return value;
            }
        }
        throw new RuntimeException("有多个实现类请指定: " + fieldType.getName());
    }

    public boolean isSameClass(Class<?> class1, Class<?> class2) {
        if (class1 == class2) {
            return true;
        }
        if (class2.isAssignableFrom(class1) || class1.isAssignableFrom(class2)) {
            return false;
        }
        return false;
    }
}

/**
 *  自定义注入Bean策略
 */
public interface  SuperAutowiredProxy {

    Object getBean(SuperAutowired superAutowired,
                   ApplicationContext atx,
                   Class<?> clazz);
}

4、测试

4.1 准备

自行先建立几个测试的Bean,然后自行扩展自定义注入的策略. 然后在配置文件配置值方便测试

alolication.yml配置文件

myBean:
  # 类名
  iService: com.burukeyou.b9_spring_ioc.service.AserviceForCook
  # bean Name
  iService2: AServiceForRank
public interface IService {
}

@Component
public class AService implements IService {

}

@Component
public class BService implements AService {

}
@Component
public class CService implements AService {

}

@Component
public class MyIServiceProxyBean implements SuperAutowiredProxy, InvocationHandler {
    
    @Override
    public Object getBean(SuperAutowired superAutowired,
                          ApplicationContext atx,
                          Class<?> clazz) {
        String extData = superAutowired.extData();

        //
        if ("db".equals(extData)){
            // 比如: 从db加载要注入的bean
            String beanName = .... 省略......;
            return atx.getBean(beanName);
        }else {
            // 动态代理生成该Bean
            if (clazz.isInterface()){
                return Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[]{clazz}, this);
            }
        }

        return null;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 过滤掉Object类的方法
        if (method.getDeclaringClass() == Object.class) {
            return method.invoke(this, args);
        }

        System.out.println("MyIServiceProxyBean pre invoke: " + method.getName());
        return null;
    }
}

@SuperAutowired注入使用案例

@Component
@Data
public class MyComponent {

    @SuperAutowired(type = AServiceForRank.class)
    private IService a;

    @SuperAutowired
    private AService b;
    
    @SuperAutowired
    private BService c;
    
    @SuperAutowired
    private CService d;

	// 从配置读取注入
    @SuperAutowired(propValue = "myBean.iService")
    private IService e;

    @SuperAutowired(propValue = "myBean.iService2")
    private IService f;

    @SuperAutowired(proxy = MyIServiceProxyBean.class)
    private IService g;

4.2 单测:

  • 没起SpringBoot环境,手动创建Spring上下文简单测试下。 有SpringBoot环境直接启动测试即可。
    @Test
    public void testMain() throws IOException {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
        ConfigurableEnvironment environment = context.getEnvironment();
        
        // 加载alolication.yml配置到容器
        List<PropertySource<?>> config = new YamlPropertySourceLoader()
                .load("config", new ClassPathResource("alolication.yml"));
        context.getEnvironment().getPropertySources().addLast(config.get(0));
        context.setEnvironment(environment);
        
        // todo 扫描包并启动容器  ---你的bean的包位置
        context.scan("com.burukeyou.b9_spring_ioc");
        context.refresh();

        // 测试此对象能否依赖注入成功
        MyComponent myComponent = context.getBean(MyComponent.class);

        context.close();
    }

最后

至此我们就对@Autowired进行了增强, 如果需要还可自行扩展更加强大的功能。 比如我们可以增加refresh功能, 比如可以缓存每个注入的实例, 然后我们可以监听某些配置或数据库的变化一键去重新替换该实例注入的值(就像Nacos刷新机制)。 再比如我们可以增加支持配置class文件本地或者网络位置,然后我们自定义ClassLoader去读取该class文件生成class对象到JVM中,然后实例化再去注入。 再比如。。。。如果还什么可扩展的好玩的实用的搞怪的功能欢迎留下你的评论。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/646351.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

8年测试岗总结,软件测试常见面试题+答案,轻松避坑...

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 1、介绍一下测试流…

JAVA开发与运维(怎么通过docker部署微服务jar包)

目标&#xff1a; 通过docker的方式部署微服务。 一、背景&#xff1a; 我们通过java开发的微服务可以打成jar包&#xff0c;我们可以直接通过裸机部署&#xff0c;也可以通过docker来部署&#xff0c;本文介绍通过docker来部署微服务。 二、首先我们介绍一下docker的发展过程…

Redis高级篇—分布式缓存 持久化 主从 哨兵 分片

分布式缓存 -- 基于Redis集群解决单机Redis存在的问题 单机的Redis存在四大问题&#xff1a; 0.学习目标 1.Redis持久化 Redis有两种持久化方案&#xff1a; RDB持久化 AOF持久化 1.1.RDB持久化 RDB全称Redis Database Backup file&#xff08;Redis数据备份文件&#xff09;&a…

jetBrian_工具的使用

文章目录 Intellij IDEA新建一个IDEA项目新建Project - ClassJDK相关设置out目录和编译版本 详细设置&#xff08;感觉有用的设置&#xff09;打开是否选择项目取消自动更新设置整体主题设置菜单和窗口字体和大小设置IDEA背景图设置编辑器主题样式注释的字体颜色代码智能提示功…

Java面试知识点(全)-分布式微服务-kafka面试知识点

Java面试知识点(全) 导航&#xff1a; https://nanxiang.blog.csdn.net/article/details/130640392 注&#xff1a;随时更新 以下是一些Kafka面试题和答案&#xff1a; 文章目录 什么是Kafka&#xff1f;Kafka主题(topic)和分区(partition)有什么作用&#xff1f;Kafka的重复数…

科一 容易忘,容易混的点(三)

年龄问题 考题&#xff1a; 申请大中型客货车驾驶证年龄不得超过 &#xff1f; 公交 、 中客、大货&#xff1a; 20 大客、重型牵引&#xff1a; 22岁 C1,C2,C5 : 18岁 C6不考倒车入库 记分转入下一周期 不会清零&#xff0c;必须前去接受处罚。不然下一周期你还是扣1…

echarts象形柱图实现电量效果柱状图

首先听到象柱形图pictorialBar&#xff0c;你会不会觉得很陌生&#xff1f; 然后&#xff0c;看看官网的示例&#xff0c;显示效果很抽象&#xff0c;觉得很不实用&#xff0c;真的有人会用到吗&#xff1f; 但是&#xff0c;我再让你看看下图&#xff0c;类似电量格效果的柱状…

访问 docker 容器的 tensorboard

demo代码示例 tensorboard --logdirlog_path :return:from torch.utils.tensorboard import SummaryWriterlogger SummaryWriter(log_dir./log/boardtest/)loss [5.5, 4.1, 4.2, 3.2, 3.3, 2.9, 2.5, 1.2, 0.8, 0.6] steps [1000, 2000, 3000, 4000, 5000, 6000, 7000, 8000…

Spark SQL数据源:JDBC

文章目录 一、Spark SQL读取关系数据库二、Spark SQL JDBC连接属性三、创建数据库与表&#xff08;一&#xff09;创建数据库&#xff08;二&#xff09;创建学生表&#xff08;二&#xff09;创建成绩表 四、读取和写入数据库表&#xff08;一&#xff09;利用dbtable属性读取…

APScheduler任务调度快速入门实践

什么是APScheduler APScheduler是一个用于任务调度和定时任务管理的Python库。它提供了一个简单而灵活的方式来定义、调度和执行任务。 APScheduler的架构由以下几个核心组件组成&#xff1a; 调度器&#xff08;Scheduler&#xff09;&#xff1a;调度器负责管理任务的调度和…

初探core组件:OpenCV数据结构与基本绘图

OpenCV数据结构与基本绘图 1. 基础图像容器 Mat 1.1 数字图像存储概述 我们有多种方法从现实世界获取数字图像&#xff1a;数码相机、扫描仪、计算机断层扫描和磁共振成像等等。在每一种情况下&#xff0c;我们&#xff08;人类&#xff09;看到的都是图像。然而&#xff0c…

不可错过的Markdown编辑利器:跟随Typora,书写卓越

在现代社会中&#xff0c;快速而优雅地处理文本已经成为许多人的必备技能。尤其是对于程序员、研究者和专业撰稿人来说&#xff0c;拥有一款功能强大且易上手的文本编辑器显得尤为重要。Typora正是这样一款受到广泛好评的Markdown编辑器&#xff0c;它以简洁的界面、丰富的功能…

《实战AI低代码》AI大模型在低代码开发项目管理中的实战经验总结

目录 一、WBS任务分解 二、知识库自动生成 三、实施风险预估 随着ChatGPT大火之后,新的AI技术和模型被证明已经具备的很高的使用价值。 诸如Copilot、Midjourney、notion等产品通过AI的加持,已经让用户能够充分地在应用层面感受到了便利性。 原本几天的工作通过AI模型,可…

Web实验三 CSS基本网页布局实验

实验原理 通过定义css样式&#xff0c;理解css属性以及页面真整体结构布局的方法及设计思想。 实验目的 理解并掌握多种css选择器的使用方法 理解并掌握后代选择器的作用及使用设计方法 理解并掌握伪类的作用、意义及使用方法 理解并掌握基于div容器页面布局的方法 理解并掌握…

TANL:STRUCTURED PREDICTION AS TRANSLATION BETWEEN AUGMENTED NATURAL LANGUAGES

原文链接&#xff1a;https://openreview.net/pdf?idUS-TP-xnXI ICLR 2021 介绍 问题 大多数解决结构性预测的方法都是在预训练模型上对特定的任务进行训练&#xff0c;存在两个局限性&#xff1a; 1&#xff09;判别分类器不能很好地利用预训练模型中对于该任务标签的已知知…

这个事实已冲击并颠覆我的认知:时间不多了

我们都知道人生短暂&#xff0c;可到底是怎么个短法&#xff1f; 十年是个模糊的表述&#xff0c;我们很难在脑海里想象十年是什么概念&#xff0c;但如果换成十个冬天&#xff0c;跟父母在一起十天&#xff0c;这样描述就会更直观些。 WaitButWhy对人生的时间进行了拆解&#…

Cesium教程(十九):Cesium粒子系统

Cesium教程(十九):Cesium粒子系统 1、粒子系统 1.1 什么是粒子系统 Cesium粒子系统是一种模拟复杂物理效应的图形技术,是由小图像组成的集合,当他们在一起形成更复杂的“模糊”对象时,会形成火、烟、云或烟火等。 1.2 初始粒子系统 效果预览 完整代码 <!DOCTYPE htm…

day05--java高级编程:Junit单元测试框架、泛型,集合:集合数组互转,迭代器,增强for循环,集合工具类,数据结构简介

补充&#xff1a;Junit单元测试框架 1. 简介 概述&#xff1a; JUnit是使用Java语言实现的单元测试框架&#xff0c;它是开源的&#xff0c;Java开发者都应当学习并使用JUnit编写单元测试。此外&#xff0c;几乎所有的IDE工具都集成了JUnit&#xff0c;这样我们就可以直接在…

CoreDX DDS应用开发指南(9)服务质量QoS

12 服务质量QoS DDS的强大功能之一是支持各种服务质量(QoS)设置。QoS设置允许应用程序开发人员定制发布者、订阅者的行为以及它们之间的通信。 从DomainParticipantFactory到DataReader和DataWriter,大多数DDS实体都有一组适用的QoS设置。QoS设置包含在一个结构中。 例如,D…

【Flutter】Flutter 如何获取当前路由

文章目录 一、前言二、Flutter 路由基础知识1. 什么是路由2. Flutter 中的路由管理 三、如何在 Flutter 中获取当前路由1. 使用 NavigatorState 类2. 使用 ModalRoute 类 四、代码示例1. 一个简单的获取当前路由的例子2. 实际业务场景中获取当前路由的例子 五、完整可运行的代码…