使用xml和annotation实现类似spring依赖注入和自动扫描类的功能

大家知道,spring依赖注入可以通过xml和annotation两种方式实现,还提供了自动扫描类的功能,这样大大简化了开发。今天也闲着没事,也实现了类似的功能。废话少说,直接上码:

先说明下要使用到的jar包:dom4j.jar和jaxen.jar(读取配置文件),junit.jar(单位测试),log4j.jar和commons-logging.jar(日志记录)。

1,类似spring的@Service注解

/**
 * 自动扫描类到容器中
 * @author zcl
 *
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Service {

	public String value() default "";
}

 2,@Resource注解

/**
 * 通过此注解实现注入的功能
 * @author zcl
 *
 */
@Target({ElementType.FIELD, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Resource {

	public String name() default "";
}

3, 配置文件的格式类似这样:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="www.zcl.com.cn">
	
 	<bean id="logService" class="cn.zcl.spring.service.impl.LogServiceImpl" />
 	<bean id ="userService" class="cn.zcl.spring.service.impl.UserServiceImpl">
 		<property name="logService" ref="logService"/>
 	</bean>
 	
 	<!-- 实现自动扫描特定包下类的功能,可以配置多个
 	<scan package="cn.zcl.spring"/> 
 	 -->
</beans>

4,封装配置文件中的<property name="" ref=""/>的类:

/**
 * 封装<property name="" ref=""/>的对象
 * @author zcl
 *
 */
public class BeanProperty {

	private String name;
	
	private String ref;

	public BeanProperty(String name, String ref) {
		this.name = name;
		this.ref = ref;
	}
//省略setter与getter()方法,请自己补上(见源码) 

 5,封装<bean id="" class=""><property name="" ref=""/></bean的类

/**
 * 存放形如:
 * <bean id="xx" class="xx">
 * 	<property name="xx" ref="xx"/>
 * </bean>
 * @author zcl
 *
 */
public class BeanDefinition {

	private String id;//存放id属性值
	
	private String className;//存放class属性值
	
	//存放对应的属性值
	private List<BeanProperty> props = new ArrayList<BeanProperty>();

	public BeanDefinition(String id, String className) {
		this.id = id;
		this.className = className;
	}
//省略setter与getter()方法,请自己补上(见源码)

6, 之后就是最重要的BeanFactory了

 由于代码量比较多。我分步描述

1)定义成员变量:

public class BeanFactory {

	private static final Log log = LogFactory.getLog(BeanFactory.class);
	
	/** 存放从配置文件中读取的bean的配置信息 */
	private List<BeanDefinition> beanDefines = new ArrayList<BeanDefinition>();
	/** 存放初始化的bean对象,其中key为id属性值,value为对应的class属性值创建的对象 */
	private Map<String, Object> beans = new HashMap<String, Object>();
	/** 存放自动扫描包的配置信息,<scan package="xx"/> */
	private List<String> packagePaths = new ArrayList<String>();

2),读取配置文件,通过dom4j,所以必需要导入dom4j的相关jar包

/**
	 * 通过dom4j读取配置信息,将读取的配置信息存放到beanDefines集合中
	 * @param fileName
	 */
	@SuppressWarnings("unchecked")
	private void readXml(String fileName) {
		
		SAXReader sReader = new SAXReader();
		URL url = BeanFactory.class.getClassLoader().getResource(fileName);
		Document document = null;
		try {
			document = sReader.read(url);
			Map<String, String> map = new HashMap<String, String>();
			map.put("ns", "www.zcl.com.cn");
			XPath xPath = document.createXPath("//ns:beans/ns:bean");
			xPath.setNamespaceURIs(map);//设置名字空间
			List<Element> elements = xPath.selectNodes(document);
			for (Element element : elements) {
				String id = element.attributeValue("id");
				String className = element.attributeValue("class");
				if (id != null && !id.trim().equals("") && className != null && !className.trim().equals("")) {
					BeanDefinition beanDefine = new BeanDefinition(id, className);
					xPath = element.createXPath("ns:property");
					xPath.setNamespaceURIs(map);
					List<Element> propertyList = xPath.selectNodes(element);
					for (Element prop : propertyList) {
						String name = prop.attributeValue("name");
						String ref = prop.attributeValue("ref");
						if (name != null && !name.trim().equals("") && ref != null && !ref.trim().equals("")) {
							BeanProperty beanProperty = new BeanProperty(name, ref);
							beanDefine.addProps(beanProperty);
						}
					}
					beanDefines.add(beanDefine);
				}
			}
			xPath = document.createXPath("//ns:beans/ns:scan");
			xPath.setNamespaceURIs(map);
			elements = xPath.selectNodes(document);
			for (Element e : elements) {
				String packageName = e.attributeValue("package");
				packagePaths.add(packageName);
			}
		} catch (DocumentException e) {
			log.error("解析xml文件失败!");
			throw new RuntimeException(e);
		}
	}

 上面的代码将读取指定xml的文件,会将<property name="" ref=""/>的内容存放在BeanProperty对象中,会将

<bean id="" class="">的信息存放在BeanDefinition中,最后再放到成员变量beanDefines中。

3),通过xml初始化读取到beanDefines的对象

 

/**
	 * beanDefines集合中的信息初始化bean并存放到Map中
	 */
	private void initBeansByXml() {
		
		for (BeanDefinition beanDefine : beanDefines) {
			try {
				/*
				 *将id作为key值,初始化的bean作为value存放到Map中 
				 */
				beans.put(beanDefine.getId(), Class.forName(beanDefine.getClassName()).newInstance());
			} catch (Exception e) {
				log.error("初始化bean失败");
				throw new RuntimeException(e);
			} 
		}
	}

 4),通过注解初始化Bean

/**
	 * 通过注解初始化Bean
	 */
	private void initBeanByAnnotation() {
		//得到经过utf-8编码的classpath路径
		URL url = BeanFactoryTest.class.getClassLoader().getResource("");
		String rootPath = null;
		try {
			//解码成标准形式的路径格式
			rootPath = java.net.URLDecoder.decode(url.getPath(),"UTF-8");
		} catch (UnsupportedEncodingException e) {
			log.error("解析项目路径时错误!");
			throw new RuntimeException(e);
		}
		//遍历每个配置了<scan package=""/>的信息
		for (String packagePath : packagePaths) {

			File dir = new File(rootPath, saxReader(packagePath));
			if (dir == null || !dir.isDirectory()) { //如果没有设置包,则出异常
				log.error("配置的package不是目录或不存在!");
				throw new RuntimeException("配置的package不是目录或不存在!");
			}
			handler(packagePath, dir);
		}
	}

 5),通过xml注入各种bean

