Spring IOC仿写,实现基于配置文件和注解注入,超级详细

前言

本篇文章是基于上次对Spring IOC和AOP的仿写扩展的,最好先看一下之前的那篇文章:仿写Spring IOC和AOP,仿写思路和代码讲解

上次仿写的IOC是基于配置文件实现注入的,这次拓展了基于注解的注入,同时调整了实现IOC的整体架构,改动的地方还是很多的,所以我会重新讲解实现IOC的整体思路和代码实现

项目已发布到GitHub:Imitate-Spring-IOC-and-AOP

整体思路

先来说一下整体的设计思路:

  • 首先要对配置文件进行扫描
  • 如果扫描到了<component-scan base-package="com.pojo"/>,说明开启了对注解的支持(这里的路径只是打个比方,每个人都不一样)
  • 获取该路径下的所有类,如果这个类有Component注解,说明这个类可以被扫描到,把这个类封装为一个BeanDefinition,以后要用到 (BeanDefinition中有两个属性,包括类对象和作用域)
  • 如果扫描到了 <bean></bean>,获取标签上的的class属性,就可以得到类对象,也封装为一个BeanDefinition,以后要用到
  • 创建一个HashMap中作为BeanDefinition容器,对象名BeanName作为key,每个BeanDefinition作为value
  • 调用getBean方法的时候传入BeanName,如果容器中包含这个BeanName,从BeanDefinition容器拿到对应的BeanDefinition
  • 如果BeanDefinition的Scope值为singleton,说明Bean的作用域是单例,就根据BeanName从Bean的单例池中获取Bean对象 (单例池也是一个HashMap),如果获取不到,说明是第一次获取这个对象,就使用反射通过BeanDefinition中的类对象属性实例化一个对象,再使用反射注入属性,最后放到单例池中,最后返回这个已经注入好的对象
  • 如果BeanDefinition的Scope值为prototype,说明要重新创建一个对象,不放到单例池中,直接通过反射创建对象并注入属性再返回

以上就是整体的思路,可能还是没有头绪,不要慌,下面是各个模块的讲解

项目结构

在这里插入图片描述

anno包下放着有关IOC的一些注解
config包下是使用注解注入时需要的配置类
context包下是对应着基于注解注入和配置文件注入的context
scan包下是IOC的核心实现类
pojo包下是测试用的实体类
Resource包下是配置文件

代码实现

注解

没什么好说的,不知道IOC各个注解作用的可以去搜索一下

