3.1 设计原则与IOC、DI

面向对象的程序设计中有六大基本原则:

  • 单一职责原则——SRP

    一个对象应该只包含单一的职责,并且该职责被完整地封装在一个类中。

  • 开闭原则——OCP

    软件实体应当对扩展开放,对修改关闭。

  • 里式替换原则——LSP

    所有引用基类的地方必须能透明地使用其子类的对象。

  • 依赖倒置原则——DIP

    高层模块不应该依赖底层模块,它们都应该依赖抽象。抽象不应该依赖于细节,细节应该依赖于抽象。

  • 接口隔离原则——ISP

    客户端不应该依赖那些它不需要的接口。

  • 迪米特原则——LOD

    一个对象应该对其他对象有最少的了解

  • 合成复用原则——CARP

    优先使用对象组合,而不是继承来达到复用的目的。

Spring的IOC(控制反转)就是遵循了依赖倒置原则的设计思想。

2004年,Martin Fowler探讨了同一个问题,既然IOC是控制反转,那么到底是“哪些方面的控制被反转了呢?”,经过详细地分析和论证后,他得出了答案:“获得依赖对象的过程被反转了”。控制被反转之后,获得依赖对象的过程由自身管理变为了由IOC容器主动注入。于是,他给“控制反转”取了一个更合适的名字叫做“依赖注入(Dependency Injection)”。他的这个答案,实际上给出了实现IOC的方法:注入。所谓依赖注入,就是由IOC容器在运行期间,动态地将某种依赖关系注入到对象之中。

所以,依赖注入(DI)和控制反转(IOC)是从不同的角度的描述的同一件事情,就是指通过引入IOC容器,利用依赖关系注入的方式,实现对象之间的解耦。顺带一提,除了依赖注入以外,函数回调也是IOC的一种实现。

什么是高层模块和底层模块呢?不可分割的原子逻辑就是底层模块,原子逻辑的再组装就是高层模块。

在Java语言中,抽象就是指接口或抽象类,两者都不能被实例化;而细节就是实现接口或继承抽象类产生的类,也就是可以被实例化的实现类。依赖倒置原则是指模块间的依赖是通过抽象来发生的,实现类之间不发生直接的依赖关系,其依赖关系是通过接口是来实现的,这就是俗称的面向接口编程。

3.2 以前的对象依赖

我们来看一下我们以前关于对象依赖,是怎么的历程

3.2.1 直接new对象

在最开始,我们是直接new对象给serice的userDao属性赋值...

class  UserService{
UserDao userDao = new UserDao();
}

3.2.2 写DaoFactory,用字符串来维护依赖关系

后来,我们发现service层紧紧耦合了dao层。我们就写了DaoFactory,在service层只要通过字符串就能够创建对应的dao层的对象了。

DaoFactory

public class DaoFactory {

private static final DaoFactory factory = new DaoFactory();
private DaoFactory(){}

public static DaoFactory getInstance(){
return factory;
}

public <T> T createDao(String className,Class<T> clazz){
try{
T t = (T) Class.forName(className).newInstance();
return t;
}catch (Exception e) {
throw new RuntimeException(e);
}
}

}

serivce

private CategoryDao categoryDao = DaoFactory.getInstance().createDao("zhongfucheng.dao.impl.CategoryDAOImpl", CategoryDao.class);

private BookDao bookDao = DaoFactory.getInstance().createDao("zhongfucheng.dao.impl.BookDaoImpl", BookDao.class);

private UserDao userDao = DaoFactory.getInstance().createDao("zhongfucheng.dao.impl.UserDaoImpl", UserDao.class);

private OrderDao orderDao = DaoFactory.getInstance().createDao("zhongfucheng.dao.impl.OrderDaoImpl", OrderDao.class);

3.2.3 DaoFactory读取配置文件

再后来,我们发现要修改Dao的实现类,还是得修改service层的源代码呀..于是我们就在DaoFactory中读取关于daoImpl的配置文件,根据配置文件来创建对象,这样一来,创建的是哪个daoImpl对service层就是透明的

DaoFactory

public class DaoFactory {

private UserDao userdao = null;

private DaoFactory(){
try{
InputStream in = DaoFactory.class.getClassLoader().getResourceAsStream("dao.properties");
Properties prop = new Properties();
prop.load(in);

String daoClassName = prop.getProperty("userdao");
userdao = (UserDao)Class.forName(daoClassName).newInstance();

}catch (Exception e) {
throw new RuntimeException(e);
}
}

private static final DaoFactory instance = new DaoFactory();

public static DaoFactory getInstance(){
return instance;
}


public UserDao createUserDao(){
return userdao;
}

}

service

UserDao dao = DaoFactory.getInstance().createUserDao();

3.3 Spring的依赖注入

通过上面的历程,我们可以清晰地发现:对象之间的依赖关系,其实就是给对象上的属性赋值!因为对象上有其他对象的变量,因此存在了依赖...

