前两篇文章介绍了关于手写Spring IOC控制反转,由Spring工厂在运行过程当中动态地建立对象的两种方式。如感兴趣,可移步:java
手写Spring之IOC基于xml动态建立对象spring
手写Spring之IOC基于注解动态建立对象segmentfault
今天将详细介绍如何手写Spring DI依赖注入,在运行过程当中如何动态地为对象的属性赋值。服务器
首先仍是建立项目,用于本次测试须要使用到junit,所以建立的是Maven项目,方便添加依赖jar包,JDK环境仍是1.7:ide
接下来在pom.xml文件中添加junit的依赖坐标:测试
<dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.10</version> <scope>test</scope> </dependency>
第一次添加时,若本地仓库中没有此版本的jar包,Maven会根据配置的镜像联网下载,默认是去中心仓库下载,中心仓库的服务器在国外,下载速度较慢,建议修改配置文件链接阿里云的Maven镜像仓库下载,速度较快,如何配置在此很少赘述。你也能够根据本身本地仓库已有的junit版本 对依赖坐标的版本进行修改,这样就能够直接使用本地仓库的jar包,不用耗时连外网去下载了。阿里云
完成后在Maven Dependencies中会有相关的jar包出现:url
进行DI注入前须要建立工厂,在运行时从工厂中取出对象为属性赋值。所以先作一些准备工做,建立几个要用到的注解:spa
MyComponent注解内容以下:prototype
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() default ""; }
MyAutowired注解内容以下:
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 MyAutowired { }
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(); }
接下来建立实体类:
User实体类内容以下,实体类中的属性值暂用注解方式写死做为测试(实际中并不会这么用),此实体类暂时为单例类(不注明scope属性默认为单例模式):
@MyComponent public class User { @MyValue("1") private Integer id; @MyValue("zhangsan") private String name; @MyValue("zhangsan") private String password; public User() { System.out.println("无参构造方法执行"); } public void login(){ System.out.println("用户登陆:id=" + id + ", name=" + name + ", password=" + password); } //setters和getters... }
而后建立UserService类,在Service类中使用依赖注入User:
UserService内容以下:
package service; import annotation.MyAutowired; import annotation.MyComponent; import entity.User; @MyComponent public class UserService { @MyAutowired User user1; @MyAutowired User user2; public void userLogin(){ System.out.println("用户1:"+user1); user1.login(); System.out.println("用户2:"+user2); user2.login(); } }
建立注解工厂类:
工厂类的内容以下:
public class AnnotationConfigApplicationContext { /**此Map容器用于存储类定义对象*/ private Map<String, Class<?>> beanDefinationFacotry=new ConcurrentHashMap<>(); /**此Map容器用于存储单例对象*/ private Map<String,Object> singletonbeanFactory=new ConcurrentHashMap<>(); /**有参构造方法,参数类型为指定要扫描加载的包名,此工厂可接收多个包路径*/ public AnnotationConfigApplicationContext(String... packageNames) { //遍历扫描指定的全部包路径 for (String packageName : packageNames) { System.out.println("开始扫描包:"+packageName); /**扫描指定的包路径*/ scanPkg(packageName); } /**进行DI依赖注入*/ dependencyInjection(); } }
在工厂类的构造方法中,能够接收多个包路径,而且遍历循环扫描每个包路径,扫描包的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 beanId=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(beanId, c); } }catch(Exception e){ throw new RuntimeException(e); } } }
扫描全部的包完成以后,对须要的属性进行注入,dependencyInjection方法以下:
/** * 此方法用于对属性进行依赖注入。 * 从工厂中获取全部的类对象,若是类中的属性上有MyAutowired注解, * 那么首先从根据属性名从工厂中获取对象,或者根据对象类型获取对象。 * 最后用该对象对属性进行注入。 */ private void dependencyInjection(){ //获取容器中全部的类定义对象 Collection<Class<?>> classes = beanDefinationFacotry.values(); //遍历每个类对象 for (Class<?> cls : classes) { //获取类对象的名字全称(包名+类名) String clsName = cls.getName(); //获取类名 clsName = clsName.substring(clsName.lastIndexOf(".")+1); //将类名(一般为大写开头)的第一个字母转换小写 String beanId=String.valueOf(clsName.charAt(0)).toLowerCase()+clsName.substring(1); //获取类中全部的属性 Field[] fields = cls.getDeclaredFields(); //遍历每个属性 for (Field field : fields) { //若是这个属性上有MyAutowired注解,进行注入操做 if(field.isAnnotationPresent(MyAutowired.class)){ try { //获取属性名 String fieldName = field.getName(); System.out.println("属性名:"+fieldName); //定义为属性注入的bean对象(此对象从容器中获取) Object fieldBean = null; //首先根据属性名从容器中取出对象,若是不为null,则赋值给fieldBean对象 if(beanDefinationFacotry.get(fieldName) != null){ fieldBean = getBean(fieldName,field.getType()); }else{ //不然按照属性的类型从容器中取出对象进行注入 //获取属性的类型(包名+类名) String type = field.getType().getName(); //截取最后的类名 type = type.substring(type.lastIndexOf(".")+1); //将类名(一般为大写开头)的第一个字母转换小写 String fieldBeanId=String.valueOf(type.charAt(0)).toLowerCase()+type.substring(1); System.out.println("属性类型ID:"+fieldBeanId); //根据转换后的类型beanId,从容器中获取对象并赋值给fieldBean对象 fieldBean = getBean(fieldBeanId,field.getType()); } System.out.println("要为属性注入的值:"+fieldBean); //若是fieldBean对象不为空,则为该属性进行注入 if(fieldBean != null){ //获取此类定义的对象的实例对象 Object clsBean = getBean(beanId, cls); //设置此属性可访问 field.setAccessible(true); //为该属性注入值 field.set(clsBean, fieldBean); System.out.println("注入成功!"); }else{ System.out.println("注入失败!"); } } catch (IllegalArgumentException | IllegalAccessException e) { e.printStackTrace(); } } } } }
在dependencyInjection方法中调用了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) || "".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>T getBean(String beanId, Class<T> c){ return (T)getBean(beanId); }
在getBean方法中从工厂容器中获取对象,而且须要调用setFieldValues方法为对象的属性赋值,该方法内容以下:
/** * 此方法用于为对象的属性赋值 * 内部是经过获取成员属性上注解的值,在转换为类型以后,经过反射为对象赋值 * @param cls 类定义对象 * @param obj 要为其赋值的实例对象 */ private 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; }
工厂类建立完毕后,开始写测试类进行测试:
测试类内容以下:
@MyComponent public class TestSpringDi { /**建立AnnotationConfigApplicationContext对象*/ AnnotationConfigApplicationContext ctx; /**建立UserService对象*/ UserService userService; /** * 初始化方法 */ @Before public void init(){ //实例化工厂类,传入entity/service/springTest三个包路径进行扫描 ctx = new AnnotationConfigApplicationContext("entity","service","springTest"); //调用工厂的getBean方法动态获取对象 userService = ctx.getBean("userService",UserService.class); } /** * 测试方法 */ @Test public void testSpringDi(){ userService.userLogin(); } /** * 销毁方法 */ @After public void close(){ ctx.close(); } }
以上是全部的代码,写完以后就能够运行程序进行测试了。运行结果以下:
从控制台打印输出的结果能够看出,UserService类中的两个User属性都已经成功注入,并调用了模拟用户登陆的login方法,输出的结果正是为User对象所设置的值。因为User类是单例的,所以UserService中的两个User属性所注入的值都是同一个对象(根据对象所映射的地址hashcode值相同能够证实这一点),并且无参的构造方法也只执行了一次。
那么如何为多例模式的对象进行注入呢?咱们在User类的注解中加上scope属性,指定为prototype:
@MyComponent(scope="prototype") public class User { ... ... }
而后再次运行程序进行测试,结果以下:
如今能够看到,为两个User属性所赋的值已是不一样的对象了,无参构造方法执行了两次。