上一篇博客介绍了如何基于xml配置文件在运行时建立实例对象,这篇博客将介绍基于注解方式怎样实现对象的建立。java
废话很少说,直接上代码。spring
首先仍是建立项目,因为此次不须要使用第三方的API,建立一个简单的Java项目便可,依然仍是JDK7的环境下。app
第二步是建立属于本身的注解。框架
MyComponent注解内容以下:ide
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对象类型,用于在运行时建立其实例对象。url
方便测试,该User类型分别建立两个单例singleton和多例prototype的User类型。spa
SingletonUser类的内容以下:.net
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类的内容以下:prototype
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<String, Class<?>> beanDefinationFacotry=new ConcurrentHashMap<>(); /**此Map容器用于存储单例对象*/ private Map<String,Object> 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>T getBean(String beanId, Class<T> 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框架的理解,并不是要写一个可用的框架。所以在代码中省略了大量的对于参数的判断和异常处理,免去代码的冗余,方便看官理解。高手请勿吐槽啊,如有问题或建议,欢迎留言指正。