Spring Web MVC

Spring Web MVC

Spring Web MVC概念

Spring Web MVC is the original web framework built on the Servlet API and has been included in the Spring Framework from the very beginning. The formal name, "Spring Web MVC," comes from the name of its source module (spring-webmvc), but it is more commonly known as "Spring MVC".

内容源于:https://docs.spring.io/springframework/reference/web/webmvc.html
Spring Web MVC​ 是基于 Servlet API 构建的原始 Web 框架,并且从一开始就包含在 Spring Framework 中。其正式名称 "Spring Web MVC" 来源于其源模块的名称(spring-webmvc),但它更常被称为 "Spring MVC"

Spring MVC是一个Web框架

MVC

MVC是Model View Controller,是软件架构的设计模式,它把软件分为模型、视图和控制器 三个基本部分

View(视图) :界面显示,专门与浏览器进行交互,对数据进行展示
Model(模型) :主体部分,处理请求的数据逻辑
Controller(控制器)用来连接视图和模型,处理用户发来请求,选择处理模型进行处理,并和对应视图进行连接,方便处理后跳转对应视图

例如:去饭店吃饭
服务员 就像一个View视图:接待客户,客户进行点单,以及给客户上菜
前台 就像一个Controller控制器:确定点餐情况,并通知给后厨(Model)
后厨就像一个Model模型:根据前台的要求来完成用户用餐制作

Spring MVC

MVC是一种架构设计模式也是一种思想,那Spring MVC就是对MVC这种架构/思想的实现 ,并且也是要给Web框架

并且创建Spring Boot项目时候

Build web, including RESTful, applications using Spring MVC. Uses Apache Tomcat as the default embedded container.

使用Spring MVC 创建一个Web应用程序包括RESTful,默认使用Apache Tomcat作为嵌入容器

这里只是Spring Boot 添加 Spring Web MVC来实现Web功能,此时Spring Boot就像一个厨房,厨房里面可以有很多东西,可以做饭,MVC就像这里做饭功能,MVC更加细化功能

并且Spring也对MVC进行了一些优化

让前后端进行分离

Spring MVC使用

1.建立连接 :将浏览器和Java程序,通过地址调用Spring程序

2.请求 :用户请求时候带来的一些参数,主要是获取对应参数功能

3.响应:执行了业务之后将结果返回给用户

这里还是一个Spring Boot 项目

这里是通过RequestMapping实现URL反射

java 复制代码
@RestController
public class HelloControllor {
    @RequestMapping("/hello")
    public String hello(){
        return "hello String Boot";
    }
 }


注解内容和方法名可以不同 ,并且这里注解内容可以不加 /斜杠

但是建议加上/,并且注解内容和方法名相同

@RequestMapping注解

用来注册接口路由器映射的,上面表示 /hello就是调用hello这个方法
路由映射 :当用户访问一个URL 时,将用户请求对应到某个类的某个方法 就是路由映射

此时这个类有@RestController ,其对类进行扫描的时候,加上这个注解就会从这个类中找是否有@RequestMapping,当然作用不仅仅是这个一个

@RequestMapping 既可修饰类,也可以修饰⽅法

当修饰类和方法时候,访问地址就是 类路径 + 方法路径

主要是为了防止因为两个类中有两个相同的方法路径导致错误

果没有类路径的话,类路径就为空,直接使用对应方法路径就可以访问

java 复制代码
@RestController
@RequestMapping("/com")
public class HelloControllor {
    @RequestMapping("/hello")
    public String hello1(){
        return "hello String Boot";
    }
}

如果有类路径却没加的话就会报错 ,有类路径的话就必须是类路径 + 方法路径

这里类路径 + 方法路径 就对了

这里注解中URL路径前如果没有加 / ,Spring启动时候,会进行判断,如果没有加,Spring会拼接一个/

这里不加 / 也是可以访问的

java 复制代码
@RestController
@RequestMapping("com")
public class HelloControllor {
    @RequestMapping("hello")
    public String hello1(){
        return "hello String Boot";
    }
}

@RequestMapping是GET还是POST请求呢
都支持 ,客户端决定

GET:从服务器获取数据

POST:向服务器传输资源

GET请求

java 复制代码
@RestController
@RequestMapping("/com")
public class HelloControllor {
    @RequestMapping("/hello")
    public String hello1(){
        return "hello String Boot";
    }
}

像这样直接获取资源就是GET


POST请求

这里在 resources / static中写一个前端提交按钮

html 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
   <form action="/com/hello" method="post">
      <input type="submit" value="提交">
   </form>
</body>
</html>

通过这个 http://127.0.0.1:8080/test.html访问

从这里看出其即支持GET和POST请求,当然其他请求也支持

这里使用postman来进行发送请求

上面构建的url没有指定请求,那这个所有请求都满足

java 复制代码
@RestController
@RequestMapping("com")
public class HelloControllor {
    @RequestMapping("hello")
    public String hello1(){
        return "hello String Boot";
    }
}




............未指定请求,默认所有请求都满足,当然我们可以指定类型

java 复制代码
@RestController
@RequestMapping("com")
public class HelloControllor {
      //只允许GET请求
    @RequestMapping(value = "/hello2" , method = RequestMethod.GET)
    public String hello2(){
        return "hello world";
    }
}


