javaWeb基础二:Servlet(java前后端交互的技术) |
您所在的位置:网站首页 › 写网页的步骤是什么 › javaWeb基础二:Servlet(java前后端交互的技术) |
2. Servlet
2.1 定义
Servlet是sun公司提供的一门用于开发动态web资源的技术,可以实现和客户端的交互,接收客户端请求和给客户端返回响应。 Sun公司在其API中提供了一个servlet接口,用户若想开发一个动态web资源需要完成以下2个步骤: 编写一个Java类,实现servlet接口。把开发好的Java类部署到web服务器中。 2.2 Servlet接口Servlet接口SUN公司定义了两个默认实现类,分别为:GenericServlet、HttpServlet。 HttpServlet指能够处理HTTP请求的servlet,它在原有Servlet接口上添加了一些与HTTP协议处理方法,它比Servlet接口的功能更为强大。因此开发人员在编写Servlet时,通常应继承这个类,而避免直接去实现Servlet接口。 HttpServlet在实现Servlet接口时,覆写了service方法,该方法体内的代码会自动判断用户的请求方式,如为GET请求,则调用HttpServlet的doGet方法,如为Post请求,则调用doPost方法。因此,开发人员在编写Servlet时,通常只需要覆写doGet或doPost方法,而不要去覆写service方法。 2.3 实现第一个Servlet基础操作步骤: 编程工具上设置Tomcat配置。(有的是单独在项目中设置) 创建web项目。 以2020版本的idea为例:(先创建基础的java项目,再在项目右键,点击Add Frameworks Support 添加,选择web项目。) 注:不同的版本、编程软件也有不同的创建方式,有得可以直接创建web项目。 创建一个类,继承HttpServlet,重写service方法。 public class MyServlet extends HttpServlet { @Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //使用getParameter方法接受key为str的value值。 String str = req.getParameter("str"); System.out.println("前端发送了一个请求"+str); //使用字符流打印返回浏览器一个响应。 resp.getWriter().print("hhhhh");//响应返回一个hhhh //注:可以直接返回HTML语言(js+css+html) //resp.getWriter().print("hhhh"); } } 注:在eclipse和myeclipse上,类能够直接继承HttpServlet,因为实现了自动导包。(idea需要手动导入servlet-api.jar包,才能正常extends HttpServlet类) servlet-api.jar包在Tomcat的lib目录下。 注册Servlet类(让web服务器识别。) 在web.xml文件中设置创建的Servlet类信息 MyServlet com.dream.servlet.MyServlet MyServlet /myservlet 编写前端页面。 欢迎来到web项目 点击发送请求 function fun01(){ location="myservlet?str=hello" }在web.xml中设置进入项目默认访问的第一个文件。 这里设置为上面的welcome.html welcome.html拓展:前端发送请求的三种方式:(前两种都只能以get方式提交,而第三种能够选择提交方式) 超链接(标签)window.locationform表单(action,method(get/post)),使用submit按钮提交 解析过程: Tomcat在加载Web应用时,就会把相应的web.xml文件中的数据读入到内存中。因此当Tomcat在解析web请求的时候,需要参考web.xml文件时,实际上只需要从内存中读取相关数据就可以了,不需要再到文件系统中读取web.xml。 图示: Servlet程序是由WEB服务器调用,web服务器收到客户端的Servlet访问请求后: 1,Web服务器首先检查是否已经装载并创建了该Servlet的实例对象。如果是,则直接执行第4步,否则,执行第2步。 2,装载并创建该Servlet的一个实例对象。 3,调用Servlet实例对象的init()方法。 4,创建一个用于封装HTTP请求消息的HttpServletRequest对象和一个代表HTTP响应消息的HttpServletResponse对象,然后调用Servlet的service()方法并将请求和响应对象作为参数传递进去。 5,WEB应用程序被停止或重新启动之前,Servlet引擎将卸载Servlet,并在卸载之前调用Servlet的destroy()方法。 测试: public class MyServlet01 extends HttpServlet { public MyServlet01(){ System.out.println("MyServlet01调用构造方法,创建对象"); } @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { this.doPost(req,resp); } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { System.out.println("MyServlet01接受到了来自前端的请求"); } @Override public void init(ServletConfig config) throws ServletException { System.out.println("MyServlet01进行初始化"); } @Override public void destroy() { System.out.println("MyServlet01销毁"); } }Servlet是一个供其他Java程序(Servlet引擎)调用的Java类,它不能独立运行,它的运行完全由Servlet引擎来控制和调度。 针对客户端的多次Servlet请求,通常情况下,服务器只会创建一个Servlet实例对象,也就是说Servlet实例对象一旦创建,它就会驻留在内存中,为后续的其它请求服务,直至web容器退出,servlet实例对象才会销毁。 在Servlet的整个生命周期内,Servlet的init方法只被调用一次。而对一个Servlet的每次访问请求都导致Servlet引擎调用一次servlet的service方法。对于每次访问请求,Servlet引擎都会创建一个新的HttpServletRequest请求对象和一个新的HttpServletResponse响应对象,然后将这两个对象作为参数传递给它调用的Servlet的service()方法,service方法再根据请求方式分别调用doXXX方法。 如果在元素中配置了一个元素,那么WEB应用程序在启动时,就会装载并创建Servlet的实例对象、以及调用Servlet实例对象的init()方法。 用途:为web应用写一个InitServlet,这个servlet配置为启动时装载,为整个web应用创建必要的公共数据。 MyServlet01 com.dream.servlet.MyServlet01 1Servlet被创建的两种情况: 1.前端发送请求,tomcat服务器发现该Servlet没有对象时就创建 2.在web.xml中配置 1,该Servlet就会在项目启动时由Tomcat创建对象。 Servlet被销毁: Tomcat关闭时调用destroy()销毁的方法 注: 一般情况下Servlet创建对象只创建一次,符合单例模式(在整个项目中该Servlet的对象是唯一),init() 初始化方法 也只调用一次,而doGet() 或 doPost() 每请求一次就会调用一次。 2.4.2 ServletConfig对象在Servlet的配置文件中,可以使用一个或多个标签为servlet配置一些初始化参数。 当servlet配置了初始化参数后,web容器在创建servlet实例对象时,会自动将这些初始化参数封装到ServletConfig对象中,并在调用servlet的init方法时,将ServletConfig对象传递给servlet。进而,程序员通过ServletConfig对象就可以得到当前servlet的初始化参数信息。 阅读ServletConfig API,并举例说明该对象的作用: 获得字符集编码 获得数据库连接信息实际的Servlet开发中,可以直接通过getServletConfig()的到ServletConfig对象。 MyServlet01 com.dream.servlet.MyServlet01 code UTF-8注:为键值对存在,放在servlet标签下,代表这个标签初始化的参数信息,在该servlet的init初始胡方法中能够获取到,使用示例: @Override public void init(ServletConfig config) throws ServletException { String code = config.getInitParameter("code"); System.out.println("MyServlet01进行初始化,获得编码格式:"+code); } 2.4.3 使用@WebServlet注解使用@WebServlet注解在Servlet类上,能够代替在web.xml配置文件中的一些相关设置,更加方便。 @WebServlet注解底层: @Target({ElementType.TYPE}) //作用于类 @Retention(RetentionPolicy.RUNTIME) //运行时有效 @Documented public @interface WebServlet { //指定Servlet 的 name 属性,等价于 没有显式指定,则该 Servlet 的取值即为类的全限定名。 String name() default ""; //该属性等价于 urlPatterns 属性。两个属性不能同时使用。标签。 String[] value() default {}; String[] urlPatterns() default {}; //指定 Servlet 的加载顺序,等价于 标签。默认为-1即不在服务器启动时创建。 int loadOnStartup() default -1; // WebInitParam[] initParams() default {};// 配置初始化参数 注解数组见下 boolean asyncSupported() default false; String smallIcon() default ""; String largeIcon() default ""; String description() default ""; String displayName() default ""; } //键值对,表示指定一组 Servlet 初始化参数,等价于标签。 @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface WebInitParam { String name(); String value(); String description() default ""; }简单使用: @WebServlet( value = "/MyServlet", loadOnStartup = 1, initParams = { @WebInitParam(name ="code",value = "UTF-8") }) public class MyServlet extends HttpServlet { public MyServlet(){ System.out.println("MyServlet调用构造方法,创建对象"); } @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { this.doPost(req,resp); } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { System.out.println("MyServlet接受到了来自前端的请求"); } @Override public void init(ServletConfig config) throws ServletException { String code = config.getInitParameter("code"); System.out.println("MyServlet进行初始化,获得编码格式:"+code); } @Override public void destroy() { System.out.println("MyServlet销毁"); } } 运行结果: MyServlet调用构造方法,创建对象 MyServlet进行初始化,获得编码格式:UTF-8 ...... MyServlet接受到了来自前端的请求 ...... MyServlet销毁 ...... Disconnected from server 2.5 线程安全问题出现原因:多个客户端访问同一个Servlet中的资源时,有可能会出现线程安全问题 测试案例: 在servlet中增加线程占用的时间(sleep方法),让多个浏览器去访问这个servlet,其中的数据读取就会出现脏读。 @WebServlet("/MyServlet") public class MyServlet extends HttpServlet{ //请求次数 int num; public MyServlet() { System.out.println("MyServlet被创建"); } protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doPost(request, response); } protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { num++; try { Thread.sleep(5000); //线程睡眠5秒 } catch (InterruptedException e) { e.printStackTrace(); } response.getOutputStream().println(num); } }在相差五秒内,前后使用两个浏览器访问这一个servlet,得到的请求次数(num)应该为: 先访问的页面出现1,而后访问的应该出现2。 但实际上两个页面都会显示2,这是因为在多线程访问一个servlet时,数据同时被两个线程处理,导致有些线程得到的数据不再准确。(在真正的项目中虽然没有sleep方法影响,servlet处理请求不会故意睡眠,但是在各种因素的影响下,我们还是不能保证数据的可靠性。) 处理方法: 1.将Servlet实现SingleThreadModel(已过时),因为当线程阻塞,就会创建新的Servlet对象(一般不使用,因为在这种情况下,servlet的对象不再是唯一的。) 2.利用线程锁机制, synchronized或lock 示例:在需要的处理请求的代码块上锁 @WebServlet("/MyServlet") public class MyServlet extends HttpServlet{ //请求次数 int num; //创建lock锁对象。 private Lock lock = new ReentrantLock(); public MyServlet() { System.out.println("MyServlet被创建"); } protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doPost(request, response); } protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { lock.lock();//上锁 num++; try { Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } response.getOutputStream().println(num); lock.unlock();//解锁 } } 2.6 重定向和转发 在前端页面,想要跳转到其他的页面或者是Servlet,可以直接通过超链接、提交表单、location三种实现,但是单纯两个页面之间的的跳转,是没有经过后端任何数据处理和数据传递的,这样的实现在大多数情况下是没有意义的。所以在需要数据处理和页面之间的数据传递时,常常会经过Servlet来实现。 Servlet跳转到某个页面/Servlet的两种方式为:重定向和转发。 重定向是通过响应(response)来完成的,而转发是通过请求(request)完成。 2.6.1 重定向 protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //在Servlet中使用 重定向方式 跳转到page.html response.sendRedirect("page.html"); } 2.6.2 转发 protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //在Servlet中使用 转发方式 跳转到page.html request.getRequestDispatcher("page.html").forward(request,response); }注:请求转发时,需要将request和response一同转发。 2.6.3 重定向和转发的区别浅显的说: 重定向是Servlet告诉浏览器,自己无法完成你的请求(第一次请求),并且通过响应告诉浏览器你想要完成这个请求应该去找谁,之后浏览器根据响应回来的信息(重定向的内容)重新给新的servlet或页面发送请求(第二次请求)。 转发则是Servlet知道自己无法完成浏览器的请求(一次请求),但是,我可以去找其他Servlet或页面帮你完成,而这些则不需要浏览器再操心。 (浏览器分别找重定向和转发借钱; 转发说:我虽然没有钱,但是我可以找别人借了,再借给你。 重定向说:我也没有钱,你找马云借吧,然后浏览器又去找马云借去了。(借了两次)) 重定向转发前端发送请求的次数2次1次跳转到普通页面(项目web文件夹下的页面)okok跳转到外部页面的区别(如http://www.baidu.com)okno跳转到受保护页面的区别(WEB-INF里的页面)nook解释: 1.转发无法跳转到外部页面的原因:当前服务器不能直接访问外部服务器。 2.重定向无法跳转到受保护页面的原因:浏览器不能直接访问WEB-INF内的资源 2.7 中文乱码问题出现原因: 前端后端编码不一致 浏览器默认使用UTF-8码表进行编码 ,Servlet使用ISO-8859-1码表进行编码 传输和接收方编码不一致导致乱码的产生 2.7.1 Request(请求)乱码Request请求分为post和get,分别有不同的解决方案 2.7.1.1 POST请求在Servlet的doPost方法中给请求的参数设置编码格式。 示例: protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //先给请求中的参数设置编码格式 request.setCharacterEncoding("UTF-8"); //再通过getParameter获取数据。 }注: 在执行 setCharacterEncoding()之前,不能执行任何 getParameter()操作。 通过 setCharacterEncoding 设置的编码方式只对 POST 方式提交的表单有效,对 GET 方式无效。 2.7.1.2 GET请求(Tomcat7.X版本)解决方案一:将获取到的数据先采用ISO-8859-1的格式解码成字节数组,在通过UTF-8的编码格式重写编码成完整数据。 示例: protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String parameter1 = request.getParameter("parameter1"); parameter1 = new String(parameter1.getBytes("ISO-8859-1"),"UTF-8"); String parameter2 = request.getParameter("parameter2"); parameter2 = new String(parameter2.getBytes("ISO-8859-1"),"UTF-8"); }解决方案二:在Tomcat根目录/conf/server.xml中设置编码格式 示例: 设置完成之后,在启动服务器的时候就会读取到URIEncoding的属性配置,然后再调用自己的一个setURIEncoding方法完成设置,形参的值为我们设置的UTF-8 。 底层setURIEncoding方法: protected String URIEncoding = null; public void setURIEncoding(String URIEncoding) { this.URIEncoding = URIEncoding; setProperty("URIEncoding", URIEncoding); } 2.7.1.3 GET请求(Tomcat8.X版本)Tomcat8.x的服务器在接收GET请求时,即使参数中有中文,也不会出现乱码,作者在底层设计上的一些改动 Tomcat的连接器组件(Connector) ,Connector是Tomcat中的一个重要的组件,它负责监听Tomcat收到的请求信息,并将这些请求信息传递给Servlet规范中所定义的Request,然后将转换后的请求交给Engine组件 去处理,最后将Engine返回的Response返回给客户端 。源码中我们可以看到, URIEncoding的默认值为UTF-8,所以在Tomcat8.x中,即使GET请求包含了中文的数据,也不会出现乱码了 public class Connector extends LifecycleMBeanBase { private Charset uriCharset = StandardCharsets.UTF_8; //查询Tomcat根目录/conf/catalina.properties配置文件中的属性 public static final boolean RECYCLE_FACADES = Boolean.parseBoolean(System.getProperty("org.apache.catalina.connector.RECYCLE_FACADES", "false")); public Connector() { this(null); } public Connector(String protocol) { setProtocol(protocol); ProtocolHandler p = null; try { Class clazz = Class.forName(protocolHandlerClassName); p = (ProtocolHandler) clazz.getConstructor().newInstance(); } catch (Exception e) { log.error(sm.getString( "coyoteConnector.protocolHandlerInstantiationFailed"), e); } finally { this.protocolHandler = p; } if (Globals.STRICT_SERVLET_COMPLIANCE) { uriCharset = StandardCharsets.ISO_8859_1; } else { uriCharset = StandardCharsets.UTF_8; } } } 2.7.2 Response(响应)乱码单独设置响应内容的编码格式 protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //设置响应内容的编码格式 response.setContentType("text/html;charset=UTF-8"); response.getWriter().println("哈喽!"); } 2.7.3 跳转到中文页面路径乱码 protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { System.out.println("MyServlet接受到了来自前端的请求"); //使用重定向的方式 跳转到 详情页面.html resp.sendRedirect(URLEncoder.encode("详情页面.html","UTF-8")); //使用转发的方式 跳转到 详情页面.html //req.getRequestDispatcher("详情页面.html").forward(req,resp); }注: 1.通过转发的方式,Servlet可以识别中文,不需要其他改变,而重定向则需要如上述配置。 e) throws ServletException, IOException { //设置响应内容的编码格式 response.setContentType("text/html;charset=UTF-8"); response.getWriter().println("哈喽!");} ### 2.7.3 跳转到中文页面路径乱码 ```java protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { System.out.println("MyServlet接受到了来自前端的请求"); //使用重定向的方式 跳转到 详情页面.html resp.sendRedirect(URLEncoder.encode("详情页面.html","UTF-8")); //使用转发的方式 跳转到 详情页面.html //req.getRequestDispatcher("详情页面.html").forward(req,resp); }注: 1.通过转发的方式,Servlet可以识别中文,不需要其他改变,而重定向则需要如上述配置。 2.不建议使用中文路径页面。 |
今日新闻 |
推荐新闻 |
CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3 |