15、文件上传下载及注解

文件上传

一般情况下,我们如果要在表单中上传文件,一般会将form的enctype参数设置为multipart/form-data。通常文件上传使用POST请求方式。

原始方法

<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<%
String path = request.getContextPath();
String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";
%>

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
  <head>
    <base href="<%=basePath%>">
   
    <title>My JSP 'index.jsp' starting page</title>
    <meta http-equiv="pragma" content="no-cache">
    <meta http-equiv="cache-control" content="no-cache">
    <meta http-equiv="expires" content="0">   
    <meta http-equiv="keywords" content="keyword1,keyword2,keyword3">
    <meta http-equiv="description" content="This is my page">
    <!--
    <link rel="stylesheet" type="text/css" href="styles.css">
    -->
  </head>
 
  <body>
  <%--
 
      1.文件上传,表单必须是post提交
      2.文件上传,还需要在表单上加上enctype属性(提交请求正文的类型)
          application/x-www-form-urlencoded(默认取值,普通提交)
          multipart/form-data(多段式提交)
    3.     文件上传,使用<input type="file" name="photo" /> 标签,并且必须有name属性
  --%>
 
      <form action="/fileupload/Aservlet" method="post" enctype="multipart/form-data">
          用户名:<input type="text" name="name" /><br>
        个人近照:<input type="file" name="photo" /><br>
        <input type="submit" value="上传"  />
      </form>
     
  </body>
</html>

Aservlet

package fileupload;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;


@WebServlet("/Aservlet")
public class Aservlet extends HttpServlet {
    // 文件上传只能用POST提交,实现这个方法就好了
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //1.获得浏览器提交上来的全部信息
        InputStream is = request.getInputStream();
    //2.提取流中的内容,再根据分隔符
        OutputStream os = System.out;
   
        byte[] bytes = new byte[1024];
        int len =  -1;
       
        while((len=is.read(bytes))!=-1){
            os.write(bytes, 0, len);
            os.flush();
          //3.手动从请求头中获得分割线是什么
        //4.使用分隔线分隔请求正文
        //5. 从分隔的每一段中 提取信息.
        }
    }

}

使用工具类方便上传

上面的方法复杂,使用第三方库可以很方便的完成文件上传。提交表达转到Bservlet

package fileupload;

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.List;

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 org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.apache.commons.io.IOUtils;

@WebServlet("/Bservlet")
public class Bservlet extends HttpServlet {
    // 需要用到commons-fileupload.jar, commons-io.jar

    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // 1. 创建配置工厂
        DiskFileItemFactory factory = new DiskFileItemFactory();
        // 2. 根据配置工厂创建解析请求中文件上传内容的解析器
        ServletFileUpload upload = new ServletFileUpload(factory);
        // 3. 判断当前请求是不是多段提交
        if (!upload.isMultipartContent(request)) {
            throw new RuntimeException("不是多段提交!");
        }
       
        try {
            // 4. 解析request对象,将已经分割过的内容放进了List
            List<FileItem> list = upload.parseRequest(request);
            if (list != null) {
                for (FileItem fileItem : list) {
                    // 判断当前段是普通字段还是文件,这个方法是判断普通段
                    if (fileItem.isFormField()) {
                        // 获得jsp里name属性对应的值,这里是username
                        String fname = fileItem.getFieldName();
                        // 获得用户输入的用户名
                        String value = fileItem.getString();
                       
                        System.out.println(fname +  "=>"+value );
                       
                    // 否则就是文件了
                    } else {
                        // 获得上传文件的文件名
                        String name = fileItem.getName();
                        // 获得文件上传段中,文件的流
                        InputStream in = fileItem.getInputStream();
                        // 字节输出流,用以保存文件
                        FileOutputStream fos = new FileOutputStream("C:\\Users\\"+name);
                        // 将输入流复制到输出流中
                        IOUtils.copy(in, fos);
                        fos.close();
                    }
                }
            }
        } catch (FileUploadException e) {
            e.printStackTrace();
        }
    }

}

