资讯专栏INFORMATION COLUMN

手写Spring之IOC基于注解动态创建对象

Andrman / 973人阅读

摘要:上一篇博客介绍了如何基于配置文件在运行时创建实例对象,这篇博客将介绍基于注解方式怎样实现对象的创建。方便测试,该类型分别创建两个单例和多例的类型。注意这种为对象注入属性值的方式耦合度较高,可根据情况使用。

上一篇博客介绍了如何基于xml配置文件在运行时创建实例对象,这篇博客将介绍基于注解方式怎样实现对象的创建。

废话不多说,直接上代码。

首先还是创建项目,由于这次不需要使用第三方的API,创建一个简单的Java项目即可,依然还是JDK7的环境下。

第二步是创建属于自己的注解。

MyComponent注解内容如下:

package annotation;  
  
import java.lang.annotation.ElementType;  
import java.lang.annotation.Retention;  
import java.lang.annotation.RetentionPolicy;  
import java.lang.annotation.Target;  
/**@Target 属性用于注明此注解用在什么位置, 
 * ElementType.TYPE表示可用在类、接口、枚举上等*/  
@Target(ElementType.TYPE)  
/**@Retention 属性表示所定义的注解何时有效, 
 * RetentionPolicy.RUNTIME表示在运行时有效*/  
@Retention(RetentionPolicy.RUNTIME)  
/**@interface 表示注解类型*/  
public @interface MyComponent {  
    /**为此注解定义scope属性*/  
    public String scope();  
} 

MyValue注解内容如下:

package annotation;

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

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyValue {
    /**定义value属性*/
    public String value();
}

第三步是创建entity对象类型,用于在运行时创建其实例对象。

方便测试,该User类型分别创建两个单例singleton和多例prototype的User类型。

SingletonUser类的内容如下:

package entity;

import annotation.MyComponent;
import annotation.MyValue;

@MyComponent(scope="singleton")
public class SingletonUser {
    @MyValue("1")
    private Integer id;
    @MyValue("zhangsan")
    private String name;
    @MyValue("zhangsan")
    private String password;
    
    public SingletonUser() {
        System.out.println("无参构造方法执行");
    }
    //setters和getters...
}

PrototypeUser类的内容如下:

package entity;

import annotation.MyComponent;
import annotation.MyValue;

@MyComponent(scope="prototype")
public class PrototypeUser {
    @MyValue("2")
    private Integer id;
    @MyValue("lisi")
    private String name;
    @MyValue("lisi")
    private String password;
    public PrototypeUser() {
        System.out.println("无参构造方法执行");
    }
    //setters和getters...
}

主角登场,创建AnnotationConfigApplicationContext工厂类

该类的内容如下:

首先定义两个Map容器用于存储对象。

package applicationContext;  
  
import java.io.File;  
import java.io.FileFilter;  
import java.net.URL;  
import java.util.Map;  
import java.util.concurrent.ConcurrentHashMap;  
  
import annotation.MyComponent;  
  
public class AnnotationConfigApplicationContext {  
    /**此Map容器用于存储类定义对象*/  
    private Map> beanDefinationFacotry=new ConcurrentHashMap<>();  
    /**此Map容器用于存储单例对象*/  
    private Map singletonbeanFactory=new ConcurrentHashMap<>();  
}

定义有参构造方法:

/**有参构造方法,参数类型为指定要扫描加载的包名*/  
public AnnotationConfigApplicationContext(String packageName) {  
    /**扫描指定的包路径*/  
    scanPkg(packageName);  
}  

在创建对象初始化时,会调用scanPkg方法扫描指定路径下的文件,所以在此定义该方法:

/** 
 * 扫描指定包,找到包中的类文件。 
 * 对于标准(类上有定义注解的)类文件反射加载创建类定义对象并放入容器中 
 */  
private void scanPkg(final String pkg){  
    //替换包名中的".",将包结构转换为目录结构  
    String pkgDir=pkg.replaceAll(".", "/");  
    //获取目录结构在类路径中的位置(其中url中封装了具体资源的路径)  
    URL url=getClass().getClassLoader().getResource(pkgDir);  
    //基于这个路径资源(url),构建一个文件对象  
    File file=new File(url.getFile());  
    //获取此目录中指定标准(以".class"结尾)的文件  
    File[] fs=file.listFiles(new FileFilter() {  
        @Override  
        public boolean accept(File file) {  
            //获取文件名  
            String fName=file.getName();  
            //判断该文件是否为目录,如为目录,递归进一步扫描其内部所有文件  
            if(file.isDirectory()){  
                scanPkg(pkg+"."+fName);  
            }else{  
                //判定文件的后缀是否为.class  
                if(fName.endsWith(".class")){  
                    return true;  
                }  
            }  
            return false;  
        }  
    });   
    //遍历所有符合标准的File文件  
    for(File f:fs){  
        //获取文件名  
        String fName=f.getName();  
        //获取去除.class之后的文件名  
        fName=fName.substring(0,fName.lastIndexOf("."));  
        //将名字(类名,通常为大写开头)的第一个字母转换小写(用它作为key存储工厂中)  
        String key=String.valueOf(fName.charAt(0)).toLowerCase()+fName.substring(1);  
        //构建一个类全名(包名.类名)  
        String pkgCls=pkg+"."+fName;  
        try{  
            //通过反射构建类对象  
            Class c=Class.forName(pkgCls);  
            //判定这个类上是否有MyComponent注解  
            if(c.isAnnotationPresent(MyComponent.class)){  
                //将类对象存储到map容器中  
                beanDefinationFacotry.put(key, c);  
            }  
        }catch(Exception e){  
            throw new RuntimeException(e);   
        }  
    }  
}

