3、Mini-spring 资源和资源加载器

133 阅读5分钟

前言:

通过前面几个章节的学习,我们可以发现具体的代码实现并不难,更多的是在于接口的定义,实现接口的类,抽象类实现接口、继承类,继承抽象类这些操作,而这些操作可以很好的隔离开每个类的基础功能、通用功能、和业务功能,当类的职责清晰后,整体的设计也会变得容易扩展和迭代。而这些知识是我认为学习过程中最重要的,因为这些良好的设计思路我们很容易复用到自己的业务系统中,去提高我们的编码水平。

所以整个系列的学习过程中,要重点关注spring各个类之间的职责和关系,几乎所有的设计都离不开接口、抽象类、实现、继承、而这些不同特性的类的使用可以非常好的隔离开类的功能和作用范围。

资源与资源加载器

前面我们通过自己手动的方式创建对象,现在来实现通过配置文件的方式,简化创建的过程。

1、 定义资源的抽象访问接口,他有三个简单的实现类。

  • ClassPathResource:classpath下资源的实现类
  • FileSystemResource:文件系统资源的实现类
  • UrlResource:对java.net.URL进行资源定位的实现类

Resource

/**
 * 资源的抽象和访问接口
 *
 */
public interface Resource {

   InputStream getInputStream() throws IOException;

}

ClassPathResource

/**
 * classpath下的资源
 *
 */
public class ClassPathResource implements Resource {

   private final String path;

   public ClassPathResource(String path) {
      this.path = path;
   }

   @Override
   public InputStream getInputStream() throws IOException {
      InputStream is = this.getClass().getClassLoader().getResourceAsStream(this.path);
      if (is == null) {
         throw new FileNotFoundException(this.path + " cannot be opened because it does not exist");
      }
      return is;
   }
}

FileSystemResource

public class FileSystemResource implements Resource {

   private final String filePath;

   public FileSystemResource(String filePath) {
      this.filePath = filePath;
   }

   @Override
   public InputStream getInputStream() throws IOException {

      try {
         Path path = new File(this.filePath).toPath();
         return Files.newInputStream(path);
      } catch (NoSuchFileException ex) {
         throw new FileNotFoundException(ex.getMessage());
      }
   }
}

UrlResource

public class UrlResource implements Resource {

   private final URL url;

   public UrlResource(URL url) {
      this.url = url;
   }

   @Override
   public InputStream getInputStream() throws IOException {
      URLConnection con = this.url.openConnection();
      try {
         return con.getInputStream();
      } catch (IOException ex) {
         throw ex;
      }
   }
}

2、定义资源加载器接口

定义资源加载器的接口,DefaultResourceLoader作为默认的实现类。资源加载器ResourceLoader相当于资源查找定位策略的抽象。

/**
 * 资源加载器接口
 */
public interface ResourceLoader {

   Resource getResource(String location);
}

DefaultResourceLoader

public class DefaultResourceLoader implements ResourceLoader {

   public static final String CLASSPATH_URL_PREFIX = "classpath:";

   @Override
   public Resource getResource(String location) {
      if (location.startsWith(CLASSPATH_URL_PREFIX)) {
         //classpath下的资源
         return new ClassPathResource(location.substring(CLASSPATH_URL_PREFIX.length()));
      } else {
         try {
            //尝试当成url来处理
            URL url = new URL(location);
            return new UrlResource(url);
         } catch (MalformedURLException ex) {
            //当成文件系统下的资源处理
            return new FileSystemResource(location);
         }
      }
   }
}

测试: 先初始化默认的资源加载器,然后调用getResource方法,根据传递参数不同,方法返回不同策略的资源加载类Resource.

public class ResourceAndResourceLoaderTest {

