在转账案例中,TransferServlet和TransferServiceImpl代码中,直接用的new关键字去创建对象,属于一个强耦合代码关系。当需要切换Dao层实现类的时候必须修改service层代码,不符合面向接口与开发的最优原则。假如在项目迭代中,dao层不再采用JDBC技术实现,而是采用mybatis实现,我们在写完MybatisAccountDaoImpl后,需要在之前所有new accountDaoImpl的地方都得进行更改。
private TransferService transferService = new TransferServiceImpl();
private AccountDao accountDao = new AccountDaoImpl();
问题解决思路:
(1)采用反射技术来实现对象的实例化,Class.forName(“全限定名”),我们可以将要创建对象的类的全限定名配置到xml中。
(2)使用工厂模式来进行解耦合。工厂对象读取配置文件信息,通过反射生成对应的对象,当service层需要dao层对象的时候直接通过工厂类获取即可,解决了service层和dao层的强耦合关系。
代码改造(一):引入工厂类
xml配置文件:
<?xml version="1.0" encoding="UTF-8" ?>
<beans>
<bean id="accountDao" class="com.nanmao.dao.impl.AccountDaoImpl"></bean>
<bean id="transferService" class="com.nanmao.service.impl.TransferServiceImpl"></bean>
</beans>
添加的maven依赖:
<!--dom4j依赖-->
<dependency>
<groupId>dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>1.6.1</version>
</dependency>
<!--xpath表达式依赖-->
<dependency>
<groupId>jaxen</groupId>
<artifactId>jaxen</artifactId>
<version>1.1.6</version>
</dependency>
在BeanFactory中,获取xml配置信息,通过反射生成对象,存储到map集合中,第三方可以通过getBean方法从map中获取对应的对象。
/**
* 使用反射技术来创建对象
*/
public class BeanFactory {
//存储创建的对象(采用map存储)
private static Map<String,Object> map = new HashMap<>();
//用静态代码块来实现在类加载的时候就完成bean对象的创建
static {
//1.解析xml对象,通过id获取到类的全限定名
InputStream resourceAsStream = BeanFactory.class.getClassLoader().getResourceAsStream("bean.xml");
SAXReader saxReader = new SAXReader();
try {
Document document = saxReader.read(resourceAsStream);
//获取跟标签,即beans标签
Element rootElement = document.getRootElement();
List<Element> beanElements = rootElement.selectNodes("//bean");
for (Element beanElement : beanElements) {
String id = beanElement.attributeValue("id");
String clazz = beanElement.attributeValue("class");
//2.通过反射生成对应的对象
Class<?> aClass = Class.forName(clazz);
Object o = aClass.newInstance();
//3.将生成的对象存储到容器中
map.put(id,o);
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 对外提供获取对象的方法
* @param id bean标签id
* @return 创建的对象
*/
public static Object getBean(String id){
return map.get(id);
}
}
TransferServiceImpl代码改造:
// private AccountDao accountDao = new AccountDaoImpl();
//通过工厂获取对象
private AccountDao accountDao = (AccountDaoImpl)BeanFactory.getBean("accountDao");
TransferServlet代码改造:
// private TransferService transferService = new TransferServiceImpl();
private TransferService transferService = (TransferServiceImpl)BeanFactory.getBean("transferService");
代码改造(二):工厂类升级
引入了工厂类后,我们发现获取对应的对象还需要我们在代码中传入对应的id值,还是比较麻烦,能不能把BeanFactory.getBean也去掉呢?可以。
最佳的形式应该是只写一个属性定义:
private AccountDao accountDao;
如果我们单纯的只定义一个属性,在调用方法时肯定会报空指针异常的,我们需要给属性赋值,可以在构造方法和set方法中进行赋值。这里我们采用set方法的形式。
xml改造:在transferService标签下添加属性标签,根据属性名通过反射调用对应的set方法完成属性的赋值
<?xml version="1.0" encoding="UTF-8" ?>
<beans>
<bean id="accountDao" class="com.nanmao.dao.impl.AccountDaoImpl"></bean>
<bean id="transferService" class="com.nanmao.service.impl.TransferServiceImpl">
<!-- set + name 找到对应的set方法,将ref中对应的对象set进去-->
<property name="AccountDao" ref="accountDao"></property>
</bean>
</beans>
TransferServiceImpl改造:
private AccountDao accountDao;
//set方法进行传值
public void setAccountDao(AccountDao accountDao) {
this.accountDao = accountDao;
}
BeanFactory改造:添加了第4步和第5步操作,利用反射调用set方法完成属性的赋值:
/**
* 使用反射技术来创建对对象
*/
public class BeanFactory {
//存储创建的对象(采用map存储)
private static Map<String,Object> map = new HashMap<>();
//用静态代码块来实现在类加载的时候就完成bean对象的创建
static {
//1.解析xml对象,通过id获取到类的全限定名
InputStream resourceAsStream = BeanFactory.class.getClassLoader().getResourceAsStream("bean.xml");
SAXReader saxReader = new SAXReader();
try {
Document document = saxReader.read(resourceAsStream);
//获取跟标签,即beans标签
Element rootElement = document.getRootElement();
List<Element> beanElements = rootElement.selectNodes("//bean");
for (Element beanElement : beanElements) {
String id = beanElement.attributeValue("id");
String clazz = beanElement.attributeValue("class");
//2.通过反射生成对应的对象
Class<?> aClass = Class.forName(clazz);
Object o = aClass.newInstance();
//3.将生成的对象存储到容器中
map.put(id,o);
}
***//4.判断bean标签下是否有需要设置的属性信息,如有,则通过反射调用进行设置
List<Element> propElements = rootElement.selectNodes("//property");
for (Element propElement : propElements) {
String name = propElement.attributeValue("name");
String ref = propElement.attributeValue("ref");
//找到当前需要处理依赖关系的bean
Element parent = propElement.getParent();
String parentId = parent.attributeValue("id");
Object parentBean = map.get(parentId);
//获取该对象中所有的方法,遍历找到对应的setAccountDao方法
Method[] methods = parentBean.getClass().getMethods();
for (Method method : methods) {
if(method.getName().equalsIgnoreCase("set" + name)){
method.invoke(parentBean, map.get(ref));
}
}
//5.将处理完的对象重新放到容器中
map.put(parentId,parentBean);
}***
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 对外提供获取对象的方法
* @param id bean标签id
* @return 创建的对象
*/
public static Object getBean(String id){
return map.get(id);
}
}