/**
	 * 通过xml注入各种bean
	 * 1,先遍历在xml文件中配置的所有的bean信息
	 * 2,然后通过id的值从map中得到当前遍历的Bean对象
	 * 3,再遍历当前bean对象中所有的属性
	 * 4,判断对象的属性名是否在配置文件中配置过
	 * 5,如果配置了则通过配置文件中的ref的值从map中取出对应的bean
	 * 6,通过setter()方法设置到bean中完成注入
	 */
	private void injectByXml() {
		
		for (BeanDefinition beanDefine : beanDefines) { //遍历配置文件中所有的bean信息
			Object obj = beans.get(beanDefine.getId()); //得到bean实体
			try {
				BeanInfo beanInfo = Introspector.getBeanInfo(obj.getClass());
				PropertyDescriptor[] propertyDescriptor = beanInfo.getPropertyDescriptors();
				for (PropertyDescriptor desc : propertyDescriptor) { //遍历bean实体里所有的属性
					for (BeanProperty beanProperty : beanDefine.getProps()) {
						//如果属性名与配置文件中的<property name="" ref/>的name的属性值相等,则注入
						if (beanProperty.getName().equals(desc.getName())) {
							Method method = desc.getWriteMethod();//得到Setter()方法
							method.setAccessible(true);//暴力破解,防止用户将setter方法丢了public后,程序无法注入
							Object val = beans.get(beanProperty.getRef());
							if (val == null) {
								log.error("找不到【" + beanProperty.getName() + "】对应的bean对象");
								throw new RuntimeException("找不到【" + beanProperty.getName() + "】对应的bean对象");
							}
							method.invoke(obj, val);//注入
						}
					}
				}
			} catch (IntrospectionException e) {
				log.error("得到beanInfo时发生异常");
				throw new RuntimeException(e);
			} catch (Exception e) {
				log.error("调用invoke()方法时异常");
				throw new RuntimeException(e);
			} 
			
		}
	}

6),通过注解注入各种bean

