大家知道,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包)。