这个指定只可以用GET请求,这里如果发送POST请求,会出现405错误也就是,这个方法不存在

通过使用对应注解Mapping也可以指定例如:@GetMapping,@PostMapping............

java 复制代码
@RestController
@RequestMapping("com")
public class HelloControllor {
    //只允许GET请求
    //@RequestMapping(value = "/hello2" , method = RequestMethod.GET)
    @GetMapping("/hello2")
    public String hello2(){
        return "hello world";
    }
}



当然也可以指定多个

java 复制代码
@RestController
@RequestMapping("com")
public class HelloControllor {
      //多个
    @RequestMapping(value = "/r1", method = {RequestMethod.GET,RequestMethod.POST})
    public String r1(String name){
        return name;
    }
}

此时这里支持GET和POST请求,并且这里要写入name对应键值对作为参数


请求

传递参数

访问不同路径,会发送不同请求,上面发送请求没有带参数,但是发送请求是可能带参数 的,这里通过Postman来模拟传参和浏览器

java 复制代码
@RestController
@RequestMapping("com")
public class HelloControllor {
      //多个
    @RequestMapping("/r2")
    public String r2(String name){
        return "接收到的参数:" + name;
    }

}

此时这里可以传入String类型参数,并且要对应参数名,否则无效

http://127.0.0.1:8080/com/r2?name=zhangsan

如果名字不对,此时name参数值就为空

Integer类型

java 复制代码
@RestController
@RequestMapping("com")
public class HelloControllor {
      //多个
    @RequestMapping("/r3")
    public Integer r3(Integer id){
        return id;
    }

}

这里如果如果传入一个字符串String类型

HTTP 400 错误 (Bad Request)表示服务器无法处理客户端的请求,通常是由于请求存在语法错误、参数问题或格式不正确

这里是参数类型不匹配问题

此时如果不传递参数

此时这里就是什么也没有

上面使用的是Integer包装类型,但是如果换成int这样基本类型呢

java 复制代码
@RestController
@RequestMapping("com")
public class HelloControllor {
      //多个
    @RequestMapping("/r3")
    public Integer r3(int id){
        //使用基本类型
        return id;
    }

}


HTTP 500 错误(内部服务器错误)表示服务器在处理请求时遇到问题,通常由服务器端的代码、配置或资源问题引起

服务器会出现这样报错:java.lang.IllegalStateException: Optional int parameter 'id' is present but cannot be translated into a null value due to being declared as a primitive type. Consider declaring it as object wrapper for the corresponding primitive type.

就是建议是使用包装类型,这样可以接收null值

复制代码
使用基本类型来接收参数时,参数必须传(除boolean类型),否则会报500错误
java 复制代码
@RestController
@RequestMapping("com")
public class HelloControllor {
      //多个
     @RequestMapping("/r4")
    public boolean r4(boolean flag){
        return flag;
    }
}

不传参数默认是false

http://127.0.0.1:8080/com/r4?flag=true

这里传递1表示true,0表示false

传递多个参数

java 复制代码
@RestController
@RequestMapping("com")
public class HelloControllor {
      //多个
    @RequestMapping("/r5")
    public String r5(String name,Integer id){
        return "传递参数name:"+name + "  id:" + id;
    }
}


这里如果是多个参数和传入参数顺序不影响传参数 ,以参数名来进行匹配,对应Key 和 Value对就行

传递对象

java 复制代码
public class Person {
    private String name;
    private Integer age;
    private Integer gender;
    private String email;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public Integer getGender() {
        return gender;
    }

    public void setGender(Integer gender) {
        this.gender = gender;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", gender=" + gender +
                ", email='" + email + '\'' +
                '}';
    }
}
java 复制代码
@RestController
@RequestMapping("com")
public class HelloControllor {
    @RequestMapping("r6")
    public Person r6(Person p){
        return p;
    }
}

这里对应参数名进行传递 就行

这里如果将这些数据放入一个类中,这样比较方便维护,并且此时其中对象即使没有初始化也有默认值,这样使用基本类型不传入参数也是可以的

后端参数重命名(后端参数映射)

java 复制代码
@RestController
@RequestMapping("com")
public class HelloControllor {
      @RequestMapping("r7")
    public String r7(@RequestParam("na") String name){
        //这里就可以使用name来代表na来进行后面操作
        return name;
    }
}

但是这里前端key和后端key不一样,此时这里前端使用na使用**@RequestParam重命名后端参数值,此时这里后端使用name
http://127.0.0.1:8080/com/r7?na=lisi

使用了重命名的话,此时这里如果
没有传入对应的参数就会报错**

1.使用@RequestParam进行重命名,参数变成必传参数 (但是可以修改)

2.@RequestParam 重命名之后,后端使用重命名之后的名称,前端url传参使用重命名之前的

非必传参数设置

java 复制代码
@RestController
@RequestMapping("com")
public class HelloControllor {
    
    @RequestMapping("r7")
    public String r7(@RequestParam(value = "na",required = false) String name){
        //这里就可以使用name来代表na来进行后面操作
        return name;
    }

} 	

此时这里required设置成false ,这里就不是必传参数了,设置成true就是必传,默认是必传

传递数组

