實現本身的BeanFactory、AOP以及聲明式事務

實現本身的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未能插入數據。回頭看看咱們的設計,咱們發現,咱們把事務處理相關的代碼放到了統一的地方,避免了與業務代碼耦合,只需在配置文件中配置哪些方法須要事務支持,哪些不須要事務支持,大大簡化了代碼複雜度。

相關文章
相關標籤/搜索