目录
三层架构
三层架构是一种常见的软件架构模式,它将应用程序划分为请求处理、响应数据层、业务逻辑层和数据访问层。每层之间通过接口进行通信,实现了高内聚、低耦合的设计原则。
- 数据访问:负责业务数据的维护操作,包括增、删、改、查等操作
- 逻辑处理:负责业务逻辑处理的代码
- 请求处理、响应数据:负责,接收页面的请求,给页面响应数据
在传统的开发中,通常会将三层架构的代码都写着一个方法里
而为了让类、接口、方法的复杂度更低,可读性更强,扩展性更好,也更利用后期的维护,我们应该尽可能让一个类或一个方法,就只做一件事情,只管一块功能。
对上面代码进行拆分,可以得到下面三个类
虽然这样使代码的复用性,便于维护,利于拓展,还需要考虑一个软件的设计原则,那就是高内聚, 低耦合问题。也就是下面要讲的分层耦合。
分层解偶
分层解耦是软件开发中的重要思想,它通过将不同功能模块进行分离(上面代码已经完成),降低了系统各部分的依赖性,提高了系统的可维护性和可扩展性。在Spring框架中,IOC和DI是实现分层解耦的关键技术。
代码中可以看到EmpController依赖于EmpService,而EmpService又依赖于EmpDao,可以思考一下,当需要修改业务逻辑代码,将EmpService改为EmpServiceA,这是我们同样需要修改controller层的代码为:
//private EmpService empService = new EmpService改为
private EmpServiceA empService = new EmpServiceA
看到这里应该就明白了耦合的关系,也就是底层代码更改,上层代码也要跟着修改,而上面代码的关系就如下图:
而分层解耦就是要把耦合关系解除这个问题
用上面代码private EmpServiceA empService = new EmpServiceA来举例
我们把他分为EmpServiceA empService和new EmpServiceA右两个部分去理解
首先如何让EmpServiceA empService这部分进行解藕?
设想一下我们常用的支付功能,需要用不同的平台如微信,支付宝等去支付,那我们是不是就需要微信和支付宝两个实现类,不难看出多态就能解决这个问题
建立一个UserService接口,把业务层代码分别实现接口,这样就解决了EmpServiceA empService的解藕,(与下面截图命名有些许出入,但原理是一样的)
同时思考下,如果我们自己不手动的去new对象,是不是new EmpServiceA这部分的耦合也就解决掉了,这时候就有个疑问,controller层的userService没有初始化new对象不就空指针了吗,这个问题不用我们考虑,下面要讲的Spring框架的IOC容器会帮我们解决这个问题
IOC & DI入门
首先了解什么是IOC,什么是DI
Inversion Of Control,简称IOC:对象的创建控制权由程序自身转移到外部(容器),这种思想称为控制反转。在Spring中,这个容器就是Spring IoC容器。
Dependency Injection,简称DI:容器为应用程序提供运行时所依赖的资源,称之为依赖注入
把需要创建的对象UserServcieB交给IOC容器管理,这个过程叫做控制反转
IOC容器向userService变量提供依赖的对象,这个过程叫做依赖注入
这是又有个疑问,如果A和B都在IOC容器里,怎么保证userService可以取到我们想要的对象,这个问题先放一放,看完下面Bean的声明和注入就知道了
注:在IOC容器中的对象统一称为Bean
理解了IOC与DI的思想,下面来实现使用IOC & DI完成业务解耦
- 使用Spring提供的注解:@Component ,就可以实现类交给IOC容器管理
- 使用Spring提供的注解:@Autowired ,就可以实现程序运行时IOC容器自动注入需要的依赖对象
通过这个例子应该对IOC和DI都有一定了解了,下面讨论一下两者的细节
Bean的声明
要把某个对象交给IOC容器管理,需要把对应的类加上如下注解之一, 衍生注解声明可以更好的区分bean是属于那一层的
注解 | 说明 | 位置 |
@Component | 声明bean的基础注解 | 不属于下面三类时,用此注解,如工具类 |
@Controller | @Component的衍生注解 | 标注在控制器类上 |
@Service | @Component的衍生注解 | 标注在业务类上 |
@Repository | @Component的衍生注解 | 标注在数据访问类上(由于与mybatis整合,用的少,会被@Mapper注解替代) |
注:
- 声明bean的时候,可一通过value属性指定bean的名字,如果没有指定,默认类名首字母小写
- 使用以上四个注解都可以声明bean,但是在springboot集成web开发中,声明控制器bean只能用@Controller。
Bean组件扫描
前面声明Bean的四大注解,想要生效,还需要被组件扫描注解@ComponentScan扫描到
@ComponentScan注解虽然没有显式配置,但实际上已经包含在了引导类注解@SpringBootApplication中,默认扫描的范围是引导类所在包及其子包
底层源码中可以看到@ComponentScan注解包含在@SpringBootApplication中
试着把dao包移出来,与@SpringBootApplication所在的包平级,会报错找不到bean
想要扫描到dao包,可以手动添加@ComponentScan注解指定要扫描的包 ,一旦使用注解,默认扫描就会被覆盖,需要把所有要扫描的包都写上(不推荐)
推荐按照springboot项目的规范,将所写的代码全部放在引导类所在包及其子包下,使spring启动时自动的扫描到这些bean对象,这样可以少很多不必要的配置
Bean的注入
在入门程序案例中,我们使用了@Autowired这个注解,完成了依赖注入的操作,而这个Autowired翻译过来叫:自动装配。
@Autowired注解,默认是按照类型进行自动装配的,要去IOC容器中找某个类型的对象,然后完成注入操作
而上面提到的如果在IOC容器中,存在多个相同类型的bean对象,会出现什么情况呢?
运行之后也给出了解决档案,下面通过这几种方案来解决:
@Primary
@Qualifier
@Resouce
使用@Primary注解:Primary中文为优先的,从翻译来看不能理解,当存在多个相同类型的Bean注入时,加上@Primary注解优先使用该bean注入
使用@Qualifier注解:指定当前要注入的bean对象。
在@Qualifier的value属性中,指定注入的bean的名称。如下指定注入名字为empServiceA的bean对象
@Qualifier注解不能单独使用,必须配合@Autowired使用
使用@Resource注解:不需要@Autowired,直接按bean的名称进行注入。通过name属性指定要注入的bean的名称。
如下指定注入名字为empServiceB的bean对象
@Autowird 与 @Resource的区别
- @Autowired 是spring框架提供的注解,而@Resource是JDK提供的注解
- @Autowired 默认是按照类型注入,而@Resource是按照名称注入