如果你想開發(fā)一個應用(1-9)

上一章的結尾,我們看到現(xiàn)有的代碼雖然經(jīng)過了一些改進,但仍然有很多壞味道,首當其沖的就是Controller太厚了,Controller應該僅僅作為一個控制器使用,要盡可能的薄。這時候,上一章里提到過的IOC和DI華麗登場了.

控制反轉

控制反轉簡單說就一句話,就是把程序資源的管理權由互相使用的雙方的代碼反轉到第三方容器。即一般來說,對象的創(chuàng)建和銷毀,使用都由用戶有代碼進行直接控制,而現(xiàn)在則由容器(Spring框架)來控制,這樣可以最大限度的減少類的耦合度,比如說無論,一個對象的new是避免不了的,而控制反轉則可將這些也交給容器,即各種類徹底的脫離直接聯(lián)系。

提到控制反轉,就不能不提依賴注入,可以說依賴注入就是控制反轉的目的之一,即當一個對象需要另一個對象實力的時候,有容器根據(jù)這個實例所依賴的實例而注入進去,這種對象之間互相依賴的情況不在由自己管理。

貌似很晦澀,下面我們依然使用注解的方式,先對一部分代碼實現(xiàn)控制反轉和依賴注入。

用戶服務

從最簡單的地方開始,注意代碼中的一部分:

UserDao userDao =new UserDao();
User user=null;
//獲取用戶
user=userDao.getUserByName(name);
if(null==user){
    //新用戶
    user=new User();
    user.setName(name);
    user.setId(userDao.save(user));
}

這部分代碼與現(xiàn)有的業(yè)務有關系么?他所需要做的,其實就是查詢用戶,有則返回userId,沒有則創(chuàng)建一個用戶并返回存儲到db中后的自增長userId。接下來就按照面向接口編程的方式一步步來重構這段代碼。

既然是用戶服務,那么首先創(chuàng)建一個service包,然后在里邊創(chuàng)建一個UserService的接口,接口目前只有一個方法,就是getUserByName,代碼如下:

public interface UserService {
    User getUserByName(String username);
}

既然有了接口,那么就會想到接口的實現(xiàn)。在理論上,我們的Controller層并不關心實現(xiàn)方式,只知道使用這個接口定義的功能就可以了,所以,將實現(xiàn)放在service的下級包,即創(chuàng)建包service.impl,然后在包內創(chuàng)建類UserServiceImpl,此類代碼如下:

@Service
public class UserServiceImpl implements UserService  {
    public User getUserByName(String username) {
        UserDao userDao =new UserDao();
        User user=null;
        //獲取用戶
        user=userDao.getUserByName(username);
        if(null==user){
            //新用戶
            user=new User();
            user.setName(username);
            user.setId(userDao.save(user));
        }
        return user;
    }
}

注意@Service注解,這代表著此類為一個服務組件,目前Spring中定義了多種組件,大部分可以互相替換,小部分有自己獨特的功能,但為了語義上的方便以及代碼的可讀性,還是建議使用推薦的注解:

  • @Service:定義一個業(yè)務層的組件
  • @Controller:定義一個控制器層的組件
  • @Repository:定義一個DB訪問層的組件
  • @Component:組件,當組件不好歸類時可以使用此注解

此時bean的默認名為類名首字母小寫,可以使用參數(shù)類現(xiàn)實確定bean名,如:

@Service("beanName")    

若此接口有多個實現(xiàn),并且實現(xiàn)均為組件,可以再調用處通過注解@Qualifier來顯式來決定使用哪一個組件,如:

@Autowired
@Qualifier("beanName")
private UserService userService;

這里還要注意@Autowired注解,代表這里引入一個組件,即注入一個Bean,一般情況下有三種通用的注解:

  • @Autowird:Spring框架提供的注解
  • @Inject:java提供
  • @Resource:java提供

這三種注解均可以聲明到set方法或一個屬性上,我喜歡放到屬性上,原因很簡單,代碼少。

下面用土土的方法運行測試一下,和之前一樣,就不在截圖,然后刪除之前的beanName。將Todo的調用也增加一個服務,并修改代碼,做到controller與dao層解耦,控制器源碼如下:

@Controller
public class TodoController {
    @Autowired
    private UserService userService;
    @Autowired
    private TodoService todoService;
    @RequestMapping(value ="/todos/{name}" ,method = RequestMethod.GET)
    public String home(@PathVariable String name, HttpServletRequest request){
        User user=userService.getUserByName(name);
        
        List<Todo> list= todoService.getTodoByUserId(user.getId());
        
        request.setAttribute("todos",list);
        request.setAttribute("userid",user.getId());
        return "todos";
    }
    @RequestMapping(value ="/todos" ,method = RequestMethod.POST)
    public String home(HttpServletResponse response, Todo todo) throws IOException {
        todoService.save(todo);
       
        User user = userService.get(todo.getUserId());
        
        return "redirect:/todos/"+user.getName();
    }
}

代碼是不是清爽了許多,之后,業(yè)務上的變更完全可以再service層消化掉,這是service的兩個接口代碼如下:

public interface UserService {
    User getUserByName(String username);
    User get(int id);
}


public interface TodoService {
    void  save(Todo todo);
    List<Todo> getTodoByUserId(int userId);
}

然后是兩個實現(xiàn)類:

@Service
public class UserServiceImpl implements UserService  {
    public User getUserByName(String username) {
        UserDao userDao =new UserDao();
        User user=null;
        //獲取用戶
        user=userDao.getUserByName(username);
        if(null==user){
            //新用戶
            user=new User();
            user.setName(username);
            user.setId(userDao.save(user));
        }
        return user;
    }

