實現本身的BeanFactory java
在使用spring時,咱們不多用"new"關鍵字建立對象,而是經過spring容器BeanFactory提供的getBean()方法獲得對象:mysql
BeanFactory ctx = new ClassPathXmlApplicationContext();
經過spring容器統一管理bean的建立,避免了代碼中四處散落的"new"關鍵字,使咱們可以統一管理對象的建立與銷燬,本節將模仿spring建立一個本身版本的BeanFactory。首先咱們定義一個Dao接口和實現,該接口提供一個方法用於保存Student:spring
public interface StudentDao { void saveStudent(); } public class StudentDaoImpl implements StudentDao { public void saveStudent(){ System.out.println("save success!"); } }
而後咱們再建立一個service,該service將調用上面定義的Dao用於存儲Student:sql
public class StudentService { private StudentDao stuDao; public void saveStudent() { stuDao.saveStudent(); } public StudentDao getStuDao() { return stuDao; } public void setStuDao(StudentDao stuDao) { this.stuDao = stuDao; } }
和spring同樣,咱們也須要一個xml文件用於定義bean對象的建立規則,該xml文件即爲beans.xml,其內容以下:數據庫
<beans> <bean id="stuDao" class="dao.impl.StudentDaoImpl" /> <bean id="stuService" class="service.StudentService"> <property name="stuDao" bean="stuDao"/> </bean> </beans>
如今,兩個JavaBean對象已經定義好了,分別是:StudentDaoImpl和StudentService,beans.xml文件也定義好了,如今咱們須要定義一個工廠(Factory),該Factory將根據beans.xml定義的對象建立規則建立JavaBean,而後把建立的JavaBean保存起來,並提供一個getBean()方法以便用戶得到這些JavaBean,這個工廠的接口與實現以下:編程
public interface BeanFactory { public Object getBean(String name); } public class ClassPathXmlApplicationContext implements BeanFactory { private Map<String, Object> beans = new HashMap<String, Object>(); public ClassPathXmlApplicationContext() { try { SAXBuilder sb = new SAXBuilder(); Document doc = (Document) sb.build(this.getClass().getClassLoader() .getResourceAsStream("beans.xml")); Element root = doc.getRootElement(); List<Element> list = (List<Element>) root.getChildren("bean"); for (int i = 0; i < list.size(); i++) { Element element = (Element) list.get(i); String id = element.getAttributeValue("id"); String clazz = element.getAttributeValue("class"); Object o = Class.forName(clazz).newInstance(); beans.put(id, o); for (Element element2 : (List<Element>) element .getChildren("property")) { String name = element2.getAttributeValue("name"); String bean = element2.getAttributeValue("bean"); Object beanObject = beans.get(bean); String methodName = "set" + name.substring(0, 1).toUpperCase() + name.substring(1); Method m = o.getClass().getMethod(methodName, beanObject.getClass().getInterfaces()[0]); m.invoke(o, beanObject); } } } catch (Exception e) { e.printStackTrace(); } } @Override public Object getBean(String name) { return beans.get(name); } }
能夠看到,在ClassPathXmlApplicationContext的構造函數中,咱們讀取並解析xml文件,而後建立對象,並把對象保存在一個HashMap中,ClassPathXmlApplicationContext類的getBean方法傳入一個bean name,而後咱們在map中查找name對應對象並返回。ide
最後,咱們建立一個測試類,用於測試咱們編寫的代碼是否正確:函數
public class Test { public static void main(String[] args) { BeanFactory ctx = new ClassPathXmlApplicationContext(); StudentService s = (StudentService) ctx.getBean("stuService"); s.saveStudent(); } }
至此,一個簡單的BeanFactory實現了,這個BeanFactory的實現使用到了xml解析技術和反射技術。性能
實現本身的AOP 測試
AOP,即面向方面編程,主要用於把日誌記錄,性能統計,異常處理等非業務邏輯代碼從業務邏輯代碼中分離出來。下面咱們經過Java動態代理實現本身的AOP功能,這個例子會在方法啓動前和啓動後打印當前時間,並計算方法耗時。首先咱們定義一個Advice接口和實現,該接口定義了方法調用前和方法調用後的行爲:
public interface Advice { void beforeMethod(Method method); void afterMethod(Method method); } public class MyAdvice implements Advice { long beginTime = 0; public void beforeMethod(Method method) { System.out.println("before time: " + System.currentTimeMillis()); beginTime = System.currentTimeMillis(); } public void afterMethod(Method method) { System.out.println("after time: " + System.currentTimeMillis()); long endTime = System.currentTimeMillis(); System.out.println(method.getName() + " running time of " + (endTime - beginTime)); } }
而後咱們定義一個xml文件,在該xml文件中定義了一個bean,這個bean有一個屬性"advice":
<beans> <bean id="testObject" class="java.util.ArrayList"> <property advice="aoptest.MyAdvice"/> </bean> </beans>
最後咱們仍是定義一個BeanFactory,該BeanFactory會解析這個xml文件:
public class BeanFactory { private Map<String, Object> beans = new HashMap<String, Object>(); public BeanFactory() { try { SAXBuilder sb = new SAXBuilder(); Document doc = (Document) sb.build(this.getClass().getClassLoader() .getResourceAsStream("aop.xml")); Element root = doc.getRootElement(); List<Element> list = (List<Element>) root.getChildren("bean"); for (int i = 0; i < list.size(); i++) { Element element = (Element) list.get(i); String id = element.getAttributeValue("id"); String clazz = element.getAttributeValue("class"); Object target = Class.forName(clazz).newInstance(); for (Element element2 : (List<Element>) element .getChildren("property")) { String adviceStr = element2.getAttributeValue("advice"); MyAdvice advice = (MyAdvice) Class.forName(adviceStr) .newInstance(); beans.put(id, getProxy(advice, target)); } } } catch (Exception e) { e.printStackTrace(); } } public Object getProxy(final MyAdvice advice, final Object target) { Object result = Proxy.newProxyInstance(target.getClass() .getClassLoader(), target.getClass().getInterfaces(), new InvocationHandler() { public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { advice.beforeMethod(method); Object retVal = method.invoke(target, args); advice.afterMethod(method); return retVal; } }); return result; } public Object getBean(String name) { return beans.get(name); } }
注意,這個beanFactory的實現,在最後調用beans.put(id, getProxy(advice, target))方法時,存入map中的是一個代理對象,並非xml中定義的原生方法。最後,咱們編寫一個測試類:
public class Test { public static void main(String[] args) throws Exception { Object bean = new BeanFactory().getBean("testObject"); ((Collection) bean).add(12); } }
在該測試類中,咱們先從BeanFactory中獲得bean,再調用bean上的add方法,該測試類的輸出以下:
before time: 1416066155411 after time: 1416066155411 add running time of 0
能夠看到,如同咱們預想的,在方法開始前打印了一下當前時間,在方法結束後又打印了時間,最後計算出了方法耗時,使用AOP的方法統計計算耗時,能夠避免把統計代碼與業務代碼耦合在一塊兒,能夠方便統計代碼的複用。
實現本身的聲明式事務
聲明式事務可讓咱們從複雜的事務處理中獲得解脫,使咱們不再須要在與事務相關的方法中處理大量的try...catch...finally代碼,這章咱們將實現本身的聲明式事務,使用到的技術是上一章節介紹的aop技術。首先咱們定義一個JdbcUtils類,該類有一個方法:getConnection,該方法會返回一個JDBC鏈接:
public final class JdbcUtils { public static Connection conn = null; public static boolean autoCommit = true; static { try { Class.forName("com.mysql.jdbc.Driver"); conn = DriverManager.getConnection( "jdbc:mysql://localhost:3306/temp", "root", ""); } catch (Exception e) { throw new ExceptionInInitializerError(e); } } private JdbcUtils() { } public static Connection getConnection() throws SQLException { conn.setAutoCommit(autoCommit); return conn; } }
注意,該類還包含一個autoCommit的靜態布爾屬性,在返回Connection以前會用該屬性定義是否自動提交。而後,咱們定義一個類用於數據庫操做:
public interface UserDao { void save1() throws Exception; void save2() throws Exception; } public class UserDaoImpl implements UserDao { public void save1() throws Exception { Connection conn = JdbcUtils.getConnection(); Statement stmt = conn.createStatement(); stmt.executeUpdate("insert into user(name, birthday, money) values('save1', '1984-10-11', 87446)"); } public void save2() throws Exception { Connection conn = JdbcUtils.getConnection(); Statement stmt = conn.createStatement(); stmt.executeUpdate("insert into user(name, birthday, money) values('save2', '1984-10-11', 87446)"); throw new RuntimeException("qq"); } }
接着,咱們定義一個Advice,該Advice在方法調用前把autoCommit設置爲false,方法執行完成以後commit方法,若是捕捉到異常就回滾事務,最後再把autoCommit設置爲true:
public class MyAdvice{ public void beforeMethod(Method method) { JdbcUtils.autoCommit = false; } public void afterMethod(Method method) throws Exception { JdbcUtils.conn.commit(); } public void finallyMethod(Method method) { JdbcUtils.autoCommit = true; } public void onException(Method method) throws SQLException { JdbcUtils.conn.rollback(); } }
而後,咱們定義一個xml文件,把bean和advice關係註冊一下:
<beans> <bean id="testObject" class="test.UserDaoImpl"> <property advice="aopframework.MyAdvice"/> </bean> </beans>
最後,定義BeanFactory解析xml文件,這段代碼的內容和第二節代碼十分類似,只有一點區別,在建立代理時候套上了try-catch-finally以便進行事務回滾:
public class BeanFactory { private Map<String, Object> beans = new HashMap<String, Object>(); public BeanFactory() { try { SAXBuilder sb = new SAXBuilder(); Document doc = (Document) sb.build(this.getClass().getClassLoader() .getResourceAsStream("aop.xml")); Element root = doc.getRootElement(); List<Element> list = (List<Element>) root.getChildren("bean"); for (int i = 0; i < list.size(); i++) { Element element = (Element) list.get(i); String id = element.getAttributeValue("id"); String clazz = element.getAttributeValue("class"); Object target = Class.forName(clazz).newInstance(); for (Element element2 : (List<Element>) element .getChildren("property")) { String adviceStr = element2.getAttributeValue("advice"); MyAdvice advice = (MyAdvice) Class.forName(adviceStr) .newInstance(); beans.put(id, getProxy(advice, target)); } } } catch (Exception e) { e.printStackTrace(); } } public Object getProxy(final MyAdvice advice, final Object target) { Object result = Proxy.newProxyInstance(target.getClass() .getClassLoader(), target.getClass().getInterfaces(), new InvocationHandler() { public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Object retVal = null; try { advice.beforeMethod(method); retVal = method.invoke(target, args); advice.afterMethod(method); } catch (Exception e) { advice.onException(method); } finally { advice.finallyMethod(method); } return retVal; } }); return result; } public Object getBean(String name) { return beans.get(name); } }
測試代碼與第二節代碼一致:
public class AopFrameworkTest { public static void main(String[] args) throws Exception { Object bean = new BeanFactory().getBean("testObject"); ((UserDao) bean).save1(); ((UserDao) bean).save2(); } }
運行後,在數據庫查看,能夠發現只有save1方法插入的數據生效了,save2未能插入數據。回頭看看咱們的設計,咱們發現,咱們把事務處理相關的代碼放到了統一的地方,避免了與業務代碼耦合,只需在配置文件中配置哪些方法須要事務支持,哪些不須要事務支持,大大簡化了代碼複雜度。