java 复制代码
@RestController
@RequestMapping("com")
public class HelloControllor {
    
  @RequestMapping("r8")
    public String r8(String[] array){
        return Arrays.toString(array);
    }

} 	

方式一

http://127.0.0.1:8080/com/r8?array=zhangsan\&array=lisi\&array=123

方式二:

http://127.0.0.1:8080/com/r8?array=zhangsan,lisi,wanger,8988

方式三:

http://127.0.0.1:8080/com/r8?array=zhangsan%2Clisi%2C123

传递集合

和数组类似,这不过这里要使用RequestParam绑定参数关系

java 复制代码
@RestController
@RequestMapping("com")
public class HelloControllor {
    
    @RequestMapping("r9")
    public String r9(@RequestParam List<String> list){
        return "size:" + list.size() + "  list:" + list;
    }

} 	

http://127.0.0.1:8080/com/r9?list=zhangsan,lisi,wanger,8988\&list=zhangsan\&list=lisi\&list=8989

传递JSON数据

JSON:JavaScript Object Notation

JSON是一种轻量级数据交互方式,基于 ECMAScript的一个子集,采用完全独立编程语言的文本格式存储和表示数据

是一种数据格式,使用文本表示数据信息,并且可以负责不同语言中数据的交互

json 复制代码
{
    "squadName": "Super hero squad",
    "homeTown": "Metro City",
    "formed": 2016,
    "secretBase": "Super tower",
    "active": true,
    "members": [
        {
            "name": "Molecule Man",
            "age": 29,
            "secretIdentity": "Dan Jukes",
            "powers": [
                "Radiation resistance",
                "Turning tiny",
                "Radiation blast"
            ]
        },
        {
            "name": "Madame Uppercut",
            "age": 39,
            "secretIdentity": "Jane Wilson",
            "powers": [
                "Million tonne punch",
                "Damage resistance",
                "Superhuman reflexes"
            ]
        },
        {
            "name": "Eternal Flame",
            "age": 1000000,
            "secretIdentity": "Unknown",
            "powers": [
                "Immortality",
                "Heat Immunity",
                "Inferno",
                "Teleportation",
                "Interdimensional travel"
            ]
        }
    ]
}

上面是格式化表示容易读,当然可以进行压缩表示

数据用键值对 Key / Value中
采用(逗号),分割键值对 ,键和值用 :进行分割
对象{ } ,保存的是无序的键值对集合{开始,}结束
数组[ ] ,保存的是有序Value值集合[开始,]结束

值可以是对象,也可以是数组,数组中也可以有多个对象

当然这里也有一些可以用于JSON数据格式化工具

https://www.json.cn/jsononline/

Java对象与json字符串转化

有 Gson 、fastjson、jackson这三种方式,这里演示jackson因为Spring MVC框架集成了JSON工具就是 jackson

当然也可以自己配置

xml 复制代码
<dependency>
  <groupId>com.fasterxml.jackson.core</groupId>
  <artifactId>jackson-databind</artifactId>
  <version>2.13.5</version>
</dependency>

放在这里测试

java 复制代码
@SpringBootTest
class DemoApplicationTests {

	@Test
	void contextLoads() throws JsonProcessingException {
		ObjectMapper objectMapper = new ObjectMapper();
		Person p = new Person();
		p.setName("zhangsan");
		p.setAge(18);
		p.setGender(0);
		p.setEmail("123@163.com");

		//将Person对象转为json
		String jsonStr = objectMapper.writeValueAsString(p);
		System.out.println(jsonStr);

		String jsonStr2 = "{\"name\":\"zhangsan\",\"age\":18,\"gender\":0,\"email\":\"123@163.com\"}";
		Person person = objectMapper.readValue(jsonStr2,Person.class);

		System.out.println(person.toString());
	}

}

