模拟Spring框架低层

模拟spring框架低层创建和管理对象(IOC、DI)

1、分析
  1. 创建工具类:指定一个目录,作为参数传入,获取这个目录下所有的.class文件,包括所在路径,遍历每个文件,获取其className类的全路径,如:javase.spring.classes.HelloController,获取beanName,helloController。(包扫描)
package javapro.spring.util;

import org.junit.Test;
import java.io.File;
import java.util.ArrayList;
import java.util.List;

//工具类:把传入全路径截取出类名,首字母小写
public class FileUtil {
	/**
	 * 1、去掉前面的包名lastIndexOf
	 * 2、按最后一个.来截取到最后
	 * 3、把首字母小写
	 * @param className
	 * @return beanName
	 */
	public static String getBeanName(String className) {
		//最后面第一次出现“."的位置
		int lastIndexOf = className.lastIndexOf(".");
		//获取ClassName
		String name = className.substring(lastIndexOf + 1);
		//将类名首字母变小写得到beanName
		char c = name.charAt(0); //得到指定索引处的字符
		//将字符转成小写
		char toLowerCase = Character.toLowerCase(c);
		//返回beanName
		return toLowerCase + name.substring(1);
	}

	/**
	 * 根据传过来的字节码文件路径解析出 类的全限定名
	 * 例如:F:\idea_Java\excellent\stage2\javaEE\spring\target\classes\com\lifeng\pojo\User.class
	 * 返回结果"com.lifeng.pojo.User
	 * 1、得到classes的起始位置序号
	 * 2、去掉 .class
	 * 3、将\\换成.
	 * @param path 传入包路径
	 */
	public static String getClassName(String path) {
		//定位到classes的起始位置,利用indexOf(),查找给定字符串在上面字符串当中的起始位置序号
		Integer index = path.indexOf("classes");
		//截取classes之后的部分
		String substring = path.substring(index + 8);
		//去掉.class
		String s = substring.substring(0, substring.length() - 6);
		String replaceAll = s.replaceAll("\\\\", "\\.");
		return replaceAll;
	}

	/**
	 * 返回类的全限定名
	 * @param packagePath 包路径
	 * @param list        用于存放beanName
	 * @return 返回所有类的全限定名
	 */
	public static List<String> readClassName(String packagePath, List<String> list) {
		//创建file对象
		File file = new File(packagePath);
		//得到该路径的文件列表
		File[] files = file.listFiles();
		//判断files是否为空,为空则代表是空目录,否则将继续遍历子目录,或者打印出文件名
		if (files.length > 0) {
			for (File f : files) {
				//判断是否有目录,true:遍历子目录
				if (f.isDirectory()) {
					readClassName(f.getAbsolutePath(), list);
				} else {
					//获取绝对路径
					String absolutePath = f.getAbsolutePath();
					//利用工具类从文件路径中得到类的全限定名和bean
					String className = FileUtil.getClassName(absolutePath);
					list.add(className);
				}
			}
		}
		return list;
	}

	//测试
	@Test
	public void test() {
		FileUtil fileUtil = new FileUtil();
		List<String> list = new ArrayList<>();
		String path = "F:\\idea_Java\\excellent\\stage1\\javase\\SE01\\classes\\javase";
		List<String> className = fileUtil.readClassName(path, list);
		for (String c : className) {
			System.out.println(c);
		}
	}
}

  1. 创建对象,反射Class.forName(className)获取到Class对象,通过Class类对象提供的newInstance创建对象实例。
  2. 把创建好的对象放入容器里面,规定:在目录下不许有同名的类名,包括不同路径。key唯一
    利用Map实现(Map<String,Object> key=beanName,Object=对象实例)
  3. Dept getBean(“dept”),从容器中获取对象
  4. 创建对象关系(利用反射),获取对象的关系(利用注解@Autowired),在对象属性上标识一个注解@Autowired,代表这个对象需要设置关联关系。从map获取要的对象,然后利用反射设置进去。
    • 增加一个注解@Autowired
    • 通过反射获取注解,如果有这个注解的属性就设置对象管理,如果没有就不关联
2、IOC、DI(依赖注入/关联对象)实现
package javapro.spring.util;

