Spring中的解耦合是如何实现的?
-
首先我们来准备一个最为简单的项目,采取分层的结构【注】此处的客户端调用通过Junit单元测试来模拟。
以下就是这一次项目的初始的所有代码:
【注】初始代码仅仅作为展示,可以直接从问题开始看!
结构为:- User类
/** * 定义一个实体类 * @version 1.0.0 * @date 2020 -08-20 */ @Data @AllArgsConstructor @NoArgsConstructor public class User { /** * 用户名 */ private String username; /** * 密码 */ private String password; }
- DAO层
/** * 用户类 DAO层 * @version 1.0.0 * @date 2020 -08-20 */ public interface UserDAO { /** * 保存【模拟】 * @param user 用户对象 */ void save(User user); /** * 查询【模拟】 * @param username 用户名 * @param password 密码 */ void query(String username,String password); } /*------------------------------------------------------------------------*/ /** * User DAO层实现类 * @version 1.0.0 * @date 2020 -08-20 */ public class UserDAOImpl implements UserDAO{ @Override public void save(User user) { System.out.println("user save method --> user : " + user); } @Override public void query(String username, String password) { System.out.println("user query method --> username : " + username + " password : " + password); } }
- Service层
/** * User 业务层 * @version 1.0.0 * @date 2020 -08-20 */ public interface UserService { /** * 保存【模拟】 * @param user 用户对象 */ void save(User user); /** * 查询【模拟】 * @param username 用户名 * @param password 密码 */ void query(String username,String password); } /*------------------------------------------------------------------------*/ /** * User 业务层实现类 * @version 1.0.0 * @date 2020 -08-20 */ public class UserServiceImpl implements UserService{ private UserDAO userDAO = new UserDAOImpl(); @Override public void save(User user) { userDAO.save(user); } @Override public void query(String username, String password) { userDAO.query(username, password); } }
-
第一次通过客户端的调用【此处通过Junit模拟】
@Test public void test(){ UserService userService = new UserServiceImpl(); User user = new User("demo-1","pwd1"); userService.save(user); userService.query("demo-2","pwd2"); }
得到的结果肯定是和预期一致的:
user save method --> user : User(username=demo-1, password=pwd1) user query method --> username : demo-2 password : pwd2
问题就出现了
在这个Junit测试的方法之中,我们可以看到当我们想要操作User的时候会有一段硬编码存在,当我们想要替换实现类(例如 UserServiceImpl2 )的时候,必须要修改原来的代码。这个是我们所不希望的,于是工厂模式就出来了。
要知道,对于工厂模式来说。它就是用来创建对象的!恰好,我们这里使用到的也是需要去创建对象的。
于是我们可以加一个工厂用来创建UserService接口实现类的对象了!
/**
* 简单工厂为例
* @version 1.0.0
* @date 2020 -08-20
*/
public class BeanFactory {
public static UserService getUserService(){
return new UserServiceImpl();
}
}
自然,我们的客户端就不需要硬编码了:
@Test
public void test(){
/*----------------------change begin-----------------------------*/
UserService userService = BeanFactory.getUserService();
/*-----------------------change end------------------------------*/
User user = new User("demo-1","pwd1");
userService.save(user);
userService.query("demo-2","pwd2");
}
输出自然和上方的输出一样。
PS : 在这里我们实现了客户端的解耦合,当我们替换UserService的实现类的时候,对于客户端调用来说,他们是不需要管的。
耦合依旧存在,仅仅是从客户端转移到了BeanFactory类之中了
于是,我们再次思考,获得对象的方式除了new之外,还可以怎么获得呢???
答案是有的 —— 通过反射!于是,我们再次改造一次我们的代码
public static UserService getUserService(){
/*----------------------change begin-----------------------------*/
UserService userService = null;
try {
Class c = Class.forName("com.demo.service.UserServiceImpl");
userService = (UserService) c.getDeclaredConstructor().newInstance();
} catch (ClassNotFoundException | NoSuchMethodException
| InstantiationException | IllegalAccessException
| InvocationTargetException e) {
e.printStackTrace();
}
return userService;
/*-----------------------change end------------------------------*/
}
输出的结果还是一样的,依旧是我们预期的输出!
再次优化
虽然我们通过反射获得了,但是当我们更换实现类的时候,可以发现还是需要去修改代码。所以,还需要继续想方法。在Spring之中有很多配置文件,我们是不是可以也通过读取配置文件的方式呢???
那我们开始把!来一个bean.properties
配置文件
service = com.demo.service.UserServiceImpl
于是,我们给BeanFactory来一个改头换面
public class BeanFactory {
// 获取配置文件
private static Properties properties = new Properties();
// 配置文件内容跟着类加载的时候初始化,并且只需要初始化一次
static {
try {
InputStream inputStream =
BeanFactory.class.getResourceAsStream("/bean.properties");
// 加载进去
properties.load(inputStream);
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
public static UserService getUserService(){
UserService userService = null;
try {
Class c = Class.forName(properties.getProperty("service"));
userService = (UserService) c.getDeclaredConstructor().newInstance();
} catch (ClassNotFoundException | NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException e) {
e.printStackTrace();
}
return userService;
}
}
输出结果和我们预期的结果肯定还是一样的!!!
再次优化,抽出一个通用的方法
既然这个可以解决UserService接口实现类创建的问题,那么在UserService接口实现类之中new出阿里的UserDAO的实现类肯定也是可以如法炮制的!
public static UserDAO getUserDAO(){
UserService userService = null;
try {
Class c = Class.forName(properties.getProperty("dao"));
userService = (UserService) c.getDeclaredConstructor().newInstance();
} catch (ClassNotFoundException | NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException e) {
e.printStackTrace();
}
return userService;
}
这里,如果今后还有PersonDAO,StudentDAO,PersonService,StudentService等等,就会产生很多冗余的代码。
仔细观察,创建对象的方法之中,仅仅变化的就是属性之中的那个key值而已!
于是,我们可以抽取出来一个通用的方法!
public Object getBean(String key){
Object object = null;
try {
Class c = Class.forName(properties.getProperty(key));
object = c.getDeclaredConstructor().newInstance();
} catch (ClassNotFoundException | NoSuchMethodException
| InstantiationException | IllegalAccessException
| InvocationTargetException e) {
e.printStackTrace();
}
return object;
}
结束
这个getBean方法是不是很熟悉呢!!!!