学习Spring的小伙伴一定对IoC容器或多或少有些了解了,IoC总结为一句话就是为了方便解耦。我们都知道Java中要使用类必须要先new对象,但是在开发中这样做对话会增加程序之间的耦合度,使得程序的维护成本直线上升。
所以有了IoC容器我们可以将对象之间的依赖关系交由Spring进行控制,避免硬编码所造成的过渡程序耦和。用户也不必再为单例模式类、属性文件解析等这些很底层的需求编写代码,可以更专注于上层的应用。
在com.user.factory下我们可以创建一个BeanFactory的类(com.user.dao和com.user.service中的接口及其实现类这边省略不谈)。Bean在计算机英语中,有可重用组件的含义。而JavaBean就用java语言编写的可重用组件。实际上javabean ≠ 实体类,很初学者认为javabean就是实体类,但是实际上是javabean > 实体类。在我们的Spring项目中,它就是创建我们的service和dao对象的。在Spring的IoC中,我们可以通过配置文件来进行对象之间依赖的管理,配置文件可以是xml也可以是properties,这边我选择的是properties。所以对于解耦的思路是:
- 需要一个配置文件来配置我们的service和dao,配置的内容:唯一标识=全限定类名(key=value)
- 通过读取配置文件中配置的内容,反射创建对象
public class BeanFactory {
//定义一个Properties对象
private static Properties props;
//使用静态代码块为Properties对象赋值
static {
try {
//实例化对象
props = new Properties();
//获取properties文件的流对象
InputStream in = BeanFactory.class.getClassLoader().getResourceAsStream("bean.properties");
props.load(in);
}
}catch(Exception e){
throw new ExceptionInInitializerError("初始化properties失败!");
}
}
/**
* 根据Bean的名称获取bean对象
* @param beanName
* @return
*/
public static Object getBean(String beanName){
Object bean = null;
try {
String beanPath = props.getProperty(beanName);
bean = Class.forName(beanPath).newInstance(); //用反射的方式创建对象,每次都会调用默认构造函数创建对象
}catch(Exception e){
e.printStackTrace();
}
return bean;
}
}
下面展示的是bean.properties中的内容,该文件存放在resources文件夹中,有唯一标志符和全限定类名组成。
accountService = com.itheima.service.impl.AccountServiceImpl
acoountDao = com.itheima.dao.impl.AccountDaoImpl
这样做的话就可以减少程序之间的耦合(只能减少,不可能避免,new有的地方是必须的)。可以通过private IAccountDao accountDao = (IAccountDao) BeanFactory.getBean("acoountDao");
来创建对象,避免使用private IAccountDao accountDao = new AccountDaoImpl();
,即通过new来创建对象,从而减少了程序之间的耦合度。
但是这样做存在的问题是bean = Class.forName(beanPath).newInstance();
通过反射机制实例化对象时,每次都会调用默认构造函数创建对象,即每次访问时都会创建一个对象,这样就变成了多例对象,这并不是我们想要的。直观的,我们通过在control层调用:
for(int i=0;i<5;i++){
IAccountService as = (IAccountService) BeanFactory.getBean("accountService");
System.out.println(as);
}
打印出来控制台的结果为,可以看到这事多例对象:
com.user.service.impl.AccountServiceImpl@610455d6
com.user.service.impl.AccountServiceImpl@511d50c0
com.user.service.impl.AccountServiceImpl@60e53b93
com.user.service.impl.AccountServiceImpl@5e2de80c
com.user.service.impl.AccountServiceImpl@1d44bcfa
简要比较下单例和多例对象:
- 多例对象:对象被创建多次,执行效率没有单例对象高,不存在线程问题
- 单例对象:只被创建一次,从而类中的成员也就只会初始化一次。但是存在线程问题
而我们在工程中想要的是单例对象。解决的思路就是创建一个容器来保存我们初识创建的对象,在之后的过程中每次使用到这个对象时可以直接到容器中去取,所用到的容器就是Map。
下面展示修改过后的代码:
public class BeanFactory {
//定义一个Properties对象
private static Properties props;
//定义一个Map,用于存放我们要创建的对象。我们把它称为容器
//这样就能保证对象只被创建了一次,就是单例的了
private static Map<String,Object> beans;
//使用静态代码块为Properties对象赋值
static {
try {
//实例化对象
props = new Properties();
//获取properties文件的流对象
InputStream in = BeanFactory.class.getClassLoader().getResourceAsStream("bean.properties");
props.load(in);
//实例化容器
beans = new HashMap<String, Object>();
//取出配置文件中所有的key
Enumeration keys = props.keys();
//遍历枚举
while(keys.hasMoreElements()){
//取出每个key
String key = keys.nextElement().toString();
//根据key获取value
String beanPath = props.getProperty(key);
//反射创建对象
Object value = Class.forName(beanPath).newInstance();
//把key和value存入容器之中
beans.put(key,value);
}
}catch(Exception e){
throw new ExceptionInInitializerError("初始化properties失败!");
}
}
/**
* 根据bean的名称获取对象
* @param beanName
* @return
*/
public static Object getBean(String beanName){
return beans.get(beanName);
}
}
这样,我们就构造出来单例对象,控制台打印信息如下所示:
com.user.service.impl.AccountServiceImpl@610455d6
com.user.service.impl.AccountServiceImpl@610455d6
com.user.service.impl.AccountServiceImpl@610455d6
com.user.service.impl.AccountServiceImpl@610455d6
com.user.service.impl.AccountServiceImpl@610455d6
补充:
- servlet是单例的
- 我们在service和dao的开发中,一般不定义成员变量,而是将变量定义到成员方法中对其进行操控修改