天天看点

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

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

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

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

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

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

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

注解内容如下:

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();
}
           

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

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

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

User类的内容如下:

注:PrototypeUser和SingletonUser的内容基本相同,仅类名和注解的scope属性不同。

PrototypeUser的注解为:@MyComponent(scope="prototype")

SingletonUser的注解为:@MyComponent(scope="singleton")

package entity;

import annotation.MyComponent;

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

 主角登场,创建AnnotationConfigApplicationContext工厂类。

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

该类的内容如下:

首先定义两个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();
				singletonbeanFactory.put(beanId,instance);
			}
			//根据beanId获取对象并返回
			return singletonbeanFactory.get(beanId);
		}
		//如果scope等于prototype,则创建并返回多例对象
		if("prototype".equals(scope)){
			return cls.newInstance();
		}
	} 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);
}
           

 定义close销毁方法:  

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

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

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

该测试类的内容如下:

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();
	}
}
           

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

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

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

而在创建多例对象时,无参的构造方法被调用了两次,两次调用getBean所获取的对象是不同的。

注:由于手写SpringIOC只是出于对Spring框架的理解,并非要写一个可用的框架。

因此在代码中省略了大量的对于参数的判断和异常处理,免去代码的冗余,方便看官理解。