@[TOC]
xml方式自定義實(shí)現(xiàn)Ioc容器
使用xml實(shí)現(xiàn)自定義簡單的Ioc容器
前言
平時開發(fā)過程中,我們都是使用Spring來進(jìn)行開發(fā),Spring核心的Ioc容器幫助我們?nèi)?chuàng)建對象這一過程被稱作控制反轉(zhuǎn)
也叫Ioc
在實(shí)例化一個對象時候,這個對象中用到一個對象類型的屬性,容器把這個對象注入到實(shí)例化對象的過程被稱作依賴注入
簡稱DI
Ioc和DI說的是一個事情,針對的側(cè)重點(diǎn)不同,IOC是站在容器角度創(chuàng)建對象,DI是站在使用的角度,注入使用對象;
沒有IOC容器的時候
模擬銀行轉(zhuǎn)賬例子
轉(zhuǎn)賬接口
public interface AccountDao {
Account queryAccountByCardNo(String cardNo) throws Exception;
int updateAccountByCardNo(Account account) throws Exception;
}
接口實(shí)現(xiàn)類
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 {
// 從連接池獲取連接
// 改造為:從當(dāng)前線程當(dāng)中獲取綁定的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;
}
}
業(yè)務(wù)接口
public interface TransferService {
void transfer(String fromCardNo,String toCardNo,int money) throws Exception;
}
實(shí)現(xiàn)類
public class TransferServiceImpl implements TransferService {
// 1 原始的new 方法創(chuàng)建dao接口實(shí)現(xiàn)類對象
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. 實(shí)例化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 {
// 設(shè)置請求體的字符編碼
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. 調(diào)用service層方法
transferService.transfer(fromCardNo,toCardNo,money);
result.setStatus("200");
} catch (Exception e) {
e.printStackTrace();
result.setStatus("201");
result.setMessage(e.toString());
}
// 響應(yīng)
resp.setContentType("application/json;charset=utf-8");
resp.getWriter().print(JsonUtils.object2Json(result));
}
}
可以看出來在controller層 new
業(yè)務(wù)層的對象,new 業(yè)務(wù)層的對象中已經(jīng)new出來dao層的實(shí)現(xiàn)類
業(yè)務(wù)層和dao層通過new關(guān)鍵字連接起來,耦合高
使用Ioc容器情況下
如圖可以看到,Ioc容器講過AB對象的示例存儲起來,main函數(shù)使用AB對象的時候,直接在Ioc容器中獲取;
基于這個理解,我們可以實(shí)現(xiàn)自己的Ioc容器;
首先呢,我們新建自己的xml,用來配置自己的需要一些示例話的bean也就是對象
<?xml version="1.0" encoding="UTF-8"?>
<beans>
<bean id="accountDao" class="com.udeam.edu.dao.impl.JdbcAccountDaoImpl">
</bean>
<!-- TransferServiceImpl service-->
<bean id="transferService" class="com.udeam.edu.service.impl.TransferServiceImpl">
</property>
</bean>
</beans>
創(chuàng)建dao層實(shí)現(xiàn)類和業(yè)務(wù)層實(shí)現(xiàn)類,并且過自己的Id從容器中獲取;
創(chuàng)建BeanFactorys
類來解析xml,實(shí)例化配置的對象
/**
* 工廠Bean
*/
public class BeanFactorys {
private final static Map<String, Object> iocMap = new HashMap<>();
static {
// 1 讀取解析beans.xml 通過反射技術(shù),生產(chǎn)bean對象,并將其存在map中
InputStream resourceAsStream = BeanFactorys.class.getClassLoader().getResourceAsStream("beans.xml");
//得到一個 文檔對象
try {
Document read = new SAXReader().read(resourceAsStream);
//獲取跟對象
Element rootElement = read.getRootElement();
/**
* xpath表達(dá)式 用法
* // 從匹配選擇的當(dāng)前節(jié)點(diǎn)選擇文檔中的節(jié)點(diǎn),而不考慮他們的位置
* / 從根節(jié)點(diǎn)獲取
* . 選取當(dāng)前節(jié)點(diǎn)
* .. 選取當(dāng)前節(jié)點(diǎn)的父節(jié)點(diǎn)
* @ 選取屬性
*
*/
// //表示讀取任意位置的bean標(biāo)簽
List<Element> list = rootElement.selectNodes("http://bean");
if (Objects.isNull(list) || list.size() == 0) {
throw new RuntimeException("無此bean標(biāo)簽");
}
list.forEach(x -> {
//獲取Id
String id = x.attributeValue("id"); //accountDao
//獲取權(quán)限定命名
String clasz = x.attributeValue("class"); //com.udeam.edu.dao.impl.JdbcAccountDaoImpl
System.out.println(id + " ---> " + clasz);
//通過反射創(chuàng)建對象
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權(quán)限定名,通過反射創(chuàng)建對象,存儲在map中,id作為key;
然后我們需要在實(shí)現(xiàn)類和控制層去替換這個new關(guān)鍵字創(chuàng)建的對象
private TransferServiceImpl transferService = (TransferServiceImpl) BeanFactorys.getBean("transferService");
private AccountDao accountDao = (AccountDao) BeanFactorys.getBean("accountDao");
但是這樣子實(shí)現(xiàn)還是有點(diǎn)不優(yōu)雅 還是有=
連接;
把=
號去掉
private AccountDao accountDao
accountDao.queryAccountByCardNo(fromCardNo);
此時,沒有賦值操作,這兒會默認(rèn)null 空出現(xiàn)空指針異常;
為此,我們需要進(jìn)行設(shè)置值,可以通過set,構(gòu)造參數(shù)等設(shè)置值;
可以在xml中使用屬性set設(shè)置值
在xml中定義一個 對象屬性 property
設(shè)置名字name和ref引用對象
<bean id="transferService" class="com.udeam.edu.service.impl.TransferServiceImpl">
<property name="AccountDao" ref="accountDao"></property>
</bean>
在剛才的BeanFactorys
中添加解析方法
在解析xml完 實(shí)例化對象到Ioc容器之后,來解析property
屬性
//獲取所有properties 屬性 并且set設(shè)置值
List<Element> prList = rootElement.selectNodes("http://property");
prList.forEach(y -> {
//獲取 property 屬性name值
String name = y.attributeValue("name"); // <property name="setAccountDao" ref = "accountDao"></property>
String ref = y.attributeValue("ref");
//獲取父節(jié)點(diǎn)id
Element parent = y.getParent();
//獲取父節(jié)點(diǎn)id
String id = parent.attributeValue("id");
//維護(hù)對象依賴關(guān)系
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設(shè)置對象
methods[i].invoke(o, iocMap.get(ref));
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
//set之后重新賦值
iocMap.put(id,o);
}
}
});
然后TransferServiceImpl
類中添加一個set方法設(shè)置我們需要設(shè)置的值
private AccountDao accountDao;
public void setAccountDao(AccountDao accountDao) {
this.accountDao = accountDao;
}
xml上中獲取這個set方法,然后利用set + 獲取的property
屬性name值 進(jìn)行判斷,然后反射設(shè)置值
if (methods[i].getName().equalsIgnoreCase("set" + name)) {
try {
//set設(shè)置對象
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框架)去幫助我們實(shí)例化對
象并且管理它黍聂,我們需要使?哪個對象躺苦,去問IoC容器要即可
控制反轉(zhuǎn)
控制:指的是對象創(chuàng)建(實(shí)例化、管理)的權(quán)利
反轉(zhuǎn):控制權(quán)交給外部環(huán)境了(spring框架产还、IoC容器)
用到的依賴
<!-- mysql數(shù)據(jù)庫驅(qū)動包 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.35</version>
</dependency>
<!--druid連接池-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.21</version>
</dependency>
<!-- servlet -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
<!-- jackson依賴 -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.6</version>
</dependency>
<!--dom4j依賴-->
<dependency>
<groupId>dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>1.6.1</version>
</dependency>
<!--xpath表達(dá)式依賴 為了快速定位xml元素-->
<dependency>
<groupId>jaxen</groupId>
<artifactId>jaxen</artifactId>
<version>1.1.6</version>
</dependency>