详解DiskFileItemFactory

protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    // 1 创建配置工厂=> 有参的构造可以 直接设置下面两个配置new             DiskFileItemFactory(new File("d:/temp"), 10240);
    DiskFileItemFactory factory = new DiskFileItemFactory();
    // 1.1 设置文件上传临时目录 => 默认位置 => tomcat/temp
    factory.setRepository(new File("d:/temp"));
    // 1.2 设置文件写入硬盘的缓冲区大小=>默认值=>10k
    factory.setSizeThreshold(10240);
}

详解ServletFileUpload

//2 根据配置工厂创建解析器(解析request对象)
ServletFileUpload upload = new ServletFileUpload(factory);
//2.1 判断当前请求是否是多段式提交
upload.isMultipartContent(request);
//2.2 设置多段中每段 段头 在解析时,使用什么码表解码 => 当段头中出现中文时,一定要调用该方式指定段头码表。Content-Disposition: form-data; name="username"
// 这种就是段头
upload.setHeaderEncoding("UTF-8");
//2.3 设置文件最大上传大小 (单位:字节)
upload.setSizeMax(1024*1024*10); // 单次请求,总上传大小限制  10兆.因为是多段式提交,所以可以一次上传多个文件。对单个文件的限制可以使用下面的方法
upload.setFileSizeMax(1024*1024);// 每个文件上传段,大小限制 1兆

FileItem表示分割后的每一段的内容,主要方法如下

boolean  isFormField()。// isFormField方法用来判断FileItem对象里面封装的数据是一个普通文本表单字段,还是一个文件表单字段。如果是普通文本表单字段,返回一个true否则返回一个false。因此可以用该方法判断是否是普通表单域还是文件上传表单域。
  // 注意下面这两个方法的区别
 
String getName()// getName方法用来获得文件上传字段中的文件名。
 
String getFieldName()// getFieldName方法用来返回表单标签的name属性的值
 
String getString("utf-8") // 空参或者传入编码方式如"UTF-8"。将FileItem对象中保存的数据流内容以一个字符串返回。如果是普通表单字段,如登录时候输入用户名。则返回用户输入的字段。如果是文件上传则返回文件的内容。
 
boolean isInMemory() // 判断FileItem对象封装的数据是保存在内存中还是硬盘中。
 
void write(File file) // write方法将FileItem对象中的内容保存到某个指定的文件中。如果FileItem对象中的内容是保存在某个临时文件中,该方法完成后,临时文件可以会被删除。该方法也可以将普通表单字段保存在一个文件中,但最主要的用途是把上传的文件内容保存在本地文件系统中。
  // 上例中也可以直接用fileItem.write(File file)来讲数据写到本地文件
 
String getContentType() // 此方法用来获得上传文件的类型,即标段字段元素描述头属性“content-type”的值,如image/jpeg。如果FileItem对象对应的是普通的表单字段,将返回null。
 
InputStream  getInputStream() // 以流的形式返回上传文件的主体内容。
OutputStream  getOutputStream() // 可以用此方法将输入流写到FileItem中
 
void  delete() // 此方法用来清空FileItem对象中封装的主体内容,如果内容是被保存在临时文件中,该方法会把临时文件删除。
 
long  getSize() // 返回上传文件的大小。
List<FileItem> list = null;
    try {
        // 2.4 解析request,将每个分段中的内容封装到FileItem中
        list = upload.parseRequest(request);
    } catch (FileUploadException e) {
        e.printStackTrace();
    }
    if (list != null) {
        for (FileItem item : list) {
            // 3.1 item 判断当前分段是否是普通表单段
            boolean flag = item.isFormField();
         
           
           // 3.2获得 表单提交的键.(input元素,name属性的值)
          // 普通段和文件上传段都有
            String fname = item.getFieldName();
         
            // 3.3 返回文件名称,普通段返回null
            String name = item.getName();
         
            // 3.4 获得文件上传中的正文,如果是普通段,如用户登录则是用户自己输入的值。若是文件,则是文件内容。以字符串形式返回段体中的内容 注意:文件上传段不建议使用该方法.使用item.getInputStream()更好
            String content = item.getString();
         
            System.out.println("是否是普通表单提交:" + flag + ",表单提交的键:" + fname + ",文件名称:" + name + ",文件内容:" + content);
    }
}

