spring(一):BeanFactory
实现简易BeanFactory(上)
我们在使用BeanFactory的时候最常用的便是通过它来获取bean。这一章节的目标是实现一个极简版本的BeanFactory。
可以参考gitHub:https://github.com/zmPersevere/litespring/tree/master/litespring_01
首先以使用者的角度来写测试用例:
@Test public void testGetBean(){ //根据配置文件初始化一个默认工厂 BeanFactory factory = new DefaultBeanFactory("petstore-v1.xml"); //通过beanId获取bean的定义 BeanDefinition bd = factory.getBeanDefinition("petStore"); //断言bean的ClassName正确 Assert.assertEquals("org.litespring.service.v1.PetStoreService" ,bd.getBeanClassName()); //根据bean的id获取Bean PetStoreService petStore = (PetStoreService) factory.getBean("petStore"); //断言获取到bean了。 Assert.assertNotNull(petStore); }
这里完成了测试用例的编写,每行代码注释标记的已经很明确了,这章节的目的便是让测试用例可以运行通过。
根据测试用例来看,需要一个BeanFactory接口,DefaultBeanFactory默认的bean工厂实现,存放各种bean定义的BeanDefinition接口。至于PetStoreService则是一个用于测试的Service。
首先来实现BeanFactory,根据spring的命名规则我们把它放到org.litespring.beans.factory下,这一章节实现它的两个方法
- getBeanDefinition(String beanId),根据beanId获取bean的定义实体。
- getBean(String beanId),根据beanId获取bean实体。
我们来实现这两个方法,spring命名规则会把具体实现放到support下,我们来实现一个默认的BeanFactory->DefaultBeanFactory。
private static final String ID_ATTRIBUTE = "id"; private static final String CLASS_ATTRIBUTE = "class"; private final Map<String,BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<String, BeanDefinition>(); public DefaultBeanFactory(String configFile) { loadBeanDefinition(configFile); } /** * 加载配置文件 * @param configFile */ private void loadBeanDefinition(String configFile){ InputStream is = null; try { //获取默认的ClassLoader ClassLoader cl = ClassUtils.getDefaultClassLoader(); //根据dom4j读取configFile is = cl.getResourceAsStream(configFile); SAXReader reader = new SAXReader(); Document doc = reader.read(is); Element root = doc.getRootElement();//获取跟节点<beans> Iterator<Element> iter = root.elementIterator();//遍历子节点<bean> while (iter.hasNext()){ Element ele = (Element)iter.next(); String id = ele.attributeValue(ID_ATTRIBUTE);//获取<bean id> String beanClassName = ele.attributeValue(CLASS_ATTRIBUTE);//获取<bean class> //把id、class都注册到bean的定义中 BeanDefinition bd = new GenericBeanDefinition(id,beanClassName); //把bean的定义放入到并发安全容器beanDefinitionMap中 this.beanDefinitionMap.put(id,bd); } }catch (DocumentException e){ //TODO 抛出异常,不推荐使用e.printStackTrace(),尽量用日志输出异常。 throw new BeanDefinitionStoreException("IOException parsing XML document" , e); }finally { if (is != null){ try { is.close();//关闭流 }catch (IOException e){ e.printStackTrace(); } } } } /** * 通过beanId获取bean的定义 * @param beanId * @return */ public BeanDefinition getBeanDefinition(String beanId) { return this.beanDefinitionMap.get(beanId); } /** * 通过BeanId获取bean * @param beanId * @return */ public Object getBean(String beanId) { //根据bean的id获取bean的定义 BeanDefinition bd = this.getBeanDefinition(beanId); if (bd == null){ throw new BeanCreationException("Bean Definition does not exist"); } //注意这里使用的是ClassLoader,ClassLoader只是把class加载进来并没有初始化,在使用时在进行初始化。 //而Class.forName会把class加载、连接、初始化。 ClassLoader cl = ClassUtils.getDefaultClassLoader(); //这里获取的实际上就是<bean class="">中的class内容 String beanClassName = bd.getBeanClassName(); try { Class<?> clz = cl.loadClass(beanClassName); //只能调用无参构造函数且是publish的. //newInstance必须保证这个类已经被加载了。 return clz.newInstance(); }catch (Exception e){ throw new BeanCreationException("create bean for " + beanClassName + " failed ", e); } }
上述的实现注释我已经写的很详尽了,其中在解析xml文件是通过dom4j这个jar包进行解析的,而ClassUtils则是从spring中直接拿过来的,还差一个BeanDefintion没有实现,下面便开始实现BeanDefintion的基本功能。
首先实现BeanDefintion这个接口,放到了org.litespring.beans包下,这一版本我们赋予其的功能是获取当前bean的className。接下来实现BeanDefintion的实现类GenericBeanDefinition。
private String id; private String beanClassName; public GenericBeanDefinition(String id,String beanClassName) { this.id = id; this.beanClassName = beanClassName; } /** * 获取bean定义中的className * @return className */ public String getBeanClassName() { return this.beanClassName; }
这里的实现就很简单了,只是把xml的各种属性转换为实体。
上述实现便可以通过我们最开始的测试用例了。在git上还完善了异常处理,这里的代码最好自己手打一下,而不是仅仅看看。
这个简易的BeanFactory我碰到的问题是对于class.newInstance()的使用,我上班路上偶然看到了一篇博客,对于newInstance方法的使用必须保证class已加载、连接,而实际使用newInstance方法只需要保证类已加载即可。
生活要多点不自量力