JavaEE中的MVC(二)Xml配置实现IOC控制反转

时间:2021-11-25 09:13:13

注:这是一个“重复造*”的过程,本篇介绍如何通过XML配置文件实现IOC。

需求:

以往在我们写服务器的时候,业务逻辑层(Biz,有些地方叫Service)通常会有个获取Dao的需求,通常情况是从DaoFactory中调用Get方法,获取所需的Dao。而现在我想改变一下想法,按照IOC这种思路,主动地给Biz注入对应的Dao。

JavaEE中的MVC(二)Xml配置实现IOC控制反转

Xml文件读取

Xml配置

Hibernate的配置文件似乎太多了,先写一个更加简单的Xml文件,取名为Student.xml,这个Student.xml没什么意义,先测试一下,将一个Xml文件解析一下,在控制台原原本本地输出,主要就通过Document对象的方法进行解析 。

<?xml version="1.0" encoding="UTF-8"?>
<beans>
<Student id="001">
<name>Tom</name>
<age>31</age>
</Student>
<Student id="002">
<name>Jom</name>
<age>15</age>
</Student>
</beans>

解析Xml文件

Java代码,运行结果就不贴出来了,就是完完整整地把上面的Xml配置内容在控制台输出来,大家可以注意一下我们可能用到的方法。

public class XmlLoad {
public static void main(String[] args) throws ParserConfigurationException, SAXException, IOException {
InputStream inputStream = XmlLoad.class.getResourceAsStream("/Student.xml");
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
Document document = builder.parse(inputStream); Element root = document.getDocumentElement();
printNode(root);
} private static void printNode(Node node) {
short nodeType = node.getNodeType();
switch (nodeType) {
case Node.TEXT_NODE:
System.out.print(node.getNodeValue());
break; case Node.ELEMENT_NODE:
Element element = (Element) node;
String tagName = element.getTagName();
System.out.print("<");
System.out.print(tagName); NamedNodeMap attrs = element.getAttributes();
for (int i = 0; i < attrs.getLength(); i++) {
Attr attr = (Attr) attrs.item(i);
printNode(attr);
}
System.out.print(">"); NodeList childNodeList = element.getChildNodes();
for (int i = 0; i < childNodeList.getLength(); i++) {
Node childNode = childNodeList.item(i);
printNode(childNode);
} System.out.print("</" + tagName + ">");
break; case Node.ATTRIBUTE_NODE:
Attr attr = (Attr) node;
String name = attr.getName();
String value = attr.getValue();
System.out.print(" " + name + "=" + value);
break; default:
break;
}
}
}

Xml配置文件转JavaBean(Set方式注入)

Xml配置

注:这里使用的是Set方法注入,其实也可以通过构造函数注入,直接通过字段强行注入也可以实现。

下面开始真正的实战,首先,写一个Xml文件,取名为beans.xml:

  • beans是最外层的标签;
  • 其中bean标签,class属性值“com.using.bean.Student“,指的是Student的全类名;
  • 然后property标签指明了各个属性值,比如:Student类的Id属性的值是12,这些都是和Javabean一一对应的。
<?xml version="1.0" encoding="UTF-8"?>
<beans>
<bean class="com.using.bean.Student">
<property name="id" value="12a"></property>
<property name="name" value="2a"></property>
<property name="age" value="31a"></property>
</bean>
<bean class="com.using.bean.Student">
<property name="id" value="12b"></property>
<property name="name" value="2b"></property>
<property name="age" value="31b"></property>
</bean>
</beans>

然后是对应的Javabean,尤其是Set方法,一定要补齐,因为下面的Demo就是通过Set方法注入值的。

