`
五月天
  • 浏览: 21215 次
  • 性别: Icon_minigender_1
  • 来自: 广州
社区版块
存档分类
最新评论

使用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包)。

 

 

分享到:
评论
4 楼 limi1986214 2010-06-03  
不错  回寝室再仔细看下
3 楼 qingwengang 2010-06-02  
不错不错,写份5000字的报告交给我!!!
2 楼 五月天 2010-06-02  
duben 写道


很好很强大,学习了...!

一起努力!加油!未来是我们的。。。
1 楼 duben 2010-06-02  


很好很强大,学习了...!

相关推荐

    Spring中文帮助文档

    6.8.1. 在Spring中使用AspectJ进行domain object的依赖注入 6.8.2. Spring中其他的AspectJ切面 6.8.3. 使用Spring IoC来配置AspectJ的切面 6.8.4. 在Spring应用中使用AspectJ加载时织入(LTW) 6.9. 更多资源 7...

    Spring API

    6.8.1. 在Spring中使用AspectJ进行domain object的依赖注入 6.8.2. Spring中其他的AspectJ切面 6.8.3. 使用Spring IoC来配置AspectJ的切面 6.8.4. 在Spring应用中使用AspectJ加载时织入(LTW) 6.9. 更多资源 7...

    springweb3.0MVC注解(附实例)

    -- ①:对web包中的所有类进行扫描,以完成Bean创建和自动依赖注入的功能 --&gt; &lt;!-- ②:启动Spring MVC的注解功能,完成请求和注解POJO的映射 --&gt; &lt;bean class="org.springframework.web.servlet.mvc....

    Spring Boot中文文档.rar

    Spring Beans和依赖注入 18.使用@SpringBootApplication Annotation 19.运行您的应用程序 19.1.从IDE运行 19.2.作为打包应用程序运行 19.3.使用Maven插件 19.4.使用Gradle插件 19.5.热插拔 20....

    spring applicationContext 配置文件

    -- 对web包中的所有类进行扫描,以完成Bean创建和自动依赖注入的功能--&gt; &lt;bean class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping" p:order="0" /&gt; &lt;!-- 配置事务...

    经典JAVA.EE企业应用实战.基于WEBLOGIC_JBOSS的JSF_EJB3_JPA整合开发.pdf

    10.2.5 使用orm.xml管理O/R映射 379 10.3 理解实体 382 10.3.1 持久化上下文和持久化单元 382 10.3.2 实体类的要求 382 10.3.3 实体的状态 383 10.3.4 管理实体的方法 384 10.4 实体的基本映射387 10.4.1 映射实体类...

    java微信公众号MVC开发框架

    微信接口服务类位于com.github.jweixin.jwx.weixin.service包中,在spring配置文件中通过扫描包载入服务,在web mvc框架和微信控制器类中都可以通过@Autowired注解注入,与其他spring普通的服务类主键使用方式一致,...

    java开源包3

    GWT Spring 使得在 Spring 框架下构造 GWT 应用变得很简单,提供一个易于理解的依赖注入和RPC机制。 Java扫雷游戏 JVMine JVMine用Applets开发的扫雷游戏,可在线玩。 public class JVMine extends java.applet....

    java开源包4

    GWT Spring 使得在 Spring 框架下构造 GWT 应用变得很简单,提供一个易于理解的依赖注入和RPC机制。 Java扫雷游戏 JVMine JVMine用Applets开发的扫雷游戏,可在线玩。 public class JVMine extends java.applet....

    SpringMVC+Hibernate全注解整合

    对web包中的所有类进行扫描,以完成Bean创建和自动依赖注入的功能 mvc:annotation-driven --&gt; &lt;mvc:annotation-driven/&gt; &lt;!-- 扫描包 --&gt; &lt;context:annotation-config/&gt; *" /&gt; ...

    java开源包8

    GWT Spring 使得在 Spring 框架下构造 GWT 应用变得很简单,提供一个易于理解的依赖注入和RPC机制。 Java扫雷游戏 JVMine JVMine用Applets开发的扫雷游戏,可在线玩。 public class JVMine extends java.applet....

    java开源包1

    GWT Spring 使得在 Spring 框架下构造 GWT 应用变得很简单,提供一个易于理解的依赖注入和RPC机制。 Java扫雷游戏 JVMine JVMine用Applets开发的扫雷游戏,可在线玩。 public class JVMine extends java.applet....

    java开源包11

    GWT Spring 使得在 Spring 框架下构造 GWT 应用变得很简单,提供一个易于理解的依赖注入和RPC机制。 Java扫雷游戏 JVMine JVMine用Applets开发的扫雷游戏,可在线玩。 public class JVMine extends java.applet....

    java开源包2

    GWT Spring 使得在 Spring 框架下构造 GWT 应用变得很简单,提供一个易于理解的依赖注入和RPC机制。 Java扫雷游戏 JVMine JVMine用Applets开发的扫雷游戏,可在线玩。 public class JVMine extends java.applet....

    java开源包6

    GWT Spring 使得在 Spring 框架下构造 GWT 应用变得很简单,提供一个易于理解的依赖注入和RPC机制。 Java扫雷游戏 JVMine JVMine用Applets开发的扫雷游戏,可在线玩。 public class JVMine extends java.applet....

    java开源包5

    GWT Spring 使得在 Spring 框架下构造 GWT 应用变得很简单,提供一个易于理解的依赖注入和RPC机制。 Java扫雷游戏 JVMine JVMine用Applets开发的扫雷游戏,可在线玩。 public class JVMine extends java.applet....

    java开源包10

    GWT Spring 使得在 Spring 框架下构造 GWT 应用变得很简单,提供一个易于理解的依赖注入和RPC机制。 Java扫雷游戏 JVMine JVMine用Applets开发的扫雷游戏,可在线玩。 public class JVMine extends java.applet....

    java开源包7

    GWT Spring 使得在 Spring 框架下构造 GWT 应用变得很简单,提供一个易于理解的依赖注入和RPC机制。 Java扫雷游戏 JVMine JVMine用Applets开发的扫雷游戏,可在线玩。 public class JVMine extends java.applet....

    java开源包9

    GWT Spring 使得在 Spring 框架下构造 GWT 应用变得很简单,提供一个易于理解的依赖注入和RPC机制。 Java扫雷游戏 JVMine JVMine用Applets开发的扫雷游戏,可在线玩。 public class JVMine extends java.applet....

Global site tag (gtag.js) - Google Analytics