【 Spring 源码分析 】- 3 SpringIOC 原理

1 XML 技术

1.1 什么是 XML

它是可扩展标记语言(Extensible Markup Language,简称 XML),是一种标记语言。

XML 全称为可扩展的标记语言。主要用于描述数据和用作配置文件。

XML 文档在逻辑上主要由一下 5 个部分组成:

  • XML 声明:指明所用 XML 的版本、文档的编码、文档的独立性信息
  • 文档类型声明:指出 XML 文档所用的 DTD
  • 元素:由开始标签、元素内容和结束标签构成
  • 注释:以结束,用于对文档中的内容起一个说明作用
  • 处理指令:通过处理指令来通知其他应用程序来处理非 XML 格式的数据,格式为 XML 文档的根元素被称为文档元素,它和在其外部出现的处理指令、注释等作为文档实体的子节点,根元素本身和其内部的子元素也是一棵树。

1.2 XML 样例

<?xml version="1.0" encoding="UTF-8"?>  
<students>  
    <student1 id="001">  
        <微信公众号>@122</微信公众号>  
        <学号>0101</学号>  
        <地址>北京海淀区</地址>  
        <座右铭>要么强大,要么听话</座右铭>  
    </student1>  
    <student2 id="002">  
        <新浪微博>@122</新浪微博>  
        <学号>0102</学号>  
        <地址>北京朝阳区</地址>  
        <座右铭>在哭泣中学会坚强</座右铭>  
    </student2>  
</students>  

<?xml version="1.0" encoding="UTF-8"?>
作用 xml 文件头部要写的话,说明了 xml 的版本和编码,utf-8 一般是网络传输用的编码。

1.3 XML 解析方式?

  • Dom4j
  • Sax
  • Pull

1.4 Dom4jSax 区别

dom4j 不适合大文件的解析,因为它是一下子将文件加载到内存中,所以有可能出现内存溢出,sax 是基于事件来对 xml 进行解析的,所以他可以解析大文件的 xml,也正是因为如此,所以 dom4j 可以对 xml 进行灵活的增删改查和导航,而 sax 没有这么强的灵活性,所以 sax 经常是用来解析大型 xml 文件,而要对 xml 文件进行一些灵活(crud)操作就用 dom4j

1.5 使用 dom4j 解析 xml

解析XML过程是通过获取 Document 对象,然后继续获取各个节点以及属性等操作,因此获取 Document 对象是第一步,大体说来,有三种方式:

1、自己创建 Document 对象

Document document = DocumentHelper.createDocument();
Element root = document.addElement("students");

其中 students 是根节点,可以继续添加其他节点等操作。

2、自己创建 Document 对象

// 创建SAXReader对象
SAXReader reader = new SAXReader();
// 读取文件 转换成Document
Document document = reader.read(new File("XXXX.xml"));

3、读取 XML 文本内容获取 Document 对象

String xmlStr = "<students>......</students>";
Document document = DocumentHelper.parseText(xmlStr);

1.6 解析 xml 代码

Xml 配置:

<?xml version="1.0" encoding="UTF-8"?>  
<students>  
    <student1 id="001">  
        <微信公众号>@122</微信公众号>  
        <学号>0101</学号>  
        <地址>北京海淀区</地址>  
        <座右铭>要么强大,要么听话</座右铭>  
    </student1>  
    <student2 id="002">  
        <新浪微博>@122</新浪微博>  
        <学号>0102</学号>  
        <地址>北京朝阳区</地址>  
        <座右铭>在哭泣中学会坚强</座右铭>  
    </student2>  
</students> 

Java 代码:

package com.snow;

import org.apache.commons.lang.StringUtils;
import org.dom4j.Attribute;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

import java.io.InputStream;
import java.util.Iterator;
import java.util.List;

public class Test001 {

	public static void main(String[] args) throws DocumentException {
		new Test001().test001();

	}

	public void test001() throws DocumentException {
		SAXReader saxReader = new SAXReader();
		Document read = saxReader.read(getClassPath("student.xml"));
		// 获取根节点
		Element rootElement = read.getRootElement();
		getNodes(rootElement);
	}

