欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 教育 > 培训 > 后端分层解耦

后端分层解耦

2025/2/27 7:22:31 来源:https://blog.csdn.net/qq_65501197/article/details/143819880  浏览:    关键词:后端分层解耦

引入

        在上篇所举的例子中,我们将所有的代码均放在HelloControl方法之中,这样会导致代码的复用性、可读性较差,难以维护。因此我们需

三层架构

        在之前的代码中,代码大体可以分为三部分:数据访问、数据逻辑处理、响应数据。每部分承担不同的职责。三层架构的设计目标是实现一个类或一个方法只实现某单一功能,即单一职责原则。这样即可提高系统的可维护性和可扩展性。

        以下是三层架构的基本组成部分:

  • Controller:控制层/表示层,接收前端发送的请求,对请求进行处理并响应数据
  • Service:业务逻辑层,处理具体的业务逻辑
  • Dao:数据访问层(Data Access Object)负责数据访问操作,包括增删查改等

        分层之后如果需要更改业务处理方式,只需更改更改Service层的代码即可,代码的复用性和可维护性大大提高。

        我们先来看dao层,访问数据的方式有很多,比如文件中的数据、数据库中的数据、接口提供的数据等等,为了能够适应不同数据,可以采用面向接口的方式进行编程:创建Controller同级目录Dao,编写接口(stu_data)规定返回数据,再编写java类(stu1)实现该接口

        Service层的逻辑也类似,于此不再赘述。 总体业务逻辑如下:

  1. 前端发起请求→
  2. controller接收并调用service层方法→
  3. service调用dao层方法→
  4. dao层获取数据并返回到service层→
  5. service层处理数据并返回到controller层→
  6. 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;//略
}

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

热搜词