内容简介:愿君多采撷,此物最相思。在
愿君多采撷,此物最相思。
概述
在 Small Spring系列一:BeanFactory(一) 中,我们用 DefaultBeanFactory
读取 bean.xlm
中的 bean
信息,并且也实现了 BeanFactory
的 getBean()
方法。但是实现的方式有些不友好,本章,我们将优化和完善 BeanFactory
。
问题
你会发现在 DefaultBeanFactory
中存在读取 bean.xml
和反射创建对象两种操作,这不符合 设计模式 的 单一职责 。因此我们新增加一个 XmlBeanDefinitionReader
来读取 bean.xml
。但解析的 BeanDefinition
如何交给 BeanFactory
处理呢?这里我们可以在 BeanFactory
接口中增加 registerBeanDefinition(String beanID, BeanDefinition bd)
方法,将 XmlBeanDefinitionReader
解析的 BeanDefinition
注册到 BeanFactory
中。(此时 XmlBeanDefinitionReader
中需要持有一个 BeanFactory
的实例)。类图如下:
BeanFactory
/** * 创建bean的实例 * @author zhenglongfei */ public interface BeanFactory { /** * 获取bean的定义 * @param beanId * @return */ BeanDefinition getBeanDefinition(String beanId); /** * 获取bean的实例 * @param beanId * @return */ Object getBean(String beanId); /** * 注册 BeanDefinition * @param beanID * @param bd */ void registerBeanDefinition(String beanID, BeanDefinition bd); }
增加registerBeanDefinition(String beanID, BeanDefinition bd)方法
DefaultBeanFactory
/** * BeanFactory的默认实现类 * * @author zhenglongfei */ public class DefaultBeanFactory implements BeanFactory { /** * 存放BeanDefinition */ private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>(); @Override public BeanDefinition getBeanDefinition(String beanId) { return this.beanDefinitionMap.get(beanId); } @Override public Object getBean(String beanId) { BeanDefinition bd = this.getBeanDefinition(beanId); if (bd == null) { throw new BeanCreationException("BeanDefinition does not exist"); } ClassLoader cl = ClassUtils.getDefaultClassLoader(); String beanClassName = bd.getBeanClassName(); try { // 使用反射创建bean的实例,需要对象存在默认的无参构造方法 Class<?> clz = cl.loadClass(beanClassName); return clz.newInstance(); } catch (Exception e) { throw new BeanCreationException("Bean Definition does not exist"); } } @Override public void registerBeanDefinition(String beanID, BeanDefinition bd) { this.beanDefinitionMap.put(beanID, bd); } }
去除loadBeanDefinition方法
XmlBeanDefinitionReader
读取解析 bean.xml
文件
/** * @author zhenglongfei */ public class XmlBeanDefinitionReader { private static final String ID_ATTRIBUTE = "id"; private static final String CLASS_ATTRIBUTE = "class"; BeanFactory beanFactory; public XmlBeanDefinitionReader(BeanFactory beanFactory) { this.beanFactory = beanFactory; } /** * 具体解析bean.xml的方法 使用dom4j * * @param configFile */ public void loadBeanDefinition(String configFile) { ClassLoader cl = ClassUtils.getDefaultClassLoader(); try (InputStream is = cl.getResourceAsStream(configFile)) { SAXReader reader = new SAXReader(); Document doc = reader.read(is); Element root = doc.getRootElement(); Iterator<Element> elementIterator = root.elementIterator(); while (elementIterator.hasNext()) { Element ele = elementIterator.next(); String id = ele.attributeValue(ID_ATTRIBUTE); String beanClassName = ele.attributeValue(CLASS_ATTRIBUTE); BeanDefinition bd = new GenericBeanDefinition(id, beanClassName); this.beanFactory.registerBeanDefinition(id, bd); } } catch (Exception e) { throw new BeanDefinitionStoreException("IOException parsing XML document", e); } } }
持有BeanFactory实例,解析bean.xml之后调用registerBeanDefinition(id, bd)将BeanDefinition注册到BeanFactory中
BeanFactoryTest
修改测试类
/** * BeanFactory 测试类 */ public class BeanFactoryTest { /** * 测试获取bean */ @Test public void testGetBean() { BeanFactory factory = new DefaultBeanFactory(); XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory); reader.loadBeanDefinition("bean-v1.xml"); BeanDefinition bd = factory.getBeanDefinition("nioCoder"); assertEquals("com.niocoder.service.v1.NioCoderService", bd.getBeanClassName()); NioCoderService nioCoderService = (NioCoderService) factory.getBean("nioCoder"); assertNotNull(nioCoderService); } /** * 测试无效的bean */ @Test public void testInvalidBean() { BeanFactory factory = new DefaultBeanFactory(); XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory); reader.loadBeanDefinition("bean-v1.xml"); try { factory.getBean("invalidBean"); } catch (BeanCreationException e) { return; } Assert.fail("expect BeanCreationException "); } /** * 测试无效的xml */ @Test public void testInvalidXML() { try { BeanFactory factory = new DefaultBeanFactory(); XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory); reader.loadBeanDefinition("bean.xml"); } catch (BeanDefinitionStoreException e) { return; } Assert.fail("expect BeanDefinitionStoreException "); } }
使用XmlBeanDefinitionReader读取bean.xml
代码下载
优化1.0
我们再来重新审视一下 BeanFactory
接口中的方法,它现在除了 getBean(String beanId)
方法之外,还有 getBeanDefinition(String beanId)
和 registerBeanDefinition(String beanID, BeanDefinition bd)
。 registerBeanDefinition()
是用于 XmlBeanDefinitionReader
使用的, XmlBeanDefinitionReader
持有 BeanFactory
的实例,那么它也会知道 BeanFactory
的 getBean()
方法,并且 BeanFactory
我们只想用它来获取 bean
的实例,不想对外暴露太多,所以我们新增一个 BeanDefinitionRegistry
接口来注册和获取 BeanDefinition
。类图如下
BeanFactory
/** * 创建bean的实例 * @author zhenglongfei */ public interface BeanFactory { /** * 获取bean的实例 * @param beanId * @return */ Object getBean(String beanId); }
只保留getBean方法
BeanDefinitionRegistry
/** * 注册BeanDefinition接口 * * @author zhenglongfei */ public interface BeanDefinitionRegistry { /** * 获取beanDefinition * * @param beanId * @return */ BeanDefinition getBeanDefinition(String beanId); /** * 注册beanDefinition * * @param beanId * @param bd */ void registerBeanDefinition(String beanId, BeanDefinition bd); }
把原先BeanFactory中getBeanDefinition()和registerBeanDefinition()提取到BeanDefinitionRegistry接口中
DefaultBeanFactory
public class DefaultBeanFactory implements BeanFactory, BeanDefinitionRegistry{ ... ... }
实现了BeanDefinitionRegistry接口
XmlBeanDefinitionReader
public class XmlBeanDefinitionReader{ ...... BeanDefinitionRegistry registry; public XmlBeanDefinitionReader(BeanDefinitionRegistry registry) { this.registry = registry; } public void loadBeanDefinition(String configFile){ ... ... this.registry.registerBeanDefinition(id, bd); } }
由BeanFactory变成BeanDefinitionRegistry实例
代码下载
优化2.0.1
观察一下测试类 BeanFactoryTest
,当我们每次使用 BeanFactory
获取实例时,都需要使用到 XmlBeanDefinitionReader
的 loadBeanDefinition
方法。这有一些麻烦,我们该如何封装一下呢?这里我们新增一个 ApplicationContext
接口来封装这些操作。但由于配置文件的不确定性,有的可能存在 classpath
目录下,有的可能存在磁盘的目录下,又或者内存,网络等等。这里我们就简单的实现从 classpath
和磁盘目录两种情况。对应的 ClassPathXmlApplicationContext
和 FileSystemXmlApplicationContext
。由于 ClassPathXmlApplicationContext
和 FileSystemXmlApplicationContext
都需要读取文件,所以我们提取出来一个 Resource
接口,类图如下:
Resource
/** * 抽取一个资源接口 * * @author zhenglongfei */ public interface Resource { /** * 获取一个流 * @return * @throws IOException */ InputStream getInputStream() throws IOException; /** * 获取文件的描述 * @return */ String getDescription(); }
提取一个获取资源的接口
ClassPathResource
public class ClassPathResource implements Resource { private String path; public ClassPathResource(String path) { this.path = path; } @Override public InputStream getInputStream() throws IOException { InputStream is = ClassUtils.getDefaultClassLoader().getResourceAsStream(this.path); if (is == null) { throw new FileNotFoundException(path + " cannot be opened"); } return is; } @Override public String getDescription() { return this.path; } }
从classpath下获取资源
FileSystemResource
public class FileSystemResource implements Resource { private String path; private File file; public FileSystemResource(String path) { this.path = path; this.file = new File(path); } @Override public InputStream getInputStream() throws IOException { return new FileInputStream(this.file); } @Override public String getDescription() { return this.file.getAbsolutePath(); } }
从磁盘目录下获取资源
XmlBeanDefinitionReader
public class XmlBeanDefinitionReader { ...... public void loadBeanDefinition(Resource resource) { try (InputStream is = resource.getInputStream()) { SAXReader reader = new SAXReader(); Document doc = reader.read(is); Element root = doc.getRootElement(); Iterator<Element> elementIterator = root.elementIterator(); while (elementIterator.hasNext()) { Element ele = elementIterator.next(); String id = ele.attributeValue(ID_ATTRIBUTE); String beanClassName = ele.attributeValue(CLASS_ATTRIBUTE); BeanDefinition bd = new GenericBeanDefinition(id, beanClassName); this.registry.registerBeanDefinition(id, bd); } } catch (Exception e) { throw new BeanDefinitionStoreException("IOException parsing XML document", e); } } ...... }
将参数修改为Resource
ApplicationContext
public interface ApplicationContext extends BeanFactory { }
用于封装读取和解析bean.xml操作的接口
ClassPathXmlApplicationContext
public class ClassPathXmlApplicationContext implements ApplicationContext { private DefaultBeanFactory factory = null; public ClassPathXmlApplicationContext(String configFile) { factory = new DefaultBeanFactory(); XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory); Resource resource = new ClassPathResource(configFile); reader.loadBeanDefinition(resource); } @Override public Object getBean(String beanId) { return factory.getBean(beanId); } }
从classpath下读取 bean.xml
FileSystemXmlApplicationContext
public class FileSystemXmlApplicationContext implements ApplicationContext { private DefaultBeanFactory factory = null; public FileSystemXmlApplicationContext(String configFile) { factory = new DefaultBeanFactory(); XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory); Resource resource = new FileSystemResource(configFile); reader.loadBeanDefinition(resource); } @Override public Object getBean(String beanId) { return factory.getBean(beanId); } }
从系统磁盘目录读取文件
ApplicationContextTest
public class ApplicationContextTest { @Test public void testGetBeanFromClassPathContext() { ApplicationContext context = new ClassPathXmlApplicationContext("bean-v1.xml"); NioCoderService nioCoderService = (NioCoderService) context.getBean("nioCoder"); Assert.assertNotNull(nioCoderService); } @Test public void testGetBeanFromFileSystemContext() { ApplicationContext context = new FileSystemXmlApplicationContext("src/test/resources/bean-v1.xml"); NioCoderService nioCoderService = (NioCoderService) context.getBean("nioCoder"); Assert.assertNotNull(nioCoderService); } }
测试ClassPathXmlApplicationContext和FileSystemXmlApplicationContext
代码下载
优化2.0.2
重复代码时万恶之源。在 ClassPathXmlApplicationContext
和 FileSystemXmlApplicationContext
的 的构造方法中有冗余代码,因此我们可以考虑使用 模板方法 来处理一下。类图如下:
AbstractApplicationContext
public abstract class AbstractApplicationContext implements ApplicationContext { private DefaultBeanFactory factory = null; public AbstractApplicationContext(String configFile) { factory = new DefaultBeanFactory(); XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory); Resource resource = this.getResourceByPath(configFile); reader.loadBeanDefinition(resource); } /** * 具体由子类实现 * * @param configFile * @return */ protected abstract Resource getResourceByPath(String configFile); @Override public Object getBean(String beanId) { return factory.getBean(beanId); } }
新增AbstractApplicationContext抽象类,里面增加getResourceByPath抽象方法
ClassPathXmlApplicationContext
public class ClassPathXmlApplicationContext extends AbstractApplicationContext { public ClassPathXmlApplicationContext(String configFile) { super(configFile); } @Override protected Resource getResourceByPath(String configFile) { return new ClassPathResource(configFile); } }
返回ClassPathResource
FileSystemXmlApplicationContext
public class FileSystemXmlApplicationContext extends AbstractApplicationContext { public FileSystemXmlApplicationContext(String configFile) { super(configFile); } @Override protected Resource getResourceByPath(String configFile) { return new FileSystemResource(configFile); } }
返回FileSystemResource
代码下载
优化3.0
至此已经完成了增加 BeanDefinitionRegistry
实现接口单一职责, ApplicationContext
封装 bean.xml
的解析和实例化。接下来我们来处理 scope
的问题。这里我们只是简单区分一下一个 bean
是否单例。为了实现接口细粒度化,我们新增 SingletonBeanRegistry
来区分一个bean是否单例。类图如下:
BeanDefinition
public interface BeanDefinition { /** * 单例 */ String SCOPE_SINGLETON = "singleton"; /** * 多例 */ String SCOPE_PROTOTYPE = "prototype"; /** * 默认为空即单例模式 */ String SCOPE_DEFAULT = ""; /** * 是否为单例 * * @return */ boolean isSingleton(); /** * 是否为多例 * * @return */ boolean isPrototype(); /** * 获取scope配置 * * @return */ String getScope(); /** * 设置scope * * @param scope */ void setScope(String scope); /** * 获取bean.xml中 bean的全名 如 "com.niocoder.service.v1.NioCoderService" * * @return */ String getBeanClassName(); }
单例多例为bean的属性,所以需要修该BeanDefinition
GenericBeanDefinition
public class GenericBeanDefinition implements BeanDefinition { private String id; private String beanClassName; private boolean singleton = true; private boolean prototype = false; private String scope = SCOPE_DEFAULT; public GenericBeanDefinition(String id, String beanClassName) { this.id = id; this.beanClassName = beanClassName; } @Override public boolean isSingleton() { return this.singleton; } @Override public boolean isPrototype() { return this.prototype; } @Override public String getScope() { return scope; } @Override public void setScope(String scope) { this.scope = scope; this.singleton = SCOPE_SINGLETON.equals(scope) || SCOPE_DEFAULT.equals(scope); this.prototype = SCOPE_PROTOTYPE.equals(scope); } @Override public String getBeanClassName() { return this.beanClassName; } }
修改BeanDefinition的实现
XmlBeanDefinitionReader
public class XmlBeanDefinitionReader { public void loadBeanDefinition(Resource resource) { ...... if (ele.attribute(SCOPE_ATTRIBUTE) != null) { bd.setScope(ele.attributeValue(SCOPE_ATTRIBUTE)); } this.registry.registerBeanDefinition(id, bd); ...... } }
如果bean.xml中配置scope属性则设置scope
SingletonBeanRegistry
public interface SingletonBeanRegistry { /** * singlebean 注册 * * @param beanName * @param singletonObject */ void registerSingleton(String beanName, Object singletonObject); /** * 获取singlebean * * @param beanName * @return */ Object getSingleton(String beanName); }
表示bean 为singleton的接口
DefaultSingletonBeanRegistry
public class DefaultSingletonBeanRegistry implements SingletonBeanRegistry { private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(64); @Override public void registerSingleton(String beanName, Object singletonObject) { Assert.notNull(beanName, "'beanName' must not be null"); Object oldObject = this.singletonObjects.get(beanName); if (oldObject != null) { throw new IllegalStateException("Could not register object [" + singletonObject + "] under bean name '" + beanName + "': there is already object [" + oldObject + "] bound"); } this.singletonObjects.put(beanName, singletonObject); } @Override public Object getSingleton(String beanName) { return this.singletonObjects.get(beanName); } }
SingletonBeanRegistry的实现类
DefaultBeanFactory
public class DefaultBeanFactory extends DefaultSingletonBeanRegistry implements BeanFactory, BeanDefinitionRegistry { ...... @Override public Object getBean(String beanId) { BeanDefinition bd = this.getBeanDefinition(beanId); if(bd == null){ return null; } if(bd.isSingleton()){ Object bean = this.getSingleton(beanId); if(bean == null){ bean = createBean(bd); this.registerSingleton(beanId, bean); } return bean; } return createBean(bd); } private Object createBean(BeanDefinition bd) { ClassLoader cl = ClassUtils.getDefaultClassLoader(); String beanClassName = bd.getBeanClassName(); try { Class<?> clz = cl.loadClass(beanClassName); // 使用反射创建bean的实例,需要对象存在默认的无参构造方法 return clz.newInstance(); } catch (Exception e) { throw new BeanCreationException("create bean for "+ beanClassName +" failed",e); } } ...... }
getBean时判断是否为singleton
BeanFactoryTest
public class BeanFactoryTest { ...... /** * 测试获取bean */ @Test public void testGetBean() { reader.loadBeanDefinition(new ClassPathResource("bean-v1.xml")); BeanDefinition bd = factory.getBeanDefinition("nioCoder"); assertTrue(bd.isSingleton()); assertFalse(bd.isPrototype()); assertEquals(BeanDefinition.SCOPE_DEFAULT, bd.getScope()); assertEquals("com.niocoder.service.v1.NioCoderService", bd.getBeanClassName()); NioCoderService nioCoderService = (NioCoderService) factory.getBean("nioCoder"); assertNotNull(nioCoderService); NioCoderService nioCoderService1 = (NioCoderService) factory.getBean("nioCoder"); assertTrue(nioCoderService.equals(nioCoderService1)); } ...... }
测试默认的bean getBean时判断是否为singleton
代码下载
代码下载
以上所述就是小编给大家介绍的《Small Spring系列二:BeanFactory(二)》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。