JavaWeb开发知识总结(annotation,Servlet3.0,文件上传,动态代理)

来源:互联网 发布:钢结构厂房起脊算法 编辑:程序博客网 时间:2024/05/29 11:50

JavaWeb开发知识总结(annotation,Servlet3.0,文件上传,动态代理)

1. annotation概述

annotation:注解,代表程序的一些特殊的功能,注解是由虚拟机进行处理的。

JDK中提供的常用的注解:

# @Override :描述子类重写父类的方法:    * JDK1.5版本中,该注解只能应用在类的继承上.    * JDK1.6版本中,该注解可以应用在类的实现上(实现接口).# @SuppressWarnings :压制警告,参数是all是压制所有的警告.# @Deprecated  :描述方法过时,标记方法过时.

自定义注解:使用@interface 关键声明

格式:public @interface 注解名称{ 内容 }

注解的属性定义:

# 注解定义时需加上`()`,如:    @interface Anno {    int a();    boolean b() default false;    }# 定义属性时:可以使用default关键字声明属性的默认值# 注解的属性的数据类型:  1. 基本数据类型:primitive type  2. String类型:String  3. Class类型:Class  4. 注解类型:annotation  5. 枚举类型:enumeration  6. 以上类型的一维数组:# 特殊的属性名称:value  * 如果使用注解的时候,只出现了value属性,value属性可以省略,但当出现多个属性时,value属性名不能省略

注解的存在阶段:

# Java类的状态:源代码阶段-->Class阶段-->运行阶段 源代码阶段            Class阶段             运行阶段.java源文件 --编译-->   .class   --加载-->   JVM中运行# 自定义的注解只存在与源代码阶段,编译后就不存在了,可以通过JDK提供的元注解来增大自定义注解的存在阶段:1. 源代码阶段:@Retention(value = RetentionPolicy.SOURCE)2. Class阶段:@Retention(value = RetentionPolicy.CLASS)3. 运行阶段:@Retention(value = RetentionPolicy.RUNTIME)

案例:自定义注解完成简单的注解单元测试的功能

package com.annotation;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;/** * 自定义注解  MyTest.java */@Retention(value=RetentionPolicy.RUNTIME) // 设置自定义注解运行阶段public @interface MyTest {}/**  * 运行的核心类  CoreRunner.java  * 和单元测试的原理相同,均需要一个程序运行的主方法  * 获取要进行单元测试的类的Class对象,通过反射获取该类的所有的方法  * 遍历所有的方法,如果方法上有注解,则通过反射执行该方法即可  */public class CoreRunner {    public static void main(String[] args) throws Exception {        /**         * 获得测试类的Class.         * 获得Class中的所有的方法.         * 遍历每个方法,查看每个方法上是否有MyTest注解.         * 有MyTest注解,这个方法就执行.         */        // 1.获得测试类的Class:        Class clazz = AnnotationDemo1.class;        // 2.获得Class中的所有的方法:规定了测试的方法必须是public        Method[] methods = clazz.getMethods();        // 3.遍历每个方法:        for(Method method:methods){            // 获取当前的方法是否含有MyTest注解            boolean flag = method.isAnnotationPresent(MyTest.class);            if(flag){                // 说明方法上有MyTest注解,执行方法                method.invoke(clazz.newInstance(), null);            }        }    }}

2. Servlet 3.0

Servlet3.0和Servlet2.5的区别:

# Servlet3.0和Servlet2.5相比提供了三个特性:1. 注解开发:可以取代web.xml的配置文件    * @WebServlet:Servlet的注解    * @WebListener:监听器的注解    * @WebFilter:过滤的注解2. 文件上传:没有提供获取上传的文件名等相关的API3. 异步请求:本质是开启新的线程进行处理请求

注解开发的原理:

// Servlet2.5中web.xml配置文件配置Servlet<servlet>  <servlet-name>servlet名称</servlet-name>  <servlet-class>servlet的完全限定名</servlet-class></servlet><servlet-mapping>  <servlet-name>servlet名称</servlet-name>  <url-pattern>servlet的访问路径</url-pattern></servlet-mapping>// Servlet3.0中使用注解@WebServlet(urlPatterns="Servlet的访问路径")   在定义Servlet时添加注解,相当于是配置文件的中访问路径public class Servlet名称 extends HttpServlet {...}

3. 文件上传

文件上传要素:必须同时满足

