上一章的結尾,我們看到現(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)