public class Student {
private String id;
private String name;
private String age;
//方法补齐
}`

解析思路:

其实思路就是解析Xml布局文件,然后读取属性值,最后通过Java反射机制,将配置的信息反射成一个对象,至于说效率问题,那肯定会比较低,想想就知道,代码都多写了好多行,还要加文件流的读取,能不慢么?不过无反射,不框架。

其中全类名”com.using.bean.Student“,干什么用?相信你们肯定用过,在写数据库连接的时候这样写:Class.forName(“com.mysql.jdbc.Driver”) ,它的作用是装载数据库驱动,而我们要通过它实现这样一个功能,通过字符串来创建实例,具体的使用方法如下:
Object obj=Class.forName(fullClassName).newInstance();

Set方式注入的关键代码如下,先找到Set方法的代码,然后通过invoke()方法注入:

    if (("set" + name.substring(0, 1).toUpperCase() + name.substring(1)).equals(methodName)) {
try {
//参数注入
method.invoke(instance, value);
} catch (Exception e) {
e.printStackTrace();
}
}

解析Xml文件

public class ClassFormat {
public static void main(String[] args) {
parseBeanFile("/beans.xml");
} public static void parseBeanFile(String beanPath) {
try {
InputStream inputStream = ClassFormat.class.getResourceAsStream(beanPath);
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
Document document = builder.parse(inputStream); NodeList stuNodeList = document.getElementsByTagName("bean");
for (int i = 0; i < stuNodeList.getLength(); i++) {
Element stuElement = (Element) stuNodeList.item(i); String className = stuElement.getAttribute("class");
Object instance = createInstance(className);
parseBeanNodes(instance, stuElement); System.out.println(instance.toString());
}
} catch (Exception e) {
e.printStackTrace();
}
} /**
* 根据全类名反射出对象
*/
public static Object createInstance(String fullClassName) {
try {
return Class.forName(fullClassName).newInstance();
} catch (Exception e) {
e.printStackTrace();
}
return null;
} /**
* 解析子节点
*/
public static void parseBeanNodes(Object instance, Element stuElement) {
NodeList propertyNodeList = stuElement.getChildNodes();
for (int j = 0; j < propertyNodeList.getLength(); j++) {
Node propertyNode = propertyNodeList.item(j);
if (propertyNode.getNodeType() == Node.ELEMENT_NODE && "property".equals(propertyNode.getNodeName())) {
Element propertyElement = (Element) propertyNode; String name = propertyElement.getAttribute("name");
String value = propertyElement.getAttribute("value");
//可以补充type属性,然后反射成Javabean的字段类型,逻辑会多一些 setProperty(instance, name, value);
}
}
} /**
* Set方法注入
*/
public static void setProperty(Object instance, String name, String value) {
Class<?> clazz = instance.getClass();
Method[] methods = clazz.getMethods();
for (Method method : methods) {
String methodName = method.getName();
if (("set" + name.substring(0, 1).toUpperCase() + name.substring(1)).equals(methodName)) {
try {
method.invoke(instance, value);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}

控制台输出:
Student [id=12a, name=2a, age=31a]
Student [id=12b, name=2b, age=31b]

最终封装,往Biz中注入Dao

思路分析

  1. 通过DaoFactory这个类实现了Dao和Biz的初始化,这和上面实例化Student是一样的,初始化完成就放到beanMap中;
  2. 然后通过解析Xml文件,了解到Biz要引用哪个Dao;
  3. 配置文件中的name属性指名是哪个Set方法,ref属性指明是哪一个Dao;
  4. 最后通过反射机制,从Set方法注入Dao。

使用场景

这里只给出思路,具体使用,在之后的文章会用到。

在我们写后台的时候,比如说你有一张User表,你可能就会去写UserDao,而这个UserDao只需要一个实例就够了,没必要创建特别多个,你可能会把它写成单例,而且是很希望程序启动的时候就创建好。

这样就可以设计一个优先级别高的Servlet,然后在这个Servlet中去调用我们的DaoFactory,DaoFactory就开始解析Xml配置文件,实例化Dao和Biz,然后往Biz中注入Dao。这样Tomcat启动的时候,我们就有了Dao和Biz对象,而且后面就不要再重复创建这些对象了。

当然,Servlet调用Biz这个耦合我们还没解决,所以Servlet调用Biz的时候你还是得通过DaoFactory来获取Biz。

Dao类:

public class Dao {
private String name="this is dao"; public Dao() {
} @Override
public String toString() {
return "Dao [ name :"+name + "]";
} public String getName() {
return name;
} public void setName(String name) {
this.name = name;
}
}

Biz类:

public class Biz {
private Dao dao; public Biz() {
} @Override
public String toString() {
return "Biz [dao=" + dao.toString() + "]";
} public Dao getDao() {
return dao;
} public void setDao(Dao dao) {
this.dao = dao;
}
}

设计Xml配置文件


<?xml version="1.0" encoding="UTF-8"?>
<beans>
<bean id="Dao" class="com.using.bean.Dao"/>
<bean id="Biz" class="com.using.bean.Biz">
<property name="Dao" ref="Dao" />
</bean>
</beans>

DaoFactory类设计

真正实现IOC注入的Java类,虽然取名叫DaoFactory,但是它也实现了Biz的初始化,命名有些不规范o(∩_∩)o

public class DaoFatory {
private static Map<String, Object> beanMap = new HashMap<>(); public static void main(String[] args) {
System.out.println(beanMap.toString());
} static {
parseBeanFile("/beans.xml");
} /**
* 根据路径解析
*/
public static void parseBeanFile(String beanPath) {
try {
InputStream inputStream = DaoFatory.class.getResourceAsStream(beanPath);
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
Document document = builder.parse(inputStream); //第一次遍历,初始化所有的对象,包括Dao和Biz
NodeList beanNodeList = document.getElementsByTagName("bean");
for (int i = 0; i < beanNodeList.getLength(); i++) {
Node bean = beanNodeList.item(i);
parseBeanNodes(bean);
} //第一次遍历,往初始化好的Biz注入Dao
for (int i = 0; i < beanNodeList.getLength(); i++) {
Node bean = beanNodeList.item(i);
parsePropertyNodes(bean);
}
} catch (Exception e) {
e.printStackTrace();
}
} /**
* 解析property
*/
public static void parsePropertyNodes(Node property) {
if(property!=null&&property.getNodeType()==Node.ELEMENT_NODE){
Element element=(Element) property;
String id = element.getAttribute("id");
Object bean=beanMap.get(id); NodeList childNodeList=property.getChildNodes();
for (int i = 0; i < childNodeList.getLength(); i++) {
Node childNode=childNodeList.item(i);
if(childNode!=null&&childNode.getNodeType()==Node.ELEMENT_NODE
&&"property".equals(childNode.getNodeName())){ Element childElement=(Element) childNode;
String childName = childElement.getAttribute("name");
String childRef = childElement.getAttribute("ref"); Object refBean=beanMap.get(childRef); setProperty(bean, refBean, childName); }
}
}
} /**
* Set参数注入
*/
public static void setProperty(Object bean,Object refBean,String childName) {
Class<?> clazz = bean.getClass();
Method[] methods = clazz.getMethods();
for (Method method : methods) {
String methodName = method.getName();
if (("set" + childName.substring(0, 1).toUpperCase() + childName.substring(1)).equals(methodName)) {
try {
method.invoke(bean, refBean);
} catch (Exception e) {
e.printStackTrace();
}
}
}
} /**
* 解析Node
*/
public static void parseBeanNodes(Node node) {
Element element = (Element) node;
String idName = element.getAttribute("id");
String className = element.getAttribute("class");
beanMap.put(idName, createInstance(className));
} /**
* 根据全类名反射成对象
*/
public static Object createInstance(String fullClassName) {
try {
return Class.forName(fullClassName).newInstance();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}

控制台输出:

{Biz=Biz [dao=Dao [ name :this is dao]], Dao=Dao [ name :this is dao]}

文章写到这,就已经打到了我想要的效果了,不过单独使用IOC思想,确实看着好奇怪,直接new一个Dao,再往Biz里面Set不是很简单嘛?现在看也确实如此,不过也不着急,之后的文章会不停地使用这里的代码