# 文件上传的要素: 三个条件必须满足1. 表单的提交的方式必须是POST. (由于Get方式有数据大小的限制,不使用get方式上传数据)2. 表单中需要有文件上传的表单元素:这个元素必须有name属性和值:<input type="file" name="upload">3. 表单的enctype属性的值必须是multipart/form-data.    (enctype的默认值是:"application/x-www-form-urlencoded",该默认值在后台中只能获取文件的名称,无法获取文件内容)

文件上传的技术:

# 文件上传技术: Servlet2.5不支持文件上传* Servlet3.0        :比2.5版本增加的功能:注解开发,文件上传,异步请求.* JSPSmartUpload    :嵌入到JSP中完成文件上传.主要用于(jsp+javabean开发模式)的.* FileUpload        :Apache的文件上传组件.* Struts2           :底层是FileUpload.

使用Servlet3.0实现文件上传:

注意事项(重点):

# form表单的设为enctype="multipart/form-data"后,Servlet中getParameter()等获取参数的方法无效,需要在Servlet上添加 @MultipartConfig 注解# 上传的文件的信息不能通过getParameter()等获取参数的方法获取,需要使用getPart()方法进行操作

文件上传存在的问题:

# 文件的重名问题:解决方法:1. 使用UUID生成随机的字符串作为文件名,数据库中存储的是文件原始名称和真实文件存储路径的映射;2. 使用时间戳+原始文件名作为存储的文件名,数据库中存储的是文件原始名称和真实文件存储路径的映射.# 同一目录下存储文件过多:解决方法:1. 按时间分:一个月一个目录,一个星期一个目录,一天一个目录2. 按数量分:一个目录下存5000个文件,创建一个新的目录,再去存放.3. 按用户分:为每个用户创建一个单独目录 存放文件.4. 按目录分离算法分:    * 使用唯一文件名.hashCode(); -- 得到一个代表当前这个文件的int类型值.    * int类型占4个字节32位.可以让hashCode值&0xf; 得到一个int值,用这个int值作为一级目录.    * 让hashCode右移4位 &0xf ;得到一个int值,用这个int值作为二级目录.依次类推.

案例实现:

