spring开发的web应用常使用三层架构,其目的在于解耦、提升可维护性和扩展性。
- controller层:接收前端发送的请求,处理之后返回响应数据。
- service层:具体的业务逻辑。
- dao层:数据访问,即Data Access Object,负责数据访问操作,如增删改查,经常需要与数据库交互。
数据流动和代码控制流的方向经常是,从前端发起,依次经过controller、service、dao,再反向传回前端。从代码实现的角度来说,通常是controller方法调用service层提供的业务逻辑代码处理请求数据,而service层的接口实现需要调用dao层提供的数据访问方法。
通常,service层和dao层的逻辑可能有多种实现,所以可以使用“接口定义+实现类”的方式,例如,在service包下定义接口,然后创建一个impl
子目录,在其中编写接口的实现,在调用者方法中利用多态的方式调用接口实现。
如果使用下面的方式:
@RestController
public class Controller {private Service service = new ServiceA();// ...
}
那么当需要修改service层的代码时,例如使用新的ServiceB
实现时,同时需要修改controller层的代码。这样就不符合解耦的需求。
spring框架的解决方案是提供一个外部容器,存储所有对应的对象,当程序需要使用某一对象时,从容器中获取对应的对象。对此spring框架使用的技术叫做IoC和DI。
- IoC即Inversion of Control,控制反转,是指对象的创建控制由程序自身转移到外部(即IoC容器)。
- DI即Dependency Injection,依赖注入,是指容器为应用程序提供运行时所依赖的资源。
- 还有一个概念叫做bean对象,IoC容器中创建和管理的对象叫bean对象。
IoC和DI的基本步骤总结如下:
- service层和dao层的实现类,交给IoC容器管理。这一步需要在实现类上加入
@Component
注解。 - 为controller和service注入运行时依赖的对象。这一步需要在对象声明时加入
@Autowired
注解。
要把某个类的对象交给spring的IoC容器管理,就需要在对应的类上加上以下注解之一:
@Component
,这个是基础注解,加上该注解就是把该类的对象声明为bean类。通常鼓励使用更准确的衍生注解,所以这个注解的典型应用是工具类。@Controller
,是衍生的注解,标注在controller类上。@RestController
已经包含了这个注解。@Service
,同样,标注在业务逻辑类上。@Repository
,同样,标注在dao类上,由于会与mybatis整合起来,所以用的比较少。
也就是说代码的形式可能如下,以service层为例,dao层类似:
// controller
@RestController
public class Controller {@Autowiredprivate Service service;// 方法直接调用service
}// service
public interface Service {// 接口定义
}// service impl
@Service
public class ServiceA implements Service {// 接口实现
}
注意,这四个注解需要生效还需要能够被组件扫描注解@ComponentScan
扫描到。由于启动类声明注解@SpringBootApplication
中默认扫描的范围是启动类所在包及其子包,所以如果你的组件不在这个包中,又希望它能被IoC容器管理,就需要显式声明组件扫描注解。spring boot的推荐做法是将所以代码放在启动类所在包及其子包下。
bean类型依赖注入使用的@Autowired
注解默认按照类型进行,那么如果IoC容器中存在多个相同类型的bean,就会在启动时报错。有三个解决方案:
@Primary
注解,表明优先使用该实现类,想要使用哪个实现类,就将它加在该类上。@Qualifier
注解,按照类型注入,指明需要注入的是哪个bean对象。@Resource
注解,按照名称注入,知名需要注入哪个bean对象。该注解是jdk提供的,前面的注解则是spring框架提供的。