   @Test
   public void testResourceLoader() throws Exception {
      DefaultResourceLoader resourceLoader = new DefaultResourceLoader();

      //加载classpath下的资源
      Resource resource = resourceLoader.getResource("classpath:hello.txt");
      InputStream inputStream = resource.getInputStream();
      String content = IoUtil.readUtf8(inputStream);
      System.out.println(content);
      assertThat(content).isEqualTo("hello world");

      //加载文件系统资源
      resource = resourceLoader.getResource("src/test/resources/hello.txt");
      assertThat(resource instanceof FileSystemResource).isTrue();
      inputStream = resource.getInputStream();
      content = IoUtil.readUtf8(inputStream);
      System.out.println(content);
      assertThat(content).isEqualTo("hello world");

      //加载url资源
      resource = resourceLoader.getResource("https://www.baidu.com");
      assertThat(resource instanceof UrlResource).isTrue();
      inputStream = resource.getInputStream();
      content = IoUtil.readUtf8(inputStream);
      System.out.println(content);
   }
}

在xml文件中定义bean

接着完善,有了资源加载器后,我们实现在xml中声明式的定义bean信息,资源加载器读取xml文件,解析出bean的信息,然后向容器中注册BeanDefinition。

1、定义BeanDefinitionReader接口

BeanDefinitionReader 读取bean信息的抽象接口。回忆一下之前我们的操作,自己手动创建对象,然后向Factory注册对象,再去getBean获取对象。现在我们实现了资源加载器,能通过资源加载器去读取各种配置文件中的内容,所以在xml中定义bean后,我希望们借助资源加载器读取后,再自动将BeanDefinition注册到容器中。

整体分为两部分:1、读取资源 2、注册BeanDefinition。所以结构上就清晰了,BeanDefinitionReader 的抽象实现类AbstractBeanDefinitionReader拥有ResourceLoader和BeanDefinitionRegistry两个属性

BeanDefinitionReader

/**
 * 读取bean定义信息与注册BeanDefinition的接口
 *
 */
public interface BeanDefinitionReader {

   BeanDefinitionRegistry getRegistry();

   ResourceLoader getResourceLoader();

   void loadBeanDefinitions(Resource resource) throws BeansException;

   void loadBeanDefinitions(String location) throws BeansException;

   void loadBeanDefinitions(String[] locations) throws BeansException;
}

AbstractBeanDefinitionReader

public abstract class AbstractBeanDefinitionReader implements BeanDefinitionReader {

   private final BeanDefinitionRegistry registry;

   private ResourceLoader resourceLoader;

   protected AbstractBeanDefinitionReader(BeanDefinitionRegistry registry) {
      this(registry, new DefaultResourceLoader());
   }

   public AbstractBeanDefinitionReader(BeanDefinitionRegistry registry, ResourceLoader resourceLoader) {
      this.registry = registry;
      this.resourceLoader = resourceLoader;
   }

   @Override
   public BeanDefinitionRegistry getRegistry() {
      return registry;
   }

   @Override
   public void loadBeanDefinitions(String[] locations) throws BeansException {
      for (String location : locations) {
         loadBeanDefinitions(location);
      }
   }

   public void setResourceLoader(ResourceLoader resourceLoader) {
      this.resourceLoader = resourceLoader;
   }

   @Override
   public ResourceLoader getResourceLoader() {
      return resourceLoader;
   }
}

XmlBeanDefinitionReader: 读取定义在Xml文件中的bean定义信息。

doLoadBeanDefinitions()方法就是去解析xml中的bean信息,并且通过反射的方式生成beanDefinition,然后注册到容器中。

public class XmlBeanDefinitionReader extends AbstractBeanDefinitionReader {

   public static final String BEAN_ELEMENT = "bean";
   public static final String PROPERTY_ELEMENT = "property";
   public static final String ID_ATTRIBUTE = "id";
   public static final String NAME_ATTRIBUTE = "name";
   public static final String CLASS_ATTRIBUTE = "class";
   public static final String VALUE_ATTRIBUTE = "value";
   public static final String REF_ATTRIBUTE = "ref";

   public XmlBeanDefinitionReader(BeanDefinitionRegistry registry) {
      super(registry);
   }

   public XmlBeanDefinitionReader(BeanDefinitionRegistry registry, ResourceLoader resourceLoader) {
      super(registry, resourceLoader);
   }

   @Override
   public void loadBeanDefinitions(String location) throws BeansException {
      ResourceLoader resourceLoader = getResourceLoader();
      Resource resource = resourceLoader.getResource(location);
      loadBeanDefinitions(resource);
   }