	public InputStream getClassPath(String xmlPath) {
		InputStream resourceAsStream = getClass().getClassLoader().getResourceAsStream(xmlPath);
		return resourceAsStream;
	}

	public static void getNodes(Element rootElement) {
		System.out.println("获取当前名称:" + rootElement.getName());
		// 获取属性信息
		List<Attribute> attributes = rootElement.attributes();
		for (Attribute attribute : attributes) {
			System.out.println("属性:" + attribute.getName() + "---" + attribute.getText());
		}
		// 获取属性value
		String value = rootElement.getTextTrim();
		if (!StringUtils.isEmpty(value)) {
			System.out.println("value:" + value);
		}
		// 使用迭代器遍历,继续遍历子节点
		Iterator<Element> elementIterator = rootElement.elementIterator();
		while (elementIterator.hasNext()) {
			Element next = elementIterator.next();
			getNodes(next);
		}
	}

}

运行,控制台打印:

获取当前名称:students
获取当前名称:student1
属性:id---001
获取当前名称:微信公众号
value:@122
获取当前名称:学号
value:0101
获取当前名称:地址
value:北京海淀区
获取当前名称:座右铭
value:要么强大,要么听话
获取当前名称:student2
属性:id---002
获取当前名称:新浪微博
value:@122
获取当前名称:学号
value:0102
获取当前名称:地址
value:北京朝阳区
获取当前名称:座右铭
value:在哭泣中学会坚强

注意:
this.getClass().getClassLoader().getResourceAsStream(xmlPath) 获取当前项目路径 xmlfsfs

2 XMLJSON 区别

  • Xml 是重量级数据交换格式,占宽带比较大。
  • JSON 是轻量级交换格式,xml 占宽带小。

所有很多互联网公司都会使用 json 作为数据交换格式

很多银行项目,大多数还是在使用 xml。

3 什么是 SpringIOC

SpringIOC 指的是控制反转,IOC 容器负责实例化、定位、配置应用程序中的对象及建立这些对象间的依赖。交由 Spring 来管理这些,实现解耦。

4 SpringIOC 原理

使用 反射机制 + XML技术

4.1 手写 SpringIOCXML 版本

package com.snow.spring;

import java.io.InputStream;
import java.util.List;

import org.apache.commons.lang.StringUtils;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

/**
 *
 * 手写Spring专题 XML方式注入bean
 *
 */
public class ClassPathXmlApplicationContext {

	// xml路径地址
	private String xmlPath;

	public ClassPathXmlApplicationContext(String xmlPath) {
		this.xmlPath = xmlPath;
	}

	/**
	 * 获取bean
	 *
	 * @param beanId
	 * @return
	 * @throws Exception
	 */
	public Object getBean(String beanId) throws Exception {
		// 1. 读取配置文件
		List<Element> elements = readerXml();
		if (elements == null) {
			throw new Exception("该配置文件没有子元素");
		}
		// 2. 使用beanId查找对应的class地址
		String beanClass = findXmlByIDClass(elements, beanId);
		if (StringUtils.isEmpty(beanClass)) {
			throw new Exception("未找到对应的class地址");
		}
		// 3. 使用反射机制 初始化对象
		Class<?> forName = Class.forName(beanClass);
		return forName.newInstance();
	}

	/**
	 * 读取配置文件信息
	 *
	 * @return
	 * @throws DocumentException
	 */
	public List<Element> readerXml() throws DocumentException {
		SAXReader saxReader = new SAXReader();
		if (StringUtils.isEmpty(xmlPath)) {
			new Exception("xml路径为空...");
		}
		Document read = saxReader.read(getClassXmlInputStream(xmlPath));
		// 获取根节点信息
		Element rootElement = read.getRootElement();
		// 获取子节点
		List<Element> elements = rootElement.elements();
		if (elements == null || elements.isEmpty()) {
			return null;
		}
		return elements;
	}

