JavaWeb入门 Spring官网 https://spring.io/
SpringFramework 配置繁琐 入门难度大
SpringBoot 简化配置、快速开发
SpringBoot可以帮助我们非常快速的构建应用程序、简化开发、提高效率。
1.SpringBootWeb快速入门 需求:使用SpringBoot开发一个web应用,浏览器发起请求/hello后,给浏览器返回字符串”Hello World~”.
编写请求处理类HelloController,添加请求方法hello,并添加注解,最后运行启动类.
1 2 3 4 5 6 7 8 9 @RestController public class HelloController { @RequestMapping("/hello") public String hello () { System.out.println("Hello World~" ); return "Hello World~" ; } }
2.HTTP协议 2.1 HTTP概述 概念 :超文本传输协议,规定了浏览器和服务器之间数据传输的规则.
特点 :
基于TCP协议:面向对象,安全
基于请求-响应模型的:一次请求对应一次响应
HTTP协议是无状态的协议:对于事务处理没有记忆能力.每次请求-响应都是独立的.
2.2 HTTP请求协议
HTTP-请求数据格式
请求行 :请求数据第一行(请求方式,资源路径,协议)
请求头 :第二行开始,格式 key:value
1 2 3 4 5 6 7 User-Agent: 浏览器版本 Accept:告诉浏览器,它所支持的数据类型 Accept-Encoding: 支持哪种编码格式 Accept-Language: 它的语言环境 Cache-Control: 缓存控制 Connection: 请求完成时,是断开还是保持连接 Host: 主机
请求体 :POST请求,存放请求参数
请求方式 :
GET 请求能够携带的参数比较少,大小有限制,会在浏览器的URL地址栏显示数据内容,不安全,但高效;
POST 请求能够携带的参数没有限制,大小没有限制,补会在浏览器的URL地址栏显示数据内容,安全,但不高效;
2.3 HTTP响应协议
HTTP-响应格式
响应行 :请求数据第一行(协议,状态码,描述)
响应头 :第二行开始,格式 key:value
1 2 3 4 5 6 7 8 9 Content-Type:表示该响应内容的类型,例如text/html,image/jpeg ; Content-Length:表示该响应内容的长度(字节数); Content-Encoding:表示该响应压缩算法,例如gzip ; Cache-Control:指示客户端应如何缓存,例如max-age=300表示可以最多缓存300秒 ; Set-Cookie: 告诉浏览器为当前页面所在的域设置cookie ;
响应体 :最后一部分,存放响应数据
响应状态码
状态码
信息
1xx
响应中-临时状态码
2xx
成功
3xx
重定向
4xx
客户端错误
5xx
服务端错误
2.4 HTTP-协议解析 目前比较流行的web服务器。如:Tomcat
3.WEB服务器 3.1 技术讲解 ASP:
微软:国内最早流行的;
在HTML中嵌入了VB的脚本,ASP+com;
在ASP开发中,基本一个页面就有几千行业务代码,页面及其乱;
维护成本高;
php:
开发速度快,功能强大,跨平台,代码简单;
无法承载大访问量的情况;
JSP/Servlet:
B/S:浏览和服务器
C/S:客户端和服务端
sun公司主推的B/S架构
基于Java语言的
可以承载三高问题带来的影响
语法像ASP,加强市场强度
3.2 Tomcat 官网:Apache Tomcat® - Welcome!
启动 startup.bat
关闭 shutdown.bat
访问 http://localhost:8080/
配置 server.xml
Tomcat也被称为Web容器,Servlet容器。Servlet程序需要依赖于Tomcat才能运行。
网站是如何进行访问的:
输入域名
检查本机hosts配置文件下有没有这个域名的映射;
有:直接返回对应的ip地址
没有:去DNS服务器找,找到返回,找不到就返回找不到
发布一个网站
将自己写的网站,放到服务器(Tomcat)中指定的web应用的文件夹(webapps)下。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 --webapps:Tomcat服务器的web目录 -ROOT -ceshi:网站目录名 -WEB-INF -classes:java程序 -lib:web应用所依赖的jar包 -web.xml:网站配置文件 -index.html 默认的首页 -static -css -style.css -js -img -......
4.请求响应
请求(HttpServletRequest):获取请求数据
响应(HttpServletResponse):设置响应数据
BS架构:客户端只需要浏览器,应用程序的逻辑和数据都存储在服务端
CS架构:客户端/服务器架构模式
4.1 请求
Postman是一款功能强大的网页调试与发送页面HTTP请求的Chrome插件
**作用:**常用于进行接口测试
简单参数
在向服务器发起请求时,向服务器传递的是一些普通的请求数据。
我们在这里讲解两种方式:
原始方式
SpringBoot方式
4.1.1 原始方式 在原始的Web程序当中,需要通过Servlet中提供的API:HttpServletRequest(请求对象),获取请求的相关信息。比如获取请求参数:
Tomcat接收到http请求时:把请求的相关信息封装到HttpServletRequest对象中
在Controller中,我们要想获取Request对象,可以直接在方法的形参中声明 HttpServletRequest 对象。然后就可以通过该对象来获取请求信息:
1 2 String request.getParameter("参数名" )
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 @RestController public class RequestController { @RequestMapping("/simpleParam") public String simpleParam (HttpServletRequest request) { String name = request.getParameter("name" ); String ageStr = request.getParameter("age" ); int age = Integer.parseInt(ageStr); System.out.println(name+" : " +age); return "OK" ; } }
以上这种方式,我们仅做了解。(在以后的开发中不会使用到)
4.1.2 SpringBoot方式 在Springboot的环境中,对原始的API进行了封装,接收参数的形式更加简单。 如果是简单参数,参数名与形参变量名相同,定义同名的形参即可接收参数。
1 2 3 4 5 6 7 8 9 10 11 12 13 @RestController public class RequestController { @RequestMapping("/simpleParam") public String simpleParam (String name , Integer age ) { System.out.println(name+" : " +age); return "OK" ; } }
结论:不论是GET请求还是POST请求,对于简单参数来讲,只要保证==请求参数名和Controller方法中的形参名保持一致==,就可以获取到请求参数中的数据值。
4.1.3 参数名不一致 如果方法形参名称与请求参数名称不一致,controller方法中的形参还能接收到请求参数值吗?
1 2 3 4 5 6 7 8 9 10 11 12 @RestController public class RequestController { @RequestMapping("/simpleParam") public String simpleParam (String username , Integer age ) { System.out.println(username+" : " +age); return "OK" ; } }
答案:运行没有报错。 controller方法中的username值为:null,age值为20
结论:对于简单参数来讲,请求参数名和controller方法中的形参名不一致时,无法接收到请求数据
那么如果我们开发中,遇到了这种请求参数名和controller方法中的形参名不相同,怎么办?
解决方案:可以使用Spring提供的@RequestParam注解完成映射
在方法形参前面加上 @RequestParam 然后通过value属性执行请求参数名,从而完成映射。代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 @RestController public class RequestController { @RequestMapping("/simpleParam") public String simpleParam (@RequestParam("name") String username , Integer age ) { System.out.println(username+" : " +age); return "OK" ; } }
注意事项 :
@RequestParam中的required属性默认为true(默认值也是true),代表该请求参数必须传递,如果不传递将报错如果该参数是可选的,可以将required属性设置为false
1 2 3 4 5 @RequestMapping("/simpleParam") public String simpleParam (@RequestParam(name = "name", required = false) String username, Integer age) {System.out.println(username+ ":" + age); return "OK" ;}
实体参数
在使用简单参数做为数据传递方式时,前端传递了多少个请求参数,后端controller方法中的形参就要书写多少个。如果请求参数比较多,通过上述的方式一个参数一个参数的接收,会比较繁琐。
此时,我们可以考虑将请求参数封装到一个实体类对象中。 要想完成数据封装,需要遵守如下规则:请求参数名与实体类的属性名相同
4.1.4 简单实体对象 定义POJO实体类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 public class User { private String name; private Integer age; 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; } @Override public String toString () { return "User{" + "name='" + name + '\'' + ", age=" + age + '}' ; } }
Controller方法:
1 2 3 4 5 6 7 8 9 @RestController public class RequestController { @RequestMapping("/simplePojo") public String simplePojo (User user) { System.out.println(user); return "OK" ; } }
4.1.5 复杂实体对象 复杂实体对象指的是,在实体类中有一个或多个属性,也是实体对象类型的。
请求参数名与形参对象属性名相同,按照对象层次结构关系即可接收嵌套实体类属性参数。
定义POJO实体类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 public class Address { private String province; private String city; public String getProvince () { return province; } public void setProvince (String province) { this .province = province; } public String getCity () { return city; } public void setCity (String city) { this .city = city; } @Override public String toString () { return "Address{" + "province='" + province + '\'' + ", city='" + city + '\'' + '}' ; } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 public class User { private String name; private Integer age; private Address address; 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 Address getAddress () { return address; } public void setAddress (Address address) { this .address = address; } @Override public String toString () { return "User{" + "name='" + name + '\'' + ", age=" + age + ", address=" + address + '}' ; } }
Controller方法:
1 2 3 4 5 6 7 8 9 @RestController public class RequestController { @RequestMapping("/complexPojo") public String complexPojo (User user) { System.out.println(user); return "OK" ; } }
4.1.6 数组集合参数 数组参数: 请求参数名与形参数组名称相同且请求参数为多个,定义数组类型形参即可接收参数
Controller方法:
1 2 3 4 5 6 7 8 9 @RestController public class RequestController { @RequestMapping("/listParam") public String listParam (@RequestParam List<String> hobby) { System.out.println(hobby); return "OK" ; } }
Postman测试:
方式一:xxxxxxxxxx?hobby=game&hobby=java
方式二:xxxxxxxxxx?hobby=game,java
集合参数: 请求参数名与形参集合对象名相同且请求参数为多个,@RequestParam 绑定参数关系
默认情况下,请求中参数名相同的多个值,是封装到数组。如果要封装到集合,要使用@RequestParam绑定参数关系
1 2 3 4 5 6 7 8 9 @RestController public class RequestController { @RequestMapping("/listParam") public String listParam (@RequestParam List<String> hobby) { System.out.println(hobby); return "OK" ; } }
Postman测试:
方式一:xxxxxxxxxx?hobby=game&hobby=java
方式二:xxxxxxxxxx?hobby=game,java
4.1.7 日期参数 因为日期的格式多种多样(如:2022-12-12 10:05:45 、2022/12/12 10:05:45),那么对于日期类型的参数在进行封装的时候,需要通过@DateTimeFormat注解,以及其pattern属性来设置日期的格式。
@DateTimeFormat注解的pattern属性中指定了哪种日期格式,前端的日期参数就必须按照指定的格式传递。
后端controller方法中,需要使用Date类型或LocalDateTime类型,来封装传递的参数。
Controller方法:
1 2 3 4 5 6 7 8 9 @RestController public class RequestController { @RequestMapping("/dateParam") public String dateParam (@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime updateTime) { System.out.println(updateTime); return "OK" ; } }
4.1.8 JSON参数 在学习前端技术时,我们有讲到过JSON,而在前后端进行交互时,如果是比较复杂的参数,前后端通过会使用JSON格式的数据进行传输。 (JSON是开发中最常用的前后端数据交互方式)
我们学习JSON格式参数,主要从以下两个方面着手:
Postman在发送请求时,如何传递json格式的请求参数
在服务端的controller方法中,如何接收json格式的请求参数
Postman发送JSON格式数据:
1 2 3 4 5 6 7 8 { "name" : "Tom" , "age" : 10 , "address" : { "province" : "beijing" , "city" : "beijing" } }
服务端Controller方法接收JSON格式数据:
传递json格式的参数,在Controller中会使用实体类进行封装。
封装规则:JSON数据键名与形参对象属性名相同,定义POJO类型形参即可接收参数。需要使用 @RequestBody标识。
@RequestBody注解:将JSON数据映射到形参的实体类对象中(JSON中的key和实体类中的属性名保持一致)
实体类:Address
1 2 3 4 5 6 7 8 9 10 11 12 13 public class Address { private String province; private String city; @Override public String toString () { return "Address{" + "province='" + province + '\'' + ", city='" + city + '\'' + '}' ; } }
实体类:User
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public class User { private String name; private Integer age; private Address address; @Override public String toString () { return "User{" + "name='" + name + '\'' + ", age=" + age + ", address=" + address + '}' ; } }
Controller方法:
1 2 3 4 5 6 7 8 9 @RestController public class RequestController { @RequestMapping("/jsonParam") public String jsonParam (@RequestBody User user) { System.out.println(user); return "OK" ; } }
4.1.9 路径参数 在现在的开发中,经常还会直接在请求的URL中传递参数。例如:
1 2 http://localhost:8080/user/1 http://localhost:8080/user/1/0
上述的这种传递请求参数的形式呢,我们称之为:路径参数。
路径参数:
前端:通过请求URL直接传递参数
后端:使用{…}来标识该路径参数,需要使用@PathVariable获取路径参数
Controller方法:
1 2 3 4 5 6 7 8 9 @RestController public class RequestController { @RequestMapping("/path/{id}") public String pathParam (@PathVariable Integer id) { System.out.println(id); return "OK" ; } }
传递多个路径参数:
Controller方法:
1 2 3 4 5 6 7 8 9 @RestController public class RequestController { @RequestMapping("/path/{id}/{name}") public String pathParam2 (@PathVariable Integer id, @PathVariable String name) { System.out.println(id+ " : " +name); return "OK" ; } }
4.2 响应 4.2.1 @ResponseBody @ResponseBody注解:
测试下响应数据:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 @RestController public class ResponseController { @RequestMapping("/hello") public String hello () { System.out.println("Hello World ~" ); return "Hello World ~" ; } @RequestMapping("/getAddr") public Address getAddr () { Address addr = new Address (); addr.setProvince("广东" ); addr.setCity("深圳" ); return addr; } @RequestMapping("/listAddr") public List<Address> listAddr () { List<Address> list = new ArrayList <>(); Address addr = new Address (); addr.setProvince("广东" ); addr.setCity("深圳" ); Address addr2 = new Address (); addr2.setProvince("陕西" ); addr2.setCity("西安" ); list.add(addr); list.add(addr2); return list; } }
4.2.2 统一响应结果 前面所编写的这些Controller方法中,返回值各种各样,没有任何的规范。
前端:只需要按照统一格式的返回结果进行解析(仅一种解析方案),就可以拿到数据。
统一的返回结果使用类来描述,在这个结果中包含:
定义在一个实体类Result来包含以上信息。代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 public class Result { private Integer code; private String msg; private Object data; public Result () { } public Result (Integer code, String msg, Object data) { this .code = code; this .msg = msg; this .data = data; } public Integer getCode () { return code; } public void setCode (Integer code) { this .code = code; } public String getMsg () { return msg; } public void setMsg (String msg) { this .msg = msg; } public Object getData () { return data; } public void setData (Object data) { this .data = data; } public static Result success () { return new Result (1 ,"success" ,null ); } public static Result success (Object data) { return new Result (1 ,"success" ,data); } public static Result error (String msg) { return new Result (0 ,msg,null ); } }
改造Controller:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 @RestController public class ResponseController { @RequestMapping("/hello") public Result hello () { System.out.println("Hello World ~" ); return Result.success("Hello World ~" ); } @RequestMapping("/getAddr") public Result getAddr () { Address addr = new Address (); addr.setProvince("广东" ); addr.setCity("深圳" ); return Result.success(addr); } @RequestMapping("/listAddr") public Result listAddr () { List<Address> list = new ArrayList <>(); Address addr = new Address (); addr.setProvince("广东" ); addr.setCity("深圳" ); Address addr2 = new Address (); addr2.setProvince("陕西" ); addr2.setCity("西安" ); list.add(addr); list.add(addr2); return Result.success(list); } }
Result(code、msg、data)
5.分层解耦 5.1 三层架构 5.1.1 介绍
单一职责原则:一个类或一个方法,就只做一件事情,只管一块功能。
这样就可以让类、接口、方法的复杂度更低,可读性更强,扩展性更好,也更利用后期的维护。
数据访问:负责业务数据的维护操作,包括增、删、改、查等操作。
逻辑处理:负责业务逻辑处理的代码。
请求处理、响应数据:负责,接收页面的请求,给页面响应数据。
按照上述的三个组成部分,在我们项目开发中呢,可以将代码分为三层:
Controller:控制层。接收前端发送的请求,对请求进行处理,并响应数据。
Service:业务逻辑层。处理具体的业务逻辑。
Dao:数据访问层(Data Access Object),也称为持久层。负责数据访问操作,包括数据的增、删、改、查。
基于三层架构的程序执行流程:
前端发起的请求,由Controller层接收(Controller响应数据给前端)
Controller层调用Service层来进行逻辑处理(Service层处理完后,把处理结果返回给Controller层)
Serivce层调用Dao层(逻辑处理过程中需要用到的一些数据要从Dao层获取)
Dao层操作文件中的数据(Dao拿到的数据会返回给Service层)
三层架构的好处:
复用性强
便于维护
利用扩展
5.2 分层解耦
高内聚指的是:一个模块中各个元素之间的联系的紧密程度,如果各个元素(语句、程序段)之间的联系程度越高,则内聚性越高,即 “高内聚”。
低耦合指的是:软件中各个层、模块之间的依赖关联程序越低越好。
涉及到Spring中的两个核心概念:
IOC容器中创建、管理的对象,称之为:bean对象
5.3 IOC&DI 上面我们引出了Spring中IOC和DI的基本概念,下面我们就来具体学习下IOC和DI的代码实现。
5.3.1 IOC&DI入门 任务:完成Controller层、Service层、Dao层的代码解耦
思路:
删除Controller层、Service层中new对象的代码
Service层及Dao层的实现类,交给IOC容器管理
为Controller及Service注入运行时依赖的对象
Controller程序中注入依赖的Service层对象
Service程序中注入依赖的Dao层对象
第1步:删除Controller层、Service层中new对象的代码
第2步:Service层及Dao层的实现类,交给IOC容器管理
使用Spring提供的注解:@Component ,就可以实现类交给IOC容器管理
第3步:为Controller及Service注入运行时依赖的对象
使用Spring提供的注解:@Autowired ,就可以实现程序运行时IOC容器自动注入需要的依赖对象
完整的三层代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 @RestController public class EmpController { @Autowired private EmpService empService ; @RequestMapping("/listEmp") public Result list () { List<Emp> empList = empService.listEmp(); return Result.success(empList); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 @Component public class EmpServiceA implements EmpService { @Autowired private EmpDao empDao ; @Override public List<Emp> listEmp () { List<Emp> empList = empDao.listEmp(); empList.stream().forEach(emp -> { String gender = emp.getGender(); if ("1" .equals(gender)){ emp.setGender("男" ); }else if ("2" .equals(gender)){ emp.setGender("女" ); } String job = emp.getJob(); if ("1" .equals(job)){ emp.setJob("讲师" ); }else if ("2" .equals(job)){ emp.setJob("班主任" ); }else if ("3" .equals(job)){ emp.setJob("就业指导" ); } }); return empList; } }
Dao层:
1 2 3 4 5 6 7 8 9 10 11 @Component public class EmpDaoA implements EmpDao { @Override public List<Emp> listEmp () { String file = this .getClass().getClassLoader().getResource("emp.xml" ).getFile(); System.out.println(file); List<Emp> empList = XmlParserUtils.parse(file, Emp.class); return empList; } }
5.3.2 IOC详解 前面我们提到IOC控制反转,就是将对象的控制权交给Spring的IOC容器,由IOC容器创建及管理对象。IOC容器创建的对象称为bean对象。
在之前的入门案例中,要把某个对象交给IOC容器管理,需要在类上添加一个注解:@Component
而Spring框架为了更好的标识web应用程序开发当中,bean对象到底归属于哪一层,又提供了@Component的衍生注解:
@Controller (标注在控制层类上)
@Service (标注在业务层类上)
@Repository (标注在数据访问层类上)
要把某个对象交给IOC容器管理,需要在对应的类上加上如下注解之一:
注解
说明
位置
@Controller
@Component的衍生注解
标注在控制器类上
@Service
@Component的衍生注解
标注在业务类上
@Repository
@Component的衍生注解
标注在数据访问类上(由于与mybatis整合,用的少)
@Component
声明bean的基础注解
不属于以上三类时,用此注解
注意事项:
声明bean的时候,可以通过value属性指定bean的名字,如果没有指定,默认为类名首字母小写。
使用以上四个注解都可以声明bean,但是在springboot集成web开发中,声明控制器bean只能用@Controller。
5.3.3 DI详解 依赖注入,是指IOC容器要为应用程序去提供运行时所依赖的资源,而资源指的就是对象。
在入门程序案例中,我们使用了@Autowired这个注解,完成了依赖注入的操作,而这个Autowired翻译过来叫:自动装配。
那如果在IOC容器中,存在多个相同类型的bean对象,会出现什么情况呢?
如何解决上述问题呢?Spring提供了以下几种解决方案:
@Primary
@Qualifier
@Resource
使用@Primary注解:当存在多个相同类型的Bean注入时,加上@Primary注解,来确定默认的实现。
使用@Qualifier注解:指定当前要注入的bean对象。 在@Qualifier的value属性中,指定注入的bean的名称。
@Qualifier注解不能单独使用,必须配合@Autowired使用
使用@Resource注解:是按照bean的名称进行注入。通过name属性指定要注入的bean的名称。
面试题 : @Autowird 与 @Resource的区别
@Autowired 是spring框架提供的注解,而@Resource是JDK提供的注解
@Autowired 默认是按照类型注入,而@Resource是按照名称注入