/*
ObjectMapper 对象中有两个方法

writeValueAsString 将对象转化为JSON字符串

readValue :将字符串转成成对应对象

JSON优点

1.易用性:语法简单,容易编写,快速进行数据交互

2.跨平台:语言可以被多种编程语言解析和生成,可以在不同平台进行数据交换和传输

3.轻量级:相对应xml更轻量,相对应protoubf可读性更高

4.易扩展:其数据结构灵活,支持对象和数组嵌套成复杂数据结构

5.安全性:JSON是纯文本形式,不包含任何可执行代码,不会执行恶意代码,有较高的安全性
传递JSON对象

需要使用@RequestBody注解,表示请求正文,与请求正文数据绑定,传入参数必须写入body正文中

java 复制代码
@RestController
@RequestMapping("com")
public class HelloControllor {
    
     @RequestMapping("/r10")
    public String r10(@RequestBody Person p){
        return p.toString();
    }

} 	

Body -> raw -> JSON格式

使用抓包工具,此时这里传入参数是在body正文中

Content - Type是json类型

如果去掉@RequestBody

传参失败

虽然发送请求时候带有参数,但是下面接收,未接收到上面传入的参数

获取URL中的参数

通过@PathVariable这个注解就可以获得对应参数

java 复制代码
@RestController
@RequestMapping("com")
public class HelloControllor {
    
    //这里如果获取参数名和此时接收这个参数名相同可以将省略原本参数名
    @RequestMapping("/r11/{age}/{name}")
    public String r11(@PathVariable("age") Integer age,@PathVariable("name") String name){
        return name +" " +  age;
    }

} 	

这里如果传入参数名和接收参数名相同,此时可以省略@PathVariable中的值

反之不相同就不可以省略

此时传入参数顺序要和其定义顺序一样,其接收使按照顺序进行接收的

如果顺序不一样,可能会因参数类型不同会报错 ,也可能会使数据接收到的值不符合预期

上传文件

@RequestPart

java 复制代码
@RestController
@RequestMapping("com")
public class HelloControllor {
    @RequestMapping("/r12")
    public String r12(@RequestPart("file") MultipartFile file) throws IOException {
        //获取文件名
        String fileName = file.getOriginalFilename();
        String fileType = file.getContentType();

        //上传指定路径
        file.transferTo(new File("D:/Java" + fileName));

        return "接收到的文件名:" + fileName + "类型:" + fileType;
    }

} 	

这里指定路径必须是已经存在的,如果不存在就会报错

获取Cookie/Session

HTTP协议自身是"无状态"协议

默认这次情况下HTTP协议的客户端和服务器之间这次通信,与下一次通信之间没有直接联系,但实际上是需要两个请求之间的关联关系

就像这次登录网站成功以后,第二次访问服务器就知道是否已经登录过了

这里令牌通常存放在Cookie中

就像去医院看病有了身份证 就可以查找出自己的就诊记录

有了会员卡 ,收银员就可以通过会员卡,查询自己会员信息及其购物信息

Cookie是客户端机制,下次发送回带上这次的"令牌",服务器中Session是需要验证这个令牌的信息

Session

就是一个会话

当客户端向服务端发送请求就会开启一个Session会话,每个客户端有自己独特的一个会话,这样服务器可以识别请求是那个客户端,当客户端结束会话服务器一段时间没有接收到客户端请求,就会结束会话

Session是服务器为了保存用户信息创建的一个特殊对象


SessionId是有服务器生成的"唯一字符串",服务器通过SessionId来确定其在那个会话中(对应会话内容)

1.当客户端向服务器发送登录请求,登录成功服务器就会在Session中新增一个会话,并Set-Cookie将SessionId发送给客户端

2.下次这个客户端发送其他请求,其都会带着这个SessionId,在Cookie字段中

3.服务器接收到请求后,匹配其SessionId对应的Session信息获取到对应的信息

如果没找到就会重新创建

Cookie 和 Session区别

1.Cookie是客户端保存用户信息一种机制,Session是服务器保存用户信息的一种机制

2.Cookie和Session主要通过对应的SessionId关联起来

3.Cookie和Session经常一起配合使用,但并不是必须

完全可以使用Cookie保存一些数据在客户端,可以不一定是SessionId等其他信息

Session中SessionId不是必须通过Set-Cookie/Cookie传递,也可以通过URL

传统方式获取Cookie

java 复制代码
@RestController
@RequestMapping("/request1")
public class RequestController {
    @RequestMapping("/getCookie")
    public String getCookie(HttpServletRequest request){
        Cookie[] cookies = request.getCookies();

        StringBuffer ret = new StringBuffer();

        if(cookies != null){
            for(Cookie cookie : cookies){
                //Cookie中可能回存放多个键和值
                System.out.println(cookie.getName() + " = " + cookie.getValue());
                ret.append(cookie.getName() + " = " + cookie.getValue());
            }
        }
        return "获取cookie成功:"+ret.toString();
    }
}

Spring MVS是基于Servlet API构建的原始Web框架,是以其为基础实现的
HttpServletRequest :对象代表客户端请求 ,当客户端通过HTTP访问服务器时候,会将其请求 头中所有信息都封装在里面,通过其对象提供的方法可以获取客户端请求的所有信息

HttpServletResponse对象代表的是服务器的请求,响应的信息都放在这个对象里 ,可以通过这个对象访问服务器响应中的所有信息

简单来说一个存放请求中的信息,一个是放的是响应中的信息

此时通过其对应方法获取Cookie,并且这两个对象是内置对象

http://127.0.0.1:8080/request1/getCookie

这里cookie中没有内容

但是我们可以直接在这里构造 ,此时就有了,但是别人的网站是没有权限随意构造的,因此cookie是不安全的,所以说使用Cookie时,后端需要进行Cookie校验

简洁方式获取cookiej,使用@CookieValue注解

java 复制代码
@RestController
@RequestMapping("/request1")
public class RequestController {
   @RequestMapping("/getCookie2")
    public String getCookie2(@CookieValue("name") String name , @CookieValue Integer age){
        return "name:" + name + " " + "age:" + age;
    }
   
}

获取和存储Session

其是服务端机制,需要先存储,再获取

java 复制代码
@RestController
@RequestMapping("/request1")
public class RequestController {
   @RequestMapping("/setSession")
    public String setSession(HttpServletRequest request,String name){
        //获取当前Session
        HttpSession session = request.getSession();
        session.setAttribute("name",name);
        session.setAttribute("userInfo",new Person("zhagnsan",18));

        return "设置session成功";
    }
    @RequestMapping("/getSession")
    public String getSession(HttpServletRequest request){
        //如果session不存在不会自己创建
        HttpSession session = request.getSession();
        String name = "";
        //获取session
        if(session != null && session.getAttribute("name") != null){
            name = (String)session.getAttribute("name");
            System.out.println("name:" + session.getAttribute("name"));
            System.out.println("userInfo:" + session.getAttribute("userInfo"));
        }
        return "获取session成功:" + name;
    }
   
}



没有session,服务器回创建一个把SessionId给客户端,后面Http请求时,把SessionId通过Cookie传递到了服务器

getSession()会获取到Cookie中的SessionId,可以SessionId通过这个获取到对应对象 ,Session 对象⽤HttpSession来描述

如果此时关闭,上次的session会消失,此时需要重新写入,才可以读取到

设置sessionhttp://127.0.0.1:8080/request1/setSession?name=edge

获取sessionhttp://127.0.0.1:8080/request1/getSession

获取session简单方式

@SessionAttribute和HttpSession作为参数

java 复制代码
@RestController
@RequestMapping("/request1")
public class RequestController {
     @RequestMapping("/getSession2")
    public String getSession2(@SessionAttribute("name") String name){
        return "获取session成功:" + name;
    }
    @RequestMapping("/getSession3")
    public String getSession3(HttpSession session ){
        return "获取session成功:" + session.getAttribute("name");
    }
   
}

http://127.0.0.1:8080/request1/setSession?name=java

http://127.0.0.1:8080/request1/getSession2
http://127.0.0.1:8080/request1/getSession3都可以访问


获取Header

HttpServletRequest 和 @RequestHeader来获取

java 复制代码
@RestController
@RequestMapping("/request1")
public class RequestController {
     @RequestMapping("/getHeader")
    public String getHeader(HttpServletRequest request){
        String userAgent = request.getHeader("User-Agent");

        return "userAgent:"+ userAgent;
    }
    @RequestMapping("/getHeader2")
    public String getHeader2(@RequestHeader("User-Agent") String userAgent){

        return "userAgent:"+ userAgent;
    }
   
}



响应

HTTP响应结果可以是数据,也可以是静态页面 ,也可以是针对响应的状态码Header等信息

返回静态界面

xml 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
  我是一个Index界面
</body>
</html>

这里如果直接使用IP和端口号,并且没有其他参数,这里默认会展示static中第一个界面

java 复制代码
@RestController
@RequestMapping("/request2")
public class ResponseController {
    @RequestMapping("/returnPage")
    public String returnPage(){
        return "/index.html";
    }
}

http://127.0.0.1:8080/request2/returnPage

发现这里并没有是被static中的对应静态界面
此时需要把@RestController改成@Controller

java 复制代码
@Controller
@RequestMapping("/request2")
public class ResponseController {
    @RequestMapping("/returnPage")
    public String returnPage(){
        return "/index.html";
    }
}

此时这里的index.html就正常展示了

@RestController和@Controller区别

@RestController = @Controller + @ResponseBody

@Controller :返回视图

@RestController:返回数据

@ResponseBody:加在对应方法/类上表示返回非视图,会将其格式进行转化,返回一个test/html信息

因为现在前后端分离模式,所以现在做后端不在关系前端相关内容

java 复制代码
@Controller
@RequestMapping("/request2")
public class ResponseController {
    @ResponseBody
    @RequestMapping("/returnPage")
    public String returnPage(){
        return "/index.html";
    }
}

此时加上@ResponseBody,又会像@Controller

返回Html代码片段

java 复制代码
@Controller
@RequestMapping("/request2")
public class ResponseController {
    @RequestMapping("/returnHtml")
    public String returnHtml(){
        return "<h1>Hello,HTML</h1>";
    }
}

此时没有加@ResponseBody,就会报错

java 复制代码
@Controller
@RequestMapping("/request2")
public class ResponseController {

    @RequestMapping("/returnHtml")
    @ResponseBody
    public String returnHtml(){
        return "<h1>Hello,HTML</h1>";
    }
}


返回JSON数据

java 复制代码
@Controller
@RequestMapping("/request2")
public class ResponseController {

    @RequestMapping("/returnJson")
    @ResponseBody
    public Person returnJson(){
        return new Person("zhangsan",18);
    }

    @RequestMapping("/returnJson2")
    @ResponseBody
    public Map<String,String> returnJson2(){
        Map<String,String> map = new HashMap<>();
        map.put("name","zhangsan");
        map.put("id","1");

        return map;
    }
}




设置状态码

java 复制代码
@Controller
@RequestMapping("/request2")
public class ResponseController {

    @RequestMapping("/setStatus")
    @ResponseBody
    public String setStatus(HttpServletResponse response){
        response.setStatus(401);
        return "状态码设置成功";

    }
}

获取Header

RequestMapping 的源码

java 复制代码
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Mapping
@Reflective({ControllerMappingReflectiveProcessor.class})
public @interface RequestMapping {
    String name() default "";

    @AliasFor("path")
    String[] value() default {};

    @AliasFor("value")
    String[] path() default {};

    RequestMethod[] method() default {};

    String[] params() default {};

    String[] headers() default {};

    String[] consumes() default {};

    String[] produces() default {};
}

value:指定映射的URL

method:指定method的类型,像GET,POSTd等

consumes:请求request内容类型

produces:返回类型内容,还可以设置返回值字符编码

params:指定requset中必须包含参数时,才让方法处理

headers: 指定request中必须包含某些指定的header值,才能让该⽅法处理请求

设置类型

java 复制代码
@Controller
@RequestMapping("/request2")
public class ResponseController {

    @RequestMapping(value = "/returnJson3",produces = "application/json;charset=utf-8")
    @ResponseBody
    public String returnJson3() {

        return "{\"success\":true}";
    }
}


设置其他header

java 复制代码
@Controller
@RequestMapping("/request2")
public class ResponseController {

    @RequestMapping("/setHeader")
    @ResponseBody
    public String setHeader(HttpServletResponse response){
        response.setHeader("MyHeader","666Header");

        return response.getHeader("MyHeader");
    }
}


综合练习

约定"前后端接口",接口又叫Application Programming Interface,前后端需要先约定好接口,这样双方按照接口文档开发

1.理解前后端交互过程

2.接口传参,数据返回,以及页面展示

加法计算器

需要计算两个数相加

请求路径:calc / sum

请求方式:GET/POST

接口描述:计算两数之和

前端代码

html 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <form action="calc/sum" method="post">
        <h1>计算器</h1>
        数字1:<input name="num1" type="text"><br>
        数字2:<input name="num2" type="text"><br>
        <input type="submit" value=" 点击相加 ">
    </form>
</body>
</html>

后端代码

java 复制代码
@RestController
@RequestMapping("/calc")
public class calcController {
    @RequestMapping("/sum")
    public String sum(Integer num1,Integer num2){
        if(num1 == null || num2 == null){
            return "参数不合法";
        }
        Integer sum = num1 + num2;

        return "计算结果为:" +sum  ;
    }
}


点击相加之后服务器就会返回结果

用户登录

需求 :用户输入账号和密码,后端需要验证账号和密码是否正确

1.如果不正确,前端跳转进行用户告知

2.如果正确,跳转到首页,显示登录用户

前后端接口

后端人员只提供两个功能

1.登录页面 :通过用户输入的账号和密码,校验是否正确返回给前端

2.首页:登录成功,显示当前用户的登录名,如果没有返回空

登录

复制代码
请求路径:/user/login
请求⽅式:POST
接口描述:校验账号密码是否正确

响应结果

复制代码
Content-Type: text/html
true//正确登录
false//密码或账号错误

登录进去

复制代码
请求路径:/user/getLoginUser
请求⽅式:GET
接⼝描述:查询当前登录的⽤⼾

响应结果

复制代码
Content-Type: text/html
响应内容: zhangsan
变量 类型 是否必须填写 含义
userName String 账号
passowrd String 密码

后端

java 复制代码
@RestController
@RequestMapping("/user")
public class LoginController {
    @RequestMapping("/login")
    public Boolean login(String userName, String password,HttpSession session){
//        if(userName == null || password == null || "".equals(userName) || "".equals(password)){
//            return  false;
//        }
        //判断姓名和密码是否为空或未填写
        if(!StringUtils.hasLength(userName) || !StringUtils.hasLength(password)){
            return  false;
        }
        //TODO 这里还只是演示是让其passowrd = userName = "admim",未使用数据库里面数据校验
        //判断账号和密码是否正确
        if("zhangsan".equals(userName) && "123456".equals(password)){
            session.setAttribute("userName","zhangsan");
            return true;
        }
        return  false;
    }
    //获取登录人
    @RequestMapping("/getLoginUser")
    public String getLoginUser(HttpSession session){
        //从session中获取信息
        String name = (String) session.getAttribute("userName");
        //如果用户已经登录
        if(StringUtils.hasLength(name)){
            return  name;
        }
        return "";
    }
}

这里StringUtils.hasLength是Spring提供的一个方法用来判断字符串是否有值,是否为空或等于"",这种情况直接返回false

java 复制代码
    @Contract("null -> false")
    public static boolean hasLength(@Nullable String str) {
        return str != null && !str.isEmpty();
    }

前端

login.html

后端根据用户输入的账号和密码进行校验,正确的login会跳转到index页面显示登录人

html 复制代码
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <title>登录页面</title>
</head>

<body>
  <h1>用户登录</h1>
  用户名:<input name="userName" type="text" id="userName"><br>
  密码:<input name="password" type="password" id="password"><br>
  <input type="button" value="登录" onclick="login()">
  
  <script src="jquery-3.7.1.js"></script>
  <script>
    //点击登录login方法
    //使用ajax来写
    function login() {
      $.ajax({
        type: "post",
        url: "/user/login",
        data: {
          "userName": $("#userName").val(),
          "password": $("#password").val()
        },
         success: function (result){
        if(result){
          location.href = "/index.html";
        }else{
          alert("输入有误");
        }
      }
      })
     
    }

  </script>
</body>

</html>
java 复制代码
1.window.location.href="book_list.html";
2.window.location.assign("book_list.html");
3.window.location.replace("book_list.html");

这里进行页面跳转有三种方法,1和2方法一样
href和assign :加载URL指定html,当页面转化为新的内容会在一个新的页面,两个页面可以进行相互跳转
replace :指定加载URL会替换之前页面,也就是两个页面公用一个窗口,所有两个页面无法相互跳转
上面使用ajax来响应登录按钮的响应

AJAX 是一种在无需重新加载整个网页的情况下,能够更新部分网页的技术。

AJAX 通过在后台与服务器交换数据,并能够更新部分网页内容

AJAX = 异步 JavaScript 和 XML,这样加载部分这样给用户体验比较好
https://www.runoob.com/ajax/ajax-intro.html
同步 :发送请求后,浏览器什么都不做,等到后端处理完数据,页面才会更新
异步:发送请求同时,浏览器可以干任何事情,将页面中一些内容加载出来

index.html:显示用户登录名

html 复制代码
<!doctype html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport"
        content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>用户登录首页</title>
</head>

<body>
    登录人: <span id="loginUser"></span>
    <!-- 本地 -->
    <script src="jquery-3.7.1.js"></script>
    <script>
        $.ajax({
            type: "get",
            url: "/user/getLoginUser",
            success: function(userName){
                $("#loginUser").text(userName);
            }
        })
    </script>
</body>

</html>


这里账号和密码有一个不正确都会提示

userName = "zhangsan" password = "123456"

此时这里页面是可以来回跳转的 href和assign

留言板

将用户输入信息,提交之后,后端存储数据,每次进入留言板留存上次的数据

需求

1.提交留言 :用户输入留言信息,将这里信息存储起来

2.展示留言:页面展示时,从后端获取存储的留言数据

java 复制代码
POST message/publish 提交留言
GET  message/getList 获取留言

lombok介绍

lombok是Java中的一个工具库,可以通过注解的方式简化开发

先导入依赖

xml 复制代码
<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
			<optional>true</optional>
		</dependency>
java 复制代码
@Data
public class MessageInfo {
    private String from;
    private String to;
    private String message;
    
}

使用@Data就会生成对应一些方法,像getter/setter,equals,toString等

这里将Java源代码,编译成.class字节码文件,这里就会将@Data注解加上的东西加到类上

正常Java运行

Lombok作用

但是这里直接使用@Data生成东西太多,这里可以选择一些进行生成特定方法

@Getter ⾃动添加getter⽅法
@Setter ⾃动添加setter⽅法
@ToString ⾃动添加toString⽅法
@EqualsAndHashCode ⾃动添加equals和hashCode⽅法
@NoArgsConstructor ⾃动添加⽆参构造⽅法
@AllArgsConstructor ⾃动添加全属性构造⽅法,顺序按照属性的定义顺序
@NonNull 属性不能为null
@RequiredArgsConstructor ⾃动添加必需属性的构造⽅法,final+@NonNull的属性为必需

@Data=@Getter+@Setter+@ToString+@EqualsAndHashCode+@RequiredArgsConstructor+@NoArgsConstructor

这里使用 EditStarters 插件,使用起来更容易

pom.xml文件中点击Generate......


这里点击之后,会自动生成对应pom.xml会自动生成对应依赖

信息对应MessageInfo类

java 复制代码
@Data
public class MessageInfo {

    private String from;
    private String to;
    private String message;

}

MessageController 类

将信息存储起来,也可以被获取

java 复制代码
@RestController
@RequestMapping("/message")
public class MessageController {
    private List<MessageInfo> messageInfos = new ArrayList<>();

    @RequestMapping("/getList")
    public List<MessageInfo> getList(){
        return messageInfos;
    }

    @RequestMapping("/publish")
    public Boolean publish(@RequestBody MessageInfo messageInfo){
        if(!StringUtils.hasLength(messageInfo.getFrom())
        ||!StringUtils.hasLength(messageInfo.getTo())
        ||!StringUtils.hasLength(messageInfo.getMessage())){
            return false;
        }
        //将这个信息添加到
        messageInfos.add(messageInfo);
        return true;
    }
}

前端

这里是将这里信息放到内存中,刷新之后,会先将上次存储的留言信息显示出来

getMessageList()先将以前的信息加载进来

html 复制代码
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>留言板</title>
    <style>
        .container {
            width: 350px;
            height: 300px;
            margin: 0 auto;
            /* border: 1px black solid; */
            text-align: center;
        }

        .grey {
            color: grey;
        }

        .container .row {
            width: 350px;
            height: 40px;

            display: flex;
            justify-content: space-between;
            align-items: center;
        }

        .container .row input {
            width: 260px;
            height: 30px;
        }

        #submit {
            width: 350px;
            height: 40px;
            background-color: orange;
            color: white;
            border: none;
            margin: 10px;
            border-radius: 5px;
            font-size: 20px;
        }
    </style>