解决乱码

文件上传时候的文件名包含中文

ServletFileUpload的setHeaderEncoding("UTF-8");设置一下即可。

段体内容乱码

getString()返回的内容乱码。fileItem.getString("UTF-8");即可。

上传文件后应该把文件保存到什么位置?

1. 上传后如果需要其他用户可以直接访问,就放到webRoot下.
2. 上传后其他用户不能直接访问, 不直接放在webRoot下。比如WEB-INF下或硬盘其他位置例如 D:\db\xxx.xxx

保存用户上传的文件时的注意事项

使用用户上传的文件名来保存文件的话,文件名可能重复。所以保存文件之前,要保证文件名不会重复。

  • 可以使用UUID生成随机字符串
  • 可以使用登录用户名+当前系统毫秒数

在一个目录下放所有用户上传的文件显然不明智。可以用如下方法

  • /upload/2017/04/15/xxxx 使用当前日期作为子文件夹名称
  • 当前登录用户的用户名作为文件夹名称
package fileupload;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.UUID;

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 org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.apache.commons.io.IOUtils;


@WebServlet("/Dservlet")
public class Dservlet extends HttpServlet {

    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
     // 1. 创建配置工厂
        DiskFileItemFactory factory = new DiskFileItemFactory();
        // 2. 根据配置工厂创建解析请求中文件上传内容的解析器
        ServletFileUpload upload = new ServletFileUpload(factory);
        // 3. 判断当前请求是不是多段提交
        if (!upload.isMultipartContent(request)) {
            throw new RuntimeException("不是多段提交!");
        }

