我们都知道, Spring IoC 和 Aop 是 Spring 的核心的功能, 因此花一点时间去研究还是很有意义的, 如果仅仅是知其所以然, 也就体会不到大师设计 Spring 的精华, 还记得那句话, Spring 为 JavaEE 开发带来了春天.
IoC 就是 Inversion of control 也就是控制反转的意思, 另一种称呼叫做依赖注入, 这个可能更直观一点, 拿个例子来说吧:
- @Component
- public class UserService {
- @Autowired
- private UserMapper mapper;
- }
比如在 UserService 可能要调用一个 Mapper, 这个 Mapper 去做 DAO 的操作, 在这里我们直接通过 @Autowired 注解去注入这个 Mapper, 这个就叫做依赖注入, 你想要什么就注入什么, 不过前提它是一个 Bean. 至于是怎么注入的, 那是 Spring 容器做的事情, 也是我们今天去探索的.
在进行分析之前, 我先声明一下, 下面的这些代码并不是从 spring 源码中直接拿过来, 而是通过一步步简化, 抽取 spring 源码的精华, 如果直接贴源码, 我觉得可能很多人都会被吓跑, 而且还不一定能够学到真正的东西.
Spring 要去管理 Bean 首先要把 Bean 放到容器里, 那么 Spring 是如何获得 Bean 的呢?
首先, Spring 有一个数据结构, BeanDefinition, 这里存放的是 Bean 的内容和元数据, 保存在 BeanFactory 当中, 包装 Bean 的实体:
- public class BeanDefinition {
- // 真正的 Bean 实例
- private Object bean;
- //Bean 的类型信息
- private Class beanClass;
- //Bean 类型信息的名字
- private String beanClassName;
- // 用于 bean 的属性注入 因为 Bean 可能有很多熟属性
- // 所以这里用列表来进行管理
- private PropertyValues propertyValues = new PropertyValues();
- }
PerpertyValues 存放 Bean 的所有属性
- public class PropertyValues {
- private final List<PropertyValue> propertyValueList = new ArrayList<PropertyValue>();
- }
PropertyValue 存放的是每个属性, 可以看到两个字段, name 和 valu.name 存放的就是属性名称, value 是 object 类型, 可以是任何类型
- public class PropertyValue {
- private final String name;
- private final Object value;
- }
定义好这些数据结构了, 把 Bean 装进容器的过程, 其实就是其 BeanDefinition 构造的过程, 那么怎么把一些类装入的 Spring 容器呢?
Spring 有个接口就是获取某个资源的输入流, 获取这个输入流后就可以进一步处理了:
- public interface Resource {
- InputStream getInputStream() throws IOException;
- }
UrlResource 是对 Resource 功能的进一步扩展, 通过拿到一个 URL 获取输入流.
- public class UrlResource implements Resource {
- private final URL url;
- public UrlResource(URL url) {
- this.url = url;
- }
- @Override
- public InputStream getInputStream() throws IOException{
- URLConnection urlConnection = url.openConnection();
- urlConnection.connect();
- return urlConnection.getInputStream();
- }
ResourceLoader 是资源加载的主要方法, 通过 location 定位 Resource,
然后通过上面的 UrlResource 获取输入流:
- public class ResourceLoader {
- public Resource getResource(String location){
- URL resource = this.getClass().getClassLoader().getResource(location);
- return new UrlResource(resource);
- }
- }
大家可能会对上面的这段代码产生疑问:
URL resource = this.getClass().getClassLoader().getResource(location);
为什么通过得到一个类的类类型, 然后得到对应的类加载器, 然后调用类加载器的 Reource 怎么就得到了 URL 这种类型呢?
我们来看一下类加载器的这个方法:
- public URL getResource(String name) {
- URL url;
- if (parent != null) {
- url = parent.getResource(name);
- } else {
- url = getBootstrapResource(name);
- }
- if (url == null) {
- url = findResource(name);
- }
- return url;
- }
从这个方法中我们可以看出, 类加载器去加载资源的时候, 先会去让父类加载器去加载, 如果父类加载器没有的话, 会让根加载器去加载, 如果这两个都没有加载成功, 那就自己尝试去加载, 这个一方面为了 java 程序的安全性, 不可能你用户自己随便写一个加载器, 就用你用户的.
接下来我们看一下重要角色, 这个是加载 BeanDefinition 用的.
- public interface BeanDefinitionReader {
- void loadBeanDefinitions(String location) throws Exception;
- }
这个接口是用来从配置中读取 BeanDefinition:
其中 registry key 是 bean 的 id,value 存放资源中所有的 BeanDefinition
- public abstract class AbstractBeanDefinitionReader implements BeanDefinitionReader {
- private Map<String,BeanDefinition> registry;
- private ResourceLoader resourceLoader;
- protected AbstractBeanDefinitionReader(ResourceLoader resourceLoader) {
- this.registry = new HashMap<String, BeanDefinition>();
- this.resourceLoader = resourceLoader;
- }
- public Map<String, BeanDefinition> getRegistry() {
- return registry;
- }
- public ResourceLoader getResourceLoader() {
- return resourceLoader;
- }
- }
最后我们来看一个通过读取 xml 文件的 BeanDefinitionReader:
- public class XmlBeanDefinitionReader extends AbstractBeanDefinitionReader {
- /**
- * 构造函数 传入我们之前分析过的 ResourceLoader 这个通过
- * location 可以 加载到 Resource
- */
- public XmlBeanDefinitionReader(ResourceLoader resourceLoader) {
- super(resourceLoader);
- }
- /**
- * 这个方法其实是 BeanDefinitionReader 这个接口中的方法
- * 作用就是通过 location 来构造 BeanDefinition
- */
- @Override
- public void loadBeanDefinitions(String location) throws Exception {
- // 把 location 传给 ResourceLoader 拿到 Resource, 然后获取输入流
- InputStream inputStream = getResourceLoader().getResource(location).getInputStream();
- // 接下来进行输入流的处理
- doLoadBeanDefinitions(inputStream);
- }
- protected void doLoadBeanDefinitions(InputStream inputStream) throws Exception {
- // 因为 xml 是文档对象, 所以下面进行一些处理文档工具的构造
- DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
- DocumentBuilder docBuilder = factory.newDocumentBuilder();
- // 把输入流解析成一个文档, java 可以处理的文档
- Document doc = docBuilder.parse(inputStream);
- // 处理这个文档对象 也就是解析 bean
- registerBeanDefinitions(doc);
- inputStream.close();
- }
- public void registerBeanDefinitions(Document doc) {
- // 得到文档的根节点, 知道根节点后获取子节点就是通过层级关系处理就行了
- Element root = doc.getDocumentElement();
- // 解析根节点 xml 的根节点
- parseBeanDefinitions(root);
- }
- protected void parseBeanDefinitions(Element root) {
- NodeList nl = root.getChildNodes();
- for (int i = 0; i <nl.getLength(); i++) {
- Node node = nl.item(i);
- //element 有属性的包装
- if (node instanceof Element) {
- Element ele = (Element) node;
- processBeanDefinition(ele);
- }
- }
- }
- protected void processBeanDefinition(Element ele) {
- /**
- * <bean id="object***" class="com.***.***"/>
- */
- // 获取 element 的 id
- String name = ele.getAttribute("id");
- // 获取 element 的 class
- String className = ele.getAttribute("class");
- BeanDefinition beanDefinition = new BeanDefinition();
- // 处理这个 bean 的属性
- processProperty(ele, beanDefinition);
- // 设置 BeanDefinition 的类名称
- beanDefinition.setBeanClassName(className);
- //registry 是一个 map, 存放所有的 beanDefinition
- getRegistry().put(name, beanDefinition);
- }
- private void processProperty(Element ele, BeanDefinition beanDefinition) {
- /**
- * 类似这种:
- <bean id="userServiceImpl" class="com.serviceImpl.UserServiceImpl">
- <property name="userDao" ref="userDaoImpl"> </property>
- </bean>
- */
- NodeList propertyNode = ele.getElementsByTagName("property");
- for (int i = 0; i <propertyNode.getLength(); i++) {
- Node node = propertyNode.item(i);
- if (node instanceof Element) {
- Element propertyEle = (Element) node;
- // 获得属性的名称
- String name = propertyEle.getAttribute("name");
- // 获取属性的值
- String value = propertyEle.getAttribute("value");
- if (value != null && value.length()> 0) {
- // 设置这个 bean 对应 definition 里的属性值
- beanDefinition.getPropertyValues().addPropertyValue(new PropertyValue(name, value));
- } else {
- //value 是 Reference 的话 就会进入到这里处理
- String ref = propertyEle.getAttribute("ref");
- if (ref == null || ref.length() == 0) {
- throw new IllegalArgumentException("Configuration problem: <property> element for property'"
- + name + "'must specify a ref or value");
- }
- // 构造一个 BeanReference 然后把这个引用方到属性 list 里
- BeanReference beanReference = new BeanReference(ref);
- beanDefinition.getPropertyValues().addPropertyValue(new PropertyValue(name, beanReference));
- }
- }
- }
- }
- }
上面用到了 BeanReference(如下), 其实这个和 PropertyValue 类似, 用不同的类型是为了更好的区分:
- public class BeanReference {
- private String name;
- private Object bean;
- }
好了, 到现在我们已经分析完了 Spring 是如何找到 Bean 并加载进入 Spring 容器的, 这里面最主要的数据结构就是 BeanDefinition,ReourceLoader 来完成资源的定位, 读入, 然后获取输入流, 进一步的处理, 这个过程中有对 xml 文档的解析和对属性的填充.
来源: http://www.bubuko.com/infodetail-2879870.html