1.1课程介绍
课程目标:
对框架的深度理解,前提是要应用。灵活应变。
项目介绍:牛客网的的讨论功能,讨论社区。
JavaWeb重点在服务端,不在前端页面。前端熟悉Html常用标签,CSS ,js
首页:帖子列表,发帖,敏感词过滤---重点,算法
登陆,查看帖子内容,回帖,权限控制,对别人的回复进行评论
登陆,发邮件,连接,验证码,账号密码判断,登陆查看个人主页,查看关注,被关注,曾经发帖,点击关注。
重点考虑性能的问题,如何不让服务器挂掉!!!
私信,回复私信
消息通知,消息队列-redis,多线程并发,消费者生产者模式。
全站搜索,专业搜索引擎,高性能,关键词高亮显示。如何中文分词,高亮显示,搜索引擎和数据库是如何同步数据的。
管理人使用:网站UV,活跃用户数。
如何提高性能,更加安全。
技术架构:
JavaEE
Spring 已经是实时上的行业标准了,包含多个框架。
Spring、SpringBoot Web应用框架
SpringMVC--如何处理浏览器的请求
MyBatis--如何访问数据库信息,用spring整合,SSM框架--实现项目基本功能,注册、登录、发帖、评论私信,时时刻刻。常规功能。
Redis--数据存在内存,高性能
Kafka--消息队列,发布消息
Elasticsearch--搜索引擎
用Spring整合,提高性能
Spring Security--提高系统安全
Spring Actuator -- 对线上系统进行监控,让运维人员随时监控系统状态
构建项目:对对项目进行创建、编译、测试、打包、生成文档,Maven
版本控制服务器:备份,版本控制,团队成员服务器
1.2搭建开发环境
安装Maven 创建编译打包管理jar包
~家目录--系统盘;.m2
中央仓库:Maven仓库,镜像--第三方对Maven的拷贝,阿里云,私服仓库
下载Maven
修改为阿里云镜像
配置环境变量,安装maven,cmd或者IDEA
Spring Initializr : 把常用的的包进行了整合,可以批量导入
配置项目基本信息,导入项目需要的jar,搜索功能,批量导入,创建一个JavaWeb项目
Spring Boot 的包中带有TomCat服务器
我们搜索的的东西就是起步依赖
起步依赖,一组包的组合,少量的依赖导入大量的包
几乎不用配置就可以
端点控制--上线后
server.port=8080//配置Tomcat服务器端口 server.servlet.context-path=/community//项目访问路径
1.3Spring入门
Spring全家桶
Spring Framewprk:基础核心---spring---spring.io
Spring Boot:构建项目,更方便
Spring Cloud:微服务,拆分为若干子项目
Spring Cloud Data Flow:Spring做数据集成,很多客服端(手机,手表,智能穿戴。。。),采集数据,如何集成。
IoC/AOP管理对象的一种思想,Spring中的Bean
Ioc:依赖注入,面向对象的管理思想
Aop:面向切面
Spring文档
https://www.docs4dev.com/docs/zh/spring-framework/5.1.3.RELEASE/reference/
控制反转:传统的方法,创建一个对象A,创建一个对象B,在A中调用B,AB间发生耦合,不利于后期维护,通过控制反转实现bean间的耦合度。--通过依赖注入--基于Ioc容器---一个Bean工厂
IOC容器帮我们管理Bean,需要知道Bean有哪些以及bean的配置信息,通过bean的配置了解bean间的关系。
在SpringBoot中,启动一个web项目不仅帮助我们启动了一个TomCat还帮助我们创建了一个Spring 容器。创建容器后会自动扫描某些包下的某些bean,并装配
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration//自动配置
@ComponentScan(//自动的组件扫描
excludeFilters = {@Filter(
type = FilterType.CUSTOM,
classes = {TypeExcludeFilter.class}
), @Filter(
type = FilterType.CUSTOM,
classes = {AutoConfigurationExcludeFilter.class}
)}
)
哪些会被扫描: @ComponentScan--自动的组件扫描,会自动扫描配置类所在包及子包下的bean bean上还要有@Controller注解 所以在bean上加Service/Componen/Repository/Controller,都是由Component实现
Service--业主组件
Componen--所有通用
Repository--数据库访问的组件
Controller--初级请求
@Service
public class AlphaService {
public AlphaService() {
System.out.print("构造器实例化");
}
@PostConstruct//该方法会在构造器后调用
public void init(){
System.out.print("初始化");
}
@PreDestroy//在对象销毁之前调用
public void destory(){
System.out.print("destory");
}
}
bean只被实例化一次,是单例模式,
通过加@Scpoe("singleton"、"prototype")--对bean是一个对象还是多个对象进行注释
"singleton"--默认是单例
"prototype"--每次访问都会创建一个new bean
用配置类装配第三方类
@Configuration
public class AlphaConfig {
@Bean
public SimpleDateFormat simpleDateFormat(){
//返回的对象会被转配到IOC容器
return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
}
}
通过依赖注入的方式获取bean
不用getBean()
写一个属性,通过注解为属性注入,通过注解指定具体实现类
IoC是控制反转的意思,这是一种面向对象的编程思想。Spring采用依赖注入的方式,实现了IoC思想。
Spring Framework的功能:Transactions(事务处理)/Scheduling/Security
@Autowired用于注入Bean,@Qualifier用于声明Bean的名称,@Bean用于装配第三方的Bean,
@Configuration用于声明配置类。
1.4SpringMVC入门
Http:
超文本传输协议
用于传输Html等内容的应用层协议,底层是TCP
规定了浏览器和服务间如何通信和通信的数据格式
https://www.ieft.org
https://developer.mozilla.org/zh-CN/ 火狐浏览器--支持中文
HTTP 流
当客户端想要和服务端进行信息交互时(服务端是指最终服务器,或者是一个中间代理),过程表现为下面几步:
- 打开一个TCP连接:TCP连接被用来发送一条或多条请求,以及接受响应消息。客户端可能打开一条新的连接,或重用一个已经存在的连接,或者也可能开几个新的TCP连接连向服务端。
- 发送一个HTTP报文:HTTP报文(在HTTP/2之前)是语义可读的。在HTTP/2中,这些简单的消息被封装在了帧中,这使得报文不能被直接读取,但是原理仍是相同的。
GET / HTTP/1.1 Host: developer.mozilla.org Accept-Language: fr
- 读取服务端返回的报文信息:
HTTP/1.1 200 OK Date: Sat, 09 Oct 2010 14:28:02 GMT Server: Apache Last-Modified: Tue, 01 Dec 2009 20:18:22 GMT ETag: "51142bc1-7449-479b075b2891b" Accept-Ranges: bytes Content-Length: 29769 Content-Type: text/html <!DOCTYPE html... (here comes the 29769 bytes of the requested web page)
- 关闭连接或者为后续请求重用连接。
当HTTP流水线启动时,后续请求都可以不用等待第一个请求的成功响应就被发送。然而HTTP流水线已被证明很难在现有的网络中实现,因为现有网络中有很多老旧的软件与现代版本的软件共存。因此,HTTP流水线已被在有多请求下表现得更稳健的HTTP/2的帧所取代。
请求由以下元素组成:
- 一个HTTP的method,经常是由一个动词像GET, POST 或者一个名词像OPTIONS,HEAD来定义客户端的动作行为。通常客户端的操作都是获取资源(GET方法)或者发送HTML form表单值(POST方法),虽然在一些情况下也会有其他操作。
- 要获取的资源的路径,通常是上下文中就很明显的元素资源的URL,它没有protocol (
http://
),domain(developer.mozilla.org
),或是TCP的port (en-US)(HTTP一般在80端口)。 - HTTP协议版本号。
- 为服务端表达其他信息的可选头部headers。
- 对于一些像POST这样的方法,报文的body就包含了发送的资源,这与响应报文的body类似。
响应报文包含了下面的元素:
- HTTP协议版本号。
- 一个状态码(status code),来告知对应请求执行成功或失败,以及失败的原因。
- 一个状态信息,这个信息是非权威的状态码描述信息,可以由服务端自行设定。
- HTTP headers,与请求头部类似。
- 可选项,比起请求报文,响应报文中更常见地包含获取的资源body。
服务器响应请求返回一个HTML文件,客户端解析HTML,发现需要引用了如图片,css,js等服务器资源,于是请求服务器,得到返回的CSS,js,图片等资源。
MVC三层架构
服务端三层架构:浏览器访问数据首先访问表现层,从表现层获取数据,表现层会调用业务层,处理业务,业务层调用数据层访问数据库数据。
MVC设计模式,和三层架构没有对应关系,主要解决表现层的问题
MVC是由DispatcherServlet调用:
老版
请求统一由前端控制器(front controller)DispatcherServlet处理,它会根据映射的注解,提供的路径找到controller,controller会把数据封装到model中,返回前端控制器;controller得到数据后会将试图模版(view template)中一些变量用model中的值进行替换,生成动态的网页
模版引擎:根据model数据动态生成HTML页面
html文件大家都看的懂。
标准表达式;那些需要替换
判断预循环:model数据是否null,根据不同数据做不同处理,循环处理集合和数组等数据
模版布局:复用一样的区域。
开发时模版的缓存不要开启,有缓存,,上线后可以开启缓存会降低服务器压力。
配置就是给某个Bean注入值。
spring boot文档:
https://docs.spring.io/spring-boot/docs/2.1.10.RELEASE/reference/htmlsingle/
查看常用配置:
# THYMELEAF (ThymeleafAutoConfiguration)
spring.thymeleaf.cache=true # Whether to enable template caching.
spring.thymeleaf.check-template=true # Whether to check that the template exists before rendering it.
spring.thymeleaf.check-template-location=true # Whether to check that the templates location exists.
spring.thymeleaf.enabled=true # Whether to enable Thymeleaf view resolution for Web frameworks.
spring.thymeleaf.enable-spring-el-compiler=false # Enable the SpringEL compiler in SpringEL expressions.
spring.thymeleaf.encoding=UTF-8 # Template files encoding.
spring.thymeleaf.excluded-view-names= # Comma-separated list of view names (patterns allowed) that should be excluded from resolution.
spring.thymeleaf.mode=HTML # Template mode to be applied to templates. See also Thymeleaf's TemplateMode enum.
spring.thymeleaf.prefix=classpath:/templates/ # Prefix that gets prepended to view names when building a URL.
spring.thymeleaf.reactive.chunked-mode-view-names= # Comma-separated list of view names (patterns allowed) that should be the only ones executed in CHUNKED mode when a max chunk size is set.
spring.thymeleaf.reactive.full-mode-view-names= # Comma-separated list of view names (patterns allowed) that should be executed in FULL mode even if a max chunk size is set.
spring.thymeleaf.reactive.max-chunk-size=0B # Maximum size of data buffers used for writing to the response.
spring.thymeleaf.reactive.media-types= # Media types supported by the view technology.
spring.thymeleaf.render-hidden-markers-before-checkboxes=false # Whether hidden form inputs acting as markers for checkboxes should be rendered before the checkbox element itself.
spring.thymeleaf.servlet.content-type=text/html # Content-Type value written to HTTP responses.
spring.thymeleaf.servlet.produce-partial-output-while-processing=true # Whether Thymeleaf should start writing partial output as soon as possible or buffer until template processing is finished.
spring.thymeleaf.suffix=.html # Suffix that gets appended to view names when building a URL.
spring.thymeleaf.template-resolver-order= # Order of the template resolver in the chain.
spring.thymeleaf.view-names= # Comma-separated list of view names (patterns allowed) that can be resolved.
封装请求和响应信息查看
@RequestMapping(" /http ")
public void http(HttpServletRequest request, HttpServletResponse response){
//获取请求数据
System.out.println(request.getMethod());
System.out.println(request.getServletPath());
Enumeration<String> enumeration = request.getHeaderNames();
while(enumeration.hasMoreElements()){
String name = enumeration.nextElement();
String value = request.getHeader(name);
System.out.println(name+":"+value);
}
}
在SpringMVC中处理请求和响应的几种方式:
GET
POST
如何响应HTML---model&view
响应JSON数据(异步请求)
@Autowired
private AlphaService alphaService;
@RequestMapping("/hello")
@ResponseBody
public String sayHello() {
return "Hello Spring Boot.";
}
@RequestMapping("/data")
@ResponseBody
public String getData() {
return alphaService.find();
}
@RequestMapping("/http")
public void http(HttpServletRequest request, HttpServletResponse response) {
// 获取请求数据
System.out.println(request.getMethod());
System.out.println(request.getServletPath());
Enumeration<String> enumeration = request.getHeaderNames();
while (enumeration.hasMoreElements()) {
String name = enumeration.nextElement();
String value = request.getHeader(name);
System.out.println(name + ": " + value);
}
System.out.println(request.getParameter("code"));
// 返回响应数据
response.setContentType("text/html;charset=utf-8");
try (
PrintWriter writer = response.getWriter();
) {
writer.write("<h1>牛客网</h1>");
} catch (IOException e) {
e.printStackTrace();
}
}
// GET请求
// /students?current=1&limit=20
@RequestMapping(path = "/students", method = RequestMethod.GET)
@ResponseBody
public String getStudents(
@RequestParam(name = "current", required = false, defaultValue = "1") int current,
@RequestParam(name = "limit", required = false, defaultValue = "10") int limit) {
System.out.println(current);
System.out.println(limit);
return "some students";
}
// /student/123
@RequestMapping(path = "/student/{id}", method = RequestMethod.GET)
@ResponseBody
public String getStudent(@PathVariable("id") int id) {
System.out.println(id);
return "a student";
}
// POST请求
@RequestMapping(path = "/student", method = RequestMethod.POST)
@ResponseBody
public String saveStudent(String name, int age) {
System.out.println(name);
System.out.println(age);
return "success";
}
// 响应HTML数据
@RequestMapping(path = "/teacher", method = RequestMethod.GET)
public ModelAndView getTeacher() {
//model层,打包好model,便于模版的熏染
ModelAndView mav = new ModelAndView();//创建modelAndView对象,用于封装数据和视图显示
mav.addObject("name", "张三");//封装数据
mav.addObject("age", 30);
mav.setViewName("/demo/view");//熏染视图模版
return mav;
}
//另一种简单的模式
@RequestMapping(path = "/school", method = RequestMethod.GET)
public String getSchool(Model model) {//是DispatcherServlet调用该方法时分析需要model,就实例化该对象并传参
//DispatcherServlet持有model数据和view视图,也能实现
model.addAttribute("name", "北京大学");
model.addAttribute("age", 80);
return "/demo/view";
}
// 响应JSON数据(异步请求)
// 注册输入用户名,当光标离开文本框,失去焦点,此时会连接数据库判断用户名是否被占用,并返回true/false,但是页面没有被刷新
//当前网页不刷新但是访问了一服务器并返回数据----异步请求
// Java对象 -> JSON字符串 -> JS对象 用json可以实现跨语言信息传输
@RequestMapping(path = "/emp", method = RequestMethod.GET)
@ResponseBody
public Map<String, Object> getEmp() {
Map<String, Object> emp = new HashMap<>();
emp.put("name", "张三");
emp.put("age", 23);
emp.put("salary", 8000.00);
return emp;
}
@RequestMapping(path = "/emps", method = RequestMethod.GET)
@ResponseBody
public List<Map<String, Object>> getEmps() {
List<Map<String, Object>> list = new ArrayList<>();
//控制与循环
Map<String, Object> emp = new HashMap<>();
emp.put("name", "张三");
emp.put("age", 23);
emp.put("salary", 8000.00);
list.add(emp);
emp = new HashMap<>();
emp.put("name", "李四");
emp.put("age", 24);
emp.put("salary", 9000.00);
list.add(emp);
emp = new HashMap<>();
emp.put("name", "王五");
emp.put("age", 25);
emp.put("salary", 10000.00);
list.add(emp);
return list;
}
HTTP协议规定了浏览器与服务器通信的四个步骤,依次是:建立连接、发送请求、接收响应、关闭连接
HTTP请求报文中,包含请求方式、请求路径、协议版本、消息头等内容
HTTP响应报文中,包含状态码、状态名、协议版本、消息头等内容
Spring MVC应用于表现层,降低了表现层代码的耦合度
Spring MVC的核心组件是DispatcherServlet,它负责分发所有的请求
Thymeleaf倡导自然模板,即以HTML文件为模板
Thymeleaf支持的语法有:标准表达式、判断与循环、模板的布局等
Thymeleaf生成动态HTML的前提是,你要为它提供模板文件与动态数据
@RequestMapping可以声明类或方法的访问路径,还可以声明请求的方式
@PathVariable可以将请求路径中的参数,绑定到控制器中方法的参数
@RequestParam可以将请求对象中的参数,绑定到控制器中方法的参数
@ResponseBody用于向浏览器响应字符串,同步异步
在控制器的方法中,我们可以直接使用Request、Response对象处理请求与响应
ModelAndView对象,既可以存储模型数据,又可以存储模板路径
1.5 MyBatis
MySQL的安装与配置——详细教程 - Winton-H - 博客园
配置:
改时区
MyBatis
在SpringBoot中,前三个(SqlSessionFactory/SqlSession/XML配置文件)被整合
mybatis-spring – spring整合Mybatis
密码被加密过,salt是随机的字符串 ,为了防止用户的密码过于简单被破解,在用户密码后面拼接一个五位的随机字符串,然后进行加密
1.6 开发社区首页
开发流程:web解决浏览器和服务器间的请求和响应,任何功能可以拆分为若干请求和响应
一次请求的执行流程:先DAO-->Service-->Controller
分布实现:
开发社区首页,显示前十个帖子。
开发分页组件,分页显示所有帖子。
评论表
第一步:开发数据访问层dao,涉及实体类,dao接口和mapper.xml配置SQL
entit----编写表实体类
dao----接口类--用@Mapper注解,编写dao接口对应的mapper配置文件,配置接口方法对应的SQL语句
第二步:开发Service层,业务层
@Service
public class UserService {
@Autowired
private UserMapper userMapper;
public User findUserById(int id){
return userMapper.selectById(id);
}
public User findUserByName(String username){
return userMapper.selectByName(username);
}
public User findUserByEmail(String email){
return userMapper.selectByEmail(email);
}
}
就是嗲用dao层mapper接口方法。
第三部:视图层
动态模版页面,参数替换。
<!--map.get("user")->user.getHeaderUrl() -->
<img th:src="${map.user.headerUrl}" class="mr-4 rounded-circle" alt="用户头像" style="width:50px;height:50px;">
<!--map.get("user")->user.getHeaderUrl() -->
<img th:src="${map.user.headerUrl}" class="mr-4 rounded-circle" alt="用户头像" style="width:50px;height:50px;">
<ul class="list-unstyled">
<!-- 循环 each ,每次遍历到集合中的一个map-->
<li class="media pb-3 pt-3 mb-3 border-bottom" th:each="map:${discussPosts}">
<a href="site/profile.html">
<!--动态数据-->
<!--map.get("user")->user.getHeaderUrl() -->
<!--/*@thymesVar id="user" type=""*/-->
<img th:src="${map.user.headerUrl}" class="mr-4 rounded-circle" alt="用户头像" style="width:50px;height:50px;">
</a>
<div class="media-body">
<h6 class="mt-0 mb-3">
<!--utext 可以处理转义字符-->
<!--/*@thymesVar id="post" type=""*/-->
<a href="#" th:utext="${map.post.title}">备战春招,面试刷题跟他复习,一个月全搞定!</a>
<!--根据帖子类型和状态决定置顶或者精华-->
<span class="badge badge-secondary bg-primary" th:if="${map.post.type==1}">置顶</span>
<span class="badge badge-secondary bg-danger" th:if="${map.post.status==1}">精华</span>
</h6>
<div class="text-muted font-size-12">
<!--帖子的作者和发布时间-格式化-->
<u class="mr-3" th:utext="${map.user.username}">寒江雪</u> 发布于 <b th:text="${#dates.format(map.post.createTime,'yyyy-MM-dd HH:mm:ss')}">2019-04-15 15:32:18</b>
<ul class="d-inline float-right">
<li class="d-inline ml-2">赞 11</li>
<li class="d-inline ml-2">|</li>
<li class="d-inline ml-2">回帖 7</li>
</ul>
</div>
</div>
</li>
</ul>
<!-- 分页 -->
<!-- 分页 -->
<nav class="mt-5" th:if="${page.rows>0}">
<ul class="pagination justify-content-center">
<li class="page-item">
<a class="page-link" th:href="@{${page.path}(current=1)}">首页</a>
</li>
<li th:class="|page-item ${page.current==1?'disabled':''}|">
<a class="page-link" th:href="@{${page.path}(current=${page.current-1})}">上一页</a></li>
<li th:class="|page-item ${i==page.current?'active':''}|" th:each="i:${#numbers.sequence(page.from,page.to)}">
<a class="page-link" href="#" th:text="${i}">1</a>
</li>
<li th:class="|page-item ${page.current==page.total?'disabled':''}|">
<a class="page-link" th:href="@{${page.path}(current=${page.current+1})}">下一页</a>
</li>
<li class="page-item">
<a class="page-link" th:href="@{${page.path}(current=${page.total})}">末页</a>
</li>
</ul>
</nav>
这个一定要注释到不然报错:
<!-- <build>-->
<!-- <plugins>-->
<!-- <plugin>-->
<!-- <groupId>org.springframework.boot</groupId>-->
<!-- <artifactId>spring-boot-maven-plugin</artifactId>-->
<!-- <version>2.4.5</version>-->
<!-- </plugin>-->
<!-- </plugins>-->
<!-- </build>-->
进一步开发分页功能:
分装分页组件 Page
根据点击的页码得到相应页面数据,显示,返回当前页页码,页面数据,总共有多少页数据。
//分装分页相关信息
//当前页码
private int current = 1;
//显示上限
private int limit = 10;
//数据总数(计算总页数)
private int rows;
//查询路径(复用分页连接)
private String path;
// 获取党旗前页的起始行
public int getOffset(){
return (current-1)*limit;//数据库从0行起
}
// 获取总的页数
public int getTotal(){
if(rows%limit==0){
return rows/limit;
}else{
return rows/limit+1;
}
}
// 获取起始页码
public int getFrom(){
int from = current-2;
return from<1 ? 1:from;//不要小于1
}
// 获取终止页码
public int getTo(){
int to = current+2;
int total = getTotal();
return to>total ? total:to;
}
// 方法调用钱,SpringMVC会自动实例化Model和Page,并将Page注入Model.
// 所以,在thymeleaf中可以直接访问Page对象中的数据
//关于分页的配置,有page分装了方法
page.setRows(discussPostService.findDiscussPostsRows(0));
page.setPath("/index");
//分页查询,前十条
List<DiscussPost> discussPostList = discussPostService.findDiscussPosts(0,page.getOffset(),page.getLimit());
//帖子列表
List<Map<String,Object>> discussPosts = new ArrayList<>();
if(discussPostList != null){
//遍历每个帖子
//将帖子和用户相关联
for(DiscussPost post : discussPostList){
Map<String,Object> map = new HashMap<>();
map.put("post",post);//帖子
// System.out.println(post.getTitle());
User user = userService.findUserById(post.getUserId());//发帖用户
// System.out.println(user.getUsername());
map.put("user",user);
discussPosts.add(map);
}
}
//添加到视图
model.addAttribute("discussPosts",discussPosts);
return "/index";
复用分页:
<!-- 分页 -->
<!-- 有数据才显示分页 -->
<nav class="mt-5" th:if="${page.rows>0}">
<ul class="pagination justify-content-center">
<li class="page-item">
<a class="page-link" th:href="@{${page.path}(current=1,limit=10)}">首页</a>
</li>
<!-- 当前页是1,就不能点击-->
<li th:class="|page-item ${page.current==1?'disabled':''}|">
<a class="page-link" th:href="@{${page.path}(current=${page.current-1})}">上一页</a></li>
<!--生产一个连续的数组-->
<!--点亮当前页-->
<li th:class="|page-item ${i==page.current?'active':''}|" th:each="i:${#numbers.sequence(page.from,page.to)}">
<a class="page-link" href="#" th:text="${i}">1</a>
</li>
<!--当前页是最后,就不能点击-->
<li th:class="|page-item ${page.current==page.total?'disabled':''}|">
<a class="page-link" th:href="@{${page.path}(current=${page.current+1})}">下一页</a>
</li>
<li class="page-item">
<a class="page-link" th:href="@{${page.path}(current=${page.total})}">末页</a>
</li>
</ul>
</nav>