我们在 JavaWeb 中使用三层架构开发的时候往往有很多耦合的地方,比如下面这个场景。
public class UserServiceImpl implements UserService {
private UserDaoImpl userDao = new UserDaoImpl();
@Override
public void addUser() {
userDao.addUser();
}
@Override
public void deleteUser() {
userDao.deleteUser();
}
}
在 Service 中调用 Dao 层,必须要 new 一个 Dao 对象,在 Controller 中也是如此调用 Service 层。
我们可以创建一个工厂类来专门用来创建对象:
public class BeanFactory {
public static UserService getUserService() {
return new UserServiceImpl();
}
public static UserDao getUserDao() {
return new UserDaoImpl();
}
...
}
于是我们调用的时候就可以直接使用类名调用了。
public class UserServiceImpl implements UserService {
private UserDaoImpl userDao = BeanFactory.getUserDao();
@Override
public void addUser() {
userDao.addUser();
}
@Override
public void deleteUser() {
userDao.deleteUser();
}
}
但是还有新的问题,并没有根本的解决耦合问题,因为只是将耦合的地方换了一个位置,在 BeanFactory 中仍然有耦合出现,还是使用到了 new 关键字,所以我们要继续解决这个问题。
我们知道在 java 中创建对象有两种方式,分别是使用 new 关键字和反射,所以我们可以使用反射解决这个问题。
public static UserDao getUserDao() {
UserDao userDao = null;
try {
Class clazz = Class.forName("edu.lsu.dao.impl.UserDaoImpl");
userDao = (UserDao) clazz.newInstance();
} catch (Exception e) {
e.printStackTrace();
}
return userDao;
}
这里使用了 Class.forName("edu.lsu.dao.impl.UserDaoImpl")
去加载类,然后调用它的 newInstance
方法创建对象。
但是如果我们还需要一个方法来获取 UserServiceImpl 类,我们是否还要写一个同样的方法呢?
public static UserService getUserService() {
UserService userService = null;
try {
Class<?> clazz = Class.forName("edu.lsu.service.impl.UserServiceImpl");
userService = (UserService) clazz.newInstance();
} catch (Exception e) {
e.printStackTrace();
}
return userService;
}
其实没必要,我们发现这两个方法有很大的相似之处,变化的地方仅仅是 Class.forName
中的参数,也就是说仅仅是一个字符串。
所以我们可以使用外部配置文件的方式将这些变化的参数单独拎出来,就像下面这样。
# Properties 集合存储该文件的内容
# 他是一个特殊的 Map: key 和 value 都是字符串类型的
userService=edu.lsu.service.impl.UserServiceImpl
这样我们只需要在 java
代码中读取配置文件,获取我们想要的信息,所以之前的代码就可以修改为下面的形式。
因为涉及 IO,IO 操作一般都是比较耗时的操作,为了避免重复访问,我们将这一操作放到静态代码块中,这样一来就可以保证在类加载的时候就读取文件,而且只读取一次。
public class BeanFactory {
private static Properties env = new Properties();
// 使用静态代码块一次性的读取资源,避免重复访问
static {
// 1.获取 IO 流
InputStream inputStream = BeanFactory.class.getResourceAsStream("/applicationContext.properties");
try {
// 2.将配置文件封装到 Properties 中
env.load(inputStream);
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
public static UserService getUserService() {
UserService userService = null;
try {
Class<?> clazz = Class.forName(env.getProperty("userService"));
userService = (UserService) clazz.newInstance();
} catch (Exception e) {
e.printStackTrace();
}
return userService;
}
}
当然这里目前仅仅有一个 getUserService
方法,为了做到通用,我们可以定义一个需要用户自己传参的方法。
/**
* 通用方法
*
* @param key 键值
* @return 返回obj
*/
public static Object getBean(String key) {
Object obj = null;
try {
Class<?> clazz = Class.forName(env.getProperty(key));
obj = clazz.newInstance();
} catch (Exception e) {
e.printStackTrace();
}
return obj;
}
这样的话用户在调用的时候就只需要传入键值。
public static void main(String[] args) {
UserService userService = (UserService) BeanFactory.getBean("userService");
userService.addUser();
}
没错,这正是 Spring 容器的基本原理。