/**
	 * 通过注解注入各种bean
	 * 1,先检查bean的Setter()方法上有无Resource注解
	 * 2,再检查属性字段上有无Resource注解
	 * 在注入时:
	 * 1,先通过Resource(name="")的name注入,若没有设置name则使用属性名
	 * 2,若没匹配的属性名,则通过类型注入
	 */
	private void injectByAnnotation() {
		
		for (Entry<String, Object> entry : beans.entrySet()) { //遍历每个bean对象
			Object obj = entry.getValue();
			try {
				//先检查setter()方法上有无设置Resource注解
				BeanInfo beanInfo = Introspector.getBeanInfo(obj.getClass());
				PropertyDescriptor[] propertyDescriptor = beanInfo.getPropertyDescriptors();
				for (PropertyDescriptor desc : propertyDescriptor) { //遍历每个属性
					Method method = desc.getWriteMethod();//得到Setter()方法
					//如果setter()方法上标识有Resource注解
					if (method != null && method.isAnnotationPresent(Resource.class)) {
						Resource resource = method.getAnnotation(Resource.class);//得到此注解
						String name = resource.name(); //得到name
						if (name == null || name.trim().equals("")) { 
							name = desc.getName();//如果没有使用name,形如:@Resource, 则将属性的名字作为name
						}
						Object val = beans.get(name); //从Map中得到此bean对象
						if (val == null) { //若为空,则通过类型注入
							for (Object o : beans.values()) {  //再次遍历Bean
								//如果需要注入的bean是Map中某个bean的类型相同或者是其超类,则注入
								if (desc.getPropertyType().isAssignableFrom(o.getClass())) { 
									val = o;
									break;
								}
							}
						}
						method.invoke(obj, val);
					}
				}
				//通过配置在属性字段上的Resource注入
				Field[] fields = obj.getClass().getDeclaredFields();
				for (Field field : fields) {
					//检查属性字段上有无Resource注解
					if (field.isAnnotationPresent(Resource.class)) { 
						Resource resource = field.getAnnotation(Resource.class);
						String name = resource.name();
						if (name == null) {
							name = field.getName();
						}
						Object val = beans.get(name);
						if (val == null) { //通过属性名注解时找不到匹配的bean,则通过类型注入
							for (Object o : beans.values()) {
								if (field.getType().isAssignableFrom(o.getClass())) {
									val = o;
									break;
								}
							}
						}
						field.setAccessible(true);
						field.set(obj, val);
					}
				}
			} catch (IntrospectionException e) {
				log.error("得到beanInfo时发生异常");
				e.printStackTrace();
			} catch (Exception e) {
				log.error("调用invoke()方法时异常");
				e.printStackTrace();
			}
		}
	}

7,编写getBean()方法得到容器中的bean

	/**
	 * 从环境中得到Bean对象
	 * @param <T>
	 * @param id
	 * @param clazz
	 * @return
	 */
	@SuppressWarnings("unchecked")
	public <T> T getBean(String id, Class<T> clazz) {
		
		return (T) beans.get(id);
	}
	
	/**
	 * 得到代理的对象
	 * @param <T>
	 * @param id
	 * @param clazz
	 * @return
	 */
	@SuppressWarnings("unchecked")
	public <T> T getBeanProxy(String id, Class<T> clazz) {
		
		final T realObj = getBean(id, clazz);
		return (T)Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), new InvocationHandler() {

			@Override
			public Object invoke(Object proxy, Method method, Object[] args)
					throws Throwable {
				
				return method.invoke(realObj, args);
			}
		});
	}

8, 最后通过构造方法来调用各个方法

 

/**
	 * 创建此对象时会依次执行下列方法
	 * */
	public BeanFactory() {
		readXml("applicationContext.xml"); //读取配置文件
		initBeansByXml(); //通过xml配置文件初始化各种对象
		initBeanByAnnotation(); //通过annotation初始化各种对象
		injectByXml(); //通过xml注入各种对象
		injectByAnnotation(); //通过annatation注入各种对象
	}

 再写个测试程序:

@Test
	public void testBeanFactory() {
		BeanFactory factory = new BeanFactory();
		UserService userService = factory.getBean("userService", UserServiceImpl.class);
		userService.add();
	}

 用法有两种方式:

xml方式:

完全和spring的配置一样

Annotation方式:

可以在配置文件中写上<scan package="xx"/>,其中xx表示根路径下的一个包,程序可以通过此配置的xx遍历基下所有的标注有@Service注解的类,并实例化。在使用Service注解时可以带上Service("userService"),若不指定默认会使用类名作为标识。

在实现注入对象的功能只需在要注入的对象的属性字段或者setter()方法标注@Resource,当然也可以这样:

@Resourc(name="xx"),当不指定xx时会以属性字段的名称去查找容器中的bean对象,若没有匹配的,则按类型匹配,其实这和spring实现的一样。

 

最后通过getBean()【得到真实的对象】或getBeanProxy()【得到代理对象】来找某个bean.注意通过getBeanProxyt得到的对象必须用接口去接收(我想大家也知道原因吧)

 

本人是第一次正经地发贴,若有什么不好的或值得改进的,请大家指点。谢谢。

 

注:附件是源代码(包括jar包)。

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值