        try {
            // 4. 解析request对象,将已经分割过的内容放进了List
            List<FileItem> list = upload.parseRequest(request);
            if (list != null) {
                for (FileItem fileItem : list) {
                    // 判断当前段是普通字段还是文件,这个方法是判断普通段


                    if (fileItem.isFormField()) {

                        // 获得jsp里name属性对应的值,这里是username
                        String fname = fileItem.getFieldName();
                        // 获得用户输入的用户名
                        String value = fileItem.getString("utf-8");

                        System.out.println(fname +  "=>"+value );

                        // 否则就是文件了
                    } else {
             
                        // 获得文件上传段中,文件的流
                        InputStream in = fileItem.getInputStream();

                        // 使用用户上传的文件名来保存文件的话,文件名可能重复。
                        // 所以保存文件之前,要保证文件名不会重复。使用UUID生成随机字符串
                        String fileName = UUID.randomUUID().toString();
                      // 格式化日期
                        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("/yyyy/MM/dd/");
                        String datePath = simpleDateFormat.format(new Date()); // 解析成    /2017/04/15/  的样子, 注意这是三个文件夹
                        String wholePath = "D:/upload"+datePath;
                        // 字节输出流,用以保存文件,也不需要后缀名,因为我们只是保存用户的数据,不需要查看他们的数据。待用户想下载的时候,再加上后缀名
                        File dir = new File(wholePath);
                        // 判断文件夹是否已经存在,不存在就新建
                        if (!dir.exists()) {
                            dir.mkdirs();
                        }
                        FileOutputStream fos = new FileOutputStream(wholePath+fileName);
                        // 将输入流复制到输出流中
                        IOUtils.copy(in, fos);
                      fos.close();
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }      
    }
}

mkdir()和mkdirs()的区别

  • mkdirs()可以建立多级目录。即使所有层级的目录都不存在。这些文件夹都会创建。比如我们事先并没有在D盘创建upload和2017等这些文件夹。

  • kdir只能用于父级目录已经存在的情况下使用,在已存在的父级目录下再新建一级。只能一级!比如File("D:\upload\2017\04")。且D:\upload\2017是已经存在的。父级 目录存且只新建一级。故file.makedir()返回true成功创建。

    但是File("D:\upload\2017\04\15")且D:\upload\2017存在,但不存在15文件夹。所以这里是想新建两级目录。因为父级目录不存在所以创建失败返回false。

多文件同时上传

同时上传多个文件,无非是多写几个<input type="file" name="photo">这样的标签,提交的时候一并上传,同时上传的文件不过是被分成了几段而已。由于List<FileItem> list = upload.parseRequest(request);返回的是全部的FileItem的,所以后台处理的代码不用变。这里用js来处理添加和删除的逻辑。

<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<%
    String path = request.getContextPath();
    String basePath = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort()
            + path + "/";
%>

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<base href="<%=basePath%>">

<title>My JSP 'index.jsp' starting page</title>
<meta http-equiv="pragma" content="no-cache">
<meta http-equiv="cache-control" content="no-cache">
<meta http-equiv="expires" content="0">
<meta http-equiv="keywords" content="keyword1,keyword2,keyword3">
<meta http-equiv="description" content="This is my page">
<!--
        <link rel="stylesheet" type="text/css" href="styles.css">
        -->
</head>

<body>

<script type="text/javascript">
    function fun1() {
        //1 创建出想要添加的行
        var tr = document.createElement("tr");
        // this表示当前标签,这里是button
        tr.innerHTML = "<td><input type='file' name='photo' /></td><td><input type='button' value='删除' onclick='fun2(this)'  /></td>";
        //2 找到表格
        var table = document.getElementById("one");
        //3 找到表格最后一行
        var lastRow = table.rows[table.rows.length-1];
        //4 insertBefore,由于浏览器解析table的原因。tr的父节点可能并不是table,所以下面的写法兼容了两种情况
        lastRow.parentNode.insertBefore(tr, lastRow);
    }
    //参数: 要删除行中的删除按钮对象,button的父节点是td,td父节点是tr,tr的父节点不一定是table。不过没关系,用tr的父节点去删除tr就行(obj.parentNode.parentNode)
  function fun2(obj){
      obj.parentNode.parentNode.parentNode.removeChild(obj.parentNode.parentNode);
  }
  </script>

    <form action="/fileupload/Dservlet" method="post" encType="multipart/form-data">
        <table border="1" id="one">
            <tr>
                <th colspan="2">照片上传</th>
            </tr>
            <tr>
                <td><input type="file" name="photo" /></td>
                <td><input type="button" value="添加"
                    onclick="fun1()" /></td>
            </tr>
            <tr>
                <td colspan="2" align="center"><input
                    type="submit" value="上传" /></td>
            </tr>
        </table>
    </form>
    </body>
</html>

文件下载

<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<%
String path = request.getContextPath();
String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";
%>

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
  <head>
    <base href="<%=basePath%>">
   
    <title>My JSP 'index.jsp' starting page</title>
    <meta http-equiv="pragma" content="no-cache">
    <meta http-equiv="cache-control" content="no-cache">
    <meta http-equiv="expires" content="0">   
    <meta http-equiv="keywords" content="keyword1,keyword2,keyword3">
    <meta http-equiv="description" content="This is my page">
    <!--
    <link rel="stylesheet" type="text/css" href="styles.css">
    -->
  </head>
  <%-- get提交,?name="name" 表示参数--%>
  <body>
    <a href="/filedownload/Aservlet?name=西电.zip">西电.zip</a> <br>
    <a href="/filedownload/Aservlet?name=课.png">课.png</a> <br>
    <a href="/filedownload/Aservlet?name=Android基础(四).md">Android基础(四).md</a> <br>
  </body>
</html>

package filedownload;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URLEncoder;

import javax.print.URIException;
import javax.servlet.ServletContext;
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 org.apache.commons.io.IOUtils;

@WebServlet("/Aservlet")
public class Aservlet extends HttpServlet {
    // 点击超链接属于GET提交
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
     // 原则: 凡是响应正文中需要输出内容, 一定要设置content-type头
        String fileName = request.getParameter("name");
        System.out.println(fileName);
        ServletContext sc = getServletContext();
        // 只会截取文件后缀名
        String type = sc.getMimeType(fileName);
        response.setContentType(type);
        // 设置这里让浏览器知道我们下载的文件名,要不默认使用Aservlet这个名称,且没有后缀名.注意这里加了attachment;filename=
        // 文件名可能是中文的,下载时候可能出现乱码。要转码成UTF-8
        response.setHeader("Content-Disposition", "attachment;filename="+URLEncoder.encode(fileName, "UTF-8"));
        // 获得资源输入流
        InputStream inputStream = sc.getResourceAsStream("/WEB-INF/resource/"+fileName);
        OutputStream outputStream = response.getOutputStream();
       
        IOUtils.copy(inputStream, outputStream);
       
    }
}

注解

什么是注解?它有什么作用?

注解就是 @xxx 这种就是注解.