@Target({ElementType.TYPE,ElementType.FIELD,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Autowired {
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Component {
    String value() default "";
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface ComponentScan {
    String value() default "";
}
@Target({ElementType.TYPE,ElementType.FIELD,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Qualifier {
    String value() default "";
}
@Target({ElementType.TYPE,ElementType.FIELD,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Resource {
    String name() default "";
}
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Scope {
    String value() default "";
}
@Target({ElementType.TYPE,ElementType.FIELD,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Value {
    String value();
}

BeanDefinition

包含了要Bean对象的类对象type和Bean的作用域scope

/**
 * @Description: //BeanDefinition,包含了类对象、作用域
 * @Author: zzy
 * @Date: 2022/3/29 13:49
 */
public class BeanDefinition {
    private Class type;
    private String scope;

    public BeanDefinition(Class type, String scope) {
        this.type = type;
        this.scope = scope;
    }

    public Class getType() {
        return type;
    }

    public void setType(Class type) {
        this.type = type;
    }

    public String getScope() {
        return scope;
    }

    public void setScope(String scope) {
        this.scope = scope;
    }
}

工具类Utils

包换了两个方法,scanBefore方法作用是扫描配置文件得到存放着标签的List,getFileFromPath方法的作用是由路径packagePath得到这个路径对应的文件夹或文件(路径类似com.zzy.pojo,是一个字符串)

/**
 * @Description: 工具类
 * @Author: zzy
 * @Date: 2022/3/29 21:03 
 */
class Utils {
    /**
     * @MethodName: ScanBeforeList
     * @Description: 扫描配置文件
     * @Author: zzy
     * @Date: 2022/3/29 15:01
     * @Param: [classPath]
     * @Return: org.w3c.dom.NodeList
     */
    static NodeList scanBefore(String classPath) throws Exception {
        InputStream inputStream = new FileInputStream(classPath);
        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
        DocumentBuilder docBuilder = factory.newDocumentBuilder();
        Document doc = docBuilder.parse(inputStream);
        Element root = doc.getDocumentElement();
        return root.getChildNodes();
    }

    /**
     * @MethodName: getFileFromPath
     * @Description: 由包路径获取到文件
     * @Author: zzy
     * @Date: 2022/3/29 15:07
     * @Param: [packagePath]
     * @Return: java.io.File
     */
    static File getFileFromPath(String packagePath, ClassLoader classLoader){
        packagePath = packagePath.replace(".", "/");
        URL resource = classLoader.getResource(packagePath);
        assert resource != null;
        String f1 = resource.getFile();
        return new File(f1);
    }
}

实体类User和Book

测试用的实体类,设置了int String int[] String[] 引用对象这几个属性,还使用了@Component、@Value、@Autowired和@Qualifier注解

/**
 * @author 张梓毅
 */
@Component("user")
public class User {
    @Value("zzy")
    private String name;
    
    @Value("20")
    private int age;
    
    private int[] scores;
    
    private String[] subjects;
    
    @Autowired
    @Qualifier("java")
    private Book book;

    public User() {
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public int[] getScores() {
        return scores;
    }

    public void setScores(int[] scores) {
        this.scores = scores;
    }

    public String[] getSubjects() {
        return subjects;
    }

    public void setSubjects(String[] subjects) {
        this.subjects = subjects;
    }

    public Book getBook() {
        return book;
    }

    public void setBook(Book book) {
        this.book = book;
    }

    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", scores=" + Arrays.toString(scores) +
                ", subjects=" + Arrays.toString(subjects) +
                ", book=" + book +
                '}';
    }
}
@Component("java")
public class Book {

    @Value("1999")
    private int number;

    public Book() {
    }

    public int getNumber() {
        return number;
    }

    public void setNumber(int number) {
        this.number = number;
    }


    @Override
    public String toString() {
        return "Book{" +
                "number=" + number +
                '}';
    }
}

配置文件applicationContext.xml

使用<component-scan base-package="MySpring.pojo"/>开启对component-scan的支持,扫描base-package属性下的包路径,还利用配置文件对数组属性进行注入

<beans>
    <!-- 添加component-scan支持,可以扫描某个包下所有类的@Component注解-->
    <component-scan base-package="MySpring.pojo"/>

    <bean id="user" class="MySpring.pojo.User">
        <property name="scores">
            <array>
                <value>98</value>
                <value>66</value>
                <value>0</value>
            </array>
        </property>
        <property name="subjects">
            <array>
                <value>数学</value>
                <value>英语</value>
                <value>语文</value>
            </array>
        </property>
    </bean>
</beans>

IOC核心实现类Scan

着重讲一下这个类,为了观看方便,先把方法本体去掉,只有方法名,后面我会依次讲解,可以先熟悉熟悉大体结构

/**
 * @Description: IOC核心实现类
 * @Author: zzy
 * @Date: 2022/3/29 21:11
 */
public class Scan {
    /**
     * 作为BeanDefinition容器
     */
    private Map<String, BeanDefinition> beanDefinitionMap = new HashMap<>();
    /**
     * 作为Bean单例池
     */
    private Map<String, Object> singletonMap = new HashMap<>();


    /**
     * @MethodName: putBeanDefinition
     * @Description: 将扫描到的BeanDefinition放到容器中
     * @Author: zzy
     * @Date: 2022/3/29 15:03
     * @Param: [classPath]
     * @Return: void
     */
    public void putBeanDefinitionFromXml(String classPath) {
    	
    }

    /**
     * @MethodName: putBeanDefinitionByAnnoFromXml
     * @Description: 通过XML中的ComponentScan扫描对应包下的所有类,封装为beanDefinition放到beanDefinitionMap中
     * @Author: zzy
     * @Date: 2022/3/29 20:06
     * @Param: [element]
     * @Return: void
     */
    public void putBeanDefinitionByAnno(Element element,Class<?> configClass){
      
    }

    /**
     * @MethodName: putBeanDefinitionByXml
     * @Description: 扫描XML配置文件下的所有bean,封装为beanDefinition放到beanDefinitionMap中
     * @Author: zzy
     * @Date: 2022/3/29 20:09
     * @Param: [element]
     * @Return: void
     */
    private void putBeanDefinitionByXml(Element element) {
        
    }

    /**
     * @MethodName: getBeanByScope
     * @Description: 获取bean对象
     * @Author: zzy
     * @Date: 2022/3/29 15:12
     * @Param: [name]
     * @Return: java.lang.Object
     */
    public Object getBeanByName(String beanName) {
        
    }
    
    /**
     * @MethodName: createBean
     * @Description: 实例化Bean对象
     * @Author: zzy
     * @Date: 2022/3/29 15:31
     * @Param: [beanName, beanDefinition]
     * @Return: java.lang.Object
     */
    private Object createBean(BeanDefinition beanDefinition) {
       
    }

    /**
     * @MethodName: InjectField
     * @Description: 注入属性
     * @Author: zzy
     * @Date: 2022/3/29 16:06
     * @Param: [beanClass, bean]
     * @Return: void
     */
    private void InjectField(Class beanClass, Object bean)  {
       
    }
}

首先要有两个HashMap,beanDefinitionMap用来存放BeanDefinition,作为BeanDefinition容器,singletonMap用来存放单例Bean对象,作为Bean对象单例池

/**
* 作为BeanDefinition容器
*/
private Map<String, BeanDefinition> beanDefinitionMap = new HashMap<>();
/**
* 作为Bean单例池
*/
private Map<String, Object> singletonMap = new HashMap<>();

putBeanDefinitionFromXml方法:
这个方法的作用是从XML配置文件中扫描每个标签,先调用Utils工具类中的scanBefore方法得到标签List,遍历List,判断这个标签是component-scan还是bean,如果是component-scan标签,调用putBeanDefinitionByAnno方法,如果是bean标签,调用putBeanDefinitionByXml方法

    /**
     * @MethodName: putBeanDefinition
     * @Description: 将扫描到的BeanDefinition放到容器中
     * @Author: zzy
     * @Date: 2022/3/29 15:03
     * @Param: [classPath]
     * @Return: void
     */
    public void putBeanDefinitionFromXml(String classPath) throws Exception {
        NodeList nodes = Utils.scanBefore(classPath);
        //遍历nodes
        for (int i = 0; i < nodes.getLength(); i++) {
            Node node = nodes.item(i);
            if (node instanceof Element) {
                Element element = (Element) node;
                //如果开启了component-scan的支持
                if ("component-scan".equals(node.getNodeName())) {
                    putBeanDefinitionByAnno(element,null);
                } else {
                    putBeanDefinitionByXml(element);
                }
            }
        }
    }

putBeanDefinitionByAnno方法:
这个方法的作用是通过配置文件中的component-scan标签中的base-package属性扫描对应路径下的文件夹或文件
这个方法有两个参数,第一个参数是为了支持XML配置文件中的component-scan标签用的,第二个参数是为了支持config类中的@ComponentScan注解用的,因为如果使用的是XML配置文件注入的话会传入配置文件路径,如果使用的是config类中的@ComponentScan注解会传入config的类对象,如果哪个不为空就对应何种方式
之后会调用Utils工具类中的getFileFromPath文件获取路径下的文件夹或文件,如果是文件夹就扫描这个文件夹下的带有@Component注解的类
将扫描到的类封装为BeanDefinition,全部放到BeanDefinitionMap中

	/**
     * @MethodName: putBeanDefinitionByAnnoFromXml
     * @Description: 通过XML中的ComponentScan扫描对应包下的所有类,封装为beanDefinition放到beanDefinitionMap中
     * @Author: zzy
     * @Date: 2022/3/29 20:06
     * @Param: [element]
     * @Return: void
     */
    public void putBeanDefinitionByAnno(Element element,Class<?> configClass){
        String packagePath = "";
        if(element != null){
            packagePath = element.getAttribute("base-package");
        }
        if(configClass != null){
            if(!configClass.isAnnotationPresent(ComponentScan.class)){
                throw new IllegalArgumentException("选择的Config类不正确!");
            }
            packagePath = configClass.getAnnotation(ComponentScan.class).value();
        }
        ClassLoader classLoader = Scan.class.getClassLoader();
        File file = Utils.getFileFromPath(packagePath, classLoader);
        //如果是文件夹,说明要获取到这个文件夹下的所有类
        if (file.isDirectory()) {
            for (File f : Objects.requireNonNull(file.listFiles())) {
                try {
                    //获取扫描的包下的类
                    String absolutePath = f.getAbsolutePath();
                    absolutePath = absolutePath.substring(absolutePath.indexOf("MySpring"), absolutePath.indexOf(".class"));
                    absolutePath = absolutePath.replace("\\", ".");
                    Class<?> beanClass = classLoader.loadClass(absolutePath);
                    String scopeStr = "singleton";
                    //通过反射获取到@Scope注解的value,判断是singleton还是prototype
                    if (beanClass.isAnnotationPresent(Scope.class) && "prototype".equals(beanClass.getAnnotation(Scope.class).value())) {
                        scopeStr = "prototype";

                    }
                    //如果这个类加了@Component注解
                    if (beanClass.isAnnotationPresent(Component.class)) {
                        Component componentAnnotation = beanClass.getAnnotation(Component.class);
                        String beanName = componentAnnotation.value();
                        //如果没有给@Component注解value值,默认为首字母小写
                        if ("".equals(beanName)) {
                            beanName = (char) (32 + beanClass.getSimpleName().charAt(0)) + beanClass.getSimpleName().substring(1);
                        }
                        //封装为BeanDefinition
                        BeanDefinition beanDefinition = new BeanDefinition(beanClass, scopeStr);
                        //将BeanDefinition加到BeanDefinitionMap中
                        beanDefinitionMap.put(beanName, beanDefinition);
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }

putBeanDefinitionByXml方法:
这个方法的作用是获取扫描到的dean标签的id和class属性,通过反射得到Class对象,封装为一个BeanDefinition并放到BeanDefinitionMap中
但是这还没有完,因为注入的属性值都存在于配置文件中,所以为了让之后注入的时候不会再扫描一遍配置文件,会在这个时候实例化Bean对象并把属性注入进去,然后把实例化的Bean对象放到singletonMap单例池中(注意要判断一下单例池是不是已经有这个bean对象了,如果没有才这样做。这里有些人可能会觉得如果这个时候就放进入,那么当类中其他属性是通过注解注入的话就不能注入了,这一点我也考虑到了,我会在之后从单例池中获取的时候会再通过注解注入一遍)

    /**
     * @MethodName: putBeanDefinitionByXml
     * @Description: 扫描XML配置文件下的所有bean,封装为beanDefinition放到beanDefinitionMap中
     * @Author: zzy
     * @Date: 2022/3/29 20:09
     * @Param: [element]
     * @Return: void
     */
    private void putBeanDefinitionByXml(Element element) throws Exception {
        //得到bean的id和class
        String beanName = element.getAttribute("id");
        String className = element.getAttribute("class");
        //根据class路径得到Class对象
        Class<?> beanClass = null;
        try {
            beanClass = Class.forName(className);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
            return;
        }
        String scopeStr = "singleton";
        //通过反射获取到@Scope注解的value,判断是singleton还是prototype
        if (beanClass.isAnnotationPresent(Scope.class) && "prototype".equals(beanClass.getAnnotation(Scope.class).value())) {
            scopeStr = "prototype";

        }
        //封装为BeanDefinition
        BeanDefinition beanDefinition = new BeanDefinition(beanClass, scopeStr);
        //放到BeanDefinitionMap中
        beanDefinitionMap.put(beanName, beanDefinition);
        //如果单例池中有这个bean了,就不用再实例化了
        if(singletonMap.containsKey(beanName)){
            return;
        }
        //利用反射实例化bean对象
        Object bean = beanClass.newInstance();
        NodeList propertyList = element.getElementsByTagName("property");
        for (int j = 0; j < propertyList.getLength(); j++) {
            Node property = propertyList.item(j);
            if (property instanceof Element) {
                //得到bean的name
                String name = ((Element) property).getAttribute("name");
                //可能是数组注入
                Node arrayNode = ((Element) property).getElementsByTagName("array").item(0);
                //利用反射得到这个属性和可访问权限
                Field field = bean.getClass().getDeclaredField(name);
                field.setAccessible(true);
                //利用反射得到set方法和可访问权限
                Method method = bean.getClass().getDeclaredMethod("set" + (char) (name.charAt(0) - 32) + name.substring(1), field.getType());
                method.setAccessible(true);
                //判断是不是数组注入
                if (arrayNode == null) {
                    //得到每个property中的value和ref
                    String value = ((Element) property).getAttribute("value");
                    String ref = ((Element) property).getAttribute("ref");
                    //判断是value还是ref
                    if (!"".equals(value) && "".equals(ref)) {
                        if ("java.lang.String".equals(field.getType().getName())) {
                            method.invoke(bean, value);
                        } else if ("int".equals(field.getType().getName())) {
                            method.invoke(bean, Integer.parseInt(value));
                        }
                    } else if (!"".equals(ref) && "".equals(value)) {
                        method.invoke(bean, getBeanByName(ref));
                    } else {
                        throw new IllegalArgumentException("id为" + beanName + "的bean配置有误");
                    }
                } else {
                    //标识是int数组还是String数组
                    boolean flag = false;
                    //得到多个value标签
                    NodeList valueList = ((Element) arrayNode).getElementsByTagName("value");
                    //根据数组类型创建数组
                    Object arr = Array.newInstance(field.getType().getComponentType(), valueList.getLength());
                    //如果是int数组,把标识改为true
                    if ("int".equals(field.getType().getComponentType().getName())) {
                        flag = true;
                    }
                    //遍历每个value节点
                    for (int k = 0; k < valueList.getLength(); k++) {
                        Node valueNode = valueList.item(k);
                        //获取value
                        String value = valueNode.getTextContent();
                        if ("".equals(value)) {
                            throw new IllegalArgumentException("id为" + beanName + "的bean配置有误");
                        }
                        if (flag) {
                            Array.set(arr, k, Integer.parseInt(value));
                        } else {
                            Array.set(arr, k, value);
                        }
                    }
                    method.invoke(bean, arr);
                }
            }
        }
        singletonMap.put(beanName, bean);
    }

getBeanByName方法
这个方法的作用是通过对象名beanName类获取到bean对象,需要考虑单例和多例的情况,单例就直接从单例池中拿,如果单例池中没有说明这个类还没有进行Bean的实例化,就实例化Bean对象加到单例池中再返回,多例的话之间实例化Bean对象,不需要加到单例池中,直接返回即可

	/**
     * @MethodName: getBeanByScope
     * @Description: 获取bean对象
     * @Author: zzy
     * @Date: 2022/3/29 15:12
     * @Param: [name]
     * @Return: java.lang.Object
     */
    public Object getBeanByName(String beanName) throws IllegalAccessException {
        //如果beanDefinitionMap包含key为beanName的键值对
        if (beanDefinitionMap.containsKey(beanName)) {
            //获取beanDefinition
            BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
            //判断是不是单例,如果是singleton
            if ("singleton".equals(beanDefinition.getScope())) {
                //从单例池中获取bean对象
                Object singletonBean = singletonMap.get(beanName);
                //如果这个bean对象还没有加进去,就去实例化这个bean
                if (singletonBean == null) {
                    //调用createBean方法来实例化Bean对象,注入也会在这个方法中完成
                    singletonBean = createBean(beanDefinition);
                    //放到单例池中
                    singletonMap.put(beanName, singletonBean);
                } else {
                    //如果单例池中存在bean对象,考虑到是通过配置文件注入的,还需要再注入一次
                    InjectField(beanDefinition.getType(), singletonBean);
                }
                //返回单例Bean对象
                return singletonBean;
            } else if ("prototype".equals(beanDefinition.getScope())) {
                //如果是多例,就直接实例化bean对象,返回,注入也会在这个方法中完成
                return createBean(beanDefinition);
            }
        }
        return null;
    }

createBean方法:
这个方法的作用是利用反射实例化bean对象,调用InjectField方法注入属性后再返回

    /**
     * @MethodName: createBean
     * @Description: 实例化Bean对象
     * @Author: zzy
     * @Date: 2022/3/29 15:31
     * @Param: [beanName, beanDefinition]
     * @Return: java.lang.Object
     */
    private Object createBean(BeanDefinition beanDefinition) throws IllegalAccessException {
        //通过反射实例化bean对象
        Class beanClass = beanDefinition.getType();
        Object bean = null;
        try {
            bean = beanClass.newInstance();
        } catch (Exception e) {
            e.printStackTrace();
        }
        //返回之前调用InjectField方法注入属性
        InjectField(beanClass, bean);
        return bean;
    }

InjectField方法:
这个方法的作用是通过注解来注入,利用反射获取Field来注入,必须是带有@Component的类才能注入,为什么没有配置文件注入呢,因为putBeanDefinitionByXml方法中已经完成了配置文件注入

    /**
     * @MethodName: InjectField
     * @Description: 注入属性
     * @Author: zzy
     * @Date: 2022/3/29 16:06
     * @Param: [beanClass, bean]
     * @Return: void
     */
    private void InjectField(Class beanClass, Object bean) throws IllegalAccessException {
        //如果这个类有@Component注解,说明要进行注入
        if (beanClass.isAnnotationPresent(Component.class)) {
            //反射获取Field
            Field[] fields = beanClass.getDeclaredFields();
            for (Field field : fields) {
                field.setAccessible(true);
                //根据@Value的值来注入
                if (field.isAnnotationPresent(Value.class)) {
                    Value value = field.getAnnotation(Value.class);
                    String beanValue = value.value();
                    if ("java.lang.String".equals(field.getType().getName())) {
                        field.set(bean, beanValue);
                    } else if ("int".equals(field.getType().getName())) {
                        field.set(bean, Integer.parseInt(beanValue));
                    }
                }
                //根据@Autowired和@Qualifier来注入引用对象
                if (field.isAnnotationPresent(Autowired.class)) {
                    String beanName = "";
                    if (field.isAnnotationPresent(Qualifier.class)) {
                        beanName = field.getAnnotation(Qualifier.class).value();
                    }
                    if ("".equals(beanName)) {
                        beanName = field.getName();
                    }
                    field.set(bean, getBeanByName(beanName));
                }
                //根据@Resource注解的name属性来注入
                if (field.isAnnotationPresent(Resource.class)) {
                    String beanName = field.getAnnotation(Resource.class).name();
                    field.set(bean, getBeanByName(beanName));
                }
            }
        }
    }

至此,Scan类就已经完成了,整理一下全部代码:

package MySpring.MyIOC.scan;


import MySpring.MyIOC.anno.*;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import java.io.File;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

/**
 * @Description: IOC核心实现类
 * @Author: zzy
 * @Date: 2022/3/29 21:11
 */
public class Scan {
    /**
     * 作为BeanDefinition容器
     */
    private Map<String, BeanDefinition> beanDefinitionMap = new HashMap<>();
    /**
     * 作为Bean单例池
     */
    private Map<String, Object> singletonMap = new HashMap<>();


    /**
     * @MethodName: putBeanDefinition
     * @Description: 将扫描到的BeanDefinition放到容器中
     * @Author: zzy
     * @Date: 2022/3/29 15:03
     * @Param: [classPath]
     * @Return: void
     */
    public void putBeanDefinitionFromXml(String classPath) throws Exception {
        NodeList nodes = Utils.scanBefore(classPath);
        //遍历nodes
        for (int i = 0; i < nodes.getLength(); i++) {
            Node node = nodes.item(i);
            if (node instanceof Element) {
                Element element = (Element) node;
                //如果开启了注解的支持
                if ("component-scan".equals(node.getNodeName())) {
                    putBeanDefinitionByAnno(element, null);
                } else {
                    putBeanDefinitionByXml(element);
                }
            }
        }
    }

    /**
     * @MethodName: putBeanDefinitionByAnnoFromXml
     * @Description: 通过XML中的ComponentScan扫描对应包下的所有类,封装为beanDefinition放到beanDefinitionMap中
     * @Author: zzy
     * @Date: 2022/3/29 20:06
     * @Param: [element]
     * @Return: void
     */
    public void putBeanDefinitionByAnno(Element element, Class<?> configClass) {
        String packagePath = "";
        if (element != null) {
            packagePath = element.getAttribute("base-package");
        }
        if (configClass != null) {
            if (!configClass.isAnnotationPresent(ComponentScan.class)) {
                throw new IllegalArgumentException("选择的Config类不正确!");
            }
            packagePath = configClass.getAnnotation(ComponentScan.class).value();
        }
        ClassLoader classLoader = Scan.class.getClassLoader();
        File file = Utils.getFileFromPath(packagePath, classLoader);
        //如果是文件夹,说明要获取到这个文件夹下的所有类
        if (file.isDirectory()) {
            for (File f : Objects.requireNonNull(file.listFiles())) {
                try {
                    //获取扫描的包下的类
                    String absolutePath = f.getAbsolutePath();
                    absolutePath = absolutePath.substring(absolutePath.indexOf("MySpring"), absolutePath.indexOf(".class"));
                    absolutePath = absolutePath.replace("\\", ".");
                    Class<?> beanClass = classLoader.loadClass(absolutePath);
                    String scopeStr = "singleton";
                    //通过反射获取到@Scope注解的value,判断是singleton还是prototype
                    if (beanClass.isAnnotationPresent(Scope.class) && "prototype".equals(beanClass.getAnnotation(Scope.class).value())) {
                        scopeStr = "prototype";

                    }
                    //如果这个类加了@Component注解
                    if (beanClass.isAnnotationPresent(Component.class)) {
                        Component componentAnnotation = beanClass.getAnnotation(Component.class);
                        String beanName = componentAnnotation.value();
                        //如果没有给@Component注解value值,默认为首字母小写
                        if ("".equals(beanName)) {
                            beanName = (char) (32 + beanClass.getSimpleName().charAt(0)) + beanClass.getSimpleName().substring(1);
                        }
                        //封装为BeanDefinition
                        BeanDefinition beanDefinition = new BeanDefinition(beanClass, scopeStr);
                        //将BeanDefinition加到BeanDefinitionMap中
                        beanDefinitionMap.put(beanName, beanDefinition);
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }

    /**
     * @MethodName: putBeanDefinitionByXml
     * @Description: 扫描XML配置文件下的所有bean,封装为beanDefinition放到beanDefinitionMap中
     * @Author: zzy
     * @Date: 2022/3/29 20:09
     * @Param: [element]
     * @Return: void
     */
    private void putBeanDefinitionByXml(Element element) throws Exception {
        //得到bean的id和class
        String beanName = element.getAttribute("id");
        String className = element.getAttribute("class");
        //根据class路径得到Class对象
        Class beanClass = null;
        try {
            beanClass = Class.forName(className);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
            return;
        }
        //封装为BeanDefinition
        BeanDefinition beanDefinition = new BeanDefinition(beanClass, "singleton");
        //放到BeanDefinitionMap中
        beanDefinitionMap.put(beanName, beanDefinition);

        //利用反射实例化bean对象
        Object bean = beanClass.newInstance();
        NodeList propertyList = element.getElementsByTagName("property");
        for (int j = 0; j < propertyList.getLength(); j++) {
            Node property = propertyList.item(j);
            if (property instanceof Element) {
                //得到bean的name
                String name = ((Element) property).getAttribute("name");
                //可能是数组注入
                Node arrayNode = ((Element) property).getElementsByTagName("array").item(0);
                //利用反射得到这个属性和可访问权限
                Field field = bean.getClass().getDeclaredField(name);
                field.setAccessible(true);
                //利用反射得到set方法和可访问权限
                Method method = bean.getClass().getDeclaredMethod("set" + (char) (name.charAt(0) - 32) + name.substring(1), field.getType());
                method.setAccessible(true);
                //判断是不是数组注入
                if (arrayNode == null) {
                    //得到每个property中的value和ref
                    String value = ((Element) property).getAttribute("value");
                    String ref = ((Element) property).getAttribute("ref");
                    //判断是value还是ref
                    if (!"".equals(value) && "".equals(ref)) {
                        if ("java.lang.String".equals(field.getType().getName())) {
                            method.invoke(bean, value);
                        } else if ("int".equals(field.getType().getName())) {
                            method.invoke(bean, Integer.parseInt(value));
                        }
                    } else if (!"".equals(ref) && "".equals(value)) {
                        method.invoke(bean, getBeanByName(ref));
                    } else {
                        throw new IllegalArgumentException("id为" + beanName + "的bean配置有误");
                    }
                } else {
                    //标识是int数组还是String数组
                    boolean flag = false;
                    //得到多个value标签
                    NodeList valueList = ((Element) arrayNode).getElementsByTagName("value");
                    //根据数组类型创建数组
                    Object arr = Array.newInstance(field.getType().getComponentType(), valueList.getLength());
                    //如果是int数组,把标识改为true
                    if ("int".equals(field.getType().getComponentType().getName())) {
                        flag = true;
                    }
                    //遍历每个value节点
                    for (int k = 0; k < valueList.getLength(); k++) {
                        Node valueNode = valueList.item(k);
                        //获取value
                        String value = valueNode.getTextContent();
                        if ("".equals(value)) {
                            throw new IllegalArgumentException("id为" + beanName + "的bean配置有误");
                        }
                        if (flag) {
                            Array.set(arr, k, Integer.parseInt(value));
                        } else {
                            Array.set(arr, k, value);
                        }
                    }
                    method.invoke(bean, arr);
                }
            }
        }
        singletonMap.put(beanName, bean);
    }

    /**
     * @MethodName: createBean
     * @Description: 实例化Bean对象
     * @Author: zzy
     * @Date: 2022/3/29 15:31
     * @Param: [beanName, beanDefinition]
     * @Return: java.lang.Object
     */
    private Object createBean(BeanDefinition beanDefinition) throws IllegalAccessException {
        //通过反射实例化bean对象
        Class beanClass = beanDefinition.getType();
        Object bean = null;
        try {
            bean = beanClass.newInstance();
        } catch (Exception e) {
            e.printStackTrace();
        }
        //返回之前调用InjectField方法注入属性
        InjectField(beanClass, bean);
        return bean;
    }

    /**
     * @MethodName: InjectField
     * @Description: 注入属性
     * @Author: zzy
     * @Date: 2022/3/29 16:06
     * @Param: [beanClass, bean]
     * @Return: void
     */
    private void InjectField(Class beanClass, Object bean) throws IllegalAccessException {
        //如果这个类有@Component注解,说明要进行注入
        if (beanClass.isAnnotationPresent(Component.class)) {
            //反射获取Field
            Field[] fields = beanClass.getDeclaredFields();
            for (Field field : fields) {
                field.setAccessible(true);
                //根据@Value的值来注入
                if (field.isAnnotationPresent(Value.class)) {
                    Value value = field.getAnnotation(Value.class);
                    String beanValue = value.value();
                    if ("java.lang.String".equals(field.getType().getName())) {
                        field.set(bean, beanValue);
                    } else if ("int".equals(field.getType().getName())) {
                        field.set(bean, Integer.parseInt(beanValue));
                    }
                }
                //根据@Autowired和@Qualifier来注入引用对象
                if (field.isAnnotationPresent(Autowired.class)) {
                    String beanName = "";
                    if (field.isAnnotationPresent(Qualifier.class)) {
                        beanName = field.getAnnotation(Qualifier.class).value();
                    }
                    if ("".equals(beanName)) {
                        beanName = field.getName();
                    }
                    field.set(bean, getBeanByName(beanName));
                }
                //根据@Resource注解的name属性来注入
                if (field.isAnnotationPresent(Resource.class)) {
                    String beanName = field.getAnnotation(Resource.class).name();
                    field.set(bean, getBeanByName(beanName));
                }
            }
        }
    }

    /**
     * @MethodName: getBeanByScope
     * @Description: 获取bean对象
     * @Author: zzy
     * @Date: 2022/3/29 15:12
     * @Param: [name]
     * @Return: java.lang.Object
     */
    public Object getBeanByName(String beanName) throws IllegalAccessException {
        //如果beanDefinitionMap包含key为beanName的键值对
        if (beanDefinitionMap.containsKey(beanName)) {
            //获取beanDefinition
            BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
            //判断是不是单例,如果是singleton
            if ("singleton".equals(beanDefinition.getScope())) {
                //从单例池中获取bean对象
                Object singletonBean = singletonMap.get(beanName);
                //如果这个bean对象还没有加进去,就去实例化这个bean
                if (singletonBean == null) {
                    //调用createBean方法来实例化Bean对象,注入也会在这个方法中完成
                    singletonBean = createBean(beanDefinition);
                    //放到单例池中
                    singletonMap.put(beanName, singletonBean);
                } else {
                    //如果单例池中存在bean对象,考虑到是通过配置文件注入的,还需要再注入一次
                    InjectField(beanDefinition.getType(), singletonBean);
                }
                //返回单例Bean对象
                return singletonBean;
            } else if ("prototype".equals(beanDefinition.getScope())) {
                //如果是多例,就直接实例化bean对象,返回,注入也会在这个方法中完成
                return createBean(beanDefinition);
            }
        }
        return null;
    }
}

还有两个context类没有说,下面来说一下

ClassPathXmlApplicationContext类

这个类只需要看第一个getBean方法就可以了,第二个getBean方法是为IOC提供AOP服务的方法,如果想知道怎么实现AOP的朋友可以看我开头发的那个文章,这里再发一下:仿写Spring IOC和AOP,仿写思路和代码讲解
这个类是基于配置文件注入的context类,第一个getBean方法就是就是调用一下Scan类的putBeanDefinitionFromXml方法,然后调用getBeanByName方法来返回Bean对象

/**
 * @MethodName:
 * @Description: 基于配置文件和注解的IOC
 * @Author: zzy
 * @Date: 2022/3/29 13:41
 * @Param:
 * @Return:
 */
public class ClassPathXmlApplicationContext extends Scan {
    private String classpath;

    public ClassPathXmlApplicationContext(String classpath) {
        this.classpath = Objects.requireNonNull(Scan.class.getClassLoader().getResource(classpath)).getFile();
    }
    
    /**
     * @MethodName: getBean
     * @Description: 提供IOC服务的方法
     * @Author: zzy
     * @Date: 2022/3/29 23:54 
     * @Param: [beanName]
     * @Return: java.lang.Object
     */
    public Object getBean(String beanName) throws Exception {
        putBeanDefinitionFromXml(classpath);
        return super.getBeanByName(beanName);
    }
    
    /**
     * @MethodName: getBean
     * @Description: 为IOC提供AOP服务的方法
     * @Author: zzy
     * @Date: 2022/3/29 23:54 
     * @Param: [beanName, cl]
     * @Return: java.lang.Object
     */
    public Object getBean(String beanName, Class cl) throws Exception {
        String pointcutName = null;
        Method pointcutMethod = null;
        Method beforeMethod = null;
        Method afterMethod = null;
        String beforeName = null;
        String afterName = null;
        //通过反射获取注解
        Aspect aspect = (Aspect) cl.getAnnotation(Aspect.class);
        if (aspect == null) {
            throw new IllegalArgumentException("该类不是切面类");
        }
        Method[] methods = cl.getDeclaredMethods();
        for (Method method : methods) {
            Pointcut pointcut = method.getAnnotation(Pointcut.class);
            if (pointcut != null) {
                pointcutMethod = method;
                pointcutName = pointcut.value();
                break;
            }
        }
        for (Method method : methods) {
            Boolean flag = null;
            String value = null;
            Before before = method.getAnnotation(Before.class);
            After after = method.getAnnotation(After.class);
            if (before != null) {
                flag = true;
                value = before.value();
                beforeMethod = method;
            }else if(after != null){
                flag = false;
                value = after.value();
                afterMethod = method;
            }
            if (flag != null && pointcutMethod != null && (value.equals(pointcutMethod.getName() + "()"))) {
                if(flag){
                    beforeName = pointcutName;
                }else{
                    afterName = pointcutName;
                }
            }else{
                if(flag != null){
                    if (flag) {
                        beforeName = value;
                    }else{
                        afterName = value;
                    }
                }
            }
        }
        putBeanDefinitionFromXml(classpath);
        Object bean = super.getBeanByName(beanName);
        Advice advice = new Advice(bean,cl.newInstance(),beforeMethod,beforeName,afterMethod,afterName);
        Object beanPorxy = AopProxy.getProxy(bean,advice);
        return beanPorxy;
    }
}

AnnotationConfigApplicationContext类

这个类和ClassPathXmlApplicationContext思路一样,但是这个类是基于注解的context,不会使用到配置文件,所以不会再调用putBeanDefinitionFromXml方法,而是调用putBeanDefinitionByAnno方法

/**
 * @MethodName:
 * @Description: 基于注解的IOC
 * @Author: zzy
 * @Date: 2022/3/29 20:17
 * @Param:
 * @Return:
 */
public class AnnotationConfigApplicationContext extends Scan {
    private Class<?> configClass;

    public AnnotationConfigApplicationContext(Class<?> configClass) {
        this.configClass = configClass;
    }

    public Object getBean(String beanName) throws Exception {
        putBeanDefinitionByAnno(null,configClass);
        return super.getBeanByName(beanName);
    }

    public Object getBean(String beanName, Class cl) throws Exception {
        String pointcutName = null;
        Method pointcutMethod = null;
        Method beforeMethod = null;
        Method afterMethod = null;
        String beforeName = null;
        String afterName = null;
        //通过反射获取注解
        Aspect aspect = (Aspect) cl.getAnnotation(Aspect.class);
        if (aspect == null) {
            throw new IllegalArgumentException("该类不是切面类");
        }
        Method[] methods = cl.getDeclaredMethods();
        for (Method method : methods) {
            Pointcut pointcut = method.getAnnotation(Pointcut.class);
            if (pointcut != null) {
                pointcutMethod = method;
                pointcutName = pointcut.value();
                break;
            }
        }
        for (Method method : methods) {
            Boolean flag = null;
            String value = null;
            Before before = method.getAnnotation(Before.class);
            After after = method.getAnnotation(After.class);
            if (before != null) {
                flag = true;
                value = before.value();
                beforeMethod = method;
            }else if(after != null){
                flag = false;
                value = after.value();
                afterMethod = method;
            }
            if (flag != null && pointcutMethod != null && (value.equals(pointcutMethod.getName() + "()"))) {
                if(flag){
                    beforeName = pointcutName;
                }else{
                    afterName = pointcutName;
                }
            }else{
                if(flag != null){
                    if (flag) {
                        beforeName = value;
                    }else{
                        afterName = value;
                    }
                }
            }
        }
        putBeanDefinitionByAnno(null,configClass);
        Object bean = super.getBeanByName(beanName);
        Advice advice = new Advice(bean,cl.newInstance(),beforeMethod,beforeName,afterMethod,afterName);
        Object beanPorxy = AopProxy.getProxy(bean,advice);
        return beanPorxy;
    }
}

IOC测试

我这里测试了ClassPathXmlApplicationContext,对User类进行依赖注入

public class MyTest {
    public static void main(String[] args) throws Exception {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("MySpring/Resource/applicationContext.xml");
        User user = (User) context.getBean("user");
        System.out.println(user.toString());
    }
}

测试结果:
可以发现属性都注入进去了
在这里插入图片描述
再测试一下是否是单例对象

public class MyTest {
    public static void main(String[] args) throws Exception {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("MySpring/Resource/applicationContext.xml");
        User user1 = (User) context.getBean("user");
        System.out.println(user1.toString());
        User user2 = (User) context.getBean("user");
        System.out.println(user2.toString());
        System.out.println(user1 == user2);
    }
}

测试结果:
是单例对象
在这里插入图片描述
再测试一下多例模式
在这里插入图片描述

public class MyTest {
    public static void main(String[] args) throws Exception {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("MySpring/Resource/applicationContext.xml");
        User user1 = (User) context.getBean("user");
        System.out.println(user1.toString());
        User user2 = (User) context.getBean("user");
        System.out.println(user2.toString());
        System.out.println(user1 == user2);
    }
}

测试结果:
在这里插入图片描述
就测试这么多吧,本人能力有限,可能做的并不太好,以后会慢慢补充更多功能

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值