什么是分层解耦,什么是三层架构,一篇文章告诉你!
发布时间:2024-10-18 07:09:46
分层解耦(重点)
1. 分层解耦是什么
定义:分层解耦指的是将系统拆分成多个层次,每一层都有特定职责,并彼此隔离。这种方法能让各层独立演变,降低模块间的耦合,提升系统的可维护性。
内聚:软件中各个功能模块内部的功能联系。 耦合:衡量软件中各个层/模块之间的依赖、关联的程度。 软件设计原则:高内聚低耦合。
常见分层(三层架构):
表现层(Controller 层):负责处理用户请求和返回数据,通常使用 Spring MVC 的 @Controller。 业务层(Service 层):包含核心业务逻辑,通常用 @Service 注解标识。 数据访问层(DAO 层):负责数据库交互(持久层),通常用 @Repository 注解标识。
解耦目的
直接实例化对象会导致类与类之间的耦合,尤其是在多层结构的代码中(例如Controller、Service、DAO层),更换实现类需要修改代码,降低了代码的灵活性和可维护性。 使用IOC容器管理对象,将依赖注入到需要的类中,解耦依赖关系,减少因为实现类更换导致的代码修改。
如何解耦呢?
在三个层中Controller层需要访问Service层获取处理的数据,Service层有需要访问DAO层获取数据,假设Service层的实现类访问了DAO层的实现类,就必须创建DAO层的实现类
private EmpDao empDao = new EmpDaoA();//面向接口编程(多态)
当DAO层的实现类名改变的时候如EmpDaoB,new 的类名也自然也要改如new EmpDaoB(),这就是耦合,如何解决耦合呢?
以前是我需要什么对象就new什么对象,现在是将所有的对象交给了外部(IOC)容器来管理,这样就可以解决new对象耦合的问题。
控制反转(IOC) 是一种将对象创建和管理职责交由外部容器(如Spring)处理的思想,避免在代码中直接创建实例的耦合关系。通过IOC,我们可以从外部容器获取我们需要的对象,控制权被“反转”给容器。 依赖注入(DI) 是实现IOC的一种方式。容器会在运行时自动将一个对象的依赖注入到该对象中,这样对象不需要显式地创建依赖对象。 Bean对象:loc容器中创建、管理的对象,称之为bean。
这是 Spring 框架中常见的注解,用于指定类的功能角色,使得 Spring 容器可以自动扫描、管理并注入依赖。以下是详细说明:
| 注解 | 说明 | 位置与用法 | | ------------- | ------------------------------------------------------------ | ------------------------------------------------------------ | | @Component | 声明一个 Bean 的基础注解,它会被 Spring 扫描并注册到容器中。适用于不属于以下三类的 Bean。 | 标注在任意类上,当无法确定具体功能时使用。 | | @Controller | @Component 的衍生注解,专门用于控制器类(例如处理 HTTP 请求的类),通常用于 MVC 模式的控制层。 | 标注在控制器类(处理用户请求的类)上。 | | @Service | @Component 的衍生注解,标识业务逻辑类。通常用于封装业务服务的实现逻辑。 | 标注在业务类上,表示业务层的服务。 | | @Repository | @Component 的衍生注解,专用于数据访问层,用于标识数据库操作类(DAO)。这个注解会处理持久化异常,使得数据访问异常可以被统一转换。 | 标注在数据访问类上(例如操作数据库的类)。不过,结合 MyBatis 时较少使用。 |
补充说明
扫描机制:前面声明bean的四大注解,要想生效,还需要被组件扫描注解@componentScan扫描。@ComponentScan注解虽然没有显式配置,但是实际上已经包含在了启动类声明注解@SpringBootApplication中,默认扫描的范围是启动类所在包及其子包。 ```java @ComponentScan(("dao","com.itheima")//手动指定扫描的包,注意,使用此注解后,默认扫描失效! @SpringBootApplication//默认扫描当前包及其子包 public class SpringbootWebReqRespApplication public static void main(String[] args){
} ```
衍生作用:@Controller、@Service、@Repository 是 @Component 的衍生注解,具备 @Component 的所有功能,但更加语义化,有助于代码的分层和管理。
示例
@Component // 通用组件
public class GeneralBean {}
@Controller // 控制器
public class UserController {}
@Service // 业务服务
public class UserService {}
@Repository // 数据访问层
public class UserRepository {}
注意事项
使用衍生注解(@Controller、@Service、@Repository)更能体现类的实际职责,便于维护和管理。 Spring 自动处理 @Repository 中的数据访问异常,但需要根据项目框架使用(例如 MyBatis 中可以不标注 @Repository )。
所以我们在编程时,只需要把Service层的实现类和DAO层的实现类交给IOC容器管理为Bean对象
@Component
public class EmpServiceA implements EmpService{//将这个实现类给IOC容器
}
在需要使用这个对象的地方创建对应类的接口类型数据上加入@Autowired注解就行了
@Autowired//自动在IOC里获取基于EmpService接口实现的类注入
private EmpService empService;//EmpService这个就是Service层实现类的接口,基于这个接口实现的类我们都可以获取到。
代码示例
DAO层和Service层定义
假设有以下接口和实现类:
// DAO接口
public interface EmpDao {
// 一些数据访问方法
}
// DAO实现类A
@Repository // 告诉Spring将此类托管为Bean
public class EmpDaoA implements EmpDao {
// 数据访问方法实现
}
// DAO实现类B
@Repository // 另一种实现类
public class EmpDaoB implements EmpDao {
// 数据访问方法实现
}
// Service接口
public interface EmpService {
// 一些业务逻辑方法
}
// Service实现类
@Service // 将此类托管为Bean
public class EmpServiceA implements EmpService {
private final EmpDao empDao;
// 构造函数注入DAO
@Autowired
public EmpServiceA(EmpDao empDao) {
this.empDao = empDao;
}
// 使用empDao进行数据处理
}
Controller层使用Service
在Controller中使用Service时,通过 @Autowired 注解自动注入 EmpService 的实现类:
@RestController
public class EmpController {
@Autowired // 自动注入EmpService接口实现类
private EmpService empService;
// 控制器方法,可以使用empService进行服务调用
}
依赖注入的多种方式
Spring提供了多种依赖注入方式,根据不同需求可以选择不同的注入方式:
构造函数注入:通过构造函数注入依赖对象,是推荐的方式。 Setter方法注入:通过Setter方法注入依赖,适合可选依赖。 字段注入:直接在字段上使用@Autowired,简单但不利于单元测试。
总结
将对象交由Spring的IOC容器管理,通过DI来解耦依赖,提升代码的灵活性和可维护性。我们通过注解如 @Component、@Autowired 等实现自动化注入,不再手动创建依赖对象,从而减少代码耦合度。
2. 三层架构
三层架构是一种常见的分层模式,其目的是将系统功能分为表现、业务和数据访问三层,以便实现模块化开发。
层次职责
表现层:与前端交互,接收 HTTP 请求并返回响应,通常用 Spring MVC 的 @Controller 注解来标识,例如 UserController。 业务层:处理业务逻辑,负责调用 DAO 层获取数据或处理逻辑。使用 @Service 注解来标识。 数据访问层:直接与数据库交互,通过 SQL 查询、ORM 等方式管理数据,通常使用 @Repository 注解。
此分层模式在上述的示例代码中已展示。通过分离每层职责,可以有效地隔离系统逻辑,降低代码耦合度。
注意事项:
单向调用:表现层只能调用业务层,业务层只能调用数据访问层。 避免跨层:不要在表现层直接调用 DAO 层,违反了三层架构的职责分工。
IOC 详解
Spring 容器在项目启动时会扫描注解并自动创建 Bean。IOC 通过注解或 XML 配置来控制对象的生命周期和依赖关系。
IOC 注解方式和 XML 配置方式示例
注解方式:
@Service
public class OrderService {
// Spring 容器会扫描注解并自动创建实例
}
XML 配置方式:
<beans>
<bean id="orderService" class="com.example.OrderService" />
</beans>
注意事项:
单例和多例:默认情况下,Spring 容器中的 Bean 是单例的,可以通过 @Scope("prototype") 注解来设为多例。 Bean 生命周期:Spring 支持 @PostConstruct 和 @PreDestroy 注解来定义 Bean 的初始化和销毁方法。
DI 详解
依赖注入支持构造器、Setter 和接口三种方式,Spring 会自动管理 Bean 的创建和注入。对于复杂依赖关系,可以通过 @Qualifier 注解精确指定 Bean。
依赖注入示例
构造器注入:
public class OrderProcessor {
private final OrderDAO orderDAO;
public OrderProcessor(OrderDAO orderDAO) {
this.orderDAO = orderDAO;
}
}
Setter 注入:
public class OrderProcessor {
private OrderDAO orderDAO;
public void setOrderDAO(OrderDAO orderDAO) {
this.orderDAO = orderDAO;
}
}
注意事项:
使用 @Qualifier 注解可以在有多个同类型 Bean 时指定注入。 构造器注入适合必须的依赖,Setter 注入则用于可选依赖。
在 Spring 中,默认情况下,当有两个相同类型的实现类同时被 @Service 注解标注,容器在注入时无法确定应该选择哪个实现类,从而导致冲突,进而抛出错误。可以通过以下方法解决:
方法一:指定 @Primary 注解
使用 @Primary 注解在一个实现类上,告诉 Spring 容器该实现类是主要候选,这样注入时会优先选择这个实现类。
@Service
public class UserServiceImpl1 implements UserService {
// ...
}
@Primary
@Service
public class UserServiceImpl2 implements UserService {
// 将优先注入这个类
}
方法二:使用 @Qualifier 指定具体实现
在注入时通过 @Qualifier 注解明确指明需要注入哪个实现类。
@Service("userServiceImpl1")
public class UserServiceImpl1 implements UserService {
// ...
}
@Service("userServiceImpl2")
public class UserServiceImpl2 implements UserService {
// ...
}
// 在注入时指定名称
@Autowired
@Qualifier("userServiceImpl1")
private UserService userService;
方法三:将实现类分组到不同的 @Configuration 中
可以使用 @Configuration 配置类,将相同接口的不同实现分组在不同的配置类中,以便在不同的场景中灵活选择实现类。
@Configuration
public class Config1 {
@Bean
public UserService userServiceImpl1() {
return new UserServiceImpl1();
}
}
@Configuration
public class Config2 {
@Bean
public UserService userServiceImpl2() {
return new UserServiceImpl2();
}
}
注意事项
不必要的实现类:在单一场景中一般避免多个相同接口的实现类,以免引入复杂性。 @Primary 和 @Qualifier 配合使用:当 @Primary 标记的类和 @Qualifier 同时使用时,以 @Qualifier 为优先。
总结
分层解耦:将系统分成表现、业务和数据访问层,以降低耦合。 三层架构:表现层处理请求,业务层实现逻辑,数据层负责数据操作。 IOC 和 DI:由 Spring 容器管理对象和依赖,解耦代码结构,提高灵活性和可测试性。