  • 注释:给程序员看的.
  • 注解:给程序看。

使用注解的目的: 其实将来使用注解目的就是为了代替传统配置文件。
比如下面三个注解

public class Demo1 implements Person {

    @Override
    //向编译器描述,该方法是被重写的.
    //帮你检查,被注解修饰方法是否是被重写的,如果不是编译报错!
    public void eat() {
       
    }
 
    @Deprecated
    //该注解告诉编译器,被修饰的方法是过时方法
    public static void show(){
        System.out.println("hello world!");
    }
}


// ***** 另外一个文件 *****
package annotation;

import java.util.ArrayList;
import java.util.List;

public class Test {
    // 告诉编译器,不要检查什么错误。

    @SuppressWarnings({ "null", "rawtypes", "unchecked" })
    // @SuppressWarnings("all") 全部警告都不检查

    public static void main(String[] args) {
        String str = null;
        str.substring(0);

        // @SuppressWarnings("unused")
        String str2 = null;

        // @SuppressWarnings("rawtypes")
        List list = new ArrayList();
        list.add("abc");
    }
}

自定义注解

package annoation;

public @interface MyAnnotation {

}

上面的内容被编译成.class再反编译过来就是这样

interface MyAnnotation extends Annotation
{
}

注解本质上就是一个接口。它扩展了java.lang.annotation.Annotation接口;在java中所有注解都是Annotation接口的子接口。

package annoation;

public @interface MyAnnotation {
    //声明属性=> 用抽象方法

    //声明一个名为name的属性  类型是String
    String name();

}

自定义注解的使用

package annoation;

public class Demo {
      // 注意一定要键name
    @MyAnnotation(name = "hello")
    public void test() {
       
    }
}

注解支持的类型

常见的比如

  • 八大基本数据类型
  • String
  • Array
  • Enum枚举
package annoation;

import java.lang.annotation.ElementType;

public @interface MyAnnotation {

    //可以使用default关键字,添加属性的默认值。就不用写 a= 了
    byte a() default 10;
    short b();
    int c();
    long d();
    float e();
    double f();
    char g();
    boolean h();

    String i();

    String[] j();

    ElementType k();

}
package annoation;

import java.lang.annotation.ElementType;

public class Demo {
 
    @MyAnnotation(
            b = 1000,
            c = 10000,
            d = 100000,
            e = 3.14f,
            f = 3.1415926,
            g = 'a',
            h = true,
            i = "tom",
            j = "jack",
            k = ElementType.FIELD)