</head>

<body>
    <div class="container">
        <h1>留言板</h1>
        <p class="grey">输入后点击提交, 会将信息显示下方空白处</p>
        <div class="row">
            <span>谁:</span> <input type="text" name="" id="from">
        </div>
        <div class="row">
            <span>对谁:</span> <input type="text" name="" id="to">
        </div>
        <div class="row">
            <span>说什么:</span> <input type="text" name="" id="say">
        </div>
        <input type="button" value="提交" id="submit" onclick="submit()">
        <!-- <div>A 对 B 说: hello</div> -->
    </div>

    <script src="jquery-3.7.1.js"></script>
    <script>
        //进入之前先导入之前的存储的数据

        getMessageList();

        function getMessageList(){
            $.ajax({
                type: "GET",
                url: "/message/getList",
                //接收List<MessageInfo> messageInfos
                success: function(messageList){
                    for(let messageInfo of messageList){
                        //构造节点
                        let divE = "<div>"+messageInfo.from +"对" + messageInfo.to + "说:" + messageInfo.message+"</div>";
                        //将这个拼接到后面
                        $(".container").append(divE);
                    }
                }
            })
        }
        function submit(){
            //1. 获取留言的内容
            var from = $('#from').val();
            var to = $('#to').val();
            var say = $('#say').val();
            if (from== '' || to == '' || say == '') {
                return;
            }
            $.ajax({
                type: "POST",
                url: "/message/publish",
                contentType: "application/json",
                //以JSON数据对象转成成字符串
                data: JSON.stringify({
                    "from": from,
                    "to": to,
                    "message": say
                }),
                //填写正确将数据拼接后面
                success: function(result){
                    if(result){
                        // 构造节点
                        var divE = "<div>" + from + "对" + to + "说:" + say + "</div>";
                        // 把节点添加到页面上    
                        $(".container").append(divE);
                        // 清空输入框的值
                        $('#from').val("");
                        $('#to').val("");
                        $('#say').val("");
                    }else{
                        alert("发表留言失败");
                    }
                }
            })            
        }
        
    </script>
