引入
在上篇所举的例子中,我们将所有的代码均放在HelloControl方法之中,这样会导致代码的复用性、可读性较差,难以维护。因此我们需
三层架构
在之前的代码中,代码大体可以分为三部分:数据访问、数据逻辑处理、响应数据。每部分承担不同的职责。三层架构的设计目标是实现一个类或一个方法只实现某单一功能,即单一职责原则。这样即可提高系统的可维护性和可扩展性。
以下是三层架构的基本组成部分:
- Controller:控制层/表示层,接收前端发送的请求,对请求进行处理并响应数据
- Service:业务逻辑层,处理具体的业务逻辑
- Dao:数据访问层(Data Access Object)负责数据访问操作,包括增删查改等
分层之后如果需要更改业务处理方式,只需更改更改Service层的代码即可,代码的复用性和可维护性大大提高。
我们先来看dao层,访问数据的方式有很多,比如文件中的数据、数据库中的数据、接口提供的数据等等,为了能够适应不同数据,可以采用面向接口的方式进行编程:创建Controller同级目录Dao,编写接口(stu_data)规定返回数据,再编写java类(stu1)实现该接口
Service层的逻辑也类似,于此不再赘述。 总体业务逻辑如下:
- 前端发起请求→
- controller接收并调用service层方法→
- service调用dao层方法→
- dao层获取数据并返回到service层→
- service层处理数据并返回到controller层→
- controller层响应前端
这样就完成了三层架构的拆分,每层的职责都很单一
分层解耦
解耦的含义即为解除耦合,这里需要引入两个概念:
- 内聚:软件中各个模块内部的功能联系
- 高内聚意味着模块内部的元素紧密相关,共同完成一个单一、明确的功能。
- 低内聚则意味着模块内部元素之间关联度较低,可能执行多个不相关的功能。
- 耦合:各个层/模块之间的依赖程序
- 低耦合意味着模块之间依赖关系较少,每个模块相对独立,易于独立地开发测试和维护。
- 高耦合则意味着模块之间依赖关系较多,一个模块的变更可能会影响到其他模块。
在软件设计当中,我们通常需要遵守“高内聚,低耦合”的原则,即模块内部各功能联系紧密,但与其他模块依赖较低。
上文的例子中,比如controller接收并调用service层方法,需要在controller创建service对象的实例,这样一旦service对象内部的类发生变化,会导致多层瘫痪。但如果直接将实例删掉会导致运行时报错:空指针异常。
为避免这一问题,我们选择将service对象的实例放入到一个容器中,controller层需要使用时从容器中取得该实例并进行使用,这就是控制反转(IOC)和依赖注入(DI)。
IOC&DI
介绍
控制反转(IOC)意为对象的创建和绑定由外部容器或框架来管理,而不是由应用程序的代码直接控制。使对象实例成为IOC容器中的bean。
依赖注入(DI)是一种实现控制反转的方式,它将依赖关系注入到组件中,而不是由组件自己创建依赖关系。运行时,IOC容器会提供该类型的bean对象并赋值。
实现IOC需要在各个层顶部加上注解@Component,实现DI则是在使用对象实例时添加注解 @Autowired即可:
@Component//实现控制反转
public class StuService1 implements StuService{@Autowired//实现依赖注入private StuDao stuDao;
//之前的代码private StuDao stuDao=new stu1();
//相关方法
}
如果不需要使用该层,只需将该层的注解注释掉即可。
IOC控制反转
在spring框架中,为了更好的标识bean对象到底属于哪一层,除了@Component注解外,还提供了三个衍生注解
注解 | 说明 | 位置 |
@Component | 声明bean的基础注解 | 不属于三层时 使用此注解 |
@Controller | @Component的衍生注解 | Controller层 |
@Service | @Component的衍生注解 | Service层 |
@Repository | @Component的衍生注解 | Dao层 |
Service层和Dao层的注解按要求添加即可,Controller层因为存在注解@RestController已包含注解@Controller,因此无需再添加。
按住ctrl键点击注解,可以看到其内部只封装了一个注解@Component,其余三个均为元注解,因此我们称这三个注解为@Component的衍生注解。
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Repository {@AliasFor(annotation = Component.class)String value() default "";
}
上文提到使用注解即可使对象实例成为IOC容器中的bean,而在IOC容器中每个bean都有其标识,也就是其名字,在声明bean时,我们可以通过注解中的value属性来指定bean的值,如果未指定则为默认名:该类的类名首字母小写。如果需要指定名称,只需在注解后加上(value = "指定值")或者( "指定值")即可(一般直接使用默认即可)。
@Service(value = "mybean")
//或者@Service("mybean")
public class StuService1 implements StuService{@Autowiredprivate StuDao stuDao;
//相关方法
}
点击控制台右边的Actuator即可查看所有的bean,其中白色背景的为自定义bean,点击后右方就会出现对应的bean的名字,我们可以看到首字母是小写的。
bean组件扫描
但加了注解bean也有不生效的可能,之前介绍的四大注解还需要被组件扫描注解@ComponentScan扫描,该注解虽未显式的配置,但实际上已包含在启动类声明注解@SpringBootApplication中,扫描范围默认为启动类所在的包及其子包。
想要其扫描其他包需要在头部添加注解@ComponentScan(),在该注解内部我们可以看到通过两个属性value和basePackages都可以指定,而这两个属性都是数组,所以应遵循数组的写法@ComponentScan({"原包名","新添加包名"}),注意这里等于覆盖了原来的路径,所以不能只写新添加的包名,应两者都写。但此种做法不推荐,还是建议遵循Springboot的规范将所有文件都放在同一软件包中。
//启动类
@ComponentScan({"myweb1","myweb2"})
@SpringBootApplication
public class MywebApplication {public static void main(String[] args) {SpringApplication.run(MywebApplication.class, args);}
}
//@ComponentScan注解内部
public @interface ComponentScan {@AliasFor("basePackages")String[] value() default {};@AliasFor("value")String[] basePackages() default {};//略
}
DI依赖注入
依赖注入是一种实现控制反转的方式,它将依赖关系注入到组件中,而不是由组件自己创建依赖关系。在之前的例子中,我们通过注解@Autowired就完成了这一系列操作:
@Service
public class StuService1 implements StuService{@Autowired//实现依赖注入private StuDao stuDao;
//相关方法
}
其意为自动装配,默认为按照类型装配,也就是在该例中,其会到容器中查找StuDao类的bean对象是否存在,但如果存在多个同一类型的bean,系统将会报错,可通过以下三个注解来解决该问题:
- @Primary:与四大注解搭配使用,提高优先级,有该注解的实例优先成为bean
- @Qualifier:与@Autowired搭配使用用于指定要注入的Bean的ID
- @Resource:使用@Resource注解替换@Autowired注解用于指定要注入的Bean的名称,但默认情况下它是基于名称匹配的,而@Autowired是基于类型匹配。
@Primary//提高该实例的优先级,优先转为bean
@Service
public class StuService1 implements StuService{//略
}//————————————————————————————————————————————————
@Service
public class StuService1 implements StuService{@Qualifier("stuDao")//指定注入该依赖@Autowiredprivate StuDao stuDao;//略
}//————————————————————————————————————————————————
@Resource(name = "stuDao")
@Service
public class StuService1 implements StuService{@Resource(name = "stuDao")private StuDao stuDao;//略
}