import javapro.spring.annotation.Autowired;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class ComponentScanParser {
	//创建map容器用于存放实例
	static Map<String, Object> beans = new HashMap<>();

	//从容器中获取对象
	public static Object getBean(String beanName) {
		return beans.get(beanName);
	}

	//拿到目录下的所有的ClassName,创建对象
	public static void config(String packagePath) throws Exception {
		//1、调用工具类获取所有的ClassName
		List<String> listClassName = new ArrayList<>();
		FileUtil.readClassName(packagePath, listClassName);

		System.out.println("包扫描");
		for (String className : listClassName) {
			System.out.println(className);
			//2、利用反射创建扫描出来的类的实例
			Class<?> clazz = Class.forName(className);
			Object instance = clazz.newInstance();
			//3、根据全限定名拿到beanName
			String beanName = FileUtil.getBeanName(className);
			//System.out.println(beanName);
			beans.put(beanName, instance);
		}
	}

	/**
	 * 依赖注入
	 * 遍历容器
	 * 1、利用对象获取需要关联的类对象
	 * 2、利用反射,得到该类的所有属性,并遍历
	 * 3、在遍历中,获取有标识注解的属性,并遍历
	 * 4、判断是否存在标识注解的属性,如果存在,则利用该属性名从beans容器里面拿出该属性对应类型的对象
	 * 5、利用反射,修改属性的访问权限
	 * 6、利用反射提供的set方法,对关联对象的关联属性,设置需要被关联的对象
	 */
	public static void inject() throws IllegalAccessException {
		//遍历容器
		for (String beanName : beans.keySet()) {
			//获取对应的对象
			Object bean = getBean(beanName);
			Class<?> clazz = bean.getClass();
			//获取所有属性
			Field[] fields = clazz.getDeclaredFields();
			//遍历所有属性
			for (Field field : fields) {
				//获取标有注解的属性
				Autowired autowired = field.getDeclaredAnnotation(Autowired.class);
				//判断是否为空
				if (autowired != null) {
					//根据属性名 从容器中获取对象
					Object relationObject = getBean(field.getName());
					//将此对象的可访问标志设置为指示的布尔值
					field.setAccessible(true);
					field.set(bean, relationObject);
				}
			}
		}
	}
}


  • 注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface ComponentScan {
	String value() default "";
}
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Autowired {
	String value() default "";
}
  • 测试类
package javapro.spring.run;

import javapro.spring.annotation.ComponentScan;
import javapro.spring.classes.Hello;
import javapro.spring.util.ComponentScanParser;

import java.net.URL;

@ComponentScan("javapro.spring.classes")
public class Run {
	public static void main(String[] args) throws Exception{
		/**
		 * 获取当前类的类对象
		 * 1、拿到当前类的绝对路径
		 * 2、取出注解上的全限定名
		 * 3、绝对路径+给定的相对包路径=要扫描的的绝对包路径
		 * 4、根据包路径扫描出所有在此包下的类,并完成类加载,最终放入容器
		 * 5、根据方法getBean()获取需要的对象
		 */
		Run run = new Run();
		Class<?> clazz = run.getClass();

		//getResource接受一个字符串参数,如果以”/”开头,就在classpath根目录下找(不会递归查找子目录),如果不以”/”开头,
		//就在调用getResource的字节码对象所在目录下找(同样不会递归查找子目录)。
		String path = clazz.getResource("/").getPath();

		//获取当前类上的注解value值
		ComponentScan componentScan = clazz.getDeclaredAnnotation(ComponentScan.class);
		String value = componentScan.value();
		//判断是否有注解
		if (value != null) {
			//将value里面的字符串转为路径的格式
			String packageName = value.replaceAll("\\.", "/");
			String packagePath = path + packageName;

			//调用工具类ComponentScanParser,将包路径传过去,初始化容器
			ComponentScanParser.config(packagePath);

			//根据beanName获取对象
			Hello hello = (Hello)ComponentScanParser.getBean("hello");
			hello.hi();

			ComponentScanParser.inject();
			System.out.println(ComponentScanParser.getBean("emp").toString());
		}
	}
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值