源码分析(3)---手写SpringIOC容器框架手写IOC(XML和注解方式)

SpringIOC的xml方式注入对象原理分析

  • IOC的作用就是把每个bean之间的关系交给第三方容器进行管理,bean的初始化等交给容器处理,即控制反转
  • 所有配置文件只要是配置了全路径,我们就可以理解为其是反射得到的(如:spring.xml中配置的bean中的class属性)
  • SpringIOC的XML版本采用的是dom4j+反射技术实现的
  • 反射的构造对象,肯定会走无参构造函数的。(无论构造函数是否私有)

XML

  • XML和JSON的区别
  1. XML是重量级的数据交换格式,占带宽比较大
  2. JSON是轻量级的交换格式,xml占带宽较小
  3. 很多互联网公司用json作为交换格式,但是很多银行项目还是使用XML作为数据的交换格式。
  • 利用
this.getClass().getClassLoader().getResourceAsStream(xmlPath)

获取当前项目路径(xmlPath是文件名)

  • XML的解析方式
    dom4j,Sax,Pull

  • Dom4j和Sax的区别

dom4j不适合大文件的解析,因为他是一下子将文件加载到内存中,所以有可能出现内存溢出,sax是基于事件对xml进行解析,所以他可以解析大文件的xml,也正是因为如此,所以dom4j可以对xml进行灵活的增删改查和导航,而sax没有那么强的灵活性,所以sax经常是用来解析大型XML文件,而要对XML文件进行一些灵活的CRUD操作,就要用dom4j。

  • 总结一下,dom4j用来操作xml的,而sax是用来读取的

手写Spring IOC XML方式注入对象

  • 思路:
    (1)解析XML文件
    (2)获取bean标签,解析出bean的id和class等属性,利用传进来的beanId找寻指定的bean进行解析
    (3)根据获取的class地址,利用反射将其类初始化

  • java代码演示:
    (1)重要,自定义实现ClassPathXmlApplication,负责解析xml,反射创建对应class属性的对象

package com.xiyou.mayi.thread5.shouxieSpringIOC.utils;

import org.apache.commons.lang.StringUtils;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

import java.io.InputStream;
import java.util.List;

/**
 * 该类的作用是用来解析xml文件,根据bean标签反射创建对象
 */
public class MyClassPathXmlApplication {

    /**
     * 保存xml的文件路径,只有文件名
     */
    private String xmlPath;

    /**
     * 构造函数
     * @param xmlPath
     */
    public MyClassPathXmlApplication(String xmlPath){
        this.xmlPath = xmlPath;
    }

    /**
     * 重写getBean方法
     * @param beanId
     * @return
     */
    public Object getBean(String beanId) throws Exception {
        // 1.读取配置文件
        List<Element> elements = readerXml();
        // 2. 使用beanId查询我们配置的bean
        String xmlByIDClass = findXmlByIDClass(elements, beanId);
        // 3. 根据class属性反射调用
        Class<?> clazz = Class.forName(xmlByIDClass);
        // 4. 创建对象
        return clazz.newInstance();
    }

    /**
     * 解析xml文件
     * @return
     */
    public List<Element> readerXml() throws DocumentException {
        SAXReader saxReader = new SAXReader();
        // 判断xml文件名是否为空
        if(StringUtils.isEmpty(xmlPath)){
            new Exception("文件路径为空");
        }
        // 读取xml文件
        Document document = saxReader.read(getClassXmlInputStream(xmlPath));
        // 获取根节点信息(beans标签)
        Element rootElement = document.getRootElement();
        // 获取子节点
        List<Element> elements = rootElement.elements();
        // 判断子节点是否为空,为空返回null
        if (elements == null || elements.isEmpty()) {
            return null;
        }
        return elements;
    }

    /**
     * 根据文件名读取classpath下的该文件路径,将其转为流的形式
     * @param xmlPath
     * @return
     */
    public InputStream getClassXmlInputStream(String xmlPath) {
        InputStream resourceAsStream = this.getClass().getClassLoader().getResourceAsStream(xmlPath);
        return resourceAsStream;
    }