</body>
</html>


这里点击这里刷新页面,也会先将前面存放的留言信息放到后面

应用分层

在阿里中有其Java项目分层

出自https://developer.aliyun.com/article/1502557

1.开放接口层 :封装service方法对外暴露为RPC接口或者通过web封装成HTTP接口进行网关安全控制或者流量控制等。

2.终端显示层 :各个模板渲染并显示的层级,我们可直接通俗的理解为前端渲染的页面。

3.web层 :主要对访问控制进行转发、参数校验、逻辑调用等,也就是我们常说的controller层。

4.service :真正逻辑处理的层级,严格来说一个controller就对应一个service,之所以用逻辑都封装在service是为了方便后续封装RPC接口时,可以通过直接调用service完成扩展。

5.manager :通用的业务处理层级,常用于第三方平台能力封装,或者中间件、缓存方案通用处理、对多mapper进行逻辑组合,总的来说manager层提供各种原子服务接口,而service层对这些原子接口进行逻辑编排。

6.mapper层 :持久层,也就是直接和数据库交互的一层。
应用分层是一种软件开发的设计思想,将应用程序分为多个层,每层都有自己的职责,多个层次来提供完整功能,需要根据项目难度,把项目分为三层、四次、五层等等

为什么要应用分层呢?

当业务较大,将其全部混在一起,代码逻辑比较混乱,扩展性差、耦合性比较高,为了让代码逻辑清晰,方便扩展,并且分层也可以加快开发

