# day37 - 面面项目第一天
学习目标
第一章-版本控制
知识点-版本控制相关的概念
1.目标
2.路径
- 什么是版本控制
- 为什么要进行版本控制
- 常见版本控制工具
3.讲解
3.1 什么是版本控制
版本控制(Revision control)是一种软体工程技巧,籍以在开发的过程中,确保由不同人所编辑的同一档案(项目代码)都得到更新。 对我们开发的项目进行版本管理,管理的这些项目具有版本号,可以看到某一个版本的所有修改内容,也可以回退到以前的某一个版本。
一句话概括: 版本控制就是一个工具(软件), 它可以帮助我们管理项目资源,只要有一次写入动作,版本号就会往上提升
3.2 为什么要进行版本控制
- 回退代码到以前的某个阶段..
- 查看以往的代码修改记录及变化
- 协同开发时,合并同一文件中不同开发者写的代码
- 协同开发时定位修改代码的责任人 可以看到代码的修改记录
- 统计工作任务量
- 备份源代码
- 保护代码不被外泄
3.3 常见版本控制工具
- CVS 早期版本管理软件
- ClearCase IBM企业级大型版本管理工具, 收费
- VSS 微软推出的版本管理工具, 较少使用
- SVN市面最流行的版本管理工具之一,拥有CVS所有功能,修复了CVS的不足
- GIT 分布式版本管理工具, 已经是趋势
4. 小结
-
什么版本控制
是一种软体工程技巧, 确保由不同人所编辑的同一档案(项目代码)都得到更新。
-
为什么要使用版本控制
工作里面要协同开发
-
常见的版本控制工具
SVN
Git
知识点-SVN介绍
1.目标
2.路径
- SVN概述
- SVN工作方式【重点】
3.讲解
3.1SVN概述
Svn(Subversion)是版本管理工具,在当前的开源项目里(J2EE),几乎95%以上的项目都用到了 SVN。Subversion 项目的初衷是为了替换当年开源社区最为流行的版本控制软件CVS,在CVS的功能的基础上有很多的提升同时也能较好的解决CVS系统的一些不足。
3.2 SVN工作方式【重点】
4.小结
- SVN: 就是一个版本管理工具
- SVN工作方式
实操-SVN的安装
1.目标
2.路径
- SVN服务器的安装
- SVN客户端的安装
3.讲解
3.1SVN服务器的安装
详情参考资料 【 01_VisualSVNServer安装步骤.html】
3.2SVN客户端的安装
详情参考资料 【02_svn客户端安装步骤.html】
唯一要注意的地方就是:这里面有一个选项一定要选择安装它。
4.小结
- 注意
目录都不要有中文和空格
- 装客户端
- 先装客户端软件
- 再装语言包
实操-创建仓库和用户
1.目标
2.路径
- 创建仓库
- 创建用户
3.讲解
3.1创建仓库
详情参考资料 【03_VisualSVN创建仓库.html】
3.2创建用户
详情参考资料 【04_VisualSVN创建用户步骤.html】
4.小结
-
创建仓库
-
用户和组
- 给用户或者组分配权限
实操-SVN功能操作【重点】
1.目标
2.路径
3.讲解
3.1检出代码(checkout)
可以看成是让我们目前的本地和服务器处于同步状态。服务器上有什么,我们本地就有什么!
不管仓库里面现在有什么,第一次跟这个仓库对话永远是检出代码: 检出的操作可以看成是让本地和服务器处于同样的状态。
-
拷贝路径
-
通过svn客户端检出代码
-
检出的结果
.svn文件夹为版本控制的标记记录
3.2提交代码
-
新建文件
新建完成后,文件会有一个?号图标,代表当前文件未纳入到版本控制中
-
添加到版本控制中
添加操作完成后,文件图标会变为+号图标,代表当前文件已经纳入到版本控制中,但是没有上传到svn服务器中。
-
提交到版本控制器服务器
提交操作完成后,图标会变为对钩,代表文件已经上传到版本控制服务器中。
3.3更新代码
Update,它是更新操作,可以将svn服务器上的内容更新到本地
3.4冲突解决
-
冲突产生的原因
多个人同时修改一个文件
. eg: A,B同时下载最新的代码都修改同一个文件,A修改后上传提交。B修改提交,B就会提交失败。因为B的文件以及过时。必须重新更新。当更新的时候出现: -
编辑冲突,标记为解决
3.5更新|回退到某个版本
选中文件,右键更新之某个版本
3.6 版本库的备份和还原【了解】
- 到版本VisualSVN的仓库目录中拷贝要备份版本库。
- 还原
4.小结
4.1SVN图标
4.2 SVN使用规范
-
先更新,再提交
SVN更新的原则是要随时更新,随时提交。当完成了一个小功能,能够通过编译并且自己测试之后,谨慎地提交。
如果在修改的期间别人也更改了svn的对应文件,那么commit就可能会失败。如果别人和自 己更改的是同一个文件,那么update时会自动进行合并,如果修改的是同一行,那么合并时会产生冲突,这种情况就需要同之前的开发人员联系,两个人一起协商解决冲突,解决冲突之后,需要两人一起测试保证解决冲突之后,程序不会影响其他功能。
在更新时注意所更新文件的列表,如果提交过程中产生了更新,则也是需要重新编译并且完成自己的一些必要测试,再进行提交。这样既能了解别人修改了哪些文件,同时也能避免SVN合并错误导致代码有错。 -
多提交
每次提交的间歇尽可能地短,以几个小时的开发工作为宜。例如在更改UI界面的时候,可以每完成一个UI界面的修改或者设计,就提交一次。在开发功能模块的时候,可以每完成一个小细节功能的测试,就提交一次,在修改bug的时候,每修改掉一个bug并且确认修改了这个bug,也就提交一次。我们提倡多提交,也就能多为代码添加上保险。
-
不要提交不能通过编译的代码 (有错的代码不要提交!!!)
代码在提交之前,首先要确认自己能够在本地编译。如果在代码中使用了第三方类库,要考虑到项目组成员中有些成员可能没有安装相应的第三方类库。项目经理在准备项目工作区域的时候,需要考虑到这样的情况,确保开发小组成员在签出代码之后能够在统一的环境中进行编译。
-
每次提交必须写明注释
在一个项目组中使用SVN,如果提交空的标注或者不确切的标注将会让项目组中其他的成员感到很无奈,项目经理无法很清晰的掌握工作进度,无法清晰的把握此次提交的概要信息。在发现错误后也无法准确的定位引起错误的文件。所以,在提交工作时,要填写明晰的标注,能够概要的描述所提交文件的信息,让项目组其他成员在看到标注后不用详细看代码就能了解你所做的修改。
-
不要提交本地自动生成的文件 (忽略的功能)
IDEA里面的编译之后的文件target, .idea文件
-
不要提交自己不明白的代码
代码在提交入SVN之后,你的代码将被项目成员所分享。如果提交了你不明白的代码,你看不懂,别人也看不懂,如果在以后出现了问题将会成为项目质量的隐患。因此在引入任何第三方代码之前,确保你对这个代码有一个很清晰的了解。
-
慎用(不要用)锁定功能
在项目中要慎用锁定的功能,在你锁定了一个文件之后别人就无法继续修改提交该文件,虽然可以杜绝冲突的问题出现,但是可能会影响项目组中其他人员的工作。平时只有在编辑那些无法合并的文件(例如图片文件,flash文件等)时,才适当的采用锁定操作。
实操-在IDEA中使用SVN
1.目标
2.路径
- 配置SVN
- IDEA SVN使用
3.讲解
3.1 配置SVN
注意: 需要认证证书时,删除这个目录下所有内容C:\Users\Eric\AppData\Roaming\Subversion
选择File>Settings>Version Controller>Subversion,分别设置命令行客户端工具和svn配置信息存储目录。如下图:
3.2 IDEA SVN使用
3.2.1 浏览仓库
选中IDEA工具栏的VCS > Browse VCS Repository > Browse Subversion Repository
此时会出现如下界面,我们点击+号,输入本地SVN地址,再点击OK即可将本地SVN地址加入进来。
如果没有记住用户名和密码时,它就会弹出界面如下,需要我们输入正确的账号和密码方能实现仓库浏览。
账号密码正确后,如下浏览:
3.2.2 上传本地项目到SVN
在IDEA中,将本地项目共享到SVN,这个操作比较简单。
要做这个练习,我们先创建一个maven工程,这个工程还没有连接到SVN。然后再做以下操作
- 确保SVN功能已经开启:菜单:VCS > Enable Version Controller Integration
- 选中Subversion,此时功能的颜色会变成黄色,表明SVN功能已经开启。
- 2020版本的idea以后(2018版本可以忽略这个步骤),需要多增加这个步骤:
- 共享操作:在项目上右键 > Subversion > Share Directory
选择要共享的目标SVN地址,接着指定要共享的目标对象,点击Share之后,会在SVN创建一个对应的版本库文件,但该项目并未立刻提交。
提交对应工程:选择对应工程 > Subversion > Commit Directory
勾选要提交的内容,并填写上提交内容的注释信息,然后点击commit提交,提交完成后,项目就会被提交到SVN
成功后再查询仓库,此时新的项目就出现了
3.2.3 Add Commit
添加新文件时,idea会访问是否将新文件添加到SVN管理中
注意,此时的文件是没有上传到svn上的,需要通过commit file才行
如果文件有修改,也是要在项目上或者修改的文件上右击Subversion > Commit File
选择要提交的内容,并填写上注释,然后选中commit即可。
3.2.4 Update
如果需要更新服务器上的文件,选中要更新的项目并右键 > Subversion > Update Directory
一般直接点击OK即可,但如果需要选择历史版本,则勾上HEAD选项。
3.2.5 checkout 检出
打开上面浏览的SVN目录信息,并选中任意一个项目,并右键,选中checkout,该功能是将SVN上的资源检出到本地。
选中本地目录,用于存储从SVN服务器上检出的项目,目录选中后,直接点击OK,进入版本选择和存储目标地址选中
接着我们选中要检出的项目存储目标地址,并选中要检出的版本信息,最后点击OK即可。
按默认的就可以了
这里我们直接勾选第一个,从现有资源创建项目。
然后一路next,最后选择finish即可完成项目的导出。
3.2.6 解决冲突
多个用户同时编辑一个文件并都直接执行提交时,容易导致冲突产生,如下:
产生了冲突
我们在工程上执行更新操作
如果文件变更发生冲突,会看到如下界面,这里会有三个选项:
Accept Yours:接受你的版本,会以自己的版本为正确版本。
Accept Theirs:接受SVN上的版本,会把服务器的版本作为正确版本。
Merge:合并,需要将冲突手动排除。
最后还要把这个文件提交
4.小结
- 大家对着文档操作
第二章-自定义MVC框架【重点】
知识点-以模块为单位创建Servlet
1.目标
2.路径
- 复习 以模块为单位创建Servlet
- 使用反射优化
3.讲解
3.1以模块为单位创建Servlet
传统方式的开发一个请求对应一个Servlet:这样的话会导致一个模块的Servlet过多,导致整个项目的Servlet都会很多.能不能做一个处理?让一个模块都用一个Servlet处理请求. 用户模块, 创建UserServlet
早期的servlet
注册 ------ RegisterServlet
登录 ----- LoginServlet
...
模块化的Servlet
注册:http://localhost:8080/day36/userServlet?method=register
登录:http://localhost:8080/day36/userServlet?method=login
激活:http://localhost:8080/day36/userServle?method=active
- 以"模块为单位"创建Servlet的方式
class UserServlet extend HttpServlet{
... doGet(HttpServletRequest request, HttpServletResponse response){
//1.获得请求参数method
String methodStr = request.getParameter("method");
//2.判断, 调用对应的方法
if("regist".equals(methodStr)){
//注册
regist(request,response);
}else if("login".equals(methodStr)){
//登录
login(request,response);
}else if("active".equals(methodStr)){
//激活
active(request,response);
}
}
public void regist(HttpServletRequest request, HttpServletResponse response){
//1. 接受请求参数
//2. 调用业务
//3. 响应
}
public void login(HttpServletRequest request, HttpServletResponse response){
//1. 接受请求参数
//2. 调用业务
//3. 响应
}
public void active(HttpServletRequest request, HttpServletResponse response){
//1. 接受请求参数
//2. 调用业务
//3. 响应
}
}
3.2使用反射优化
发现在上面的doGet方法里面,有大量的if语句,能不能不写if语句
注册:http://localhost:8080/day31/userServlet?method=regist
登录:http://localhost:8080/day31/userServlet?method=login
激活:http://localhost:8080/day31/userServlet?method=active
- 添加依赖
<dependencies>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat7-maven-plugin</artifactId>
<version>2.2</version>
<configuration>
<!-- 指定端口 -->
<port>82</port>
<!-- 请求路径 -->
<path>/</path>
</configuration>
</plugin>
</plugins>
- 优化后
package com.itheima.servlet01;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.lang.reflect.Method;
/*
用于处理一切与用户有关的请求!
*/
@WebServlet("/user")
public class UserServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
try {
//1. 获取请求参数,必须要携带一个参数: method
String methodName = req.getParameter("method");
//2. 找方法
Method m = this.getClass().getMethod(methodName , HttpServletRequest.class , HttpServletResponse.class);
//3. 调用方法
m.invoke(this ,req , resp );
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req, resp);
}
public void register(HttpServletRequest req, HttpServletResponse resp){
System.out.println("调用了UserServlet的register方法~!~");
}
public void login(HttpServletRequest req, HttpServletResponse resp){
System.out.println("调用了UserServlet的login方法~!~");
}
}
4.小结
- 一个模块就创建一个Servlet, 每个请求都携带请求参数method=xx
知识点-BaseServlet
1.目标
2.路径
- BaseServlet分析
- BaseServlet编写
3.讲解
3.1BaseServlet分析
以模块为单元创建Servlet
-- 用户模块
注册:http://localhost:8080/day31/userServlet?method=regist
登录:http://localhost:8080/day31/userServlet?method=login
激活:http://localhost:8080/day31/userServlet?method=active
class UserServlet extend HttpServlet{
//找到对应的方法 执行
... doGet(HttpServletRequest request, HttpServletResponse response){
//1. 获得method请求参数的值【说白了就是方法名】
String methodStr = request.getParameter("method");
//2.获得字节码对象
Class clazz = this.getClass();
//3.根据方法名反射获得Method
Method method = clazz.getMethod(methodStr,HttpServletRequest.class,HttpServletResponse.class);
//4.调用
method.invoke(this,request,response)
}
public void regist(HttpServletRequest request, HttpServletResponse response){
//1. 接受请求参数
//2. 调用业务
//3. 响应
}
public void login(HttpServletRequest request, HttpServletResponse response){
//1. 接受请求参数
//2. 调用业务
//3. 响应
}
public void active(HttpServletRequest request, HttpServletResponse response){
//1. 接受请求参数
//2. 调用业务
//3. 响应
}
}
-- 订单模块
package com.itheima.servlet;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.lang.reflect.Method;
/*
专门处理一切有关订单的请求
*/
@WebServlet("/order")
public class OrderServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
try {
//1. 获取参数method:
String methodName = req.getParameter("method");
//2. 找方法,让方法执行!
Method method = this.getClass().getMethod(methodName, HttpServletRequest.class, HttpServletResponse.class);
//3. 调用
//以前的调用: 对象.方法(参数)
//this.register(req, resp);
//反射: 方法对象.invoke(对象,参数1,参数2...)
method.invoke(this,req, resp);
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req, resp);
}
public void add(HttpServletRequest req, HttpServletResponse resp){
System.out.println("执行了OrderServlet的add...");
}
public void update(HttpServletRequest req, HttpServletResponse resp){
System.out.println("执行了OrderServlet的update...");
}
}
}
- 以模块划分创c建servlet的问题分析
- 以模块来创建servlet在一定程度上可以减少我们的servlet创建的数量,并且可以把同一个模块的代码给聚集起来,统一管理 (UserServlet | ProductServlet | OrderServlet)
- 但是也要看到这种模式的问题,进行横向对比,把UserServlet和ProductServlet进行对比,两个servlet里面的doGet方法和doPost方法几乎一模一样。唯一的不同就是它们所处理的功能不同。
- 此时就可以考虑把这些共性的代码给提取出来, 这里最好采用父类向上的抽取,而不是以前我们习惯的工具类方法抽取。因为这里连方法名都一样了。
3.2 BaseServlet编写
- 步骤
- 定义一个类,名字一般叫做BaseXXX, BaseServlet。
- 这个类也是一个Servlet, 把刚才的UserServlet和OrderServlet的doGet方法和dw让UserServlet和OrderServlet继承BaseServlet即可
package com.itheima.servlet02;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.lang.reflect.Method;
/*
1. 这个类是所有模块化Servlet的父亲类
2. 它用来存放共性的代码 doGet和doPost方法
3. 要想存放doGet和doPost代码,那么这个类必须是一个Servlet
4. 所有的模块化的Servlet都来继承这个BaseServlet即可,不需要去继承HttpServlet了。
5. BaseServlet只是用来做判断请求,调用方法的,不管是Get请求过来还是post请求过来
都会执行子类的方法,所以只需要重写service方法即可,不需要重写doGet和doPost方法了!
6. 有几个疑问:
6.1 这里的this 是谁? 子类对象!
6.2 这个BaseServlet也是一个Servlet,请问,要不要注册这个Servlet?
不用!,因为这个类这是用来判断请求参数,调用方法的。它没有处理请求的能力,所以不用写映射!
*/
public class BaseServlet extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
try {
//1. 获取请求参数,必须要携带一个参数: method
String methodName = req.getParameter("method");
//2. 找方法
Method m = this.getClass().getMethod(methodName , HttpServletRequest.class , HttpServletResponse.class);
//3. 调用方法
m.invoke(this ,req , resp );
} catch (Exception e) {
e.printStackTrace();
}
}
}
- UserServlet
package com.itheima.servlet02;
import javax.servlet.Servlet;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.lang.reflect.Method;
/*
用于处理一切与用户有关的请求!
*/
@WebServlet("/user02")
public class UserServlet extends BaseServlet {
public void register(HttpServletRequest req, HttpServletResponse resp){
System.out.println("调用了UserServlet的register方法~!~");
}
public void login(HttpServletRequest req, HttpServletResponse resp){
System.out.println("调用了UserServlet的login方法~!~");
}
}
- OrderServlet
package com.itheima.servlet02;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.lang.reflect.Method;
/*
用于处理一切与订单有关的请求!
*/
@WebServlet("/order02")
public class OrderServlet extends BaseServlet {
public void add(HttpServletRequest req, HttpServletResponse resp){
System.out.println("调用了OrderServlet的add方法~!~");
}
public void update(HttpServletRequest req, HttpServletResponse resp){
System.out.println("调用了OrderServlet的update方法~!~");
}
}
4.小结
- 把公共的代码抽取到父类BaseServlet, 模块的Servlet继承BaseServlet就可以了
- BaseServlet里面的异常打印不要删除
- 在BaseServlet里面反射的API是getMethod 也就意味着模块里面的Servlet的方法应该是public的
知识点-自定义MVC框架
1.目标
2.路径
- BaseServlet问题分析
- 自定义MVC框架初级版本
- 自定义MVC框架终极版本
3.讲解
3.1BaseServlet问题分析
这种实现要求客户端的每个请求都必须传递一个method参数,否则无法找到对应的web方法,另外这个控制器(Servlet)也必须==要继承父类BaseServlet,因为是在父类的service()中完成请求解析与调用。这种方式不是很便捷并且耦合度比较高。
接下来想进一步重构BaseServlet,让其用起来更方便更便捷,让它们的耦合关系更松散,比如写一个总控制器类 (类似BaseServlet),这个总控制器继承HttpServlet类,其他的控制器(子控制器 【类似UserServlet...】)是普通Java类,谁都不用继承(不用继承总的控制器类,也不用继承HTTPServlet类),然后由总控制器动态的去调用子控制器的业务方法,这种动态调用是采用在参数中不加入method的方式来实现的。
-
问题
-
- 所有的请求必须携带一个method的参数,否则会抛出异常。
- 所有的模块servlet必须继承BaseServlet,才能够使用上判断的逻辑
-
-
解决
-
- 创建一个总的控制器类BaseServlet (其实就是类似刚才的BaseServlet)
- 这个BaseServlet要捕获所有的请求,(比如:捕获 *.do 这样的请求)
- 模块化的类,就是一个普通的java类(不用继承BaseServlet,也不用继承HTTPServlet)
- 传递参数的时候,也不用携带method参数,由总的控制器类(BaseServlet) 根据请求来决定到底调用哪一个模块类的方法执行。
BaseServlet中,可以灵活的处理客户端的请求,应对部分项目开发没有问题。
-
3.2自定义SpringMVC框架初级版本
3.2.1思路
- 创建@RequestMapping注解
- 注解的作用主要是为了给servlet里面的方法做映射的。
- 注解一般是作用于方法上,并且保留到运行的时候还要存在于字节码
- 创建UserServlet , 定义方法,
- 在这个方法上面添加@RequestMapping注解
- 创建BaseServlet 继承HttpServlet, 映射的路径配置 *.do
- 在BaseServlet的 重写的service()方法里面
//1.获得请求的URI和项目部署路径, 截取获得映路径 eg: /user/login
//2.扫描某个包里面的所有类的字节码对象集合List
//3.遍历字节码对象集合List
//4.获得类里面的所有的Method
//5.遍历所有的Method
//6.获得method上面的RequestMapping注解 获得注解的value属性值
//7.判断value属性值是否和获得映路径一致, 一致 就调用method
3.2.2代码实现
- RequestMapping注解
package com.itheima.servlet03;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/*
这个注解打在方法身上,用于表示什么样的请求地址,能够执行这个方法。
@RequestMapping("/user/register")
public void register(){
}
步骤:
1. 定义注解,里面给出一个value属性,用于设置映射地址
2. 这个注解只能打在方法上,所以要表示出来它的位置
3. 这个注解要保留到运行阶段还存在,因为当程序运行的时候,我们需要去解析类的
字节码,取到方法身上的注解,所以这个注解不能在前面的阶段就丢失掉了
源码 --- 编译 --- 运行
*/
@Target(ElementType.METHOD) // 表示这个@RequestMapping注解,只能打在方法上
@Retention(RetentionPolicy.RUNTIME) // 表示这个注解一直到运行阶段都还存在!
public @interface RequestMapping {
String value();
}
- UserServlet
package com.itheima.servlet03;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/*
这时用于处理一切有关用户请求的类,它不是一个Servlet!
1. 在里面定义方法
2. 给方法打上注解
*/
public class UserServlet {
//以后浏览器只要输入了: localhost:82/user/reigster.do
@RequestMapping("/user/register")
public void register(HttpServletRequest req , HttpServletResponse resp){
System.out.println("调用了UserServlet的register方法!~!~");
}
//以后浏览器只要输入了: localhost:82/user/login.do
@RequestMapping("/user/login")
public void login(HttpServletRequest req , HttpServletResponse resp){
System.out.println("调用了UserServlet的login方法!~!~");
}
}
- BaseServlet
package com.itheima.servlet03;
import com.itheima.utils.ClassScannerUtils;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.List;
/*
这是唯一的一个Servlet,它能抓请求,它只抓住尾巴带 .do的请求
1. 它的映射地址是: *.do
2. 重写service方法,在service方法里面就要做决定,判断去执行哪个类的哪个方法!
*/
@WebServlet("*.do")
public class BaseServlet extends HttpServlet {
//来一次请求,service方法就会被执行一次!
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
try {
//1. 获取请求地址 localhost:82/user/register.do
//System.out.println(req.getRequestURL()); // http://localhost:82/user/register.do
//System.out.println(req.getRequestURI()); // /user/register.do
String uri = req.getRequestURI();
//2. 截取请求地址 /user/register
String path = uri.substring(0 , uri.lastIndexOf('.')); // /user/register
//3. 扫描com.itheima.servlet03 包,得到包下所有类的字节码class
List<Class<?>> classList = ClassScannerUtils.getClasssFromPackage("com.itheima.servlet03");
//4. 遍历每一份字节码,并且取出这份字节码(类)的所有方法
for (Class<?> clazz : classList) {
Method[] methods = clazz.getMethods();
//5. 遍历每一方法,得到方法身上的注解 @RequestMapping
for (Method method : methods) {
//6. 取出注解里面的value值 /user/register
//6.1 得到注解对象
RequestMapping annotation = method.getAnnotation(RequestMapping.class);
//6.2 得到注解里面的值
if(annotation != null){
String value = annotation.value(); // /user/register
//7. 用上面截取到的字符串地址和value值比较,如果一样,就让方法执行!
if(value .equals(path)){
//正向调用:
//XXX x = new XXX();
//x.xxx();
//UserServlet us = new UserServlet();
//us.register(req, resp);
//反射调用:
method.invoke(clazz.newInstance(), req, resp);
}
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
3.3自定义SpringMVC框架终极版本
存在问题:
- 在总的控制器里面扫描指定包的时候,扫描的类有点多了。
- 等来了请求再去解析,再去反射创建对象调用 ---> 影响处理请求的速度的
- 扫描的包名写死了
3.1.1思路
-
在项目部署的时候,就马上启动扫描的工作了。
a. 把扫描的工作提前放到servlet的init方法去做: init --- service - destroy
b. 让这个init方法调用的时机再提前一些,提前到项目发布的时候就执行。
c. 设置
1 -
DispatcherServlet注册的时候,不要使用注解来注册了,而是使用xml来注册, 在xml里面注册的时候,就可以配置servlet的初始化参数,用户指定扫描具体那个包。
-
扫描得到包下的所有类了之后,不是每一个类我们都要查看它的所有方法有没有requestMapping这个注解
- 只看类上有没有一个注解@Controller , 这个注解是自定义的注解。
- 谁身上有这个注解,我们就解析这个类里面的所有方法。
-
在init方法里面完成扫描的工作之后,需要使用一个Map集合来进行映射关系,也就是完成扫描工作之后,使用一个map集合来包装 映射的地址和controller的对象。 map集合里面就包装了请求地址和调用方法的一个关系。 KEY : 请求地址 , value : 包装的javaBean。
class MethodBean{
private Method method; //具体的方法对象
private Object obj; //调用这个方法要用的实例对象
}
Map<String , MethodBean> map ;
MethodBean mb = new MethodBean(方法对象 , 类的实例对象);
map.put("/user/register" , mb);
-
在请求来的时候,在service里面获取请求的地址
-
截获请求的地址了之后,就可以直接问map要方法来调用。
MethodBean mb = map.get("/user/register");
Method m = mb.getMethod();
m.invoke(mb.getObj() , req , resp);
注册:
1. Servlet注册时候要用xml 注册, 不要使用注解测试
2. 需要让这个Servlet初始化的时机更提前一些,所以需要写上 <load-on-startup>1</load-on-startup>
3. 需要在注册的时候,提供一个初始化参数,这个初始化参数就是用来设置扫描哪个包!
<init-param>
<param-name>packageName</param-name>
<param-value>com.itheima.servlet</param-value>
</init-param>
4. 也只抓尾巴带 .do的请求
init :
1. 扫描包,找类,找方法,把注解的内容给分解出来。
2. 就得到了什么样的地址会执行什么类的什么方法,可以使用一个JavaBean和Map集合来封装他们
class MethodBean{
private Method m; //这个方法
private Object o; // 调用这个方法要用的对象
}
map.put(映射地址 ,MethodBean对象 );
service :
1. 截取地址, /user/register
2. 拿着这个地址,去问map要东西:
3.1.2代码实现
- Controller
打在类上
package com.itheima.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/*
1. 这是专门打在类身上的注解,做进一步的筛选|过滤工作,
避免我们扫描太多类。
2. 这个注解仅仅是一个标记而已,并不需要写任何内容,所以这是一个空注解
3. 这个注解是打在类身上,所以要打上元注解 @Target(ElementType.TYPE)
4. 也要保留到运行阶段: @Retention(RetentionPolicy.RUNTIME)
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Controller {
}
- RequestMapping
打在方法上
package com.itheima.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/*
这个注解打在方法身上,用于表示什么样的请求地址,能够执行这个方法。
@RequestMapping("/user/register")
public void register(){
}
步骤:
1. 定义注解,里面给出一个value属性,用于设置映射地址
2. 这个注解只能打在方法上,所以要表示出来它的位置
3. 这个注解要保留到运行阶段还存在,因为当程序运行的时候,我们需要去解析类的
字节码,取到方法身上的注解,所以这个注解不能在前面的阶段就丢失掉了
源码 --- 编译 --- 运行
*/
@Target(ElementType.METHOD) // 表示这个@RequestMapping注解,只能打在方法上
@Retention(RetentionPolicy.RUNTIME) // 表示这个注解一直到运行阶段都还存在!
public @interface RequestMapping {
String value();
}
- UserController
package com.itheima.controller;
import com.itheima.annotation.Controller;
import com.itheima.annotation.RequestMapping;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/*
这是用来处理一切与用户有关请求的类
1. 类身上打注解 @Controller
2. 方法上打注解 @RequestMapping
*/
@Controller
public class UserController {
@RequestMapping("/user/register")
public void register(HttpServletRequest req, HttpServletResponse resp){
System.out.println("调用了UserController的register方法~!");
}
@RequestMapping("/user/login")
public void login(HttpServletRequest req, HttpServletResponse resp){
System.out.println("调用了UserController的login方法~!");
}
}
- MethodBean
主要是用来封装 被调用的方法和 调用这个方法用到的实例对象
package com.itheima.bean;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.lang.reflect.Method;
/*
这是用来封装具体的controller对象和它的方法。
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class MethodBean {
private Method method ; //真正要调用的方法!
private Object object ; //调用这个方法要用到的对象!
}
- DispatcherServlet
package com.itheima.servlet;
import com.itheima.annotation.Controller;
import com.itheima.annotation.RequestMapping;
import com.itheima.bean.MethodBean;
import com.itheima.utils.ClassScannerUtils;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/*
1. 这是唯一的一个Servlet,它能抓请求,抓住的是尾巴带 .do 的请求
2. 在这个类里面重写两个方法: init 和 service
init : 在init方法里面做初始化工作,请求还没到来之前,我们先去扫描包,准备好调用的方法和对象
service : 当请求来的时候,在service里面去执行方法即可
注册:
1. 这个servlet不能使用注解注册,必须使用xml注册
2. 在xml里面的注册的时候,可以让servlet初始化的时机提前,提前到项目发布:
<load-on-startup>1</load-on-startup>
3. 在注册的时候,使用初始化参数,来配置扫描的包名,以便在init方法能读取到扫描哪个包!
4. 映射的地址路径还是 *.do
init:
1. 读取注册时候指定的初始化的包名
2. 扫描这个包下的所有类
3. 遍历每一个类,如果哪个类身上有注解 @Controller,那么就获取它的所有方法
4. 遍历每一个方法,获取方法身上的注解 @RequestMapping
4.1 要考虑有的方法没有写上@RequestMapping的情况,得到的对象为空的情况
5. 获取注解里面的value值 @RequestMapping("/user/register") ==== /user/register
6. 创建MethodBean,封装(方法, 对象)
7. 在外面创建一个Map集合,KEY : 注解的映射地址, Value : methodBean对象
map.put("/user/register" , methodBean);
service:
1. 获取请求地址
2. 截取字符串 /user/register
3. 拿着这个字符串去map集合里面找出来MethodBean对象
4. 调用方法即可。
*/
public class DispatcherServlet extends HttpServlet {
Map<String , MethodBean > map = new HashMap<>();
@Override
public void init(ServletConfig config) throws ServletException {
try {
//1. 获取初始化包名
String packageName = config.getInitParameter("packageName");
//2. 扫描包下的所有类
List<Class<?>> classList = ClassScannerUtils.getClasssFromPackage(packageName);
//3. 遍历每一个类,
for (Class<?> clazz : classList) {
//4. 看一下这个类身上有没有注解@Controller
if (clazz.isAnnotationPresent(Controller.class)){
//5. 获取类中的所有方法
Method[] methods = clazz.getMethods();
//6. 遍历每一个方法
for (Method method : methods) {
//7. 获取方法身上的注解@RequestMapping里面的value值
RequestMapping annotation = method.getAnnotation(RequestMapping.class);
//8. 如果有这个注解,就取它的value值
if(annotation != null){
String mapping = annotation.value(); // /user/register
//封装MethodBean
MethodBean methodBean = new MethodBean(method ,clazz.newInstance() );
map.put(mapping , methodBean);
}
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
try {
//1. 获取请求地址:
String uri = req.getRequestURI(); // /user/register.do
String path = uri.substring(0 , uri.lastIndexOf('.')); // /user/register
//2. 拿着路径去map集合里面找对象执行
MethodBean methodBean = map.get(path);
//3. 要考虑没有找到可以执行方法的情况
if(methodBean != null){
Method method = methodBean.getMethod();
Object object = methodBean.getObject();
//调用方法
method.invoke(object , req , resp);
}else{
System.out.println("没有找到可执行的方法:" + path);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
- web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
version="2.5">
<!--注册DispatcherServlet-->
<servlet>
<servlet-name>dispatcher</servlet-name>
<servlet-class>com.itheima.servlet.DispatcherServlet</servlet-class>
<!--告诉DispatcherServlet要扫描的包是哪个-->
<init-param>
<param-name>packageName</param-name>
<param-value>com.itheima.controller</param-value>
</init-param>
<!--让DispatcherServlet在项目发布的时候,就进行初始化,执行init方法-->
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatcher</servlet-name>
<url-pattern>*.do</url-pattern>
</servlet-mapping>
</web-app>
4.小结
- 我们自定义MVC框架的目的: 锻炼大家的能力. 但是我们后面的大项目, 工作里面的开发是使用别人写好的框架 SpringMVC
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构