`
五月天
  • 浏览: 21612 次
  • 性别: 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依赖注入的实现原理

    Spring依赖注入(Dependency Injection,简称DI)是Java应用开发中常用的设计模式,它极大地提高了代码的可测试性和可维护性。在Spring框架中,依赖注入是核心特性之一,通过控制反转(Inversion of Control,IoC)...

    详解Spring_3.0基于Annotation的依赖注入实现

    ### 详解Spring 3.0基于Annotation的依赖注入实现 #### 概述 Spring框架作为一个广泛使用的Java开发框架,提供了强大的依赖注入(Dependency Injection, DI)能力,帮助开发者轻松管理和组织复杂的Java应用。随着...

    Spring - Annotation 自动匹配注入IOC

    总结一下,Spring的注解自动匹配注入IoC是通过注解来简化bean的管理和依赖注入。通过`@Autowired`、`@Qualifier`等注解,可以方便地完成bean的自动装配。结合`@Component`系列注解进行组件扫描,以及`@Configuration...

    spring 的Annotation方式

    本文旨在深入探讨Spring框架中基于注解的依赖注入机制,特别是`@Repository`、`@Service`、`@Controller`和`@Component`等核心注解的使用方法,以及如何利用`&lt;context:component-scan&gt;`自动扫描功能,实现类级别的...

    Spring Annotation简介一

    Spring Annotation通过在类、方法或字段上添加特定的注解,可以实现自动配置,从而实现依赖注入和组件扫描等功能。 1. **依赖注入(Dependency Injection, DI)**:Spring Annotation中最常用的注解之一是`@...

    4Spring自动装配——annotation resource方式

    在Spring框架中,自动装配(Auto-Wiring)是一种简化依赖注入(Dependency Injection,DI)配置的方式,它允许Spring容器自动管理Bean之间的依赖关系。本文将深入探讨如何通过注解(Annotation)和`@Resource`来实现...

    spring3.0依赖注入详解

    本文将深入探讨Spring 3.0中依赖注入的新特性,特别是如何使用`@Repository`、`@Service`、`@Controller`和`@Component`注解来标记类为Bean,以及如何利用`&lt;context:component-scan/&gt;`元素自动扫描和注册这些Bean。...

    spring自动扫描和管理Bean的示例

    除了自动扫描,Spring还提供了注解来管理Bean的生命周期和依赖注入。例如: - **@Autowired**:用于自动装配Bean的依赖。Spring会尝试根据类型或名称找到合适的Bean进行注入。 - **@Qualifier**:配合@Autowired...

    扩展Spring—使用Annotation将配置资源注入到Bean中

    在Spring框架中,注解(Annotation)的使用极大地简化了传统XML配置的复杂性,使得开发者可以更加专注于业务逻辑的实现。本篇文章将深入探讨如何通过注解将配置资源注入到Bean中,以此来理解Spring的注解驱动开发。 ...

    spring和mybatis整合(mapper代理自动扫描方式实现)

    Spring是一个全面的后端开发框架,提供了依赖注入、AOP(面向切面编程)、事务管理等功能;而MyBatis则是一个轻量级的持久层框架,它将SQL与Java代码分离,使数据库操作更加灵活。本文将详细介绍如何将Spring和...

    Spring实现自动装配

    Spring框架是Java开发中不可或缺的一部分,它以其强大的依赖注入(Dependency Injection,简称DI)特性而闻名,其中自动装配(Auto-Wiring)是DI的一种实现方式。自动装配允许开发者减少手动配置bean之间的依赖关系...

    Spring annotation

    Spring框架是Java开发中不可或缺的一部分,它通过提供丰富的注解简化了依赖注入、配置管理和AOP(面向切面编程)等任务。本文将深入探讨Spring注解及其在实际开发中的应用。 1. **依赖注入(Dependency Injection, ...

    Spring的Autowired自动装配(XML版本+Annotation版本+源码+解析)

    在Java开发领域,Spring框架是应用最广泛的轻量级框架之一,它提供了强大的依赖注入(Dependency Injection,简称DI)功能,极大地简化了企业级应用的开发。本篇将重点讲解Spring的@Autowired自动装配机制,包括XML...

    Spring2.5 自动扫描classpath

    这项功能允许开发者无需在XML配置文件中显式声明bean,而是通过在类上添加特定注解(如@Service、@Repository、@Controller等)来让Spring容器自动发现并管理这些bean。这一特性极大地简化了Spring应用的配置,提高...

    spring注入原理

    Spring框架是Java开发中不可或缺的一部分,它通过提供依赖注入(Dependency Injection,简称DI)和面向切面编程(Aspect-Oriented Programming,简称AOP)等核心功能,极大地简化了企业级应用的开发工作。...

Global site tag (gtag.js) - Google Analytics