首页
题库
公司真题
专项练习
面试题库
在线编程
面试
面试经验
AI 模拟面试
简历
求职
学习
基础学习课
实战项目课
求职辅导课
专栏&文章
竞赛
搜索
我要招人
发布职位
发布职位、邀约牛人
更多企业解决方案
AI面试、笔试、校招、雇品
HR免费试用AI面试
最新面试提效必备
登录
/
注册
何人听我楚狂声
字节跳动_抖音_后端开发工程师
关注
已关注
取消关注
#声哥今天更新了吗#
白天把《四月是你的谎言》看完了,心情十分沉重,就放到晚上更新了
@何人听我楚狂声:
手撸一个 Spring —— 2. 基于注解
精华
上一篇中,读取 xml 解析为 BeanDefinition 的类 XmlBeanDefinitionReader 的代码在如下链接:https://github.com/CN-GuoZiyang/My-Spring-IOC/blob/82967670e5/src/main/java/top/guoziyang/springframework/reader/XmlBeanDefinitionReader.java,微信公众号似乎不允许内置链接。 上一节我们已经实现了一个包含基本功能的 IOC 容器,并且也解决了各种依赖问题。没看过上一篇的同学可以在我的讨论帖记录中查找。 这一节我们来对这个框架进行扩展,实现基于注解自动注入 bean 的方式,免去繁琐的配置文件。 本节最终的代码为:https://github.com/CN-GuoZiyang/My-Spring-IOC/tree/8a3a9c640e532c5d4aa8d62f18b42fa336c94f2e 定义注解 首先我们需要定义一些注解类,仿照 Spring,我们定义如下的五个注解:、@Component、@Scope、@Autowired、@Qualifier 和 @Value。他们分别的作用如下: @Component:这个注解用于类,表示这个类是一个需要被注册进容器的 bean @Scope:这个注解用于类,默认值是 "singleton",还可使用 "prototype",标明这个 bean 是单例还是多例的 @Autowired:这个注解用于属性,表示向属性自动注入容器中对应类型的 bean @Qualifier:这个注解用于属性,需要传递一个字符串,表示给这个属性注入对应名称的 bean @Value:这个注解用于属性,表示向这个属性注入某个值(基本类型) 我们来定义注解,如下: @Retention(RetentionPolicy.RUNTIME)@Target(ElementType.TYPE)public @interface Component { String name() default "";} @Retention(RetentionPolicy.RUNTIME)@Target(ElementType.TYPE)public @interface Scope { String value() default "singleton";} @Retention(RetentionPolicy.RUNTIME)@Target(ElementType.FIELD)public @interface Autowired{} @Retention(RetentionPolicy.RUNTIME)@Target(ElementType.FIELD)public @interface Qualifier { String value();} @Retention(RetentionPolicy.RUNTIME)@Target(ElementType.FIELD)public @interface Value { public String value();} 定义注解同样需要一些注解(禁止套娃),@Retention 注解用来标示注解类型的生命周期,由于我们的这些注解都是需要在运行时通过反射获取的,所以这个注解的值都是 RetentionPolicy.RUNTIME。@Target 注解用来定义注解能够被应用于源码的哪些位置,例如 ElementType.FIELD 为属性,ElementType.TYPE 是类。 由于不是 SpringBoot,我们仍然需要在配置文件中书写自动注入的扫描范围,来指导框架扫描。配置文件如下: <?xml version="1.0" encoding="UTF-8"?><beans> <component-scan base-package="top.guoziyang.main"></component-scan></beans> 启动后,框架会自动扫描该包及其子包下所有使用注解标明的 Bean,并注入容器。 扫描注解 由于配置文件发生了改变,自然我们需要改变 xml 文件的解析方式,在 XmlBeanDefinitionReader 类的 parseBeanDefinitions() 方法中,一旦我们发现了 component-scan 标签,说明我们是使用注解来注入 Bean 的: protected void parseBeanDefinitions(Element root) { ... for(int i = 0; i < nodeList.getLength(); i ++) { if(nodeList.item(i) instanceof Element) { Element ele = (Element)nodeList.item(i); if(ele.getTagName().equals("component-scan")) { basePackage = ele.getAttribute("base-package"); break; } } } if(basePackage != null) { parseAnnotation(basePackage); return; } ...} parseAnnotation() 方***获取到目标包下所有的类,并遍历解析: protected void parseAnnotation(String basePackage) { Set<Class<?>> classes = getClasses(basePackage); for(Class clazz : classes) { processAnnotationBeanDefinition(clazz); }} getClass() 就是用来获取到包下的所有类,它的实现较为繁琐,具体代码在:https://github.com/CN-GuoZiyang/My-Spring-IOC/blob/8a3a9c640e/src/main/java/top/guoziyang/springframework/reader/XmlBeanDefinitionReader.java#L141,当然这个功能也可以用 guava 直接实现。 processAnnotationBeanDefinition() 这个方法利用了反射机制来获取类上的注解,以判断对应的类是否是需要注册的 bean,并寻找相关的注解,如 @Scope,形成对应的 BeanDefinition: protected void processAnnotationBeanDefinition(Class<?> clazz) { if(clazz.isAnnotationPresent(Component.class)) { String name = clazz.getAnnotation(Component.class).name(); if(name == null || name.length() == 0) { name = clazz.getName(); } String className = clazz.getName(); boolean singleton = true; if(clazz.isAnnotationPresent(Scope.class) && "prototype".equals(clazz.getAnnotation(Scope.class).value())) { singleton = false; } BeanDefinition beanDefinition = new BeanDefinition(); processAnnotationProperty(clazz, beanDefinition); beanDefinition.setBeanClassName(className); beanDefinition.setSingleton(singleton); getRegistry().put(name, beanDefinition); }} processAnnotationProperty() 则是对类的每一个属性进行判断,来确定每个属性是否需要注入等: protected void processAnnotationProperty(Class<?> clazz, BeanDefinition beanDefinition) { Field[] fields = clazz.getDeclaredFields(); for(Field field : fields) { String name = field.getName(); if(field.isAnnotationPresent(Value.class)) { Value valueAnnotation = field.getAnnotation(Value.class); String value = valueAnnotation.value(); if(value != null && value.length() > 0) { // 优先进行值注入 beanDefinition.getPropertyValues().addPropertyValue(new PropertyValue(name, value)); } } else if(field.isAnnotationPresent(Autowired.class)) { if(field.isAnnotationPresent(Qualifier.class)) { Qualifier qualifier = field.getAnnotation(Qualifier.class); String ref = qualifier.value(); if(ref == null || ref.length() == 0) { throw new IllegalArgumentException("the value of Qualifier should not be null!"); } BeanReference beanReference = new BeanReference(ref); beanDefinition.getPropertyValues().addPropertyValue(new PropertyValue(name, beanReference)); } else { String ref = field.getType().getName(); BeanReference beanReference = new BeanReference(ref); beanDefinition.getPropertyValues().addPropertyValue(new PropertyValue(name, beanReference)); } } }} 其实就是将原本从 xml 获取到的各种信息,通过反射的方式从注解中获取了出来。整体的逻辑并没有什么变化。 实际上,由于产生的结果一致(生成 beanDefinition 并存入 registry),可以仿照 Spring 的实现使用委托模式,这样耦合度就不会太高。但是由于使用注解同样还需要读取配置文件,较为繁琐,就没有解耦(实际上是我偷懒了)。 写了这么多,其实都仅仅是对 XmlBeanDefinitionReader 这个类的改动,因为这个类的职责就是,从文件中解析出 BeanDefinition。这个类的实现在 https://github.com/CN-GuoZiyang/My-Spring-IOC/blob/8a3a9c640e/src/main/java/top/guoziyang/springframework/reader/XmlBeanDefinitionReader.java 中。 看看效果 由于我们使用了注解,就需要修改相应的 bean,加上上面定义的注解即可: @Component(name = "helloWorldService")@Scope("prototype")public class HelloWorldServiceImpl implements HelloWorldService { @Value("Hello, world") private String text; @Override public void saySomething() { System.out.println(text); }} @Component(name = "wrapService")public class WrapService { @Autowired private HelloWorldService helloWorldService; public void say() { helloWorldService.saySomething(); }} 测试代码如下: public class Main() { public static void annotationTest() throws Exception { ApplicationContext applicationContext = new ClassPathXmlApplicationContext("application-annotation.xml"); WrapService wrapService = (WrapService) applicationContext.getBean("wrapService"); wrapService.say(); HelloWorldService helloWorldService = (HelloWorldService) applicationContext.getBean("helloWorldService"); HelloWorldService helloWorldService2 = (HelloWorldService) applicationContext.getBean("helloWorldService"); System.out.println("prototype验证:相等" + (helloWorldService == helloWorldService2)); WrapService wrapService2 = (WrapService) applicationContext.getBean("wrapService"); System.out.println("singleton验证:相等" + (wrapService == wrapService2)); }} 其实和第一节的测试一样,结果也是一致的。 结束语 到这里,自己手写的 IOC 容器的简单实现就完成了!还是挺有成就感的。使用体验和 Spring 基本没啥差别(误)。 下一篇文章,会基于已经实现的 IOC 容器,在其上层手撸一个 SpringMVC 的简单实现。 所以,不要停下来啊!
点赞 8
评论 2
声哥今天更新了吗
全部评论
推荐
最新
楼层
暂无评论,快来抢首评~
相关推荐
2025-12-30 20:50
蚌埠坦克学院 嵌入式软件开发
大家实习都在做什么?嵌入式实习生的真实日常
很多人对嵌入式实习都有幻想:一上来就写驱动、调外设、玩硬件。但真正开始实习后,才发现更多时候是在打基础、补短板、熟环境。下面是大多数嵌入式实习生的真实日常。一、看资料、看代码,比写代码多刚进公司,最常做的不是敲代码,而是:看芯片手册、原理图、数据手册看现有工程代码,理清启动流程熟悉寄存器定义、外设配置方式这是必经阶段,不看懂这些,后面根本没法改代码。二、从简单外设开始“刷存在感”真正上手时,通常是这些任务:点灯、按键、串口、定时器改已有 demo,而不是从零写修一些小 bug、适配新板子看起来简单,但这是融入项目的最快方式。三、跟着做功能,而不是独立设计多数实习生做的是:按需求加功能模块修改参...
大家实习都在做什么?
点赞
评论
收藏
分享
2025-12-29 22:36
武汉大学 Java
20251225【小红书】面试算法真题(共1题)
题目1:最大抵扣金额计算
查看1道真题和解析
点赞
评论
收藏
分享
2025-11-17 17:25
电子科技大学 Java
27届找Java日常实习,简历求拷打
目前背了一周八股,leetcode hot100刷了30道,什么时候可以开始投简历?求助各位大佬帮忙优化简历🙏🙏🙏
别学ee了:
确实,准备是准备不完的,感觉只有被拷打进步才快😂
点赞
评论
收藏
分享
2025-12-11 11:13
已编辑
湖南大学 安卓
北邮毕业拿字节228万offer
非常夸张,已经超过学校本身的意义了,和学校没啥关系,绝对是个人能力问题!!!牛的人在任何地方都牛!听说他是 2025 年毕业,进了 seed,赶上了 8 月增发那 100 万期权,算下来才有这么多。感觉在印钞厂上班一样,好夸张,已经不是羡慕了,十分震惊!第二个百万offer如下,也是字节给北邮开的200whttps://www.nowcoder.com/feed/main/detail/a8518f8e5eb9487a803f18612dd8036a?sourceSSR=users
BloodEngin...:
上个厕所都挣几百?
字节开奖
点赞
评论
收藏
分享
2025-12-29 15:56
蓝禾技术_电商事业部_电商运营管培生(准入职员工)
蓝禾科技内推,蓝禾科技内推码
面经:蓝禾的流程推得很快,基本上投完两天就接到了hr的初试电话,直接进行了初试。初试全程大概三十分钟,hr小哥态度很和善。主要问了实践经历获奖的情况最有成就感的事情对电商运营的理解选择公司的标准等,都是比较常规的问题。禾你一起,做不可能的事 | 蓝禾26届校招启动啦公司介绍:蓝禾2008年成立于深圳,是一家集产品、设计、研发、品牌、营销、大数据运营于一体的创新型科技公司,旗下拥有图拉斯TORRAS、锐舞RANVOO、卡斯酷CASEKOO等多个品牌招聘岗位:运营(国内)、运营(国外)、营销、设计、研发技术、职能工作地点:深圳内推链接:https://lanhevip.jobs.feishu.cn...
点赞
评论
收藏
分享
评论
点赞成功,聊一聊 >
3
收藏
分享
评论
提到的真题
返回内容
全站热榜
更多
1
...
都 2026 年了,还在神话 AI Agent开发吗?
3525
2
...
为什么你的实习是“无效实习”?又该如何做
3478
3
...
最后一天,你的2025牛客年度报告即将封存!
2710
4
...
双非终于上岸了!!!!
1942
5
...
uu们,面试的时候一定要大大方方的啊!
1786
6
...
我的秋招回忆录:从惨败到硕果的成长之路
1468
7
...
字节实习生涨薪保真?
1295
8
...
简历求锐评,211硕士找实习
1201
9
...
2025找工年终总结
1083
10
...
第一次感受到世界上有些人真的有优越感
1029
创作者周榜
更多
正在热议
更多
#
对2025年忏悔
#
9942次浏览
186人参与
#
机械/制造每日一题
#
82056次浏览
1430人参与
#
一人说一家双休的公司
#
13752次浏览
150人参与
#
非技术2023笔面经
#
316866次浏览
2685人参与
#
实习没人带,苟住还是跑路?
#
18740次浏览
341人参与
#
机械求职避坑tips
#
82591次浏览
537人参与
#
工作中,努力重要还是选择重要?
#
248368次浏览
2412人参与
#
如果秋招能重来,我会____
#
79615次浏览
471人参与
#
应届生,你找到工作了吗
#
97519次浏览
596人参与
#
面试紧张时你会有什么表现?
#
21126次浏览
154人参与
#
为了找工作你投递了多少公司?
#
99491次浏览
685人参与
#
春招前还要继续实习吗?
#
12313次浏览
137人参与
#
每个月的工资都是怎么分配的?
#
81686次浏览
665人参与
#
虾皮求职进展汇总
#
362611次浏览
2767人参与
#
影石Insta360求职进展汇总
#
169341次浏览
1345人参与
#
哪些公司笔/面试难度大?
#
7477次浏览
35人参与
#
AI时代,哪些岗位最容易被淘汰
#
25965次浏览
223人参与
#
你面试被问到过哪些不会的问题?
#
107272次浏览
1879人参与
#
秋招被确诊为……
#
280415次浏览
1589人参与
#
大疆的机械笔试比去年难吗
#
96321次浏览
767人参与
牛客网
牛客网在线编程
牛客网题解
牛客企业服务