目录
- 1. controller层是干什么的?
- 1.1 controller原理图
- 1.2 controller层为什么要存在?
- 1.2.1 分离关注点
- 1.2.2 响应HTTP请求
- 1.2.3 数据处理与转换
- 1.2.4 错误处理与状态管理
- 1.2.5 流程控制
- 1.2.6 依赖注入与测试
- 1.3 controller层的优点
- 1.3.1 多端支持
- 1.3.2 安全性保障
- 1.3.3 性能优化
- 1.4 All in All
- 2. 研究Restful风格
- 2.1 什么是Restful风格?
- 2.1.1 功能
- 2.1.2 Restful风格与传统风格的区别
- 2.1.2.1 请求方法
- 2.1.2.2 数据格式
- 2.1.2.3 状态码使用
- 2.1.2.4 异常处理
- 2.1.2.5 无状态性
- 2.1.2.6 总结
- 3. controller层代码
- 3.1 定义controller风格的注解
- 3.1.1 @RestController
- 3.1.2 @RequestMapping
- 3.1.3 @Slf4j
- 3.2 依赖注入注解
- 3.2.1 @Resource和@Autowired的相同点
- 3.2.2 @Resource和@Autowired的不同点
- 3.2.3 总结
- 3.3 依赖注入三种方式
- 3.3.1 字段注入(最简单,有风险)
- 3.3.2 构造器注入(最安全,推荐)
- 3.3.3 Setter方法注入
- 4. controller层接口代码详解
- 4.0 HTTP常见请求方法合集
- 4.1 模糊查询---接口
- 4.2 findAll---接口
- 4.3 根据id查询---接口
- 4.4 增加---接口
- 4.5 删除---接口
- 4.5 更新---接口
- 5 总结
1. controller层是干什么的?
1.1 controller原理图
在Web应用程序中,控制器层(Controller)是模型-视图-控制器(MVC)架构模式的一个关键组成部分。控制器负责接收用户的输入,调用业务逻辑层来处理这些输入,并返回适当的响应给视图层。
举个例子吧:
平时登陆账号的时候,你想要输入账号和密码吧,然后点击登陆的时候,用户请求是最先传到controller层的,然后controller层再传到service层,为什么不直接传给service层?例如,密码长度不符合要求,或者账号不是有效的电子邮件格式,那么没有必要将请求转发到服务层或数据库层进行更深入的验证。通过在控制器层进行初步验证,可以减少不必要的服务层或数据库层操作,从而提高系统性能和响应速度。另外,在控制器层进行格式验证可以作为安全措施的一部分,防止恶意或格式错误的数据到达后端服务,减少潜在的安全风险。以前的黑客不就玩的sql注入吗,当时的开发者都没有意识到在数据库查询中直接使用用户输入的危险。
1.2 controller层为什么要存在?
在现代Web应用程序中,用户请求通常不会直接传递给服务层(Service),而是通过控制器层(Controller)来处理,这种设计主要是基于以下几个原因:
1.2.1 分离关注点
控制器层和业务逻辑层分离,使得每一层都只关注自己的职责。控制器层关注如何接收请求、验证输入和调用相应的业务逻辑。服务层则关注业务规则和业务流程的具体实现。
1.2.2 响应HTTP请求
在Spring框架中,Controller通过@Controller或@RestController注解标识,配合@RequestMapping等注解,能够响应不同路径的HTTP请求。如果没有Controller,系统将无法正确解析和响应用户请求,导致功能无法实现。
1.2.3 数据处理与转换
Controller负责将接收到的数据(如用户输入的用户名和密码)传递给Service层进行验证,并将验证结果转换为适合客户端展示的格式。例如,使用@ResponseBody注解可以将Java对象自动转换为JSON格式,方便前端处理。
1.2.4 错误处理与状态管理
Controller还负责处理请求过程中可能出现的错误情况,并管理HTTP状态码。如果去掉Controller,这些错误处理和状态管理将无处安放,可能导致用户体验不佳和安全风险
1.2.5 流程控制
Controller通过方法调用和返回值决定后续执行流程。例如,如果用户登录失败,Controller可以决定重新显示登录页面和错误信息;如果登录成功,则跳转到用户的主页。这种流程控制能力是细粒度处理请求不可或缺的。
1.2.6 依赖注入与测试
Spring框架中的Controller可以利用依赖注入(DI)集成其他组件(如Service、DAO等),并通过@Autowired注解自动装配所需的依赖。这使得单元测试变得更加容易,同时也降低了组件间的耦合度。
1.3 controller层的优点
1.3.1 多端支持
在一个多端应用(如网页、移动端)中,不同的前端可能发送类似的请求(如获取用户信息)。Controller可以统一处理这些请求,减少重复代码。
1.3.2 安全性保障
Controller可以结合Spring Security等安全框架,提供认证、授权和跨站请求伪造(CSRF)防护等功能,增强应用的安全性。
1.3.3 性能优化
通过合理的Controller设计(如缓存、异步处理),可以提升系统的响应速度和并发处理能力。
1.4 All in All
Controller在Web应用中扮演着核心角色,负责处理请求、响应、数据转换、错误处理和流程控制等关键任务。去掉Controller将导致系统无法正常响应用户请求,破坏架构的模块化和可维护性。因此,在实际开发中应充分重视Controller的设计和实现。
以下是控制器层的详细阐述:
2. 研究Restful风格
2.1 什么是Restful风格?
Restful就是一个资源定位及资源操作的风格。不是标准也不是协议,只是一种风格。基于这个风格设计的软件可以更简洁,更有层次,更易于实现缓存等机制。
2.1.1 功能
- 资源:互联网所有的事物都可以被抽象为资源
- 资源操作:使用POST、DELETE、PUT、GET,使用不同方法对资源进行操作。
- 分别对应 添加、 删除、修改、查询。
2.1.2 Restful风格与传统风格的区别
2.1.2.1 请求方法
Restful风格:Restful风格的Controller全面使用HTTP的方法,包括GET、POST、PUT、DELETE等。每种方法都有具体的含义,例如GET用于获取资源,POST用于创建新资源,PUT用于更新资源,DELETE用于删除资源。
传统风格:传统风格的Controller通常只使用GET和POST请求方法。这意味着不管需要进行什么样的操作,基本都依赖于这两种方法,可能导致一个操作对应多个URL。
2.1.2.2 数据格式
Restful风格:Restful风格的接口通常使用JSON或XML作为数据交换格式,它们易于解析和生成,同时具有良好的可读性。
传统风格:通常使用xml。
2.1.2.3 状态码使用
Restful风格:Restful风格的Controller充分利用了HTTP状态码来表示不同的结果状态,如200(成功)、201(已创建)、404(未找到)、500(服务器错误)等,这些状态码能明确告诉客户端请求的处理结果。
传统风格:传统风格的Controller中,状态码的使用较为单一,通常只有200(成功)被频繁使用,其他状态码较少出现。
2.1.2.4 异常处理
Restful风格:Restful风格的Controller采用统一的异常处理器,可以集中管理错误处理逻辑,并返回统一的错误信息和状态码。
传统风格:传统风格的异常处理可能分散在不同的Controller中,导致代码重复和管理不便。
2.1.2.5 无状态性
Restful风格:Restful风格的设计是无状态的,每次请求都包含了所有必需的信息,这使得系统更加简单且易于扩展。
传统风格:传统风格的Controller可能会依赖存储在服务器端的会话(Session)状态,这导致系统在水平扩展时面临挑战。
2.1.2.6 总结
Restful风格的Controller在URL设计、HTTP方法使用、状态码管理等方面具有明显优势,使得后端服务更加标准化和易于维护。在实际开发中,应当尽量采用Restful风格的设计原则来构建Web应用的后端服务。
3. controller层代码
package com.goblin.BIbackend.controller;import com.goblin.BIbackend.common.BaseResponse;
import com.goblin.BIbackend.common.ListWithTotal;
import com.goblin.BIbackend.common.ResultUtils;
import com.goblin.BIbackend.model.entity.Complaints;
import com.goblin.BIbackend.service.ComplaintsService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;import javax.annotation.Resource;
import java.util.List;@RestController
@RequestMapping("/complaints")
@Slf4j
public class ComplaintsController {@Resourceprivate ComplaintsService complaintsService;//模糊查询@PostMapping("/search")public BaseResponse<ListWithTotal<Complaints>> search(@RequestBody Complaints keyword) {log.info("查询投诉信箱:{}", keyword);List<Complaints> complaints = complaintsService.search1(keyword);int total = complaints.size();log.info("查询投诉信箱总数:{}", total);ListWithTotal<Complaints> list = new ListWithTotal<>(complaints, total);return ResultUtils.success(list);}@GetMapping("/list")public BaseResponse<ListWithTotal<Complaints>> list() {List<Complaints> complaints = complaintsService.getList();int total = complaintsService.count1();log.info("查询投诉信箱总数:{}", total);ListWithTotal<Complaints> list = new ListWithTotal<>(complaints, total);return ResultUtils.success(list);}@GetMapping("/select2")public BaseResponse<Complaints> select2(Long id) {log.info("查询投诉信息:{}", id);Complaints complaints = complaintsService.select2(id);log.info("查询投诉信息:{}", complaints);return ResultUtils.success(complaints);}@PostMapping("/insert2")public BaseResponse<Complaints> insert2(@RequestBody Complaints complaints) {Complaints result = complaintsService.insert2(complaints);return ResultUtils.success(result);}@DeleteMapping("/delete2")public BaseResponse<Long> delete2(Long id) {complaintsService.delete2(id);return ResultUtils.success(id);}@PutMapping("/update2")public BaseResponse<Complaints> update2(@RequestBody Complaints complaints) {complaintsService.update2(complaints);return ResultUtils.success(complaints);}}
这段代码是一个Spring Boot应用程序中的ComplaintsController类,它使用Spring MVC框架来处理(Complaints)相关的HTTP请求。以下是对代码的逐行解析:
3.1 定义controller风格的注解
@RestController
@RequestMapping("/complaints")
@Slf4j
这段代码的主要作用是定义一个处理与"/complaints"相关的HTTP请求的控制器,并自动生成日志对象。
3.1.1 @RestController
这是Spring 4之后新加入的注解,原来在@Controller中返回json需要@ResponseBody来配合,如果直接用@RestController替代@Controller就不需要再配置@ResponseBody,默认返回json格式。这个注解标识这个类是一个RestController,即控制器(Controller),主要用来处理由DispatcherServlet分发的请求,还可以对数据进行校验等操作。
DispatcherServlet是Spring MVC框架中的一个核心组件,它作为前端控制器(Front Controller),负责将接收到的HTTP请求转发到相应的处理器(Controller)。DispatcherServlet是Spring MVC中的中央调度器,它扩展了Servlet API,提供了一种机制来映射请求URL到对应的处理器方法。
3.1.2 @RequestMapping
这个注解用于映射Web请求,即访问"/complaints"路径时,会执行该类下的某个方法。它可以应用于类或方法上,当应用于类上时,表示类中的所有响应请求的方法都是以该地址作为父路径。
3.1.3 @Slf4j
这是Lombok库中的一个注解,用于自动生成日志对象。在使用时,只需在类上添加此注解,就可以自动生成名为log的SLF4J、Log4j、java.util.logging、Commons Logging、Logback以及java.lang.Appendable的实例。
3.2 依赖注入注解
@Resourceprivate ComplaintsService complaintsService;
先说说@Resource和@Autowired。@Resource和@Autowired都是Spring框架提供的依赖注入(DI)注解,用于实现自动装配bean。
3.2.1 @Resource和@Autowired的相同点
1.依赖注入:两者都用于将Spring容器中的bean自动注入到字段、构造函数或setter方法中。
2.减少代码:通过使用这两个注解,可以减少样板代码,即不需要手动编写代码来从容器中获取bean。
3.支持类型注入:它们都支持通过类型来自动装配,意味着Spring会查找容器中相应类型的bean来注入。
3.2.2 @Resource和@Autowired的不同点
1.来源:@Autowired是Spring自己的注解,属于org.springframework.beans.factory.annotation包。
@Resource是Java EE的注解,适用于更广泛的Java应用,不仅限于Spring框架。
2.注入方式:@Autowired:默认按类型(byType)注入,需要时可以结合@Qualifier指定具体的bean名称。
@Resource:默认按名称(byName)注入,也可通过配置type属性改为按类型注入。
3.参数配置:@Autowired:只有一个required参数,表示是否需要这个依赖。
@Resource:包含多个参数,最重要的是name和type,灵活性更高。
4.使用场景:@Autowired更常用于Spring应用程序中,特别是在使用Spring框架的注解配置时。
@Resource可以用于那些需要兼容Java EE注解的场合,或者在某些特定情况下,开发者可能更倾向于使用Java EE的注解风格。
5.应用建议:@Autowired:更适合Spring环境,特别是当需要更灵活的依赖注入时(例如构造器注入)。
@Resource:更适合与Spring解耦的情境,或者在EE环境中使用。
在Spring配置中,如果同时使用了@Autowired和@Resource,Spring会首先尊重@Autowired的配置。
7.异常处理:@Autowired在找不到bean时会抛出NoSuchBeanDefinitionException。
@Resource在找不到bean时可能会抛出ResourceException。
3.2.3 总结
@Autowired和@Resource都可以用来实现依赖注入,但它们在自动装配的默认行为、来源、使用场景和异常处理等方面存在差异。在Spring应用程序中,推荐使用@Autowired,因为它提供了更好的Spring集成和更灵活的配置选项。
3.3 依赖注入三种方式
依赖注入是一种设计模式,用来实现对象之间的依赖关系。传统的面向对象编程中,类与类之间的依赖关系是通过硬编码来实现的,而依赖注入则将这种依赖关系交给外部容器来处理,从而实现了解耦。
在Spring中依赖注入有三大类:字段注入、构造器注入、Setter方法注入。
3.3.1 字段注入(最简单,有风险)
字段注入是通过在类的字段上使用注解来直接注入依赖对象的一种方式,虽然简单直接,但并不推荐使用,因为它破坏了类的封装性。
字段注入会引起以下的问题:
- 对象的外部可见性
- 可能导致循环依赖
- 无法设置注入的对象为final,也无法注入静态变量
什么是字段注入?举例如下:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;@Component
public class MyClass {// 使用@Autowired注解进行字段注入@Autowiredprivate MyService myService;public void doSomething() {myService.performAction();}
}
字段注入非常的简便,通过以上代码我们可以轻松的使用MyService类。
MyService myService= new MyService();
myService.performAction();
这样执行结果为空指针异常,这就是字段注入的第一个问题:对象的外部可见性。
public class TestA(){@Autowiredprivate TestB testB;}
public class TestB(){@Autowiredprivate TestA testA;}
上面两段代码卡bug,这段代码在idea中不会报任何错误,但是启动项目时会发现报错,大致意思是:创建Bean失败,原因是当前Bean已经作为循环引用的一部分注入到了其他Bean中。
这就是字段注入的第二个问题:可能导致循环依赖
字段注入还有第三个问题:无法设置注入的对象为final,也无法注入静态变量,原因是变量必须在类实例化进行初始化。
3.3.2 构造器注入(最安全,推荐)
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;@Component
public class MyClass {private final MyService myService;// 使用@Autowired注解进行构造器注入@Autowiredpublic MyClass(MyService myService) {this.myService = myService;}public void doSomething() {myService.performAction();}
}
在这个例子中,MyClass的构造函数接收一个MyService类型的参数,并通过@Autowired注解告诉Spring框架自动注入一个合适的MyService实例。这样,当创建MyClass的实例时,Spring会自动调用带有@Autowired注解的构造函数,并将相应的依赖项传递给它。
构造器注入通过在类的构造函数上使用注解来自动装配依赖对象。与字段注入相比,构造器注入有以下优点:
1.强制依赖关系:构造器注入要求必须提供所有必需的依赖项,否则将无法创建类的实例。这有助于确保类在使用之前已经正确地初始化了其依赖项。
2.不可变性:由于构造器注入是在构造函数中完成的,一旦对象被创建,其依赖项就无法更改。这有助于保持对象的不变性,并减少潜在的错误和副作用。
3.可测试性:构造器注入使得单元测试更加容易,因为可以在测试时轻松地提供模拟或存根依赖项。
3.3.3 Setter方法注入
Setter方法注入是依赖注入的一种方式,它**通过在目标类中定义一个或多个setter方法来接收依赖项的实例。**这种方式允许将依赖项的创建和管理从使用对象的类中分离出来,从而实现解耦和易于维护的代码。
public class MyClass {private MyDependency myDependency;// Setter method for dependency injectionpublic void setMyDependency(MyDependency myDependency) {this.myDependency = myDependency;}public void doSomething() {// Use the injected dependencymyDependency.performAction();}
}
在这个例子中,MyClass 有一个 myDependency 属性,它需要被注入一个 MyDependency类型的实例。通过定义一个名为 setMyDependency的setter方法,我们可以在外部容器(如Spring)中配置并注入所需的依赖项。
当Spring容器启动时,它会扫描所有的bean定义,并根据配置文件或其他配置信息自动调用相应的setter方法,将依赖项注入到目标对象中。这样,我们就可以在不修改目标类的代码的情况下,灵活地更改依赖项的实现或配置。
需要注意的是,尽管setter方法注入是一种常见的依赖注入方式,但它并不是唯一的选择。其他依赖注入方式,如构造器注入、字段注入和接口注入等,也可以用来满足不同的需求和场景。
4. controller层接口代码详解
4.0 HTTP常见请求方法合集
在RESTful API设计中,通常使用GET、POST、PUT、PATCH和DELETE方法来操作资源。这些方法的选择反映了操作的语义,有助于保持API的直观性和一致性。例如,使用GET来请求数据,使用POST来创建新资源,使用PUT来更新现有资源,使用DELETE来删除资源。
- GET:请求获取指定的资源。
- POST:向指定资源提交数据进行处理请求(例如提交表单或者上传文件)。数据被包含在请求体中。POST请求可能会导致新的资源的建立和/或已有资源的修改。
- PUT:从客户端向服务器传送的数据取代指定的文档的内容。
- DELETE:请求服务器删除指定的内容。
- PATCH:对资源进行部分修改。
4.1 模糊查询—接口
@PostMapping("/search")public BaseResponse<ListWithTotal<Complaints>> search(@RequestBody Complaints keyword) {log.info("查询投诉信箱:{}", keyword);List<Complaints> complaints = complaintsService.search1(keyword);int total = complaints.size();log.info("查询投诉信箱总数:{}", total);ListWithTotal<Complaints> list = new ListWithTotal<>(complaints, total);return ResultUtils.success(list);}
模糊查询:客户端返回一个Complaints 类对象到controller层,用POST接口。
在Spring框架中,@PostMapping(“/search”)是一个注解,用于将HTTP的POST请求映射到特定的处理方法。这里的"/search"是URL路径的一部分,表示当客户端向服务器发送一个POST请求到"/search"路径时,该请求将被路由到带有@PostMapping(“/search”)注解的方法进行处理。
具体来说,@PostMapping(“/search”)中的"search"是一个字符串字面量,它定义了处理POST请求的URL路径。在这个例子中,当客户端发送一个POST请求到"/search"路径时,服务器会调用带有@PostMapping(“/search”)注解的方法来处理这个请求。这个方法可以接收和处理来自客户端的数据,并返回相应的响应。
BaseResponse是一个通用的响应类,用于封装API接口的返回结果。它包含一些常见的属性,如code、message和data。BaseResponse表示返回的数据类型是一个包含投诉的列表和总数的对象。
@RequestBody Complaints keyword是一个Spring框架中的注解,用于将HTTP请求体中的数据绑定到方法参数上。在上面的代码中,Complaints是一个Java类,表示投诉信息的数据结构。keyword 是这个类的一个实例,用于接收客户端发送的投诉信息。
当客户端发送一个POST请求到服务器的"/search"路径时,请求体中的数据会被解析并转换为Complaints类的实例。然后,这个实例会被传递给search方法作为参数keyword。
ResultUtils.success(list)
是一个方法调用,它是一个工具类中的一个静态方法。这个方法的作用是创建一个成功的响应对象,并将传入的参数list
作为数据返回给客户端。
在上面的代码中,list
是一个包含投诉信息的列表,而ResultUtils.success(list)
方法会创建一个BaseResponse
类型的对象,并设置状态码为成功(例如200),同时将list
作为响应的数据部分。这样,当客户端接收到这个响应时,它将能够获取到包含投诉信息的数据。
ResultUtils.success()`方法封装一些常见的响应结构,如状态码、消息和数据等,以便在应用程序中统一处理响应结果。
4.2 findAll—接口
@GetMapping("/list")public BaseResponse<ListWithTotal<Complaints>> list() {List<Complaints> complaints = complaintsService.getList();int total = complaintsService.count1();log.info("查询投诉信箱总数:{}", total);ListWithTotal<Complaints> list = new ListWithTotal<>(complaints, total);return ResultUtils.success(list);}
findAll:请求获取所有的Complaints对象,用GET接口。
4.3 根据id查询—接口
@GetMapping("/select2")public BaseResponse<Complaints> select2(Long id) {log.info("查询投诉信息:{}", id);Complaints complaints = complaintsService.select2(id);log.info("查询投诉信息:{}", complaints);return ResultUtils.success(complaints);}
4.4 增加—接口
@PostMapping("/insert2")public BaseResponse<Complaints> insert2(@RequestBody Complaints complaints) {Complaints result = complaintsService.insert2(complaints);return ResultUtils.success(result);}
增加:将一个Complaints 对象传到controller层,用POST接口。
4.5 删除—接口
@DeleteMapping("/delete2")public BaseResponse<Long> delete2(Long id) {complaintsService.delete2(id);return ResultUtils.success(id);}
删除:传id给controller层,请求删除指定的Complaints对象,用DELETE接口。
4.5 更新—接口
@PutMapping("/update2")public BaseResponse<Complaints> update2(@RequestBody Complaints complaints) {complaintsService.update2(complaints);return ResultUtils.success(complaints);}
更新:将一个Complaints 对象传到controller层取代原有Complaints ,从客户端向服务器传送的数据取代指定的文档的内容,用PUT接口。
5 总结
文章首先讲了controller层的重要性和优点,然后讲了restful风格和传统风格,接着对代码进行分析:分析了依赖注入的三种方法,最后是对接口的解析,不可谓不深刻。看完这篇文章应该能知道什么是controller层。代码谁都会写,真正的区别在于对代码底层的理解。