IoC的基本知识
IoC的概念是控制反转
谁控制谁,控制什么:传统Java SE程序设计,我们直接在对象内部通过new进行创建对象,是程序主动去创建依赖对象;而IoC是有专门一个容器来创建这些对象,即由Ioc容器来控制对象的创建;谁控制谁?当然是IoC 容器控制了对象;控制什么?那就是主要控制了外部资源获取(不只是对象包括比如文件等)。
为何是反转,哪些方面反转了:有反转就有正转,传统应用程序是由我们自己在对象中主动控制去直接获取依赖对象,也就是正转;而反转则是由容器来帮忙创建及注入依赖对象;为何是反转?因为由容器帮我们查找及注入依赖对象,对象只是被动的接受依赖对象,所以是反转;哪些方面反转了?依赖对象的获取被反转了。
传统应用程序示意图 有IoC容器后程序结构示意图
该部分来源于 https://www.cnblogs.com/NancyStartOnce/p/6813162.html
此处就不再介绍更多知识,可以自己检索相关知识点。
IoC的简单实现
这里有两种方式实现IoC,基于XML配置文件和注解方式。
基本步骤:
a.定义xml或者注解描述对象的依赖关系。
b.通过程序解析xml或者注解。
c.通过反射创建需要实例化的对象,并放到容器中存储。
xml方式实现IoC
1.创建maven工程并加入解析xml的依赖
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.tgb.myspring</groupId>
<artifactId>myspring</artifactId>
<version>0.0.1-SNAPSHOT</version>
<dependencies>
<!-- 解析xml的工具 -->
<dependency>
<groupId>dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>1.6.1</version>
</dependency>
</dependencies>
<build>
<sourceDirectory>src</sourceDirectory>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.3</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
1.创建需要注入的测试类BeanA、BeanB。此处先贴一下工程结构,结尾处也有下载地址。
BeanA代码
public class BeanA {
private BeanB beanB;
public BeanB getBeanB() {
return beanB;
}
public void setBeanB(BeanB beanB) {
this.beanB = beanB;
}
}
BeanB代码
package com.lzy.ioc.test;
import com.lzy.ioc.annotation.Bean;
public class BeanB {
private String name ;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
2.创建applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans>
<bean name="beanA" class="com.lzy.ioc.test.BeanA">
<property name="beanB" ref="beanB"></property>
</bean>
<bean name="beanB" class="com.lzy.ioc.test.BeanB">
<property name ="name" value="lzy"></property>
</bean>
</beans>
这个xml文件很简单,只是描述出了BeanA和BeanB的关系。
3.创建Bean和Property两个类用于描述xml文件
package com.lzy.ioc.config;
import java.util.ArrayList;
import java.util.List;
/**
* 描述bean的基本信息
* <bean name="B" class="com.lzy.ioc"> <property name ="a" ref="A"></property>
* </bean> 主要有name 、 class、 property
*
* @author admin
*
*/
public class Bean {
/** bean名称 */
private String name;
/** 类的全路径包名 */
private String className;
/** 类的全路径包名 */
private List<Property> properties = new ArrayList<>();
//get/set自己生产
}
package com.lzy.ioc.config;
/**
* bean 的属性值 <property name ="name" value="zhangsan" ref="bean2"></property>
*
* @author admin
*
*/
public class Property {
/**名称*/
private String name;
/**值*/
private String value;
/**引用 */
private String ref;
//get/set自己生产
}
4.创建ConfigManager类解析这个xml文件
package com.lzy.ioc.config.parse;
import java.io.InputStream;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import com.lzy.ioc.config.Bean;
import com.lzy.ioc.config.Property;
/**
* 读取配置文件,返回Bean
*
* @author admin
*
*/
public class ConfigManager {
/**
* 将配置文件转换成Bean返回
*
* @param path
* @return
*/
public static Map<String, Bean> getConfig(String path){
//存储解析出来的bean
Map<String, Bean> beanMap = new HashMap<>();
// 1.创建解析器
SAXReader saxReader = new SAXReader();
// 2.读取配置 文件
InputStream is = ConfigManager.class.getResourceAsStream(path);
Document document = null;
try {
document = saxReader.read(is);
} catch (DocumentException e) {
e.printStackTrace();
throw new RuntimeException("请检查xml配置文件");
}
// 3.定义xpath表达式,用于匹配所有bean
String xpath = "//bean";
// 4.从document中找出所有的bean元素
List<Element> beanNodes = document.selectNodes(xpath);
if(beanNodes != null){
for (Element beanNode : beanNodes) {
Bean bean = new Bean();
// 将xml中的bean描述信息封装到自定义的Bean对象中
String name = beanNode.attributeValue("name");
String className = beanNode.attributeValue("class");
bean.setName(name);
bean.setClassName(className);
// 将xml中的property封装到bean中
List<Element> children = beanNode.elements("property");
if(children != null){
for (Element child : children) {
// 获取xml中的Property属性的信息,并封装到Property对象中
Property property = new Property();
String pName = child.attributeValue("name");
String pValue = child.attributeValue("value");
String pRef = child.attributeValue("ref");
property.setName(pName);
property.setRef(pRef);
property.setValue(pValue);
// 将property封装到bean对象中
bean.getProperties().add(property);
}
// 将bean存储到beanMap中
beanMap.put(name, bean);
}
}
}
return beanMap;
}
}
5.经过以上步骤我们已经将xml转化成我们能处理的java对象,现在就可以创建Bean工厂了,这里 我们需要先定义一个接口和抽象类,并实现xml模式的实现类。
接口AbstraBeanFactory.java
package com.lzy.ioc.main;
/**
* bean 工厂类
*
* 只负责bean的基本管理,不负责bean 的创建
*
* @author admin
*
*/
public interface BeanFactory {
/**
* 根据bean的名称获取bean
*
* @param beanName
* @return
*/
Object getBean(String beanName);
}
抽象类AbstraBeanFactory
package com.lzy.ioc.main;
import java.util.HashMap;
import java.util.Map;
public abstract class AbstraBeanFactory implements BeanFactory{
/** bean 容器,用于存储创建的bean */
protected Map<String,Object> context = new HashMap<>();
@Override
public Object getBean(String beanName) {
return context.get(beanName);
}
}
我们的xml Bean工厂实现类
package com.lzy.ioc.main;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import com.lzy.ioc.config.Bean;
import com.lzy.ioc.config.Property;
import com.lzy.ioc.config.parse.ConfigManager;
import com.lzy.ioc.utils.BeanUtils;
/**
*
* 使用xml 方式注入bean
*
* @author admin
*
*/
public class ClassPathXmlApplicationContext extends AbstraBeanFactory {
//配置信息
private Map<String, Bean> config;
public ClassPathXmlApplicationContext(String path) {
// 1.读取配置信息,将xml转化成com.lzy.ioc.config.Bean对象
config = ConfigManager.getConfig(path);
// 2.遍历出所有的com.lzy.ioc.config.Bean,根据信息创建实例
if(config != null){
for(Entry<String, Bean> en :config.entrySet()){
// 获取bean描述信息
String beanName = en.getKey();
Bean bean = en.getValue();
// 判断该bean是否已经存在,存在就不再创建,不存在就继续创建
Object object = context.get(beanName);
if(Objects.isNull(object)){
// 创建需要注入的bean
Object creatBean = creatBean(bean);
// 放入到容器中
context.put(beanName, creatBean);
}
}
}
}
/**
* 根据Bean对象的描述信息创建 注入到ioc容器的对象实例
*
* 实现:获取bean中的className,根据反射实例化该对象
*
* @param bean
* @return
*/
private Object creatBean(Bean bean) {
// 1.获取class的路径
String className = bean.getClassName();
Class clazz = null;
try {
clazz = Class.forName(className);
} catch (ClassNotFoundException e) {
e.printStackTrace();
throw new RuntimeException("请检查bean的Class配置" + className);
}
// 2.根据clazz创建实例对象
Object beanObj = null;
try {
beanObj = clazz.newInstance();
} catch (InstantiationException | IllegalAccessException e) {
e.printStackTrace();
throw new RuntimeException("bean没有空参构造"+className);
}
// 3.为创建的对象填充数据
if(Objects.nonNull(bean.getProperties())){
for (Property property : bean.getProperties()) {
// 1.基本类型value注入
// 获取注入的属性名称
String name = property.getName();
// 根据属性名称获取对应的set方法
Method setMethod = BeanUtils.getWriteMethod(beanObj, name);
Object parm = null;
if(Objects.nonNull(property.getValue())){
// 获取注入的属性值
String value = property.getValue();
parm = value;
}
// 2.如果是复杂对象的注入
if(Objects.nonNull(property.getRef())){
// 先从当前容器中获取,看是否已经被创建
Object exsiBean = context.get(property.getRef());
if(Objects.isNull(exsiBean)){
// 创建bean并放到容器中
exsiBean = creatBean(config.get(property.getRef()));
context.put(property.getRef(), exsiBean);
}
parm = exsiBean;
}
// 3.调用set方法将值设置到对象中
try {
setMethod.invoke(beanObj, parm);
} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
e.printStackTrace();
throw new RuntimeException("bean的属性"+parm+"没有对应的set方法,或者参数不正确"+className);
}
}
}
return beanObj;
}
}
这里用到了BeanUtils工具类,我也先贴一下
package com.lzy.ioc.utils;
import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
/**
* 根据对象和属性获取set方法
*
* @author admin
*
*/
public class BeanUtils {
/**
* 获取set方法
*
* @param beanObj set方法所属的类
* @param name set方法字段
* @return
*/
public static Method getWriteMethod(Object beanObj, String name) {
Method method = null;
try {
// 1.分析bean对象 -->BeanInfo
BeanInfo beanInfo = Introspector.getBeanInfo(beanObj.getClass());
// 2.根据beaninfo获取所有属性的描述器
PropertyDescriptor[] pds = beanInfo.getPropertyDescriptors();
// 3.遍历属性器
if (pds != null) {
for (PropertyDescriptor pd : pds) {
// 获取当前属性
String pName = pd.getName();
if (pName.equals(name)) {
// 获取set方法
method = pd.getWriteMethod();
}
}
}
} catch (IntrospectionException e) {
e.printStackTrace();
}
if (method == null) {
throw new RuntimeException("请检查" + name + "属性的set方法是否创建");
}
return method;
}
/**
* 通过对象直接给属性赋值,不通过set方法
*
* @param bean set方法所属的类
* @param fieldName 属性字段名
* @param value 注入的值
* @throws IllegalArgumentException
* @throws IllegalAccessException
* @throws SecurityException
* @throws NoSuchFieldException
*/
public static void setValue(Object bean, String fieldName, Object value)
throws IllegalArgumentException, IllegalAccessException, SecurityException, NoSuchFieldException {
Field privateVar = bean.getClass().getDeclaredField(fieldName);
privateVar.setAccessible(true);
privateVar.set(bean, value);
}
/**
* 不通过get方法获取属性值
*
*
* @param bean get方法所属的类
* @param fieldName 属性字段名
* @return
* @throws IllegalArgumentException
* @throws IllegalAccessException
* @throws SecurityException
* @throws NoSuchFieldException
*/
public static Object getValue(Object bean, String fieldName)
throws IllegalArgumentException, IllegalAccessException, SecurityException, NoSuchFieldException {
Field privateVar = bean.getClass().getDeclaredField(fieldName);
privateVar.setAccessible(true);
return privateVar.get(bean);
}
}
经过以上步骤我们就完成了一个非常简单的基于xml配置文件的ioc容器也就是Bean工厂,现在来测试一下
package com.lzy.ioc.test;
import com.lzy.ioc.main.AnnotationApplicationContext;
import com.lzy.ioc.main.ClassPathXmlApplicationContext;
public class Test {
public static void main(String[] args) {
// 测试注解模式
//AnnotationApplicationContextTest();
// 测试配置文件模式
ClassPathXmlApplicationContextTest();
}
/**
* Annotation 方式注入
*
*/
/*public static void AnnotationApplicationContextTest(){
AnnotationApplicationContext ioc = new AnnotationApplicationContext("com.lzy.ioc");
BeanB b = (BeanB) ioc.getBean("beanB");
System.out.println(b.toString());
}*/
/**
* XML 方式注入
*
*/
public static void ClassPathXmlApplicationContextTest(){
String path = "applicationContext.xml";
ClassPathXmlApplicationContext ioc = new ClassPathXmlApplicationContext(path);
BeanA a = (BeanA) ioc.getBean("beanA");
System.out.println(a.getBeanB().getName());
}
}
这里可以输出name的值就说明注入成功了,更详细的测试可以使用debug模式查看ClassPathXmlApplicationContext ioc的Map<String,Object> context容器,这里面存储了所有的bean。
注解方式实现IoC
注解模式相比xml模式更加简洁,开发过程中就能完成依赖的配置,不用切换到xml配置文件。
1.定义两个注解Bean、Autowired,被Bean注解的类表示要添加到IoC容器中,被Autowired注解的表示需要自动注入值。
package com.lzy.ioc.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 标记为需要扫描实例化的类
*
* @author admin
*
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Bean {
}
package com.lzy.ioc.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 标记 为需要自动注入
*
* @author admin
*
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Autowired {
}
2.将注解添加到测试的Bean上,既是BeanA、BeanB上。
package com.lzy.ioc.test;
import com.lzy.ioc.annotation.Bean;
@Bean
public class BeanB {
private String name ;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
package com.lzy.ioc.test;
import com.lzy.ioc.annotation.Autowired;
import com.lzy.ioc.annotation.Bean;
@Bean
public class BeanA {
@Autowired
private BeanB beanB;
public BeanB getBeanB() {
return beanB;
}
public void setBeanB(BeanB beanB) {
this.beanB = beanB;
}
}
这样添加注解的意思是容器需要把BeanA和BeanB都实例化到容器,并且把BeanB注入到BeanA的属性上。
3.创建注解模式的IOC容器 AnnotationApplicationContext
package com.lzy.ioc.main;
import java.io.File;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Map.Entry;
import java.util.Set;
import com.lzy.ioc.annotation.Autowired;
import com.lzy.ioc.annotation.Bean;
import com.lzy.ioc.utils.BeanUtils;
/**
* bean 工厂,使用注解注入bean的bean工厂
*
*
* @author admin
*
*/
public class AnnotationApplicationContext extends AbstraBeanFactory {
/**
* 初始化bean工厂
*
* @param packageName
*/
public AnnotationApplicationContext(String packageName) {
// 1.获取扫描根包的实际路径
scanPackage(packageName);
// 为容器内的bean注入属性
injectionField();
}
/**
* 扫描给的包下的类,并实例化到容器中,此处只实例化,不做属性注入
*
* @param packageName
*/
public void scanPackage(String packageName){
String currentpath = ClassLoader.getSystemResource("").getPath();
String filePath = currentpath + (packageName.replace(".", "\\"));
List<String> annotationClasses = new ArrayList<>();
getAnnotationClassName(filePath, annotationClasses);
for (String path : annotationClasses) {
Class<?> clazz = null;
try {
clazz = Class.forName(path);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
// 获取类上面的所有注解
Annotation[] annotations = clazz.getAnnotations();
for (Annotation annotation : annotations) {
if (annotation instanceof Bean) {
Object newInstance = null;
try {
// 实例化Bean,并将类的首字母小写的名称作为实例化后的bean名称
newInstance = clazz.newInstance();
String beanName = path.substring(path.lastIndexOf(".") + 1);
char firstChar = Character.toLowerCase(beanName.charAt(0));
String replaceFirst = beanName.replaceFirst(beanName.substring(0, 1),
Character.toString(firstChar));
context.put(replaceFirst, newInstance);
} catch (InstantiationException | IllegalAccessException e) {
e.printStackTrace();
}
}
}
}
}
/**
* 向容器中注入属性
* 使用的set方法 注入的,所有必须包含set方法
*
*/
public void injectionField(){
Set<Entry<String, Object>> beans = context.entrySet();
for (Entry<String, Object> beanEntry : beans) {
Object bean = beanEntry.getValue();
String beanName = beanEntry.getKey();
// 获取需要注入的属性
Field[] declaredFields = bean.getClass().getDeclaredFields();
for (Field field : declaredFields) {
Annotation[] fieldAnnotions = field.getAnnotations();
// 处理字段上的注入注解
for (Annotation fieldAnnotation : fieldAnnotions) {
String fieldName = field.getName();
if(fieldAnnotation instanceof Autowired){
// 判断容器中是否有该bean
if(context.containsKey(fieldName)){
// 将容器中的值通过set方法注入到bean中
/*
//这里使用的是 set方法注入
Method getMethod = BeanUtils.getWriteMethod(bean, fieldName);
try {
getMethod.invoke(bean, context.get(fieldName));
} catch (IllegalAccessException | IllegalArgumentException
| InvocationTargetException e) {
e.printStackTrace();
}*/
// 不在依赖属性的set方法为属性注入值
try {
BeanUtils.setValue(bean, fieldName, context.get(fieldName));
} catch (IllegalArgumentException | IllegalAccessException | SecurityException
| NoSuchFieldException e) {
e.printStackTrace();
}
}
}else{
// 容器中没有该值,需要创建该bean
// TODO
throw new RuntimeException("请检查"+ beanName + "类中的" + fieldName + "字段是否已经注入到容器中");
}
}
break;
}
}
}
/**
* 获取路径下的所有全类名
*
* @param filePat
* @param annotationClasses
*/
public void getAnnotationClassName(String filePath, List<String> annotationClasses) {
String currentpath = ClassLoader.getSystemResource("").getPath();
File file = new File(filePath);
File[] childFiles = file.listFiles();
for (File childFile : childFiles) {
if (childFile.isDirectory()) {
// 目录
getAnnotationClassName(childFile.getPath(), annotationClasses);
} else {
// 文件
String childPath = childFile.getPath();
if (childPath.endsWith(".class")) {
// 是class文件
String packageNameOfClass = childPath.substring(currentpath.length() - 1, childPath.length() - 6)
.replace("\\", ".");
annotationClasses.add(packageNameOfClass);
}
}
}
}
}
4.创建测试代码,我们可以通过debug模式查看容器内部是否已经创建了被注解加持的对象。
package com.lzy.ioc.test;
import com.lzy.ioc.main.AnnotationApplicationContext;
import com.lzy.ioc.main.ClassPathXmlApplicationContext;
public class Test {
public static void main(String[] args) {
// 测试注解模式
AnnotationApplicationContextTest();
// 测试配置文件模式
//ClassPathXmlApplicationContextTest();
}
/**
* Annotation 方式注入
*
*/
public static void AnnotationApplicationContextTest(){
AnnotationApplicationContext ioc = new AnnotationApplicationContext("com.lzy.ioc");
BeanB b = (BeanB) ioc.getBean("beanB");
System.out.println(b.toString());
}
/**
* XML 方式注入
*
*/
public static void ClassPathXmlApplicationContextTest(){
String path = "applicationContext.xml";
ClassPathXmlApplicationContext ioc = new ClassPathXmlApplicationContext(path);
BeanA a = (BeanA) ioc.getBean("beanA");
//System.out.println(a.getBeanB().getName());
}
}
至此我们的两种简单IOC就实现完成了,最后贴一下码云的代码地址:
https://gitee.com/liubluesnow/MyIOC