以上是初始化方法,在创建AnnotationConfigApplicationContext对象时即会扫描指定的包路径,并加载类定义对象到容器中。

接下来定义getBean方法,用于获取工厂所创建的对象:

/**
 * 根据传入的bean的id值获取容器中的对象,类型为Object
 */
public Object getBean(String beanId){
    //根据传入beanId获取类对象
    Class cls = beanDefinationFacotry.get(beanId);
    //根据类对象获取其定义的注解
    MyComponent annotation = cls.getAnnotation(MyComponent.class);
    //获取注解的scope属性值
    String scope = annotation.scope();
    try {
        //如果scope等于singleton,创建单例对象
        if("singleton".equals(scope)){
            //判断容器中是否已有该对象的实例,如果没有,创建一个实例对象放到容器中
            if(singletonbeanFactory.get(beanId)==null){
                Object instance = cls.newInstance();
                setFieldValues(cls,instance);
                singletonbeanFactory.put(beanId,instance);
            }
            //根据beanId获取对象并返回
            return singletonbeanFactory.get(beanId);
        }
        //如果scope等于prototype,则创建并返回多例对象
        if("prototype".equals(scope)){
            Object instance = cls.newInstance();
            setFieldValues(cls,instance);
            return instance;
        }
        //目前仅支持单例和多例两种创建对象的方式
    } catch (InstantiationException e) {
        e.printStackTrace();
    } catch (IllegalAccessException e) {
        e.printStackTrace();
    }
    //如果遭遇异常,返回null
    return null;
}
/**
 * 此为重载方法,根据传入的class对象在内部进行强转,
 * 返回传入的class对象的类型
 */
public T getBean(String beanId, Class c){
    return (T)getBean(beanId);
}

在获取对象时需要为其成员属性赋值,调用了setFieldValue方法,因此定义该方法:

/**
 * 此方法用于为对象的属性赋值
 * 内部是通过获取成员属性上注解的值,在转换为类型之后,通过反射为对象赋值
 * @param cls 类定义对象
 * @param obj 要为其赋值的实例对象
 */
