如果你想開發一個應用(1-9)
上一章的結尾,我們看到現有的代碼雖然經過了一些改進,但仍然有很多壞味道,首當其沖的就是Controller太厚了,Controller應該僅僅作為一個控制器使用,要盡可能的薄。這時候,上一章裏提到過的IOC和DI華麗登場了.
控制反轉
控制反轉簡單說就一句話,就是把程序資源的管理權由互相使用的雙方的代碼反轉到第三方容器。即一般來說,對象的創建和銷毀,使用都由用戶有代碼進行直接控制,而現在則由容器(Spring框架)來控制,這樣可以最大限度的減少類的耦合度,比如說無論,一個對象的new是避免不了的,而控制反轉則可將這些也交給容器,即各種類徹底的脫離直接聯系。
提到控制反轉,就不能不提依賴註入,可以說依賴註入就是控制反轉的目的之一,即當一個對象需要另一個對象實力的時候,有容器根據這個實例所依賴的實例而註入進去,這種對象之間互相依賴的情況不在由自己管理。
貌似很晦澀,下面我們依然使用註解的方式,先對一部分代碼實現控制反轉和依賴註入。
用戶服務
從最簡單的地方開始,註意代碼中的一部分:
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));
}
這部分代碼與現有的業務有關系麽?他所需要做的,其實就是查詢用戶,有則返回userId,沒有則創建一個用戶並返回存儲到db中後的自增長userId。接下來就按照面向接口編程的方式一步步來重構這段代碼。
既然是用戶服務,那麽首先創建一個service包,然後在裏邊創建一個UserService
的接口,接口目前只有一個方法,就是getUserByName,代碼如下:
public interface UserService {
User getUserByName(String username);
}
既然有了接口,那麽就會想到接口的實現。在理論上,我們的Controller層並不關心實現方式,只知道使用這個接口定義的功能就可以了,所以,將實現放在service的下級包,即創建包service.impl
,然後在包內創建類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:定義一個業務層的組件
- @Controller:定義一個控制器層的組件
- @Repository:定義一個DB訪問層的組件
- @Component:組件,當組件不好歸類時可以使用此註解
此時bean的默認名為類名首字母小寫,可以使用參數類現實確定bean名,如:
@Service("beanName")
若此接口有多個實現,並且實現均為組件,可以再調用處通過註解@Qualifier來顯式來決定使用哪一個組件,如:
@Autowired
@Qualifier("beanName")
private UserService userService;
用土土的方法運行測試一下,和之前一樣,就不在截圖,然後刪除之前的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();
}
}
代碼是不是清爽了許多,之後,業務上的變更完全可以再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);
}
然後是兩個實現類:
@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);
}
}
啊哈,壞味道又出現了,其實這時候我猜你一定想到了,對將dao層頁一樣的組件化。
倉儲組件
有了現有的經驗,其實是一件再簡單不過的事了,首先將dao包內的兩個類改為接口,並創建實現包,最終兩個接口的代碼如下:
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);
}
然後服務層的代碼就變為下面這樣了:
@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層實現類的代碼(只貼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實現層,也就是兩個倉儲組件的代碼,就是因為,怎麽說呢,他的味道依然很差,但貌似還沒有什麽好辦法,因為使用jdbc就是要這麽寫,並且可以看到,其中的有效代碼一個方法中也就是一兩行,剩下的大部分都是對jdbc的管理代碼,我們不是應該多關註業務麽?需要關註這些模板代碼麽?幸好框架給出了解決方案,具體解決方法下一章在進行實現。
截止到本章的源碼:github(1-9)
如果你想開發一個應用(1-9)