   @Override
   public void loadBeanDefinitions(Resource resource) throws BeansException {
      try {
         InputStream inputStream = resource.getInputStream();
         try {
            doLoadBeanDefinitions(inputStream);
         } finally {
            inputStream.close();
         }
      } catch (IOException ex) {
         throw new BeansException("IOException parsing XML document from " + resource, ex);
      }
   }

   protected void doLoadBeanDefinitions(InputStream inputStream) {
      Document document = XmlUtil.readXML(inputStream);
      Element root = document.getDocumentElement();
      NodeList childNodes = root.getChildNodes();
      for (int i = 0; i < childNodes.getLength(); i++) {
         if (childNodes.item(i) instanceof Element) {
            if (BEAN_ELEMENT.equals(((Element) childNodes.item(i)).getNodeName())) {
               //解析bean标签
               Element bean = (Element) childNodes.item(i);
               String id = bean.getAttribute(ID_ATTRIBUTE);
               String name = bean.getAttribute(NAME_ATTRIBUTE);
               String className = bean.getAttribute(CLASS_ATTRIBUTE);

               Class<?> clazz = null;
               try {
                  clazz = Class.forName(className);
               } catch (ClassNotFoundException e) {
                  throw new BeansException("Cannot find class [" + className + "]");
               }
               //id优先于name
               String beanName = StrUtil.isNotEmpty(id) ? id : name;
               if (StrUtil.isEmpty(beanName)) {
                  //如果id和name都为空,将类名的第一个字母转为小写后作为bean的名称
                  beanName = StrUtil.lowerFirst(clazz.getSimpleName());
               }

               BeanDefinition beanDefinition = new BeanDefinition(clazz);

               for (int j = 0; j < bean.getChildNodes().getLength(); j++) {
                  if (bean.getChildNodes().item(j) instanceof Element) {
                     if (PROPERTY_ELEMENT.equals(((Element) bean.getChildNodes().item(j)).getNodeName())) {
                        //解析property标签
                        Element property = (Element) bean.getChildNodes().item(j);
                        String nameAttribute = property.getAttribute(NAME_ATTRIBUTE);
                        String valueAttribute = property.getAttribute(VALUE_ATTRIBUTE);
                        String refAttribute = property.getAttribute(REF_ATTRIBUTE);

                        if (StrUtil.isEmpty(nameAttribute)) {
                           throw new BeansException("The name attribute cannot be null or empty");
                        }

                        Object value = valueAttribute;
                        if (StrUtil.isNotEmpty(refAttribute)) {
                           value = new BeanReference(refAttribute);
                        }
                        PropertyValue propertyValue = new PropertyValue(nameAttribute, value);
                        beanDefinition.getPropertyValues().addPropertyValue(propertyValue);
                     }
                  }
               }
               if (getRegistry().containsBeanDefinition(beanName)) {
                  //beanName不能重名
                  throw new BeansException("Duplicate beanName[" + beanName + "] is not allowed");
               }
               //注册BeanDefinition
               getRegistry().registerBeanDefinition(beanName, beanDefinition);
            }
         }
      }
   }
}

测试:

在resource文件夹下创建spring.xml配置文件。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
            http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context-4.0.xsd">

    <bean id="person" class="org.springframework.test.ioc.bean.Person">
        <property name="name" value="derek"/>
        <property name = "age" value="18"/>
        <property name="car" ref="car"/>
    </bean>

    <bean id="car" class="org.springframework.test.ioc.bean.Car">
        <property name="brand" value="porsche"/>
    </bean>
</beans>

直接通过BeanDefinitionReader读取对应路径下的xml文件,然后getBean获取容器中的对象。

@Test
public void testXmlFile() throws Exception {
   DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
   XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
   beanDefinitionReader.loadBeanDefinitions("classpath:spring.xml");

   Person person = (Person) beanFactory.getBean("person");
   System.out.println(person);
   assertThat(person.getName()).isEqualTo("derek");
   assertThat(person.getCar().getBrand()).isEqualTo("porsche");

   Car car = (Car) beanFactory.getBean("car");
   System.out.println(car);
   assertThat(car.getBrand()).isEqualTo("porsche");
}