public void setFieldValues(Class cls,Object obj){
    //获取类中所有的成员属性
    Field[] fields = cls.getDeclaredFields();
    //遍历所有属性
    for (Field field : fields) {
        //如果此属性有MyValue注解修饰,对其进行操作
        if(field.isAnnotationPresent(MyValue.class)){
            //获取属性名
            String fieldName = field.getName();
            //获取注解中的值
            String value = field.getAnnotation(MyValue.class).value();
            //获取属性所定义的类型
            String type = field.getType().getName();
            //将属性名改为以大写字母开头,如:id改为ID,name改为Name
            fieldName = String.valueOf(fieldName.charAt(0)).toUpperCase()+fieldName.substring(1);
            //set方法名称,如:setId,setName...
            String setterName = "set" + fieldName;
            try {
                //根据方法名称和参数类型获取对应的set方法对象
                Method method = cls.getDeclaredMethod(setterName, field.getType());
                //判断属性类型,如类型不一致,则转换类型后调用set方法为属性赋值
                if("java.lang.Integer".equals(type) || "int".equals(type)){
                    int intValue = Integer.valueOf(value);
                    method.invoke(obj, intValue);
                } else if("java.lang.String".equals(type)){
                    method.invoke(obj, value);
                }
                //作为测试,仅判断Integer和String类型,其它类型同理
            } catch (NoSuchMethodException | SecurityException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (IllegalArgumentException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            }
        }
    }
}

定义close销毁方法:

/** 
 * 销毁方法,用于释放资源 
 */  
public void close(){  
    beanDefinationFacotry.clear();  
    beanDefinationFacotry=null;  
    singletonbeanFactory.clear();  
    singletonbeanFactory=null;  
}

至此,该工厂类的内容全部完成。接下来写测试类:

测试类的内容如下:

package springTest;  
  
import applicationContext.AnnotationConfigApplicationContext;  
import entity.PrototypeUser;  
import entity.SingletonUser;  
  
public class springIocTest {  
    public static void main(String[] args) {  
        //创建AnnotationConfigApplicationContext对象  
        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext("entity");  
        //仅使用key作为参数获取对象,需要强转  
        SingletonUser singletonUser1=(SingletonUser) ctx.getBean("singletonUser");  
        System.out.println("单例User对象:"+singletonUser1);  
        //使用key和类对象作为参数获取对象,无需强转  
        SingletonUser singletonUser2=ctx.getBean("singletonUser",SingletonUser.class);  
        System.out.println("单例User对象:"+singletonUser2);  
        //仅使用key作为参数获取对象,需要强转  
        PrototypeUser prototypeUser1=(PrototypeUser) ctx.getBean("prototypeUser");  
        System.out.println("多例User对象:"+prototypeUser1);  
        //使用key和类对象作为参数获取对象,无需强转  
        PrototypeUser prototypeUser2=ctx.getBean("prototypeUser",PrototypeUser.class);  
        System.out.println("多例User对象:"+prototypeUser2);  
        //销毁资源  
        ctx.close();  
    }  
}

运行此测试类,控制台输出结果如下:

可以看到,在创建单例对象时,无参的构造方法只调用了一次,并且两次调用getBean方法获取的对象是一致的。而在创建多例对象时,无参的构造方法被调用了两次,两次调用getBean所获取的对象是不同的。

如果需要看对象的属性是否注入成功,可以在两个User类中增加toSrting方法

@Override
public String toString() {
    return "SingletonUser [id=" + id + ", name=" + name + ", password=" + password + "]";
}

再次执行程序,结果如下:

从控制台的输处结果中可以看到,所获取的每个对象都已经有值。注意:这种为对象注入属性值的方式耦合度较高,可根据情况使用。

写在最后:由于手写SpringIOC只是出于对Spring框架的理解,并非要写一个可用的框架。因此在代码中省略了大量的对于参数的判断和异常处理,免去代码的冗余,方便看官理解。高手请勿吐槽啊,若有问题或建议,欢迎留言指正。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/68431.html

相关文章

  • 手写SpringDI依赖注入

    摘要:如感兴趣,可移步手写之基于动态创建对象手写之基于注解动态创建对象今天将详细介绍如何手写依赖注入,在运行过程中如何动态地为对象的属性赋值。完成后在中会有相关的包出现进行注入前需要创建工厂,在运行时从工厂中取出对象为属性赋值。 前两篇文章介绍了关于手写Spring IOC控制反转,由Spring工厂在运行过程中动态地创建对象的两种方式。如感兴趣,可移步: 手写Spring之IOC基于xml...

    Cruise_Chan 评论0 收藏0
  • 慕课网_《Spring入门篇》学习总结

    摘要:入门篇学习总结时间年月日星期三说明本文部分内容均来自慕课网。主要的功能是日志记录,性能统计,安全控制,事务处理,异常处理等等。 《Spring入门篇》学习总结 时间:2017年1月18日星期三说明:本文部分内容均来自慕课网。@慕课网:http://www.imooc.com教学示例源码:https://github.com/zccodere/s...个人学习源码:https://git...

    Ververica 评论0 收藏0
  • 手写SpringIOC基于xml动态创建对象

    Spring作为Java Web最为流行的框架之一,其功能之强大,封装细节之全面不用过多赘述。使用Spring的方式很简单,不需要关注细节,把对象的创建和对象之间的关系都交给框架来管理,仅仅做好配置文件和实现具体的业务逻辑即可。可以说Spring为我们在编写Java Web应用时省去了大量重复的代码,并且可以降低对象与对象之间的耦合度。但若只是知其然,而不知其所以然,在编程时也难免会遇到各种问题,...

    monw3c 评论0 收藏0
  • 明天面试?吓得我赶紧手写了一个Spring

    摘要:你都是如何回答面试官的问题的我不知道,我一般会通过手写一个来加深自己的印象。如今,已然成为了一个生态。运行阶段主要是完成容器启动以后,完成用户请求的内部调度,并返回响应结果。因此,要先写一个针对类名首字母处理的工具方法。 引言 几乎每个面试的程序员都会碰到Spring相关的面试问题,或浅或深。你都是如何回答面试官的问题的?——我不知道,我一般会通过手写一个Spring来加深自己的印象。...

    stefanieliang 评论0 收藏0
  • Spring入门IOC和AOP学习笔记

    摘要:入门和学习笔记概述框架的核心有两个容器作为超级大工厂,负责管理创建所有的对象,这些对象被称为。中的一些术语切面切面组织多个,放在切面中定义。 Spring入门IOC和AOP学习笔记 概述 Spring框架的核心有两个: Spring容器作为超级大工厂,负责管理、创建所有的Java对象,这些Java对象被称为Bean。 Spring容器管理容器中Bean之间的依赖关系,使用一种叫做依赖...

    wenyiweb 评论0 收藏0

发表评论

0条评论

Andrman

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<