    //数组属性在赋值时使用大括号. => j = { "jack","rose" }
    //如果数组中只有一个元素.那么可以忽略大括号 j = "jack"
    public void test() { 
    }
}

注意

如果注解中,必填属性只有一个. 这个属性的名字是"value".那么在赋值时不需要加属性的键.

package annoation;

import java.lang.annotation.ElementType;
public @interface MyAnnotation {

    String value();
  // 或者String[] value();
 
}

package annoation;

public class Demo {
      // 没加value="good",直接下面这样就行
    @MyAnnotation("good")
    public void test() {
    }
}

可以使用default关键字,添加属性的默认值。就不用写 a=

int a() default 10;

元注解

package annoation;


import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

//元注解 (4个)
//修饰注解的注解
//RetentionPolicy.SOURCE  注解会保留到Java源文件这一阶段 ,编译成class文件后就不存在了
//RetentionPolicy.CLASS 注解在源文件和编译成class文件后也还存在
//RetentionPolicy.RUNTIME 注解在源文件、编译后的class文件、以及运行时期都存在
//
@Retention(RetentionPolicy.RUNTIME)
//@Target 注解支持加在什么位置
//ElementType.CONSTRUCTOR 构造方法
//ElementType.METHOD 方法
// 比如下面,注解可以加、在构造函数,方法和类上
@Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.TYPE})

//@Inherited 所加的注解可否被继承
@Inherited
//@Documented 生成java文档时候也保留注解
@Documented
public @interface MyAnnotation2 {

}

注解小例子

package annoation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface MyAnnotation3 {

    double value();
}

模拟银行转账,注解的作用是:单笔转账不得超过1000

package annoation;


import java.lang.reflect.Method;

public class BankTransfer {

    @MyAnnotation3(10000)
    public static void trans(String person, String another, double money) throws ClassNotFoundException, NoSuchMethodException {

//        Method m =    BankService.class.getMethod("zz",String.class,String.class,double.class);
//      也可以上面的写法
      // 1. 获得注解所在的反射对象   
        Method m = Class.forName("annoation.BankTransfer").getMethod("trans", String.class, String.class, double.class);
          //2 判断方法是否被注解修饰
        if (m.isAnnotationPresent(MyAnnotation3.class)) {
              //3 获得注解的属性值
            MyAnnotation3 myAnnotation = m.getAnnotation(MyAnnotation3.class);
            double maxmoney = myAnnotation.value();
         
            if (money > maxmoney) {
                throw new RuntimeException("单次转账不能超过" + maxmoney + "元!");
            }

            System.out.println(person + "给" + another + "转了" + money + "元!");
        } else {
            //没被注解修饰
            throw new RuntimeException("系统异常,不能转账!");
        }
    }

    public static void main(String[] args) throws NoSuchMethodException, ClassNotFoundException {
        trans("我", "你", 100000);
    }
}

重写JDBCUtils

代替配置文件,使用注解

package annoation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
// 加到类上
@Target(ElementType.TYPE)
public @interface JDBCInfo {

    String className();
    String url();
    String user();
    String password();

}
package annoation;

import java.sql.*;

@JDBCInfo(className = "com.mysql.jdbc.Driver", url = "jdbc:mysql://localhost:3306/example", user = "root", password ="admin")
public class JDBCUtils {
    private static String driver;
    private static String url;
    private static String user;
    private static String password;

    // 静态代码块,随着类的加载而加载,只加载一次
    static {
        try {
            //获得注解中配置的属性
            //1 获得注解所在的反射对象
            //2 获得注解的实现类
            JDBCInfo info = Class.forName("annoation.JDBCUtils").getAnnotation(JDBCInfo.class);
            //3 获得4个属性值
            driver = info.className();
            url=info.url();
            user=info.user();
            password=info.password();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }

    }
    public static Connection getConnection() {
        Connection connection = null;
        try {
            connection = DriverManager.getConnection(url, user, password);
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException("创建连接失败!");
        }
        return connection;
    }
    // 释放资源
    // 参数可能为空
    // 调用close要抛出异常,即使出现异常也能关闭
public void close(Connection conn, Statement state, ResultSet result) {
        try {
            if (result != null) {         
                result.close();
            }
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            try {
                if (state != null) {
                state.close();
                }
            } catch (SQLException e) {
                    e.printStackTrace();
                } finally {
                     try {
                        if (conn != null) {
                            conn.close();
                        }
                    } catch (SQLException e) {
                        e.printStackTrace();
                }
            }
        }
    }