    /**
     * 根据子节点列表和指定的beanId查询,对应的class路径
     * @param elements
     * @param beanId
     * @return
     * @throws Exception
     */
    public String findXmlByIDClass(List<Element> elements, String beanId) throws Exception {
        // 遍历查询出来的子节点,找到对应的beanId的bean标签,找到其class属性
        for(Element element : elements){
            // 拿出其id属性对应的值
            String beanIdValue = element.attributeValue("id");
            // 给子标签的id属性为空或者与传入的不相等
            if (StringUtils.isEmpty(beanIdValue) || !beanIdValue.equals(beanId)) {
                continue;
            }
            // 此时beanId已经找到了对应的bean标签.获取对应的class属性的值
            String classPath = element.attributeValue("class");
            if (StringUtils.isNotEmpty(classPath)) {
                return classPath;
            }
        }
        return null;
    }
}

(2)自己写的xml文件

<beans>
	<bean id="userService" class="com.xiyou.mayi.thread5.shouxieSpringIOC.service.impl.UserServiceImpl"/>
</beans>

(3)启动类

package com.xiyou.mayi.thread5.shouxieSpringIOC;

import com.xiyou.mayi.thread5.shouxieSpringIOC.utils.MyClassPathXmlApplication;

public class Test {
    public static void main(String[] args) {
        MyClassPathXmlApplication myClassPathXmlApplication = new MyClassPathXmlApplication("spring2.xml");
        try {
            Object userService = myClassPathXmlApplication.getBean("userService");
            System.out.println(userService);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

(4)输出结果:

com.xiyou.mayi.thread5.shouxieSpringIOC.service.impl.UserServiceImpl@70177ecd

可以发现我们自己实现的ClassPathXmlApplication根据传进的beanId,找到对应的class属性,利用反射成功创建了对象。

手写注解版本的SpringIOC容器

  • IOC容器实现思路:
    (1)使用Java反射机制扫描包,获取包下面所有的类
    (2)判断类上是否有注入bean的注解
    (3)使用Java反射机制初始化该类
    (4)将得到的对象存入concurrentHashMap中,key是定义的id的名字(默认是类名小写)

  • IOC依赖注入实现思路:
    (1)使用反射机制,获取当前类的所有属性
    (2)判断当前属性上是否有注解
    (3)默认使用属性名称,查找bean容器对象,将IOC中初始化好的对象赋值给该属性。

  • 注意一点,如果一个类没有受到Spring的管理,那么里面的属性也不会被Spring注入值,就是类上不加@Servicec等属性,即使属性上有@Autowrited也不会注入

  • 代码实现:
    (1)架构图
    在这里插入图片描述
    (2)pom文件

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.xiyou</groupId>
    <artifactId>spring-annotation-ioc</artifactId>
    <version>1.0-SNAPSHOT</version>

    <dependencies>
        <!-- 引入Spring-AOP等相关Jar -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>3.0.6.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>3.0.6.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aop</artifactId>
            <version>3.0.6.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-orm</artifactId>
            <version>3.0.6.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjrt</artifactId>
            <version>1.6.1</version>
        </dependency>
        <dependency>
            <groupId>aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.5.3</version>
        </dependency>
        <dependency>
            <groupId>cglib</groupId>
            <artifactId>cglib</artifactId>
            <version>2.1_2</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/com.mchange/c3p0 -->
        <dependency>
            <groupId>com.mchange</groupId>
            <artifactId>c3p0</artifactId>
            <version>0.9.5.2</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.37</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/dom4j/dom4j -->
        <dependency>
            <groupId>dom4j</groupId>
            <artifactId>dom4j</artifactId>
            <version>1.6.1</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/commons-lang/commons-lang -->
        <dependency>
            <groupId>commons-lang</groupId>
            <artifactId>commons-lang</artifactId>
            <version>2.6</version>
        </dependency>

    </dependencies>
</project>

(3)自定义注解,一个是如同@Service,一个如同@Resource
类似@Service

package com.xiyou.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

// 自定义注解 注入到Spring容器
@Target({ ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
public @interface ExtService {

}

类似@Resource

package com.xiyou.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

//自定义注解 从Spring容器获取bean
@Target({ ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME)
public @interface ExtResource {

}

(4)Service层

  • OrderService
package com.xiyou.service;


public interface OrderService {

	public void addOrder();

}

  • UserService
package com.xiyou.service;

public interface UserService {

	public void add();

}

  • OrderServiceImpl
package com.xiyou.service.impl;

import com.xiyou.annotation.ExtService;
import com.xiyou.service.OrderService;

@ExtService
public class OrderServiceImpl implements OrderService {

	@Override
	public void addOrder() {
		System.out.println("addOrder");
	}

}

  • UserservicecImpl
package com.xiyou.service.impl;

import javax.annotation.Resource;

import com.xiyou.annotation.ExtResource;
import com.xiyou.annotation.ExtService;
import com.xiyou.service.OrderService;
import com.xiyou.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;

//将该类注入到spring容器里面
@ExtService
public class UserServiceImpl implements UserService {
	// 从Spring容器中读取bean
	@ExtResource
	private OrderService orderServiceImpl;

	public void add() {
		orderServiceImpl.addOrder();
		System.out.println("我是使用反射机制运行的方法");
	}

}

这里我们在UserService中依赖了OrderServiceImpl,这里需要自动注入对象给属性,即依赖注入

(5)反射工具类,根据指定的包,扫描到该包下面的所有类(这里不做强调,太麻烦了,直接从网上找的)

package com.xiyou.utils;

import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.net.JarURLConnection;
import java.net.URL;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

public class ClassUtil {

	/**
	 * 取得某个接口下所有实现这个接口的类
	 */
	public static List<Class> getAllClassByInterface(Class c) {
		List<Class> returnClassList = null;

		if (c.isInterface()) {
			// 获取当前的包名
			String packageName = c.getPackage().getName();
			// 获取当前包下以及子包下所以的类
			List<Class<?>> allClass = getClasses(packageName);
			if (allClass != null) {
				returnClassList = new ArrayList<Class>();
				for (Class classes : allClass) {
					// 判断是否是同一个接口
					if (c.isAssignableFrom(classes)) {
						// 本身不加入进去
						if (!c.equals(classes)) {
							returnClassList.add(classes);
						}
					}
				}
			}
		}

		return returnClassList;
	}

	/*
	 * 取得某一类所在包的所有类名 不含迭代
	 */
	public static String[] getPackageAllClassName(String classLocation, String packageName) {
		// 将packageName分解
		String[] packagePathSplit = packageName.split("[.]");
		String realClassLocation = classLocation;
		int packageLength = packagePathSplit.length;
		for (int i = 0; i < packageLength; i++) {
			realClassLocation = realClassLocation + File.separator + packagePathSplit[i];
		}
		File packeageDir = new File(realClassLocation);
		if (packeageDir.isDirectory()) {
			String[] allClassName = packeageDir.list();
			return allClassName;
		}
		return null;
	}

	/**
	 * 从包package中获取所有的Class
	 * 
	 * @param packageName
	 * @return
	 */
	public static List<Class<?>> getClasses(String packageName) {

		// 第一个class类的集合
		List<Class<?>> classes = new ArrayList<Class<?>>();
		// 是否循环迭代
		boolean recursive = true;
		// 获取包的名字 并进行替换
		String packageDirName = packageName.replace('.', '/');
		// 定义一个枚举的集合 并进行循环来处理这个目录下的things
		Enumeration<URL> dirs;
		try {
			dirs = Thread.currentThread().getContextClassLoader().getResources(packageDirName);
			// 循环迭代下去
			while (dirs.hasMoreElements()) {
				// 获取下一个元素
				URL url = dirs.nextElement();
				// 得到协议的名称
				String protocol = url.getProtocol();
				// 如果是以文件的形式保存在服务器上
				if ("file".equals(protocol)) {
					// 获取包的物理路径
					String filePath = URLDecoder.decode(url.getFile(), "UTF-8");
					// 以文件的方式扫描整个包下的文件 并添加到集合中
					findAndAddClassesInPackageByFile(packageName, filePath, recursive, classes);
				} else if ("jar".equals(protocol)) {
					// 如果是jar包文件
					// 定义一个JarFile
					JarFile jar;
					try {
						// 获取jar
						jar = ((JarURLConnection) url.openConnection()).getJarFile();
						// 从此jar包 得到一个枚举类
						Enumeration<JarEntry> entries = jar.entries();
						// 同样的进行循环迭代
						while (entries.hasMoreElements()) {
							// 获取jar里的一个实体 可以是目录 和一些jar包里的其他文件 如META-INF等文件
							JarEntry entry = entries.nextElement();
							String name = entry.getName();
							// 如果是以/开头的
							if (name.charAt(0) == '/') {
								// 获取后面的字符串
								name = name.substring(1);
							}
							// 如果前半部分和定义的包名相同
							if (name.startsWith(packageDirName)) {
								int idx = name.lastIndexOf('/');
								// 如果以"/"结尾 是一个包
								if (idx != -1) {
									// 获取包名 把"/"替换成"."
									packageName = name.substring(0, idx).replace('/', '.');
								}
								// 如果可以迭代下去 并且是一个包
								if ((idx != -1) || recursive) {
									// 如果是一个.class文件 而且不是目录
									if (name.endsWith(".class") && !entry.isDirectory()) {
										// 去掉后面的".class" 获取真正的类名
										String className = name.substring(packageName.length() + 1, name.length() - 6);
										try {
											// 添加到classes
											classes.add(Class.forName(packageName + '.' + className));
										} catch (ClassNotFoundException e) {
											e.printStackTrace();
										}
									}
								}
							}
						}
					} catch (IOException e) {
						e.printStackTrace();
					}
				}
			}
		} catch (IOException e) {
			e.printStackTrace();
		}

		return classes;
	}

	/**
	 * 以文件的形式来获取包下的所有Class
	 * 
	 * @param packageName
	 * @param packagePath
	 * @param recursive
	 * @param classes
	 */
	public static void findAndAddClassesInPackageByFile(String packageName, String packagePath, final boolean recursive,
			List<Class<?>> classes) {
		// 获取此包的目录 建立一个File
		File dir = new File(packagePath);
		// 如果不存在或者 也不是目录就直接返回
		if (!dir.exists() || !dir.isDirectory()) {
			return;
		}
		// 如果存在 就获取包下的所有文件 包括目录
		File[] dirfiles = dir.listFiles(new FileFilter() {
			// 自定义过滤规则 如果可以循环(包含子目录) 或则是以.class结尾的文件(编译好的java类文件)
			public boolean accept(File file) {
				return (recursive && file.isDirectory()) || (file.getName().endsWith(".class"));
			}
		});
		// 循环所有文件
		for (File file : dirfiles) {
			// 如果是目录 则继续扫描
			if (file.isDirectory()) {
				findAndAddClassesInPackageByFile(packageName + "." + file.getName(), file.getAbsolutePath(), recursive,
						classes);
			} else {
				// 如果是java类文件 去掉后面的.class 只留下类名
				String className = file.getName().substring(0, file.getName().length() - 6);
				try {
					// 添加到集合中去
					classes.add(Class.forName(packageName + '.' + className));
				} catch (ClassNotFoundException e) {
					e.printStackTrace();
				}
			}
		}
	}
}

(6)自定义实现IOC的注解方式,可以进行依赖注入

package com.xiyou.spring;

import com.xiyou.annotation.ExtService;
import com.xiyou.utils.ClassUtil;
import org.apache.commons.lang.StringUtils;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 自定义实现注解
 */
public class MyClassPathAnnotationContext {

    /**
     * 定义扫描包的范围
     */
    private String packageName;

    /**
     * 该Map用来保存初始化的bean(即类上带注解的类)
     * key是方法名小写即id,value是创建好的对象
     */
    private ConcurrentHashMap<String, Object> initBean;

    /**
     * 构造函数,初始化传递过来的要扫描的包的范围,即basePackage
     * @param packageName
     */
    public MyClassPathAnnotationContext(String packageName){
        this.packageName = packageName;
    }

    /**
     * 找到类上面标注了我们制定注解的类,返回类的Class对象
     * @return
     * @throws Exception
     */
    public List<Class> findClassExisService() throws Exception{
        // 判断待扫描的包是否为空
        if (StringUtils.isEmpty(packageName)) {
            throw new Exception("扫包地址不能为空!");
        }
        // 1. 使用反射技术获取当前包写的所有的类(这个方法我们不去纠结,这个是网上找的)
        List<Class<?>> classesByPackageName = ClassUtil.getClasses(packageName);
        // 2. 循环遍历判断类上是否有注解
        List<Class> exisClassesAnnotation = new ArrayList<>();
        for (Class clazz : classesByPackageName) {
            ExtService annotation = (ExtService) clazz.getDeclaredAnnotation(ExtService.class);
            // 如果能得到这个注解,表示这个对象不为空的时候,表示该类有注解
            if (annotation != null) {
                // 将当前得到的类的class反射对象保存
                exisClassesAnnotation.add(clazz);
                continue;
            }
        }
        return exisClassesAnnotation;
    }

    /**
     * 根据传进来的带有注解的类的class将其变为map,其key是方法名小写,value是利用反射创建出来的对象
     * @param listClassesAnnotation
     * @return
     */
    public ConcurrentHashMap<String, Object> initBean(List<Class> listClassesAnnotation) throws IllegalAccessException, InstantiationException {
        // 创建一个对象
        ConcurrentHashMap<String, Object> concurrentHashMap = new ConcurrentHashMap<>();
        for (Class clazz : listClassesAnnotation) {
            Object newInstance = clazz.newInstance();
            // 获取key,就是当前类的名字,将其小写 clazz.getSimpleName()就是拿到父类的名字
            String beanId = toLowerCaseFirstOne(clazz.getSimpleName());
            // 存入到map中,key是类名小写,value是建立的对象
            concurrentHashMap.put(beanId, newInstance);
        }
        return concurrentHashMap;
    }

    /**
     * 根据传进来的父类的名字,将其首字母小写返回
     * @param simpleName
     * @return
     */
    public String toLowerCaseFirstOne(String simpleName) {
        if (Character.isLowerCase(simpleName.charAt(0))) {
            return simpleName;
        }
        else {
            return (new StringBuilder()).append(Character.toLowerCase(simpleName.charAt(0))).append(simpleName.substring(1)).toString();
        }
    }

    /**
     * 根据beanId查询对象,传进来beanId得到对应的对象
     * @param beanId
     * @return
     */
    public Object getBean(String beanId) throws Exception {
        // 1. 使用反射机制获取指定包下面的所有的含有指定注解的类
        List<Class> classExisServiceList = findClassExisService();
        if (classExisServiceList == null || classExisServiceList.isEmpty()) {
            throw new Exception("没有需要初始化的bean");
        }
        // 2. 初始化这些带指定注解的类放到一个Map中 key是方法名首字母小写, value就是该对象
        initBean = initBean(classExisServiceList);
        if (initBean == null || initBean.isEmpty()) {
            throw new Exception("初始化bean为空!");
        }
        // 3. 根据传进来的beanId得到bean对象,直接从Map中取值
        Object object = initBean.get(beanId);
        // 4. 找到该类中哪些属性有指定注解,将其属性进行赋值操作 Object是当前类的对象
        attriAssign(object);
        return object;
    }

    /**
     * 进行属性赋值 即@Resource的作用,此时的Map已经存了值,存了所有的类上有指定注解的类
     * @param object 传进来的是一个对象,表示当前的用户请求的类(根据beanId)
     */
    public void attriAssign(Object object) throws IllegalAccessException {
        // 得到当前类的class类型
        Class<?> classInfo = object.getClass();
        // 获得该类的所有属性
        Field[] declaredFields = classInfo.getDeclaredFields();
        // 循环遍历该属性
        for (Field field : declaredFields) {
            // 这里假设每个属性都有注解,不做判断,得到属性名
            String name = field.getName();
            // 使用属性名找class对象,该属性名应该和map中的key一致,也就是和类名首字母小写一致
            Object ob = initBean.get(name);
            if (ob != null) {
                // 设置权限,我们可以操作私有属性
                field.setAccessible(true);
                // 给该属性赋值,第一个参数是属性是哪个类中的,第二个属性是该属性要赋值的对象的值
                field.set(object, ob);
                continue;
            }
        }
    }
}

(7)测试类

package com.xiyou;

import com.xiyou.service.UserService;
import com.xiyou.spring.MyClassPathAnnotationContext;

public class Test {
    public static void main(String[] args) throws Exception {
        MyClassPathAnnotationContext myClassPathAnnotationContext = new MyClassPathAnnotationContext("com.xiyou");
        UserService userService = (UserService)myClassPathAnnotationContext.getBean("userServiceImpl");
        userService.add();
    }
}

(8)输出结果:

addOrder
我是使用反射机制运行的方法
  • 0
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值