<!--fileupload.jsp--><% @ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%><!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"><html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><title>文件上传</title></head><body><h1>文件上传</h1><form action="${ pageContext.request.contextPath }/FileUploadServlet" method="post" enctype="multipart/form-data">    <table border="1">        <tr>            <td>文件描述</td>            <td>                <textarea rows="4" cols="20" name="filedesc"></textarea>            </td>        </tr>        <tr>            <td>文件上传</td>            <td>                <input type="file" name="upload" />            </td>        </tr>        <tr>            <td colspan="2">                <input type="submit" value="上传"/>            </td>        </tr>    </table></form></body></html>
package com.itheima.servlet;import java.io.File;import java.io.FileOutputStream;import java.io.IOException;import java.io.InputStream;import java.io.OutputStream;import javax.servlet.ServletException;import javax.servlet.ServletInputStream;import javax.servlet.annotation.MultipartConfig;import javax.servlet.annotation.WebServlet;import javax.servlet.http.HttpServlet;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import javax.servlet.http.Part;import com.itheima.utils.UpLoadUtils;/** * 文件上传的Servlet  FileUploadServlet.java */@WebServlet(urlPatterns="/FileUploadServlet")@MultipartConfig  // 该注解是将传统的获取参数的方法进行封装public class FileUploadServlet extends HttpServlet {    private static final long serialVersionUID = 1L;    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {        this.doPost(request, response);    }    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {        // 设置request缓冲区的编码,防止中文数据乱码        request.setCharacterEncoding("UTF-8");        // 获取文件的描述信息        String filedesc = request.getParameter("filedesc");        // 获取代表文件部分的对象        Part part = request.getPart("upload");        // 获取文件大小        // int size = part.getSize();        // 获取表单中该文件的input标签的name属性值        // String name = part.getName();        // Servlet3.0没有提供获取上传文件名的方法,需要从part的头信息中进行截取        // 获取请求头的Content-Disposition字段值        // 获取的文件的格式是:form-data; name="upload"; filename="文件名"        String header = part.getHeader("Content-Disposition");        // 分割头信息获取文件名        int lindex = header.lastIndexOf("\""); // 获取最后一个双引号的索引        int findex = header.substring(0, lindex).lastIndexOf("\"");        String filename = header.substring(findex + 1, lindex);        // 定义在服务器存储文件的根目录        String realPath = request.getServletContext().getRealPath("/upload");        File file = new File(realPath);        // 存储文件的目录不存在则创建目录        if(!file.exists()) {            file.mkdir();        }        // 获取存储时的文件名,防止文件的重名        String realFileName = UpLoadUtils.setFileName(filename);        // 获取文件存储路径,防止同一目录下文件过多,使用目录分离算法建立存储目录        String storePath = UpLoadUtils.getPath();        // 根据文件存储的根目录和存储路径获取存储的完全路径        file = new File(realPath+storePath);        // 如果文件存储的目录不存在则创建        if(!file.exists()) {            file.mkdirs();        }        // 获取要存储文件的对象        file = new File(file,realFileName);        // 存储文件,使用IO流读写文件,上传文件信息存储在part中        // 是从part中获取字节输入流对象,不是获取request中的输入流对象        InputStream is = part.getInputStream();        OutputStream os = new FileOutputStream(file);        // 读取文件并保存到指定目录        byte[] bys = new byte[1024];        int len = 0;        while((len = is.read(bys)) != -1) {            os.write(bys, 0, len);        }        // 释放资源        os.close();        // is.close(); 输入流资源是从part中获取的,可以不用释放,part会进行管理        // 给出提示        response.setContentType("text/html;charset=UTF-8");        response.getWriter().println("<h1>文件上传成功</h1>");    }}
package com.itheima.utils;import java.text.SimpleDateFormat;import java.util.Date;/** * 文件上传的工具类  UpLoadUtils.java */public class UpLoadUtils {    /**     * 根据文件名称获取文件保存的目录     * 按照时间进行分离目录     * 存储路径格式:yyyy-MM-dd/HH-mm-ss     * @param filename 文件名     * @return 返回文件的存储路径     */    public static String getPath() {        // 获取当前日期        Date date = new Date();        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd#HH-mm-ss");        String format = sdf.format(date);        return "/"+format.split("#")[0]+"/"+format.split("#")[1];    }    /**     * 根据时间生成文件的名称     * 防止文件重名     * 新文件名格式:yyyy_MM_dd_HH_mm_ss_SS-原始文件名     * @param filename 文件名称     * @return 返回文件的新的名称     */    public static String setFileName(String filename) {        Date date = new Date();        SimpleDateFormat sdf = new SimpleDateFormat("yyyy_MM_dd_HH_mm_ss_SS");        String format = sdf.format(date);        return format+"-"+filename;    }}

4. 动态代理

代理模式:是java开发的设计模式,其特点是代理类与委托类有同样的接口,代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类,以及委托类处理消息后再次处理消息等。代理类与委托类之间通常会存在关联关系,一个代理类的对象与一个委托类的对象关联,代理类的对象本身并不真正实现服务,而是通过调用自定义类的相关方法,实现对委托类的代理访问。 按照代理的创建时期,代理类可以分为两种。

静态代理:由程序员创建或特定工具自动生成源代码,再对其编译。在程序运行前,代理类的.class文件就已经存在了。

动态代理:动态代理类与普通类的唯一区别就是其字节码是由 JVM 在运行时动态生成的而非预存在于任何一个 .class 文件中。

Java的JDK中提供创建动态代理类API,当时只能创建针对接口的动态代理类。创建动态代理的要求是:被代理的类实现了接口。

4.1 Java中动态代理类和接口

1. Java.lang.reflect.Proxy:动态代理类,提供一组静态方法为一组接口动态的生成子类对象和代理类

方法 说明 InvocationHandler getInvocationHandler(Object proxy) 用于获取指定代理对象所关联的调用处理器 Class getProxyClass(ClassLoader loader,Class… interfaces) 用于获取指定类加载器和一组接口上的动态代理类对象 static boolean isProxyClass(Class cl) 用于判断指定类对象是不是动态代理对象 Object newProxyInstance(ClassLoader loader,Class[] interfaces,InvocationHandler h) 用于为指定类加载器和一组接口生成动态代理对象,并自定义调用处理器

2. java.lang.reflect.InvocationHandler:调用处理器接口,实现invoke方法,用于实现对于被代理类的代理访问

// 该接口就只有一个方法,该方法是用户处理被代理类的访问控制/** * 该方法负责集中处理动态代理类上的所有方法调用(就是对被代理类的访问进行控制) * proxy:代理类实例 * method:被调用的方法对象(就是被代理类中的方法) * args:调用该方法的参数*/public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;

4.2 动态代理的使用步骤(增强已知类)

  1. 创建需要被代理的类的对象;
  2. 创建动态代理类对象;
  3. 实现对被代理类对象访问控制的invoke方法。

案例:创建Servlet访问的统一的字符集编码过滤器,解决获取参数的GET和POST方式的中文乱码问题

import java.io.IOException;import java.lang.reflect.InvocationHandler;import java.lang.reflect.Method;import java.lang.reflect.Proxy;import javax.servlet.Filter;import javax.servlet.FilterChain;import javax.servlet.FilterConfig;import javax.servlet.ServletException;import javax.servlet.ServletRequest;import javax.servlet.ServletResponse;import javax.servlet.http.HttpServletRequest;/** * 统一的字符集编码过滤器 * @ClassName:  CharacterEncodingFilter * @Description:(统一的字符集编码过滤器) */public class CharacterEncodingFilter implements Filter {    @Override    public void init(FilterConfig filterConfig) throws ServletException {    }    @Override    public void doFilter(ServletRequest request, ServletResponse response,            FilterChain chain) throws IOException, ServletException {        // 1.获取被代理类的对象        final HttpServletRequest hsrequest = (HttpServletRequest) request;        // 2.创建动态代理对象        HttpServletRequest myrequest = (HttpServletRequest) Proxy.newProxyInstance(hsrequest.getClass().getClassLoader(), hsrequest.getClass().getInterfaces(), new InvocationHandler() {            @Override            // 3.实现invoke方法,实现对被代理类对象的访问的控制            public Object invoke(Object proxy, Method method, Object[] args)                    throws Throwable {                // 对request对象的getParameter方法的get和post方法获取参数进行字符集编码的处理                // 获取方法的名称                String methodName = method.getName();                // 当方法名时getParameter时,进行增强                if("getParameter".equals(methodName)) {                    // 获取请求的方式                    String reqMethod = hsrequest.getMethod();                    // 当是get方式请求                    if("get".equalsIgnoreCase(reqMethod)) {                        // get方式需将其中的值取出,进行编码转换                        // 先调用原始的方法获取其中的值,然后进行编码的转换                        String value = (String) method.invoke(hsrequest, args);                        return new String(value.getBytes("ISO-8859-1"),"UTF-8");                    } else if("post".equalsIgnoreCase(reqMethod)) {                        // 当请求是post方式,post方式只需设置request的缓冲区的编码即可                        hsrequest.setCharacterEncoding("UTF-8");                        // 原始调用原始的获取参数的方法                        return method.invoke(hsrequest, args);                    }                }                // 针对request中其余的方法执行原有的调用即可                return method.invoke(hsrequest, args);            }        });        // 4.将请求放行,注意放行的是代理对象        chain.doFilter(myrequest, response);    }    @Override    public void destroy() {    }}// 字符集过滤器需要在项目的web.xml中进行配置  <!-- 配置字符集过滤器 -->  <filter>    <filter-name>CharacterEncodingFilter</filter-name>    <filter-class>com.filter.CharacterEncodingFilter</filter-class>  </filter>  <!-- 配置过滤器的映射 -->  <filter-mapping>    <filter-name>CharacterEncodingFilter</filter-name>    <url-pattern>/*</url-pattern>  </filter-mapping>

4.3 类加载器

java.lang.ClassLoader:类加载器,将类的字节码加载到JVM中并为创建类对象,然后该类才能被使用。Proxy类与普通类的唯一区别就是其字节码是由 JVM 在运行时动态生成的而非预存在于任何一个 .class 文件中。每次生成动态代理类对象时都需要指定一个类加载器对象:newProxyInstance()方法第一个参数。

Java中类加载器:

# Java中类加载器分为三类:加载器间是层级关系,不是子父类关系1. 引导类加载器:负责加载本地环境的JDK中/jre/lib/rt.jar,该包是Java的核心api        |2. 扩展类加载器:负责加载本地环境中JDK中/jre/lib/ext/\*.jar,该包是Java的扩展API        |3. 应用类加载器:负责加载自定义类路径下所有的class文件# Java中为保证类只被加载一次:使用的是类加载器的全盘委托机制# 全盘委托机制原理:所有的类加载都要先经过`应用类加载器`,`应用类加载器`获得所有的class文件后不进行操作直接向`扩展类加载器`传递这些class文件,`扩展类加载器`拿到所有的class文件后不进行操作直接向`引导类加载器`传递这些class文件;`引导类加载器`获取所有的class文件后查找其中属于自己负责加载的class文件进行加载,将剩余的class文件传递给`扩展类加载器`,`扩展类加载器`拿到class文件后只加载属于自己负责加载的class文件进行加载,将剩余的class文件传递给`应用类加载器`,`应用类加载器`获取剩余的class文件进行全部的加载。这种机制保证了每个class文件只被加载一次。