100行代码撸完SpringIOC容器
Posted 码农的自我修养
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了100行代码撸完SpringIOC容器相关的知识,希望对你有一定的参考价值。
用过Spring框架的人一定都知道Spring的依赖注入控制反转;通俗的讲就是负责实例化对象 和 管理对象间的依赖 实现解耦。
我们来对比两段代码:
UserController{ UserService userService = new UserService(); userService.insert(user); } UserController{ @Autowwired UserService userService; userService.insert(user); }
乍一看好像没什么区别,好像都是一样的。在controller里面创建了一个service对象然后调用它里面的方法。但是换个角度想想, 如果还有2个,3个,甚至n个类需要用到这个service呢,那它岂不是要被创建n次,这样就会极大的浪费资源,分分钟就内存溢出了。
企业开发案例:
我们只需要在xml配置文件里面指定配置参数,然后在类上加上spring注解它就能帮我们管理对象了,那它又是怎么实现的呢? 答案是: xml解析 + 反射 + 工厂模式。
手写IOC容器,演示它的加载过程:
1. 模仿spring,我们也定义一个注解
@Target({ ElementType.TYPE }) @Retention(RetentionPolicy.RUNTIME) public @interface MyService { } @Target({ TYPE, FIELD, METHOD }) @Retention(RUNTIME) public @interface MyAutowired { }
2. 像使用spring注解一样,使用我们刚才的注解
@MyService public class OrderService { public void add() { System.out.println("Order....add()"); } } @MyService public class UserService { @MyAutowired private OrderService orderService; public void add() { orderService.add(); System.out.println("User....add()"); } }
3. 演示容器加载核心代码
/** * SpringIOC容器实现过程 */ public class MyClassPathXmlApplicationContext { // 1. 指定扫描包的范围 private String packageName ; // Spring的bean容器 (容器启动时初始化所有bean,默认单例) key:beanName(默认类名小写) value:bean对象 private ConcurrentHashMap<String, Object> beans; public MyClassPathXmlApplicationContext(String packageName) throws Exception { // 类加载时初始化这些参数 this.packageName = packageName; this.beans = new ConcurrentHashMap<String, Object>(); initBeans();// 初始化所有类 initEntryField();// 初始化所有类的属性 } /** * 初始化bean(IOC控制反转) */ public void initBeans() throws Exception { // 2. 通过java反射扫描指定包下面所有类的class地址 List<Class<?>> classes = ClassUtil.getClasses(packageName); ConcurrentHashMap<String, Object> classExistAnnotation = this.findClassExistAnnotation(classes); if(classExistAnnotation == null || classExistAnnotation.isEmpty()) throw new Exception("cannot find bean"); } /** * 判断类上面是否存在bean的注解 */ private ConcurrentHashMap<String, Object> findClassExistAnnotation(List<Class<?>> classes) throws InstantiationException, IllegalAccessException { for(Class<?> classInfo : classes) { // 3. 获取指定路径中有ioc注解的类 MyService annotation = classInfo.getAnnotation(MyService.class); if(annotation!=null) { String className = classInfo.getSimpleName();// 获取类名称 // 4. 将类名首字母小写,获取bean名称。 String beanaName = StringUtil.toLowerCaseFirstOne(className); // 5. 反射初始化对象 Object object = classInfo.newInstance(); // 6. 将bean对象存入spring容器中 beans.put(beanaName, object); } } return beans;// 对象初始化完成 } /** * 初始化属性 */ private void initEntryField() throws Exception { // 7. 遍历spring容器中所有的bean对象 for (Entry<String, Object> entry : beans.entrySet()) { Object bean = entry.getValue(); this.attriAssign(bean); } } /** * 依赖注入实现原理(DI依赖注入) */ public void attriAssign(Object object) throws Exception { Class<? extends Object> classInfo = object.getClass(); // 8. 使用反射机制,获取当前类的所有属性值 Field[] fields = classInfo.getDeclaredFields(); for(Field field : fields) { // 9. 获取有@MyAutowired注解的属性 MyAutowired myAutowired = field.getAnnotation(MyAutowired.class); if(myAutowired != null) { // 10. 获取属性名称 String beanName = field.getName(); // 11. 默认使用属性名称,查找bean容器对象 Object bean = this.getBean(beanName); if(bean != null) { field.setAccessible(true);// 允许访问私有属性 // 12. 将得到的bean对象赋值给当前对象的属性上。(bean名称=属性名称) field.set(object, bean); } } } } /** * 通过bean名称去spring容器里面获取bean对象 */ public Object getBean(String beanName) throws Exception { if(StringUtils.isEmpty(beanName)) throw new Exception("beans.factory.BeanCreationException"); Object object = beans.get(beanName); return object; } /** * 测试 */ public static void main(String[] args) throws Exception { MyClassPathXmlApplicationContext applicationContext = new MyClassPathXmlApplicationContext("com.wulei.service"); UserService userService = (UserService)applicationContext.getBean("userService"); userService.add(); } }
依赖注入:比如userController里面的userService属性加上bean注解,在类被加载时通过反射获取service的对象,并且赋值给该属性,这就叫做依赖注入。
控制反转:controller引用service的实例,不需要通过new来创建,将创建对象的责任转移给spring容器,这就叫反转; ioc容器实现对象的创建,以及外部资源的获取(其他类的属性和方法),这就叫控制。
以上是关于100行代码撸完SpringIOC容器的主要内容,如果未能解决你的问题,请参考以下文章