像前面MVC,就是一种分层思想的体现,分为Model、View、Controller,将其视图和业务分开,通过一个控制器连接

但现在比较流行的是前后端分离,因此有了新的一种架构,将其分为表现层、业务逻辑层和数据层
表现层 :和用户进行交互,展示数据结果和接收用户指令,最靠近用户的一层
业务逻辑层 :负责业务处理,业务的具体实现
数据层 :负责存储和管理应用程序的数据

Controller:控制层。接收前端发送的请求,对请求进⾏处理,并响应数据。

Service:业务逻辑层。处理具体的业务逻辑。

Dao:数据访问层。负责数据访问操作,包括数据的增、删、改、查

MVC和三层架构

二者都是软件工程领域中的架构模式

但是二者是从不同角度进行分层,都是为了解耦、分层和代码复用

MVC强调数据和视图 分离,将数据展示和处理分开,通过一个连接器 进行连接

三层架构强调的是高内聚和低耦合 ,将交互页面、业务处理和数据库数据处理 的逻辑分块
高内聚 :一格模块中各个元素之间联系的紧密程度,各个元素之间联系越高,内举行越高
低耦合:软件中各个层、模块之间的联系程度越低越好,这样修改一个部分的代码其他模块代码修改越少越好

相关推荐
ooseabiscuit1 小时前
Laravel3.x核心特性全解析
java·数据库·spring
倾颜7 小时前
从 textarea 到 AI 输入框:用 Tiptap 实现 / 命令、@ 引用和结构化请求
前端·langchain·next.js
kyriewen8 小时前
程序员连夜带团队跑路,省了23万:这AI太贵,真的用不起了
前端·javascript·openai
kyriewen9 小时前
你写的代码没有测试,就像出门不锁门——Jest + Testing Library 从入门到不慌
前端·单元测试·jest
yuzhiboyouye10 小时前
web前端英语面试
前端·面试·状态模式
是宇写的啊11 小时前
Spring AOP
java·spring
canonical_entropy11 小时前
下一代低代码渲染框架 nop-chaos-flux 的设计原则
前端·低代码·前端框架
东方小月11 小时前
5分钟搞懂Harness Engineering(驾驭工程):从提示词到AI Agent的进化之路
前端·后端·架构
我叫黑大帅11 小时前
为什么需要 @types/react?解决“无法找到模块 react 的声明文件”报错
前端·javascript·面试