最近决定自己手写出一个简单的后端的框架从而做到熟悉java基础特性以及熟悉后端框架,由于没有阅读过ssm等框架的源码,写出的东西可能会有一些不足之处,也希望大佬们能够不吝指导~~
1.首先创建需要的包、类
其中annotation包中包含一些基础的注解,这里直接用的Spring中的注解名称,就不作详细解释了。其中只有Value注解中有值,其他两个注解都是无值的注解
Annotation类中对所有包下的类进行注解扫描,然后进行相应的依赖注入后存入Collection类的容器中
2.扫描所有类,获取类对象
这里由于个人不明白获取设定包下的所有类如何实现,并且这也不是本次的重点,就直接百度抄下来了一段代码(其实还是懒得写…),出处见代码中注释
public static List<Class<?>> getClasses(String packageName){
// ————————————————
// 版权声明:本文为CSDN博主「治愈君」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
// 原文链接:https://blog.csdn.net/weixin_39827826/article/details/90598705
//第一个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;
}
public static void findAndAddClassesInPackageByFile(String packageName, String packagePath, final boolean recursive, List<Class<?>> classes){
//获取此包的目录 建立一个File
// ————————————————
// 版权声明:本文为CSDN博主「治愈君」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
// 原文链接:https://blog.csdn.net/weixin_39827826/article/details/90598705
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();
}
}
}
}
3.遍历类对象,检索Component注解
这里遍历所有的类对象,如果出现@Component注解则进行依赖注入
//注解扫描
static void annotationScan(){
//用反射的方式获取包下的所有类 对其注解进行解析
List<Class<?>> classes = getClasses("com.guorui"); //这里扫描的包名可以通过解析xml配置类实现改变
System.out.println(classes.size());
//解析 找出有Config注解的类
for (Class cl:classes){
cl.getDeclaredAnnotations();
java.lang.annotation.Annotation annotation = cl.getAnnotation(Component.class);
if (annotation!=null)
{
System.out.println("检索到一个类有Component注释,该类为" + cl +",创建实例对象...");
Object obj = createComponent(cl);//创建实例对象 返回一个Object类型的对象
obj = autoWired(obj);//对创建的实例对象进行依赖注入
StringBuilder sb = new StringBuilder();
sb.append(Character.toLowerCase(cl.getSimpleName().charAt(0))).append(cl.getSimpleName().substring(1)).toString();
//将类名首字母转小写变成对象形式
Collection.add(sb.toString(),obj);//写入核心容器
}
}
}
4.对@Component注解类进行依赖注入
1.先通过反射获取这个类的实例对象(这里直接创建默认的实例对象 也就是说@Component的类中必须含有一个无参构造器),创建出的实例对象是Object类型,如果后续需要获取这个javaBean,需要先强制转型(Spring中好像也是这样的)
2.扫描这个类中的Value AutoWired注解,通过反射获取有注解的实例域,如果是Value则获取注解中的值,将其转换成相对应的实例域的基本数据类型然后用field.set以实例对象为参数进行注入。 这里由于基本数据类型的注入和引用类型不太一样,偷个懒就没有写基本数据类型的注入,也就是说只能用包装类
而如果是AutoWired依赖注入 就必须要等待要注入的引用类型的对象已经存在于核心容器中,然后才能用set方法。这里要自定义一个异常:注入的对象类不是Component注解类。
但是如果一个Student对象需要依赖注入Teacher域 而这个Teacher对象又需要注入一个Student域 这好像就形成了一个逻辑漏洞?
思考了一下,解决方法:遇到AutoWired先检测核心容器中是否存在该对象,如果存在直接赋值,如果不存在就先创建一个临时对象(不放入核心容器只为了赋值,这里要判断这个对象中是否含有被注入的域,如果含有该域并且也需要AutoWired注入,则跳过这个注入,那么就会和后面创建的实例对象不同)。
而由于Autowired注入不是本次的重点,就先只写了Value注解的注入(还是因为懒)
//对实例对象进行依赖注入
private static Object autoWired(Object obj){
Class cl = obj.getClass();
Field[] fields = cl.getDeclaredFields();
for (Field field:fields){
if (field.getDeclaredAnnotation(Autowired.class) != null){
//该实例域被AutoWired注解 需要注入一个引用对象(在核心容器中获取)
}
if (field.getDeclaredAnnotation(Value.class) != null){
//被Value注解 需要注入一个基本类型 这里进行类型判断
Object element = field.getDeclaredAnnotation(Value.class).value();
Object autoElement = null;
System.out.println("类型:" + field.getType().toString());
switch (field.getType().toString()){
case "class java.lang.String":
System.out.println("执行》。。");
autoElement = element.toString();
break;
case "class java.lang.Integer":
autoElement = new Integer(Integer.parseInt(element.toString()));
break;
}
field.setAccessible(true);
try {
System.out.println("写入实例域值"+autoElement);
field.set(obj, autoElement);
} catch (IllegalAccessException e) {
System.out.println("注入失败...类型不匹配!!");
e.printStackTrace();
}
}
}
return obj;
}
4.依赖注入结束后,将实例对象放入核心容器
package com.guorui.mySpring.myIOC;
//IOC中的核心容器 底层用hashMap实现
import java.util.HashMap;
import java.util.Map;
public class Collection {
//key表示容器中对象的名称 value储存这个对象
static HashMap<String,Object> javaBean= new HashMap<>();
//add方法向核心容器中添加一个javabean对象
static void add(String name,Object obj){
javaBean.put(name,obj);//进行添加
}
}
在Annotation类中调用add静态方法,直接将实例对象写入
5.用Main类执行上述操作
package com.guorui.mySpring.myIOC;
import com.guorui.test.springTest.javabean.Student;
/**
* Spring的主方法 进行程序初期的核心容器的创建和javabean对象的导入
*/
public class Main {
public static void main(String[] args) {
//创建核心容器和注解扫描
Collection collection = new Collection();
//先进行注解扫描 由于是静态方法 所以不用创建对象
Annotation.annotationScan();
Student s = (Student)Collection.javaBean.get("student");
System.out.println(s);
System.out.println(s.getAge()+""+s.getName());
}
}