    public User get(int id) {
        UserDao userDao =new UserDao();
        return userDao.get(id);
    }
}


@Service
public class TodoServiceImpl implements TodoService {
    public void save(Todo todo) {
        TodoDao todoDao=new TodoDao();
        todoDao.save(todo);
    }

    public List<Todo> getTodoByUserId(int userId) {
        TodoDao todoDao=new TodoDao();
        return todoDao.getTodoByUserId(userId);
    }
}

啊哈,壞味道又出現(xiàn)了,其實這時候我猜你一定想到了,對將dao層頁一樣的組件化。

倉儲組件##

有了現(xiàn)有的經(jīng)驗,其實是一件再簡單不過的事了,首先將dao包內的兩個類改為接口,并創(chuàng)建實現(xiàn)包,最終兩個接口的代碼如下:

public interface TodoDao {
    public List<Todo> getAll();
    public List<Todo> getTodoByUserId(int userId);
    public void save(Todo todo);
}

public interface UserDao {
    public User getUserByName(String name);
    public User get(int id);
    public int save(User user);
}

然后服務層的代碼就變?yōu)橄旅孢@樣了:

@Service
public class UserServiceImpl implements UserService  {
    @Autowired
    private UserDao userDao;
    public User getUserByName(String username) {
        User user=null;
        //獲取用戶
        user=userDao.getUserByName(username);
        if(null==user){
            //新用戶
            user=new User();
            user.setName(username);
            user.setId(userDao.save(user));
        }
        return user;
    }
    public User get(int id) {
        return userDao.get(id);
    }
}


@Service
public class TodoServiceImpl implements TodoService {

    @Autowired
    private TodoDao todoDao;
    public void save(Todo todo) {
        todoDao.save(todo);
    }

    public List<Todo> getTodoByUserId(int userId) {
        return todoDao.getTodoByUserId(userId);
    }
}

然后是dao層實現(xiàn)類的代碼(只貼user部分):

package com.niufennan.jtodos.dao.impl;

import com.niufennan.jtodos.dao.UserDao;
......

@Repository
public class UserDaoImpl implements UserDao{
    public User getUserByName(String name){
        Connection connection= null;
        PreparedStatement statement=null;
        ResultSet resultSet=null;
        List<Todo> list=new ArrayList<Todo>();
        try{
            connection = DatabaseHelper.getConnection();
            statement= connection.prepareStatement("select * from users where name=?");
            statement.setString(1,name);
            resultSet=statement.executeQuery();
            if (resultSet.next()){
                User user=new User();
                user.setId(resultSet.getInt("id"));
                user.setName(resultSet.getString("name"));;
                return user;
            }
        }catch (SQLException ex){
            new RuntimeException(ex);
        }
        finally {
            DatabaseHelper.close(resultSet,statement,connection);
        }
        return null;
    }
    public User get(int id){
        Connection connection= null;
        PreparedStatement statement=null;
        ResultSet resultSet=null;
        List<Todo> list=new ArrayList<Todo>();
        try{
            connection = DatabaseHelper.getConnection();
            statement= connection.prepareStatement("select * from users where id=?");
            statement.setInt(1,id);
            resultSet=statement.executeQuery();
            if (resultSet.next()){
                User user=new User();
                user.setId(resultSet.getInt("id"));
                user.setName(resultSet.getString("name"));;
                return user;
            }
        }catch (SQLException ex){
            new RuntimeException(ex);
        }
        finally {
            DatabaseHelper.close(resultSet,statement,connection);
        }
        return null;
    }
    public int save(User user){
        Connection connection=null;
        PreparedStatement statement=null;
        ResultSet resultSet=null;
        try {
            connection = DatabaseHelper.getConnection();
            statement=connection.prepareStatement("INSERT INTO users(name) VALUES (?);", Statement.RETURN_GENERATED_KEYS);
            statement.setString(1,user.getName());
            statement.executeUpdate();
            resultSet=statement.getGeneratedKeys();
            //獲取自增長Id
            if(resultSet.next()){
                return resultSet.getInt(1);
            }else{
                return -1;
            }
        }catch (SQLException ex){
            throw  new RuntimeException(ex);
        }finally {
            DatabaseHelper.close(null,statement,connection);
        }
    }
}

我之所以最后在貼出dao實現(xiàn)層,也就是兩個倉儲組件的代碼,就是因為,怎么說呢,他的味道依然很差,但貌似還沒有什么好辦法,因為使用jdbc就是要這么寫,并且可以看到,其中的有效代碼一個方法中也就是一兩行,剩下的大部分都是對jdbc的管理代碼,我們不是應該多關注業(yè)務么?需要關注這些模板代碼么?幸好框架給出了解決方案,具體解決方法下一章在進行實現(xiàn)。

截止到本章的源碼:github(1-9)

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,835評論 6 534
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,676評論 3 419
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,730評論 0 380
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,118評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,873評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,266評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,330評論 3 443
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,482評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,036評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,846評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,025評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,575評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,279評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,684評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,953評論 1 289
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,751評論 3 394
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,016評論 2 375

推薦閱讀更多精彩內容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,670評論 25 708
  • 文/伊若 我們一直在思索,新手如何成為大師。古語云,熟能生巧。那是要不斷地練習嗎?那單純的練習真的可以鑄就大師嗎?...
    伊若閱讀 886評論 0 10
  • 昨天的時候,為了配合學校防恐視頻檢查,強制請了一天假。睡覺的時候,總想著第二天能多睡會,便不那么急切。正好室友補作...
    找果凍的小女孩閱讀 272評論 0 0