@[TOC]
xml方式自定义实现Ioc容器
使用xml实现自定义简单的Ioc容器
前言
平时开发过程中,我们都是使用Spring来进行开发,Spring核心的Ioc容器帮助我们去创建对象这一过程被称作控制反转
也叫Ioc
在实例化一个对象时候,这个对象中用到一个对象类型的属性,容器把这个对象注入到实例化对象的过程被称作依赖注入
简称DI
Ioc和DI说的是一个事情,针对的侧重点不同,IOC是站在容器角度创建对象,DI是站在使用的角度,注入使用对象;
没有IOC容器的时候
模拟银行转账例子
转账接口
public interface AccountDao {
Account queryAccountByCardNo(String cardNo) throws Exception;
int updateAccountByCardNo(Account account) throws Exception;
}
接口实现类
public class JdbcAccountDaoImpl implements AccountDao {
public void init() {
System.out.println("初始化方法.....");
}
public void destory() {
System.out.println("销毁方法......");
}
@Override
public Account queryAccountByCardNo(String cardNo) throws Exception {
//从连接池获取连接
Connection con = DruidUtils.getInstance().getConnection();
String sql = "select * from account where cardNo=?";
PreparedStatement preparedStatement = con.prepareStatement(sql);
preparedStatement.setString(1,cardNo);
ResultSet resultSet = preparedStatement.executeQuery();
Account account = new Account();
while(resultSet.next()) {
account.setCardNo(resultSet.getString("cardNo"));
account.setName(resultSet.getString("name"));
account.setMoney(resultSet.getInt("money"));
}
resultSet.close();
preparedStatement.close();
//con.close();
return account;
}
@Override
public int updateAccountByCardNo(Account account) throws Exception {
// 从连接池获取连接
// 改造为:从当前线程当中获取绑定的connection连接
Connection con = DruidUtils.getInstance().getConnection();
String sql = "update account set money=? where cardNo=?";
PreparedStatement preparedStatement = con.prepareStatement(sql);
preparedStatement.setInt(1,account.getMoney());
preparedStatement.setString(2,account.getCardNo());
int i = preparedStatement.executeUpdate();
preparedStatement.close();
//con.close();
return i;
}
}
业务接口
public interface TransferService {
void transfer(String fromCardNo,String toCardNo,int money) throws Exception;
}
实现类
public class TransferServiceImpl implements TransferService {
// 1 原始的new 方法创建dao接口实现类对象
private AccountDao accountDao = new JdbcAccountDaoImpl();
@Override
public void transfer(String fromCardNo, String toCardNo, int money) throws Exception {
Account from = accountDao.queryAccountByCardNo(fromCardNo);
Account to = accountDao.queryAccountByCardNo(toCardNo);
from.setMoney(from.getMoney()-money);
to.setMoney(to.getMoney()+money);
accountDao.updateAccountByCardNo(to);
//int c = 1/0;
accountDao.updateAccountByCardNo(from);
}
}
controller层
@WebServlet(name="transferServlet",urlPatterns = "/transferServlet")
public class TransferServlet extends HttpServlet {
// 1. 实例化service层对象
private TransferService transferService = new TransferServiceImpl();
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doPost(req,resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 设置请求体的字符编码
req.setCharacterEncoding("UTF-8");
String fromCardNo = req.getParameter("fromCardNo");
String toCardNo = req.getParameter("toCardNo");
String moneyStr = req.getParameter("money");
int money = Integer.parseInt(moneyStr);
Result result = new Result();
try {
// 2. 调用service层方法
transferService.transfer(fromCardNo,toCardNo,money);
result.setStatus("200");
} catch (Exception e) {
e.printStackTrace();
result.setStatus("201");
result.setMessage(e.toString());
}
// 响应
resp.setContentType("application/json;charset=utf-8");
resp.getWriter().print(JsonUtils.object2Json(result));
}
}
可以看出来在controller层 new
业务层的对象,new 业务层的对象中已经new出来dao层的实现类
业务层和dao层通过new关键字连接起来,耦合高
使用Ioc容器情况下
如图可以看到,Ioc容器讲过AB对象的示例存储起来,main函数使用AB对象的时候,直接在Ioc容器中获取;
基于这个理解,我们可以实现自己的Ioc容器;
首先呢,我们新建自己的xml,用来配置自己的需要一些示例话的bean也就是对象
创建dao层实现类和业务层实现类,并且过自己的Id从容器中获取;
创建BeanFactorys
类来解析xml,实例化配置的对象
/**
* 工厂Bean
*/
public class BeanFactorys {
private final static Map iocMap = new HashMap<>();
static {
// 1 读取解析beans.xml 通过反射技术,生产bean对象,并将其存在map中
InputStream resourceAsStream = BeanFactorys.class.getClassLoader().getResourceAsStream("beans.xml");
//得到一个 文档对象
try {
Document read = new SAXReader().read(resourceAsStream);
//获取跟对象
Element rootElement = read.getRootElement();
/**
* xpath表达式 用法
* // 从匹配选择的当前节点选择文档中的节点,而不考虑他们的位置
* / 从根节点获取
* . 选取当前节点
* .. 选取当前节点的父节点
* @ 选取属性
*
*/
// //表示读取任意位置的bean标签
List list = rootElement.selectNodes("//bean");
if (Objects.isNull(list) || list.size() == 0) {
throw new RuntimeException("无此bean标签");
}
list.forEach(x -> {
//获取Id
String id = x.attributeValue("id"); //accountDao
//获取权限定命名
String clasz = x.attributeValue("class"); //com.udeam.edu.dao.impl.JdbcAccountDaoImpl
System.out.println(id + " ---> " + clasz);
//通过反射创建对象
try {
Object o = Class.forName(clasz).newInstance();
//存入ioc容器
iocMap.put(id, o);
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
});
} catch (DocumentException e) {
e.printStackTrace();
}
// 2 对外提供获取示例对象接口
}
/**
* 对外提供获取bean接口
*
* @param id
* @return
*/
public static Object getBean(String id) {
return iocMap.get(id);
}
}
可以看到在类加载的时候通过解析xml获取其中的bean 的class权限定名,通过反射创建对象,存储在map中,id作为key;
然后我们需要在实现类和控制层去替换这个new关键字创建的对象
private TransferServiceImpl transferService = (TransferServiceImpl) BeanFactorys.getBean("transferService");
private AccountDao accountDao = (AccountDao) BeanFactorys.getBean("accountDao");
但是这样子实现还是有点不优雅 还是有=
连接;
把=
号去掉
private AccountDao accountDao
accountDao.queryAccountByCardNo(fromCardNo);
此时,没有赋值操作,这儿会默认null 空出现空指针异常;
为此,我们需要进行设置值,可以通过set,构造参数等设置值;
可以在xml中使用属性set设置值
在xml中定义一个 对象属性 property
设置名字name和ref引用对象
在刚才的BeanFactorys
中添加解析方法
在解析xml完 实例化对象到Ioc容器之后,来解析property
属性
//获取所有properties 属性 并且set设置值
List prList = rootElement.selectNodes("//property");
prList.forEach(y -> {
//获取 property 属性name值
String name = y.attributeValue("name"); //
String ref = y.attributeValue("ref");
//获取父节点id
Element parent = y.getParent();
//获取父节点id
String id = parent.attributeValue("id");
//维护对象依赖关系
Object o = iocMap.get(id);
//找到所有方法
Method[] methods = o.getClass().getMethods();
for (int i = 0; i < methods.length; i++) {
//方法就是set属性反方
if (methods[i].getName().equalsIgnoreCase("set" + name)) {
try {
//set设置对象
methods[i].invoke(o, iocMap.get(ref));
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
//set之后重新赋值
iocMap.put(id,o);
}
}
});
然后TransferServiceImpl
类中添加一个set方法设置我们需要设置的值
private AccountDao accountDao;
public void setAccountDao(AccountDao accountDao) {
this.accountDao = accountDao;
}
xml上中获取这个set方法,然后利用set + 获取的property
属性name值 进行判断,然后反射设置值
if (methods[i].getName().equalsIgnoreCase("set" + name)) {
try {
//set设置对象
methods[i].invoke(o, iocMap.get(ref));
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
//set之后重新赋值
iocMap.put(id,o);
}
这个过程可以看做是简单的set注入方式,类似于Spring中的set注入;
IoC解决了什么问题
IoC解决对象之间的耦合问题
我们不⽤⾃⼰去new对象了,⽽是由IoC容器(Spring框架)去帮助我们实例化对
象并且管理它,我们需要使⽤哪个对象,去问IoC容器要即可
控制反转
控制:指的是对象创建(实例化、管理)的权利
反转:控制权交给外部环境了(spring框架、IoC容器)
用到的依赖
mysql
mysql-connector-java
5.1.35
com.alibaba
druid
1.1.21
javax.servlet
javax.servlet-api
3.1.0
provided
com.fasterxml.jackson.core
jackson-databind
2.9.6
dom4j
dom4j
1.6.1
jaxen
jaxen
1.1.6
代码
传送门