上一篇博客介绍了如何基于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<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框架的理解,并非要写一个可用的框架。因此在代码中省略了大量的对于参数的判断和异常处理,免去代码的冗余,方便看官理解。高手请勿吐槽啊,若有问题或建议,欢迎留言指正。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。