上一次介绍了JavaWeb中请求响应相关的内容,这次来了解一下JavaWeb中分层解耦的思想及其实现,重点在于理解控制反转(IOC)依赖注入(DI)

一、三层架构

1、概述

1.1 controller(接收请求、响应数据)

控制层,接收前端发送的请求,对请求进行处理,并响应数据。

1.2 service(逻辑处理)

业务逻辑层,处理具体的业务逻辑。

1.3 dao(数据访问)

数据访问层(Data Access Object)(持久层),负责数据访问操作,包括数据的增、删、改、查。

1.4 对比

以前的方式与三层架构方式对比。

二、分层解耦

1、 基本概念

1)内聚:软件中各个功能模块内部的功能联系。
2)耦合:衡量软件中各个层/模块之间的依赖、关联的程度。

软件设计原则:高内聚低耦合。

2、问题导入

探讨传统的MVC模式,三层架构代码书写存在的问题。我们先看一个代码示例,再对问题进行剖析。

注:MVC模式是软件工程中常见的一种软件架构模式,该模式把软件系统(项目)分为三个基本部分:模型(Model)、视图(View)和控制器(Controller)。

1)Dao层(dao包下接口和对应实现类)
1.1)BookDao接口(com.itweb.dao.BookDao)

1
2
3
4
5
package com.itweb.dao;

public interface BookDao {
void save(); // 抽象方法
}

1.2)BookDaoImpl实现类(com.itweb.dao.impl.BookDaoImpl)

1
2
3
4
5
6
7
8
9
10
package com.itweb.dao.impl;

import com.itweb.dao.BookDao;

public class BookDaoImpl implements BookDao { // BookDao接口的实现类
@Override
public void save() { // 重写接口中的抽象方法
System.out.println("book dao save...");
}
}

2)Service层(service包下接口和对应实现类)
2.1)BookService接口(com.itweb.service.BookService)

1
2
3
4
5
package com.itweb.service;

public interface BookService {
void save();
}

2.2)BookServiceImpl实现类(com.itweb.service.impl.BookServiceImpl)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package com.itweb.service.impl;

import com.itweb.dao.BookDao;
import com.itweb.dao.impl.BookDaoImpl;
import com.itweb.service.BookService;

public class BookServiceImpl implements BookService {
// 在业务层(Service层)的接口实现类中调用数据层(Dao层)的接口实现类,高耦合的根源。
private BookDao bookDao=new BookDaoImpl(); // 面向接口编程
@Override
public void save() {
bookDao.save(); // 调用BookDaoImpl实现类中的方法
}
}

3)存在问题分析

3.1)在业务层(Service层)的接口实现类中调用数据层(Dao层)的接口实现类时,存在较高耦合性(即数据层实现类如果修改,则在业务层中调用的实现类也要修改,导致需要重新编译、测试、部署…),究其原因,是调用的实现类导致高耦合(凡是new出来的对象的调用都存在一定的耦合性)

3.2)解决办法

思路1:将new出来的实现类去掉,直接写接口,即将 private BookDao bookDao=new BookDaoImpl(); 改为 private BookDao bookDao; 但这样也是存在问题的,对象从哪里来呢?(IOC–>解耦)

思路2:在BookServiceImpl实现类中,业务层实现依赖 dao对象运行(因为在Service层中调用了dao对象的save方法),那么这种依赖关系该如何绑定呢?(依赖注入–>绑定)

综上,由此引出了两个重要话题:IOC(控制反转)和 DI(依赖注入)

3、IOC & DI概述

3.1 控制反转(IOC)

Inversion Of Control,简称IOC。对象的创建控制权由程序自身转移到外部(容器),这种思想称为控制反转。

Spring对 IOC思想进行了实现:Spring提供了一个容器,称为IOC容器(即Spring架构中的 Core Container),用来充当IOC思想中的外部。IOC容器负责对象的创建、初始化等一系列工作,被创建或被管理的对象在IOC容器中统称为 Bean(Spring中对象都称为Bean)

3.2 依赖注入(DI)

Dependency Injection,简称DI。容器为应用程序提供运行时所依赖的资源(即在容器中建立bean与bean之间的依赖关系的整个过程)称为依赖注入。

3.3 Bean对象

IOC容器中创建、管理的对象,称为bean。

