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

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. 文件上传:没有提供获取上传的文件名等相关的API
3. 异步请求:本质是开启新的线程进行处理请求

注解开发的原理:

// 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文件只被加载一次。

你可能感兴趣的:(java,注解,动态代理,Web)