	/**
	 * 使用beanid查找该Class地址
	 *
	 * @param elements
	 * @param beanId
	 * @return
	 * @throws Exception
	 */
	public String findXmlByIDClass(List<Element> elements, String beanId) throws Exception {
		for (Element element : elements) {
			// 读取节点上是否有value
			String beanIdValue = element.attributeValue("id");
			if (beanIdValue == null) {
				throw new Exception("使用该beanId为查找到元素");
			}
			if (!beanIdValue.equals(beanId)) {
				continue;
			}
			// 获取Class地址属性
			String classPath = element.attributeValue("class");
			if (!StringUtils.isEmpty(classPath)) {
				return classPath;
			}
		}
		return null;
	}

	/**
	 * 读取xml配置文件
	 *
	 * @param xmlPath
	 * @return
	 */
	public InputStream getClassXmlInputStream(String xmlPath) {
		InputStream resourceAsStream = getClass().getClassLoader().getResourceAsStream(xmlPath);
		return resourceAsStream;
	}

}

创建 User 实体类:

package com.snow.spring.entity;

public class User {

	private Integer id;
	private String userName;

	public Integer getId() {
		return id;
	}

	public void setId(Integer id) {
		this.id = id;
	}

	public String getUserName() {
		return userName;
	}

	public void setUserName(String userName) {
		this.userName = userName;
	}

}

spring 配置文件:

<beans xmlns="http://www.springframework.org/schema/beans"
	   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
	   xmlns:context="http://www.springframework.org/schema/context"
	   xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"
	   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.xsd
         http://www.springframework.org/schema/aop
         http://www.springframework.org/schema/aop/spring-aop.xsd
         http://www.springframework.org/schema/tx
     	 http://www.springframework.org/schema/tx/spring-tx.xsd">

	<bean id="user" class="com.snow.spring.entity.User"></bean>

</beans>

测试:

package com.snow;

import com.snow.spring.ClassPathXmlApplicationContext;
import com.snow.spring.entity.User;

public class Test001 {

	public static void main(String[] args) throws Exception {
		ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
		User user = (User) applicationContext.getBean("user");
		System.out.println(user);

	}
	
}

控制台打印:

com.snow.spring.entity.User@6f496d9f

4.2 手写 SpringIOC 注解 版本

1⃣️ 定义注解

package com.snow.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 *
 * 自定义注解 从Spring容器获取bean
 *
 */