3.4 小结:(Spring实现充分解耦过程)

  • 1.使用 IOC容器管理 bean对象 (IOC)
  • 2.在IOC容器内将有依赖关系的 bean进行关系绑定 (DI)
  • 3.最终效果
    • 使用对象时不仅可以直接从IOC容器中获取,并且获取到的bean已经绑定了所有的依赖关系

4、IOC & DI入门程序

同样以我们问题导入那个案例作为入门程序,在原基础上做一些修改即可。具体为:

1、在对应的实现类上方添加 @Component注解实现控制反转
2、在删掉new对象的上方添加 @Autowired注解实现依赖注入

1)Dao层(dao包下接口和对应实现类)
1.1)BookDao接口(com.itweb.dao.BookDao)

1
2
3
4
5
package com.itweb.dao;

public interface BookDao {
void save(); // 抽象方法
}

1.2)BookDaoImpl实现类(com.itweb.dao.impl.BookDaoImpl)

1
2
3
4
5
6
7
8
9
10
11
12
package com.itweb.dao.impl;

import com.itweb.dao.BookDao;
import org.springframework.stereotype.Component;

@Component //将当前类交给IOC容器管理,成为IOC容器中的bean
public class BookDaoImpl implements BookDao { // BookDao接口的实现类
@Override
public void save() { // 重写接口中的抽象方法
System.out.println("book dao save...");
}
}

2)Service层(service包下接口和对应实现类)
2.1)BookService接口(com.itweb.service.BookService)

1
2
3
4
5
package com.itweb.service;

public interface BookService {
void save();
}

2.2)BookServiceImpl实现类(com.itweb.service.impl.BookServiceImpl)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package com.itweb.service.impl;

import com.itweb.dao.BookDao;
import com.itweb.dao.impl.BookDaoImpl;
import com.itweb.service.BookService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component //1.将当前类交给IOC容器管理,成为IOC容器中的bean
public class BookServiceImpl implements BookService {
// 在业务层(Service层)的接口实现类中调用数据层(Dao层)的接口实现类,高耦合的根源。
// private BookDao bookDao=new BookDaoImpl();

@Autowired // 2.运行时,IOC容器会提供该类型的bean对象,并赋值给该变量(依赖注入)
private BookDao bookDao; // 删除业务层中使用new方式创建的dao对象
@Override
public void save() {
System.out.println("book service save...");
bookDao.save(); // 调用BookDaoImpl实现类中的方法
}
}

5、IOC & DI详解

5.1 Bean的声明

要把某个对象交给IOC容器管理,需要在对应的类上加上如下注解之一

注:Controller(请求处理、响应数据),Service(逻辑处理),Dao(数据访问)

声明bean的时候,可以通过value属性指定bean的名字,如果没有指定,默认为类名首字母小写。

使用以上四个注解都可以声明bean,但是在springboot集成web开发中,声明控制器bean只能用@Controller。

5.2 Bean组件扫描

前面声明bean的四大注解,要想生效,还需要被组件扫描注解@ComponentScan扫描。

注:@ComponentScan注解虽然没有显式配置,但是实际上已经包含在了启动类声明注解@SpringBootApplication中,默认扫描的范围是启动类所在包及其子包。

@SpringBootApplication具有包扫描作用,默认扫描当前包及其子包

5.3 Bean注入

@Autowired注解,即自动装配,默认是按照类型进行,如果存在多个相同类型的bean,将会报出如下错误

通过以下几种方案来解决
1)**@Primary**
见名知意,该注解的作用即设置bean的优先级

1
2
3
4
@Primary
@Service
public class EmpServiceA implements EmpService {
}

2)**@Qualifier**
配合@Autowired一起使用

1
2
3
4
5
@RestController
public class EmpController {
@Autowired
@Qualifier("empServiceA")
private EmpService empService ;

3)**@Resource**
指定名称,按名称注入

1
2
3
4
@RestController
public class EmpController {
@Resource(name= "empServiceB")
private EmpService empService ;

4)小结

  • 1.依赖注入的注解

    • @Autowired: 默认按照类型自动装配。
    • 如果同类型的bean存在多个:
      • @Primary
      • @Autowired +@Qualifier(“bean的名称”)
      • @Resource(name=”bean的名称”)
  • 2.@Resource 与@Autowired区别(重点–面试题)

    • @Autowired 是spring框架提供的注解而@Resource是JDK提供的注解。
    • @Autowired默认是按照类型注入,而 @Resource默认是按照名称注入。