手寫實(shí)現(xiàn)IOC與AOP思想
源碼參照github:https://github.com/Liuap/MySpring
程序入口:MybatisTest.mapperTest()
1. IOC
在IOC實(shí)現(xiàn)時(shí)囤萤,對beans.xml文件的掃描與將對象放入容器(Map)是自上而下進(jìn)行唐断,更關(guān)鍵的是先'bean'標(biāo)簽級別财忽,然后再是property級別
-
先在容器(Map)里放入bean級別的反射對象,然后根據(jù)property獲取ref所指的對象(map.get(ref))注入(set方法)到父級bean
private static Map<String,Object> map = new HashMap<>(); static { // 完成第一步 InputStream resourceAsStream = BeanFactory.class.getClassLoader().getResourceAsStream("beans.xml"); SAXReader saxReader = new SAXReader(); try { Document document = saxReader.read(resourceAsStream); Element rootElement = document.getRootElement(); //獲取所有bean標(biāo)簽的元素形成list List<Element> beanList = rootElement.selectNodes("http://bean"); for (Element element : beanList) { // id = "userMapper" class = "com.pal.mapper.impl.UserMapperImpl" String id = element.attributeValue("id"); String aClass = element.attributeValue("class"); //利用反射獲取對象 Class<?> aClass1 = Class.forName(aClass); Object instance = aClass1.newInstance(); // 此時(shí)id為小寫首字母百姓,為了和set方法的對象對應(yīng)焚刚,instance是根據(jù)class反射得到的對象 map.put(id,instance); } //實(shí)例化完成則檢查xml中的依賴關(guān)系图焰,將需要的依賴作為參數(shù)放入set達(dá)到注入 List<Element> propertyList = rootElement.selectNodes("http://property"); //解析property獲取父元素 <property name="UserMapper" ref="UserMapper"/> for (Element element : propertyList) { String name = element.attributeValue("name"); String ref = element.attributeValue("ref"); //需要被注入的父級bean Element parent = element.getParent(); //根據(jù)ID調(diào)用父級對象 String parentId = parent.attributeValue("id"); Object parentObject = map.get(parentId); //反射得到方法數(shù)組,然后找到對應(yīng)name的父對象的set方法裆馒,反射調(diào)用,參數(shù)為對象,從前邊夠早的對象map獲取 Method[] methods = parentObject.getClass().getMethods(); for (Method method : methods) { //找到set方法 if (method.getName().equalsIgnoreCase("set"+name)){ //這時(shí)凡桥,反射調(diào)用父級bean的set方法蟀伸,然后從已有bean級別的map中找ref的對象,然后注入父級bean method.invoke(parentObject,map.get(ref)); } } map.put(parentId,parentObject); } } catch (Exception e) { e.printStackTrace(); } } /** * 根據(jù)id獲取對象:注意這里的id要與beans.xml的id對應(yīng)唬血,大小寫敏感 * @param id * @return */ public static Object getBean(String id){ return map.get(id); }
-
Beans.xml文件要與BeanFactory和各個(gè)類中的set注入方式相對應(yīng)
<beans> <!--id標(biāo)識對象望蜡,class是類的全限定類名--> <!-- id和ref必須小寫,因?yàn)榭胶蓿铋_始將id作為map的id來封裝容器脖律,然后根據(jù)property在對依賴進(jìn)行注入,此時(shí)有map.get(ref)來對注入對象進(jìn)行檢索腕侄,大寫找不到小泉,就沒法更新注入 --> <bean id="connectionUtils" class="com.pal.untils.ConnectionUtils"/> <bean id="userMapper" class="com.pal.mapper.impl.UserMapperImpl"> <property name="ConnectionUtils" ref="connectionUtils"/> </bean> <bean id="userService" class="com.pal.service.impl.UserServiceImpl"> <!-- name用來定位set方法芦疏,ref為真正的引用,即set方法的參數(shù) --> <property name="UserMapper" ref="userMapper"/> </bean> <bean id="transactionManager" class="com.pal.untils.TransactionManager"> <property name="ConnectionUtils" ref="connectionUtils"/> </bean> <bean id="proxyFactory" class="com.pal.factory.ProxyFactory"> <property name="TransactionManager" ref="transactionManager"/> </bean> </beans>
/** * 注入方式 */ public class TransactionManager { private ConnectionUtils connectionUtils; public void setConnectionUtils(ConnectionUtils connectionUtils) { this.connectionUtils = connectionUtils; } // 單例模式相對跟耦合 // private TransactionManager(){ // // } // private static TransactionManager transactionManager = new TransactionManager(); // // public static TransactionManager getInstance(){ // return transactionManager; // } ... }
-
因?yàn)榉瓷鋾?shí)例化成員變量(未驗(yàn)證)微姊,所以要注入的對象不能在一級bean類中使用非方法體里直接使用酸茴,
/** * 這個(gè)類正常是不需要的,只是為了做不想再寫一次mybatis底層才這么寫 */ public class UserMapperImpl implements UserMapper{ private ConnectionUtils connectionUtils; public void setConnectionUtils(ConnectionUtils connectionUtils) { this.connectionUtils = connectionUtils; } //反射獲取對象時(shí)兢交,會實(shí)例化類中對象薪捍,由于UserMapperImpl是bean級別引用,所以此時(shí)ConnectionUtils還沒注入配喳,直接用就報(bào)錯(cuò) // InputStream resourceAsStream; // // { // try { // resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml"); // } catch (IOException e) { // e.printStackTrace(); // } // } // // /** // * 構(gòu)建sqlSessionFactory時(shí)可以設(shè)置連接酪穿,以此控制事務(wù) // */ // SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream); // SqlSession sqlSession = sqlSessionFactory.openSession(connectionUtils.getCurrentThreadConn()); // UserMapper mapper = sqlSession.getMapper(UserMapper.class); @Override public List<User> queryAll() { // 方法體里不會報(bào)錯(cuò) InputStream resourceAsStream = null; try { resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml"); } catch (IOException e) { e.printStackTrace(); } SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream); SqlSession sqlSession = sqlSessionFactory.openSession(connectionUtils.getCurrentThreadConn()); UserMapper mapper = sqlSession.getMapper(UserMapper.class); List<User> users = mapper.queryAll(); return users; } ... }
2. AOP
核心思想:動態(tài)代理(JDK或Cglib)
-
以Service層實(shí)現(xiàn)事務(wù)控制為例,不改變原有業(yè)務(wù)代碼的情況下實(shí)現(xiàn)橫切增強(qiáng)
- 事務(wù)問題晴裹,兩次update之間出現(xiàn)異潮患茫或者線程切換時(shí),前一次如果默認(rèn)提交事務(wù)涧团,后一次未執(zhí)行就會造成數(shù)據(jù)不一致
//這樣寫會有事務(wù)問題 userMapper.update(fromUser,fromUserBalance-money); // int i = 1/0; userMapper.update(toUser,toUserBalance+money);
-
- 先使線程綁定Connection只磷,本方法內(nèi)的所有事務(wù)都由一個(gè)Connection完成
// 存儲當(dāng)前線程的連接 private ThreadLocal<Connection> threadLocal = new ThreadLocal<>(); /** * 以下用mybatis獲取連接僅為方便,邏輯上是不該這么寫的 */ InputStream resourceAsStream; { try { resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml"); } catch (IOException e) { e.printStackTrace(); } } SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream); SqlSession sqlSession = sqlSessionFactory.openSession(); /** * 從當(dāng)前線程獲取連接 * Mapper使用 SqlSession sqlSession = sqlSessionFactory.openSession(connectionUtils.getCurrentThreadConn()); */ public Connection getCurrentThreadConn() { /** * 判斷當(dāng)前線程中是否已經(jīng)綁定連接泌绣,如果沒有綁定钮追,需要從連接池獲取一個(gè)連接綁定到當(dāng)前線程 */ Connection connection = threadLocal.get(); if(connection == null) { // 從連接池拿連接并綁定到線程 connection = sqlSession.getConnection(); // 綁定到當(dāng)前線程 threadLocal.set(connection); } return connection; }
-
- 常規(guī)事務(wù)控制寫法:這樣多個(gè)業(yè)務(wù)方法就有很多冗余代碼
try{ //關(guān)閉自動提交事務(wù) connectionUtils.getCurrentThreadConn().setAutoCommit(false); //業(yè)務(wù)代碼,增赞别、改畏陕、刪 ... //提交 connectionUtils.getCurrentThreadConn().commit(); } catch (Exception throwables) { //出錯(cuò):回滾 connectionUtils.getCurrentThreadConn().rollback(); throw throwables; }
-
- 使用動態(tài)代理實(shí)現(xiàn)橫切增強(qiáng)(方法執(zhí)行前后增強(qiáng))
/** * Jdk動態(tài)代理 * @param obj 委托對象 * @return 代理對象 */ public Object getJdkProxy(Object obj) { // 獲取代理對象 return Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(), new InvocationHandler() { @Override //真正在執(zhí)行的時(shí)候,service.transfer("Tim","Bob",100); proxy = service,method = transfer public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Object result = null; try { transactionManager.beginTransaction(); result = method.invoke(obj, args); transactionManager.commit(); } catch (Exception throwables) { transactionManager.rollback(); throw throwables; } return result; } }); } /** * 使用cglib動態(tài)代理生成代理對象,不需要對象的接口 * @param obj 委托對象 * @return */ public Object getCglibProxy(Object obj) { return Enhancer.create(obj.getClass(), new MethodInterceptor() { @Override // 參數(shù):代理對象的引用仿滔、動態(tài)執(zhí)行的方法、參數(shù)犹芹、方法封裝 public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { Object result = null; try { transactionManager.beginTransaction(); result = method.invoke(obj, objects); transactionManager.commit(); } catch (Exception throwables) { transactionManager.rollback(); throw throwables; } return result; } }); }