spring最核心的部分莫過於ioc和aop了,博主菜逼一枚,若是有哪裏理解的不對或者代碼上有瑕疵的地方歡迎你們指正,你們互相學習,還有就是這只是模仿一下spring思想,只是把事務管理和bean管理簡單模仿一下,徹底不表明spring,若是想深刻理解請看spring源碼,下面就開始咱們簡單的模仿,純手打,以爲還行就贊一下吧~java
這個項目不是web項目,只是一個簡單的java項目,測試用junit,廢話很少說了,下面上代碼:mysql
項目的目錄結構:web
說明:圖中劃紅線的部分都是核心部分spring
紅線部分說明:sql
① BeanFactory:全部bean的核心生成器(spring容器), ② ConnBean:jdbc鏈接生成器(沒用鏈接池哦~)數據庫
③Transaction:事務管理的代理類, ④ beans.properties:配置文件tomcat
其他的沒劃線的就是domain、dao、service、controller這些web基本層次結構,待會會說多線程
--------------------------------------------------------------------------------------------------------------併發
主要幾個類的代碼:dom
① BeanFactory:
package sun.juwin.factory; import java.io.BufferedReader; import java.io.InputStreamReader; import java.util.HashMap; /** * 本類用來讀取配置文件中的信息對每一個接口對象生成具體的實現 * 主要是將接口做爲key,實例做爲value存儲進去,這是個單例, * spring默認爲每一個層次生成實現也是單例,但能夠經過@Scope * 來指定,咱們簡單模仿一下,只是單例 */ public class BeanFactory { private static HashMap<String, Object> mapResult; public static HashMap<String, Object> getBean() { if (mapResult == null) { synchronized (BeanFactory.class) {//雙重檢查的單例,防止多線程訪問時屢次new對象 if (mapResult == null) { BufferedReader bf = null; String line = null; try { /** *下面這句代碼經過流來讀取資源包下面的配置文件,爲了省去沒必要要的麻煩, * 咱們沒有用xml,而是用了properties */ InputStreamReader inputStreamReader = new InputStreamReader(BeanFactory.class.getClassLoader().getResourceAsStream("beans.properties")); bf = new BufferedReader(inputStreamReader); mapResult = new HashMap<>(); while ((line = bf.readLine()) != null) {//每次僅讀一行 if ("".equals(line)){//有可能讀到換行時隔了一行(即只有一個換行符) continue; } String[] point = line.trim().split("=");//按照等號拼接 if (point.length > 2) { throw new Exception("beans文件格式不對!"); } Object obj = Class.forName(point[1].trim()).newInstance();//反射實例化出目標對象 mapResult.put(point[0].trim(), obj);//而後以鍵值對的形式存入 } } catch (Exception e) { e.printStackTrace(); } } } } return mapResult; } }
上面的類能夠經過配置文件來實例化不一樣的對象,符合ioc最基本的思想,下面讓咱們來看看配置文件beans.properties的內容吧:
userDao = sun.juwin.dao.impl.UserDaoImpl userDetailDao = sun.juwin.dao.impl.UserDetailDaoImpl
這裏面只有兩句話,指定dao層接口對象的實現類的路徑,其實已經很接近spring的xml裏對bean的配置了,只不過這裏是properties文件,簡化了許多
② TransactionProxy代理類:
package sun.juwin.proxy.transctional; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.sql.Connection; /** * 事務代理類,經過這個類能夠爲要執行的方法加上事務管理 */ public class TransactionProxy implements InvocationHandler { private Object targetObj; public Object getTargetObj(Object targetObj){ this.targetObj = targetObj; return Proxy.newProxyInstance(this.targetObj.getClass().getClassLoader(), this.targetObj.getClass().getInterfaces(), this); } /*下面這個方法會在被代理類執行方法時調用,拿到被代理類的要執行的method對象*/ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Object result = null; Connection connection = (Connection)args[0];//要求第一個參數必須是conn try{ connection.setAutoCommit(false);//開啓事務 result = method.invoke(this.targetObj, args);//執行目標方法 connection.commit();//事務提交 System.out.print("commit success!"); }catch (Exception e){ connection.rollback();//事務回滾 System.err.println("rollback!"); e.printStackTrace(); }finally { connection.close();//關閉鏈接 System.out.println("connection closed!"); } return result; } }
說明:java在1.3版本的時候就爲咱們提供了一個用做代理類實現的接口InvacationHandler,經過實現這個接口能夠很隨意的寫一個耦合度特別低的動態代理類(即這一個代理類能夠代理任何類)
③ ConnBean,用來生成一個數據庫鏈接對象,在不用鏈接池的狀況下,咱們用ThreadLocal進行封裝,代碼以下:
package sun.juwin.db; import java.sql.Connection; import java.sql.DriverManager; /*原始產生數據庫鏈接的類*/ public class ConnBean { private static ThreadLocal<Connection> conn = new ThreadLocal<>(); private ConnBean(){} public static Connection getConn(){ Connection connection = conn.get(); if(connection == null){ synchronized (ConnBean.class){//因爲用到了ThreadLocal,所以該單例僅僅相對於當前線程是單例的 if(connection == null){ try{ Connection realConn = DriverManager.getConnection("jdbc:mysql://localhost:3306/db_useradd", "root", ""); conn.set(realConn); }catch (Exception e){ e.printStackTrace(); } } } } return conn.get();//返回給當前線程一個Connection對象 } }
以上就是核心的一些實現代碼,下面讓咱們來看一下咱們的業務吧:
實體類:User,UserDetail,要求添加一個User的同時要添加一個UserDetail
User:
private Long id; private String userName; private String address; private int money;
UserDetail:
private Long id; private int age; private String realname;
dao層的接口和實現:
UserDao:
public interface UserDao { public void save(User user, Connection conn)throws Exception; }
UserDaoImpl:
public class UserDaoImpl implements UserDao{ @Override public void save(User user, Connection conn) throws Exception { Statement statement = conn.createStatement();//爲了省去沒必要要的麻煩,咱們不用預編譯語句 String sql = "insert into tb_user (userName, address, money) values ('" + user.getUserName() + "', '" + user.getAddress() + "', " + user.getMoney() + ")"; statement.executeUpdate(sql); statement.close(); } }
UserDetailDao:
public interface UserDetailDao { public void save(UserDetail userDetail, Connection connection) throws Exception; }
UserDetailDaoImpl:
public class UserDetailDaoImpl implements UserDetailDao { @Override public void save(UserDetail userDetail, Connection connection) throws Exception { Statement statement = connection.createStatement(); String sql = "insert into user_detail (age, realname) values (" +userDetail.getAge()+", '" +userDetail.getRealname()+"')"; statement.executeUpdate(sql); } }
UserService:
public interface UserService { public void saveService(Connection connection, User user) throws Exception; }
UserServiceImpl
/** * 業務層 * juwin * 2015-12-04 */ public class UserServiceImpl implements UserService { //下面的dao層實例由BeanFactory經過properties配置文件幫咱們生成對應的實例對象 private UserDao userDao = (UserDao) BeanFactory.getBean().get("userDao"); private UserDetailDao userDetailDao = (UserDetailDao) BeanFactory.getBean().get("userDetailDao"); @Override public void saveService( Connection connection, User user)throws Exception { /** * 這個業務層方法執行了兩個dao層方法,能夠看作一個事務, * 任意一個dao層調用過程當中若是發生異常,整個業務方法進行的全部dao層操做就會回滾 */ userDao.save(user, connection); /*要求在添加user的同時生產一個對應的detail,這裏偷個懶,就本身new一個UserDetail對象吧*/ UserDetail userDetail = new UserDetail(); userDetail.setAge(22); userDetail.setRealname("juwin"); userDetailDao.save(userDetail, connection); throw new Exception("攔-路-虎");//這個異常是用來測試事務會不會回滾的,正常狀況下不加這個 } }
UserController:
/** * 程序入口,相似於controller層 */ public class UserController { public void SaveUser(User user)throws Exception{ /** * 這一步很關鍵,爲每個執行這個操做的線程分配一個connection鏈接對象 * 說明:在實際web開發中客戶端經過發送http請求到業務後臺,這時候tomcat會爲此次請求分配一個線程 * 所以就出現了併發甚至並行的現象,假象一下,咱們若是隻是利用單例寫一個生成connection對象的方法, * 那麼多線程併發訪問的時候就有可能出現:線程1利用完connection對象將其狀態修改成close,而此時線程2 * 也要用connection,這時候就會報「connection已經關閉」的異常 * 所以咱們採用ThreadLocal,爲單獨一個線程生成一個單例的connection對象 */ Connection connection = ConnBean.getConn(); /** * 下面這個實例要加一層事務代理,就是讓TransactionProxy這個代理類攪合一下, * 這樣咱們再利用service層對象調用任何方法時,都會加上事務管理了 */ UserService userService = (UserService) new TransactionProxy().getTargetObj(new UserServiceImpl()); userService.saveService(connection,user); } }
測試類:
public class UserAddTest { @Test public void Test1() throws Exception{ User user = new User(); user.setUserName("weixiaojie1993"); user.setAddress("beijing"); user.setMoney(1); UserController userController = new UserController(); userController.SaveUser(user); System.out.print("Done !"); } }
ok,大功告成了,如今讓咱們用junit來測試一下吧:
service層不加
throw new Exception("攔-路-虎");
執行結果:
能夠看出來事務已經提交了,咱們來看看數據庫裏面的變化:
tb_user表:
user_detail表:
而後在業務層加上
throw new Exception("攔-路-虎");
運行結果:
仔細觀察劃綠色線的部分就能發現,事務已經回滾了,看數據庫表也是沒有記錄的
咱們主鍵id因爲是遞增的,所以咱們還要肯定一下事務是否是真的回滾了,咱們把異常代碼去掉,而後再往裏面插入成功一次數據,運行後的數據庫表記錄以下:
tb_user:
user_detail:
你們仔細看id,已是3了,說明原來事務成功回滾了
說明:其實connection對象沒必要每次都做爲參數傳遞給方法,這裏只是爲了更清楚的展現connection的流向,其實咱們用ThreadLocal封裝成一個單例的時候就已經註定了本次訪問(即當前線程從controller層調用到dao層)全部get到的connection對象都是同一個;
最後,我的感受這個程序有個很是要命的地方,就是我要給service層加事務代理,這樣就致使了sevice層的對象不能經過配置文件來實例化,正在糾結中。。之後還會優化,這只是簡單實現如下,真正的spring要複雜的多得多,第一次發表博客,之後也會多發一些,你們互相學習~