@Target({ ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME)
public @interface ExtResource {

}

package com.snow.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 *
 * 自定义注解 注入到Spring容器
 *
 */
@Target({ ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
public @interface ExtService {

}

2⃣️ 手写 注解版本注入 bean

package com.snow.spring;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;

import org.apache.commons.lang.StringUtils;

import com.snow.annotation.ExtService;
import com.snow.utils.ClassUtil;

/**
 *
 * 手写 注解版本注入bean
 *
 */
@SuppressWarnings({ "rawtypes", "unchecked" })
public class ClassPathXmlApplicationContext {

	// 扫包范围
	private String packageName;

	ConcurrentHashMap<String, Object> initBean = null;

	public ClassPathXmlApplicationContext(String packageName) {
		this.packageName = packageName;
	}

	/**
	 * 使用beanID查找对象
	 *
	 * @param beanId
	 * @return
	 * @throws Exception
	 */
	public Object getBean(String beanId) throws Exception {

		// 1. 使用反射机制获取该包下所有的类已经存在bean的注解类
		List<Class> listClassesAnnotation = findClassExisService();
		if (listClassesAnnotation == null || listClassesAnnotation.isEmpty()) {
			throw new Exception("没有需要初始化的bean");
		}
		// 2. 使用Java反射机制初始化对象
		initBean = initBean(listClassesAnnotation);
		if (initBean == null || initBean.isEmpty()) {
			throw new Exception("初始化bean为空!");
		}
		// 3. 使用beanID查找查找对应bean对象
		Object object = initBean.get(beanId);
		// 4. 使用反射读取类的属性,赋值信息
		attriAssign(object);
		return object;
	}

	/**
	 * 使用反射机制获取该包下所有的类已经存在bean的注解类
	 *
	 * @return
	 * @throws Exception
	 */
	private List<Class> findClassExisService() throws Exception {
		// 1.使用反射机制获取该包下所有的类
		if (StringUtils.isEmpty(packageName)) {
			throw new Exception("扫包地址不能为空!");
		}
		// 2.使用反射技术获取当前包下所有的类
		List<Class<?>> classesByPackageName = ClassUtil.getClasses(packageName);
		// 3.存放类上有bean注入注解
		List<Class> exisClassesAnnotation = new ArrayList<Class>();
		// 4.判断该类上属否存在注解
		for (Class classInfo : classesByPackageName) {
			ExtService extService = (ExtService) classInfo.getDeclaredAnnotation(ExtService.class);
			if (extService != null) {
				exisClassesAnnotation.add(classInfo);
				continue;
			}
		}
		return exisClassesAnnotation;
	}

	/**
	 * 初始化bean对象
	 *
	 * @param listClassesAnnotation
	 * @return
	 * @throws InstantiationException
	 * @throws IllegalAccessException
	 */
	private ConcurrentHashMap<String, Object> initBean(List<Class> listClassesAnnotation)
			throws InstantiationException, IllegalAccessException {
		ConcurrentHashMap concurrentHashMap = new ConcurrentHashMap<String, Object>();
		for (Class classInfo : listClassesAnnotation) {
			// 初始化对象
			Object newInstance = classInfo.newInstance();
			// 获取父类名称
			String beanId = toLowerCaseFirstOne(classInfo.getSimpleName());
			concurrentHashMap.put(beanId, newInstance);
		}
		return concurrentHashMap;
	}

	/**
	 * 首字母转小写
	 *
	 * @param s
	 * @return
	 */
	private static String toLowerCaseFirstOne(String s) {
		if (Character.isLowerCase(s.charAt(0))) {
			return s;
		} else {
			return (new StringBuilder()).append(Character.toLowerCase(s.charAt(0))).append(s.substring(1)).toString();
		}
	}

	/**
	 * 使用反射读取类的属性,赋值信息
	 *
	 * @param object
	 * @throws IllegalArgumentException
	 * @throws IllegalAccessException
	 */
	private void attriAssign(Object object) throws IllegalArgumentException, IllegalAccessException {
		// 1.获取类的属性是否存在 获取bean注解
		Class<? extends Object> classInfo = object.getClass();
		Field[] declaredFields = classInfo.getDeclaredFields();
		for (Field field : declaredFields) {
			// 属性名称
			String name = field.getName();
			// 2.使用属性名称查找bean容器赋值
			Object bean = initBean.get(name);
			if (bean != null) {
				// 私有访问允许访问
				field.setAccessible(true);
				// 给属性赋值
				field.set(object, bean);
				continue;
			}
		}

	}

}

3⃣️ service 层

package com.snow.service;

public interface OrderService {

	public void addOrder();

}

package com.snow.service.impl;

import com.snow.annotation.ExtService;
import com.snow.service.OrderService;

@ExtService
public class OrderServiceImpl implements OrderService {

	@Override
	public void addOrder() {
		System.out.println("addOrder");
	}

}

package com.snow.service;

public interface UserService {

	public void add();

}

package com.snow.service.impl;

import com.snow.annotation.ExtResource;
import com.snow.annotation.ExtService;
import com.snow.service.OrderService;
import com.snow.service.UserService;

//将该类注入到spring容器里面
@ExtService
public class UserServiceImpl implements UserService {

    // 从Spring容器中读取bean
	@ExtResource
	private OrderService orderServiceImpl;

	public void add() {
		orderServiceImpl.addOrder();
		System.out.println("我是使用反射机制运行的方法");
	}

}

4⃣️ 测试

package com.snow;

import com.snow.service.UserService;
import com.snow.spring.ClassPathXmlApplicationContext;

public class Test001 {

	public static void main(String[] args) throws Exception {
		ClassPathXmlApplicationContext app = new ClassPathXmlApplicationContext("com.snow.service.impl");
		UserService userService = (UserService) app.getBean("userServiceImpl");
		userService.add();
	}

}

控制台打印:

addOrder
我是使用反射机制运行的方法

4.3 常用反射工具类

package com.snow.utils;

import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.net.JarURLConnection;
import java.net.URL;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

/**
 *
 * 反射工具类
 *
 */
public class ClassUtil {

	/**
	 * 取得某个接口下所有实现这个接口的类
	 * 
	 */
	public static List<Class> getAllClassByInterface(Class c) {
		List<Class> returnClassList = null;

		if (c.isInterface()) {
			// 获取当前的包名
			String packageName = c.getPackage().getName();
			// 获取当前包下以及子包下所以的类
			List<Class<?>> allClass = getClasses(packageName);
			if (allClass != null) {
				returnClassList = new ArrayList<Class>();
				for (Class classes : allClass) {
					// 判断是否是同一个接口
					if (c.isAssignableFrom(classes)) {
						// 本身不加入进去
						if (!c.equals(classes)) {
							returnClassList.add(classes);
						}
					}
				}
			}
		}

		return returnClassList;
	}

	/*
	 * 取得某一类所在包的所有类名 不含迭代
	 * 
	 */
	public static String[] getPackageAllClassName(String classLocation, String packageName) {
		// 将packageName分解
		String[] packagePathSplit = packageName.split("[.]");
		String realClassLocation = classLocation;
		int packageLength = packagePathSplit.length;
		for (int i = 0; i < packageLength; i++) {
			realClassLocation = realClassLocation + File.separator + packagePathSplit[i];
		}
		File packeageDir = new File(realClassLocation);
		if (packeageDir.isDirectory()) {
			String[] allClassName = packeageDir.list();
			return allClassName;
		}
		return null;
	}

	/**
	 * 从包package中获取所有的Class
	 * 
	 * @param packageName
	 * @return
	 */
	public static List<Class<?>> getClasses(String packageName) {

		// 第一个class类的集合
		List<Class<?>> classes = new ArrayList<Class<?>>();
		// 是否循环迭代
		boolean recursive = true;
		// 获取包的名字 并进行替换
		String packageDirName = packageName.replace('.', '/');
		// 定义一个枚举的集合 并进行循环来处理这个目录下的things
		Enumeration<URL> dirs;
		try {
			dirs = Thread.currentThread().getContextClassLoader().getResources(packageDirName);
			// 循环迭代下去
			while (dirs.hasMoreElements()) {
				// 获取下一个元素
				URL url = dirs.nextElement();
				// 得到协议的名称
				String protocol = url.getProtocol();
				// 如果是以文件的形式保存在服务器上
				if ("file".equals(protocol)) {
					// 获取包的物理路径
					String filePath = URLDecoder.decode(url.getFile(), "UTF-8");
					// 以文件的方式扫描整个包下的文件 并添加到集合中
					findAndAddClassesInPackageByFile(packageName, filePath, recursive, classes);
				} else if ("jar".equals(protocol)) {
					// 如果是jar包文件
					// 定义一个JarFile
					JarFile jar;
					try {
						// 获取jar
						jar = ((JarURLConnection) url.openConnection()).getJarFile();
						// 从此jar包 得到一个枚举类
						Enumeration<JarEntry> entries = jar.entries();
						// 同样的进行循环迭代
						while (entries.hasMoreElements()) {
							// 获取jar里的一个实体 可以是目录 和一些jar包里的其他文件 如META-INF等文件
							JarEntry entry = entries.nextElement();
							String name = entry.getName();
							// 如果是以/开头的
							if (name.charAt(0) == '/') {
								// 获取后面的字符串
								name = name.substring(1);
							}
							// 如果前半部分和定义的包名相同
							if (name.startsWith(packageDirName)) {
								int idx = name.lastIndexOf('/');
								// 如果以"/"结尾 是一个包
								if (idx != -1) {
									// 获取包名 把"/"替换成"."
									packageName = name.substring(0, idx).replace('/', '.');
								}
								// 如果可以迭代下去 并且是一个包
								if ((idx != -1) || recursive) {
									// 如果是一个.class文件 而且不是目录
									if (name.endsWith(".class") && !entry.isDirectory()) {
										// 去掉后面的".class" 获取真正的类名
										String className = name.substring(packageName.length() + 1, name.length() - 6);
										try {
											// 添加到classes
											classes.add(Class.forName(packageName + '.' + className));
										} catch (ClassNotFoundException e) {
											e.printStackTrace();
										}
									}
								}
							}
						}
					} catch (IOException e) {
						e.printStackTrace();
					}
				}
			}
		} catch (IOException e) {
			e.printStackTrace();
		}

		return classes;
	}

	/**
	 * 以文件的形式来获取包下的所有Class
	 * 
	 * @param packageName
	 * @param packagePath
	 * @param recursive
	 * @param classes
	 */
	public static void findAndAddClassesInPackageByFile(String packageName, String packagePath, final boolean recursive,
			List<Class<?>> classes) {
		// 获取此包的目录 建立一个File
		File dir = new File(packagePath);
		// 如果不存在或者 也不是目录就直接返回
		if (!dir.exists() || !dir.isDirectory()) {
			return;
		}
		// 如果存在 就获取包下的所有文件 包括目录
		File[] dirfiles = dir.listFiles(new FileFilter() {
			// 自定义过滤规则 如果可以循环(包含子目录) 或则是以.class结尾的文件(编译好的java类文件)
			public boolean accept(File file) {
				return (recursive && file.isDirectory()) || (file.getName().endsWith(".class"));
			}
		});
		// 循环所有文件
		for (File file : dirfiles) {
			// 如果是目录 则继续扫描
			if (file.isDirectory()) {
				findAndAddClassesInPackageByFile(packageName + "." + file.getName(), file.getAbsolutePath(), recursive,
						classes);
			} else {
				// 如果是java类文件 去掉后面的.class 只留下类名
				String className = file.getName().substring(0, file.getName().length() - 6);
				try {
					// 添加到集合中去
					classes.add(Class.forName(packageName + '.' + className));
				} catch (ClassNotFoundException e) {
					e.printStackTrace();
				}
			}
		}
	}
}

4.4 Maven 相关依赖

<dependencies>
		<!-- 引入Spring-AOP等相关Jar -->
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-core</artifactId>
			<version>3.0.6.RELEASE</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-context</artifactId>
			<version>3.0.6.RELEASE</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-aop</artifactId>
			<version>3.0.6.RELEASE</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-orm</artifactId>
			<version>3.0.6.RELEASE</version>
		</dependency>
		<dependency>
			<groupId>org.aspectj</groupId>
			<artifactId>aspectjrt</artifactId>
			<version>1.6.1</version>
		</dependency>
		<dependency>
			<groupId>aspectj</groupId>
			<artifactId>aspectjweaver</artifactId>
			<version>1.5.3</version>
		</dependency>
		<dependency>
			<groupId>cglib</groupId>
			<artifactId>cglib</artifactId>
			<version>2.1_2</version>
		</dependency>

		<!-- https://mvnrepository.com/artifact/com.mchange/c3p0 -->
		<dependency>
			<groupId>com.mchange</groupId>
			<artifactId>c3p0</artifactId>
			<version>0.9.5.2</version>
		</dependency>
		<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<version>5.1.37</version>
		</dependency>

		<!-- https://mvnrepository.com/artifact/dom4j/dom4j -->
		<dependency>
			<groupId>dom4j</groupId>
			<artifactId>dom4j</artifactId>
			<version>1.6.1</version>
		</dependency>
		<!-- https://mvnrepository.com/artifact/commons-lang/commons-lang -->
		<dependency>
			<groupId>commons-lang</groupId>
			<artifactId>commons-lang</artifactId>
			<version>2.6</version>
		</dependency>

	</dependencies>

4.5 SpringIOC 容器核心接口

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

不知所起 一往而深

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值