    public static void main(String[] args) {
        System.out.println(getConnection());
    }
}

以前写的使用properties来保存并读取配置文件。对比一下

package jdbc;

import java.io.FileInputStream;
import java.io.InputStream;
import java.sql.*;
import java.util.Properties;

public class JDBCUtil {
    private static String driver;
    private static String url;
    private static String user;
    private static String password;

    // 静态代码块,随着类的加载而加载,只加载一次
    static {
        try {
            Properties prop = new Properties();
            // load()接收InputStream,所以向上转型
            InputStream is = new FileInputStream("src/jdbc/jdbc_setting.properties");
            prop.load(is);

            driver = prop.getProperty("ClassName");
            url = prop.getProperty("url");
            user = prop.getProperty("user");
            password = prop.getProperty("password");
            Class.forName(driver);

            is.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static Connection getConnection() {
        Connection connection = null;
        try {
            connection = DriverManager.getConnection(url, user, password);
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException("创建连接失败!");
        }
        return connection;
    }

    // 释放资源
    // 参数可能为空
    // 调用close要抛出异常,即使出现异常也能关闭
    public static void close(Connection conn, Statement state, ResultSet result) {
        if (result != null) {
            try {
                result.close();
            } catch (SQLException e) {
                e.printStackTrace();
            } finally {
                if (state != null) {
                    try {
                        state.close();
                    } catch (SQLException e) {
                        e.printStackTrace();
                    } finally {
                        if (conn != null) {
                            try {
                                conn.close();
                            } catch (SQLException e) {
                                e.printStackTrace();
                            }
                        }
                    }
                }
            }
        }
    }

    public static void main(String[] args) {
        System.out.println(getConnection());
    }
}


枚举简介

通常描述数量固定的属性。比如性别,一周从周一到周日,这些都是固定的 。为了定义这些量,我们可以这样写。

public class Sex { 
      // 静态代码块最先执行,所以这里定义的时候不初始化也不会报错
    public static final Sex MALE;
    public static final Sex FEMALE;

    static {
        MALE = new Sex();
        FEMALE = new Sex();
    }
      // 或者不用静态代码块的方法,直接下面这样
      //  public static final Sex MALE = new Sex();
      // public static final Sex FEMALE = new Sex();
 
    public static void main(String[] args) {

        //男性
        Sex male = Sex.MALE;
        //女性
        Sex female = Sex.FEMALE;
       
    }
}

如果使用枚举就很省事

package annoation;

public enum Sex2 {
    MALE, FEMALE;
}

测试一下

public static void main(String[] args) {
    //男性
    Sex2 male = Sex2.MALE;
    //女性
    Sex2 female = Sex2.FEMALE;  
}

by @sunhaiyu

2017.4.15

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,835评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,598评论 1 295
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,569评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,159评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,533评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,710评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,923评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,674评论 0 203
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,421评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,622评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,115评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,428评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,114评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,097评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,875评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,753评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,649评论 2 271

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 170,569评论 25 707
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,100评论 18 139
  • 本文章涉及代码已放到github上annotation-study 1.Annotation为何而来 What:A...
    zlcook阅读 28,923评论 15 116
  • Spring Boot 参考指南 介绍 转载自:https://www.gitbook.com/book/qbgb...
    毛宇鹏阅读 46,367评论 6 343
  • 网络监听用到的类为Reachability.h,这个Xcode项目里面是不自带的,需要从github上面下载,在使...
    TomatosX阅读 1,010评论 0 5