Spring提供了好几种的方式来给属性赋值

  • 1) 通过构造函数
  • 2) 通过set方法给属性注入值
    1. p名称空间
  • 4)自动装配(了解)
  • 5) 注解

3.3.1 搭建测试环境

UserService中使用userDao变量来维护与Dao层之间的依赖关系,UserAction中使用userService变量来维护与Service层之间的依赖关系。

userDao

public class UserDao {

public void save() {
System.out.println("DB:保存用户");
}
}

userService

public class UserService {

private UserDao userDao;

public void save() {
userDao.save();
}
}

userAnction

public class UserAction {

private UserService userService;

public String execute() {
userService.save();
return null;
}
}

3.3.2 构造函数给属性赋值

其实我们在讲解创建带参数的构造函数的时候已经讲过了...我们还是来回顾一下呗..

我们测试service和dao的依赖关系就好了....在serice中加入一个构造函数,参数就是userDao

public UserService(UserDao userDao) {
this.userDao = userDao;

//看看有没有拿到userDao
System.out.println(userDao);
}

applicationContext.xml配置文件

<!--创建userDao对象-->
<bean id="userDao" class="UserDao"/>

<!--创建userService对象-->
<bean id="userService" class="UserService">
<!--要想在userService层中能够引用到userDao,就必须先创建userDao对象-->
<constructor-arg index="0" name="userDao" type="UserDao" ref="userDao"></constructor-arg>
</bean>

测试:可以成功获取到userDao对象

// 创建容器对象
ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");

//得到service对象
UserService userService = (UserService) ac.getBean("userService");

3.3.3 通过set方法给属性注入值

我们这里也是测试service和dao层的依赖关系就好了...在service层通过set方法来把userDao注入到UserService中为UserService添加set方法

public class UserService {

private UserDao userDao;


public void setUserDao(UserDao userDao) {
this.userDao = userDao;

//看看有没有拿到userDao
System.out.println(userDao);
}

public void save() {
userDao.save();
}
}

applicationContext.xml配置文件:通过property节点来给属性赋值

  • 引用类型使用ref属性
  • 基本类型使用value属性
<!--创建userDao对象-->
<bean id="userDao" class="UserDao"/>

<!--创建userService对象-->
<bean id="userService" class="UserService">
<property name="userDao" ref="userDao"/>
</bean>

3.3.4 内部Bean

我们刚才是先创建userDao对象,再由userService对userDao对象进行引用...我们还有另一种思维:先创建userService,发现userService需要userDao的属性,再创建userDao...我们来看看这种思维方式是怎么配置的:

applicationContext.xml配置文件:property节点内置bean节点

<!--
1.创建userService,看到有userDao这个属性
2.而userDao这个属性又是一个对象
3.在property属性下又内置了一个bean
4.创建userDao
-->
<bean id="userService" class="UserService">
<property name="userDao">
<bean id="userDao" class="UserDao"/>
</property>
</bean>

我们发现这种思维方式和服务器访问的执行顺序是一样的,但是如果userDao要多次被其他service使用的话,就要多次配置了...

3.3.5 p名称空间注入属性值

p名称控件这种方式其实就是set方法的一种优化,优化了配置而已...p名称空间这个内容需要在Spring3版本以上才能使用...我们来看看:

applicationContext.xml配置文件:使用p名称空间

<bean id="userDao" class="UserDao"/>

<!--不用写property节点了,直接使用p名称空间-->
<bean id="userService" class="UserService" p:userDao-ref="userDao"/>

3.3.6 自动装配

Spring还提供了自动装配的功能,能够非常简化我们的配置

自动装载默认是不打开的,自动装配常用的可分为两种:

  • 根据名字来装配
  • 根据类型类装配
3.3.6.1XML配置根据名字

applicationContext.xml配置文件:使用自动装配,根据名字

<bean id="userDao" class="UserDao"/>

<!--
1.通过名字来自动装配
2.发现userService中有个叫userDao的属性
3.看看IOC容器中没有叫userDao的对象
4.如果有,就装配进去
-->
<bean id="userService" class="UserService" autowire="byName"/>

3.3.6.2 XML配置根据类型

applicationContext.xml配置文件:使用自动装配,根据类型

值得注意的是:如果使用了根据类型来自动装配,那么在IOC容器中只能有一个这样的类型,否则就会报错!

<bean id="userDao" class="UserDao"/>

<!--
1.通过名字来自动装配
2.发现userService中有个叫userDao的属性
3.看看IOC容器UserDao类型的对象
4.如果有,就装配进去
-->
<bean id="userService" class="UserService" autowire="byType"/>

3.3.7 使用注解来实现自动装配

@Autowired注解来实现自动装配

  • 可以在构造器上修饰
  • 也可以在setter方法上修饰
  • 来自java的@Inject的和@AutoWired有相同的功能

如果没有匹配到bean,又为了避免异常的出现,我们可以使用required属性上设置为false。【谨慎对待】

测试代码

@Component
public class UserService {

private UserDao userDao ;


@Autowired
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
}

顺利拿到userDao的引用