15愁铺、文件上傳下載及注解

文件上傳

一般情況下再来,我們?nèi)绻诒韱沃猩蟼魑募衫迹话銜?huì)將form的enctype參數(shù)設(shè)置為multipart/form-data。通常文件上傳使用POST請(qǐng)求方式芒篷。

原始方法

<%@ 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屬性(提交請(qǐng)求正文的類型)
          application/x-www-form-urlencoded(默認(rèn)取值,普通提交)
          multipart/form-data(多段式提交)
    3.     文件上傳,使用<input type="file" name="photo" /> 標(biāo)簽,并且必須有name屬性
  --%>
 
      <form action="/fileupload/Aservlet" method="post" enctype="multipart/form-data">
          用戶名:<input type="text" name="name" /><br>
        個(gè)人近照:<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提交搜变,實(shí)現(xiàn)這個(gè)方法就好了
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //1.獲得瀏覽器提交上來的全部信息
        InputStream is = request.getInputStream();
    //2.提取流中的內(nèi)容,再根據(jù)分隔符
        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.手動(dòng)從請(qǐng)求頭中獲得分割線是什么
        //4.使用分隔線分隔請(qǐng)求正文
        //5. 從分隔的每一段中 提取信息.
        }
    }

}

使用工具類方便上傳

上面的方法復(fù)雜,使用第三方庫可以很方便的完成文件上傳针炉。提交表達(dá)轉(zhuǎn)到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. 創(chuàng)建配置工廠
        DiskFileItemFactory factory = new DiskFileItemFactory();
        // 2. 根據(jù)配置工廠創(chuàng)建解析請(qǐng)求中文件上傳內(nèi)容的解析器
        ServletFileUpload upload = new ServletFileUpload(factory);
        // 3. 判斷當(dāng)前請(qǐng)求是不是多段提交
        if (!upload.isMultipartContent(request)) {
            throw new RuntimeException("不是多段提交挠他!");
        }
       
        try {
            // 4. 解析request對(duì)象,將已經(jīng)分割過的內(nèi)容放進(jìn)了List
            List<FileItem> list = upload.parseRequest(request);
            if (list != null) {
                for (FileItem fileItem : list) {
                    // 判斷當(dāng)前段是普通字段還是文件,這個(gè)方法是判斷普通段
                    if (fileItem.isFormField()) {
                        // 獲得jsp里name屬性對(duì)應(yīng)的值,這里是username
                        String fname = fileItem.getFieldName();
                        // 獲得用戶輸入的用戶名
                        String value = fileItem.getString();
                       
                        System.out.println(fname +  "=>"+value );
                       
                    // 否則就是文件了
                    } else {
                        // 獲得上傳文件的文件名
                        String name = fileItem.getName();
                        // 獲得文件上傳段中篡帕,文件的流
                        InputStream in = fileItem.getInputStream();
                        // 字節(jié)輸出流殖侵,用以保存文件
                        FileOutputStream fos = new FileOutputStream("C:\\Users\\"+name);
                        // 將輸入流復(fù)制到輸出流中
                        IOUtils.copy(in, fos);
                        fos.close();
                    }
                }
            }
        } catch (FileUploadException e) {
            e.printStackTrace();
        }
    }

}

詳解DiskFileItemFactory

protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    // 1 創(chuàng)建配置工廠=> 有參的構(gòu)造可以 直接設(shè)置下面兩個(gè)配置new             DiskFileItemFactory(new File("d:/temp"), 10240);
    DiskFileItemFactory factory = new DiskFileItemFactory();
    // 1.1 設(shè)置文件上傳臨時(shí)目錄 => 默認(rèn)位置 => tomcat/temp
    factory.setRepository(new File("d:/temp"));
    // 1.2 設(shè)置文件寫入硬盤的緩沖區(qū)大小=>默認(rèn)值=>10k
    factory.setSizeThreshold(10240);
}

詳解ServletFileUpload

//2 根據(jù)配置工廠創(chuàng)建解析器(解析request對(duì)象)
ServletFileUpload upload = new ServletFileUpload(factory);
//2.1 判斷當(dāng)前請(qǐng)求是否是多段式提交
upload.isMultipartContent(request);
//2.2 設(shè)置多段中每段 段頭 在解析時(shí),使用什么碼表解碼 => 當(dāng)段頭中出現(xiàn)中文時(shí),一定要調(diào)用該方式指定段頭碼表。Content-Disposition: form-data; name="username"
// 這種就是段頭
upload.setHeaderEncoding("UTF-8");
//2.3 設(shè)置文件最大上傳大小 (單位:字節(jié))
upload.setSizeMax(1024*1024*10); // 單次請(qǐng)求,總上傳大小限制  10兆.因?yàn)槭嵌喽问教峤唬钥梢砸淮紊蟼鞫鄠€(gè)文件尉剩。對(duì)單個(gè)文件的限制可以使用下面的方法
upload.setFileSizeMax(1024*1024);// 每個(gè)文件上傳段,大小限制 1兆

FileItem表示分割后的每一段的內(nèi)容局齿,主要方法如下

boolean  isFormField()。// isFormField方法用來判斷FileItem對(duì)象里面封裝的數(shù)據(jù)是一個(gè)普通文本表單字段,還是一個(gè)文件表單字段。如果是普通文本表單字段,返回一個(gè)true否則返回一個(gè)false度陆。因此可以用該方法判斷是否是普通表單域還是文件上傳表單域。
  // 注意下面這兩個(gè)方法的區(qū)別
 
String getName()// getName方法用來獲得文件上傳字段中的文件名昌犹。
 
String getFieldName()// getFieldName方法用來返回表單標(biāo)簽的name屬性的值
 
String getString("utf-8") // 空參或者傳入編碼方式如"UTF-8"坚芜。將FileItem對(duì)象中保存的數(shù)據(jù)流內(nèi)容以一個(gè)字符串返回。如果是普通表單字段斜姥,如登錄時(shí)候輸入用戶名鸿竖。則返回用戶輸入的字段。如果是文件上傳則返回文件的內(nèi)容铸敏。
 
boolean isInMemory() // 判斷FileItem對(duì)象封裝的數(shù)據(jù)是保存在內(nèi)存中還是硬盤中缚忧。
 
void write(File file) // write方法將FileItem對(duì)象中的內(nèi)容保存到某個(gè)指定的文件中。如果FileItem對(duì)象中的內(nèi)容是保存在某個(gè)臨時(shí)文件中杈笔,該方法完成后闪水,臨時(shí)文件可以會(huì)被刪除。該方法也可以將普通表單字段保存在一個(gè)文件中蒙具,但最主要的用途是把上傳的文件內(nèi)容保存在本地文件系統(tǒng)中球榆。
  // 上例中也可以直接用fileItem.write(File file)來講數(shù)據(jù)寫到本地文件
 
String getContentType() // 此方法用來獲得上傳文件的類型朽肥,即標(biāo)段字段元素描述頭屬性“content-type”的值,如image/jpeg持钉。如果FileItem對(duì)象對(duì)應(yīng)的是普通的表單字段衡招,將返回null。
 
InputStream  getInputStream() // 以流的形式返回上傳文件的主體內(nèi)容每强。
OutputStream  getOutputStream() // 可以用此方法將輸入流寫到FileItem中
 
void  delete() // 此方法用來清空FileItem對(duì)象中封裝的主體內(nèi)容始腾,如果內(nèi)容是被保存在臨時(shí)文件中,該方法會(huì)把臨時(shí)文件刪除空执。
 
long  getSize() // 返回上傳文件的大小浪箭。
List<FileItem> list = null;
    try {
        // 2.4 解析request,將每個(gè)分段中的內(nèi)容封裝到FileItem中
        list = upload.parseRequest(request);
    } catch (FileUploadException e) {
        e.printStackTrace();
    }
    if (list != null) {
        for (FileItem item : list) {
            // 3.1 item 判斷當(dāng)前分段是否是普通表單段
            boolean flag = item.isFormField();
         
           
           // 3.2獲得 表單提交的鍵.(input元素,name屬性的值)
          // 普通段和文件上傳段都有
            String fname = item.getFieldName();
         
            // 3.3 返回文件名稱,普通段返回null
            String name = item.getName();
         
            // 3.4 獲得文件上傳中的正文辨绊,如果是普通段奶栖,如用戶登錄則是用戶自己輸入的值。若是文件门坷,則是文件內(nèi)容驼抹。以字符串形式返回段體中的內(nèi)容 注意:文件上傳段不建議使用該方法.使用item.getInputStream()更好
            String content = item.getString();
         
            System.out.println("是否是普通表單提交:" + flag + ",表單提交的鍵:" + fname + ",文件名稱:" + name + ",文件內(nèi)容:" + content);
    }
}

解決亂碼

文件上傳時(shí)候的文件名包含中文

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

段體內(nèi)容亂碼

getString()返回的內(nèi)容亂碼拜鹤。fileItem.getString("UTF-8");即可。

上傳文件后應(yīng)該把文件保存到什么位置?

1. 上傳后如果需要其他用戶可以直接訪問,就放到webRoot下.
2. 上傳后其他用戶不能直接訪問, 不直接放在webRoot下流椒。比如WEB-INF下或硬盤其他位置例如 D:\db\xxx.xxx

保存用戶上傳的文件時(shí)的注意事項(xiàng)

使用用戶上傳的文件名來保存文件的話敏簿,文件名可能重復(fù)。所以保存文件之前宣虾,要保證文件名不會(huì)重復(fù)惯裕。

  • 可以使用UUID生成隨機(jī)字符串
  • 可以使用登錄用戶名+當(dāng)前系統(tǒng)毫秒數(shù)

在一個(gè)目錄下放所有用戶上傳的文件顯然不明智⌒逑酰可以用如下方法

  • /upload/2017/04/15/xxxx 使用當(dāng)前日期作為子文件夾名稱
  • 當(dāng)前登錄用戶的用戶名作為文件夾名稱
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. 創(chuàng)建配置工廠
        DiskFileItemFactory factory = new DiskFileItemFactory();
        // 2. 根據(jù)配置工廠創(chuàng)建解析請(qǐng)求中文件上傳內(nèi)容的解析器
        ServletFileUpload upload = new ServletFileUpload(factory);
        // 3. 判斷當(dāng)前請(qǐng)求是不是多段提交
        if (!upload.isMultipartContent(request)) {
            throw new RuntimeException("不是多段提交蜻势!");
        }

        try {
            // 4. 解析request對(duì)象,將已經(jīng)分割過的內(nèi)容放進(jìn)了List
            List<FileItem> list = upload.parseRequest(request);
            if (list != null) {
                for (FileItem fileItem : list) {
                    // 判斷當(dāng)前段是普通字段還是文件,這個(gè)方法是判斷普通段


                    if (fileItem.isFormField()) {

                        // 獲得jsp里name屬性對(duì)應(yīng)的值,這里是username
                        String fname = fileItem.getFieldName();
                        // 獲得用戶輸入的用戶名
                        String value = fileItem.getString("utf-8");

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

                        // 否則就是文件了
                    } else {
             
                        // 獲得文件上傳段中鹉胖,文件的流
                        InputStream in = fileItem.getInputStream();

                        // 使用用戶上傳的文件名來保存文件的話握玛,文件名可能重復(fù)。
                        // 所以保存文件之前甫菠,要保證文件名不會(huì)重復(fù)挠铲。使用UUID生成隨機(jī)字符串
                        String fileName = UUID.randomUUID().toString();
                      // 格式化日期
                        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("/yyyy/MM/dd/");
                        String datePath = simpleDateFormat.format(new Date()); // 解析成    /2017/04/15/  的樣子, 注意這是三個(gè)文件夾
                        String wholePath = "D:/upload"+datePath;
                        // 字節(jié)輸出流,用以保存文件寂诱,也不需要后綴名拂苹,因?yàn)槲覀冎皇潜4嬗脩舻臄?shù)據(jù),不需要查看他們的數(shù)據(jù)痰洒。待用戶想下載的時(shí)候瓢棒,再加上后綴名
                        File dir = new File(wholePath);
                        // 判斷文件夾是否已經(jīng)存在浴韭,不存在就新建
                        if (!dir.exists()) {
                            dir.mkdirs();
                        }
                        FileOutputStream fos = new FileOutputStream(wholePath+fileName);
                        // 將輸入流復(fù)制到輸出流中
                        IOUtils.copy(in, fos);
                      fos.close();
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }      
    }
}

mkdir()和mkdirs()的區(qū)別

  • mkdirs()可以建立多級(jí)目錄。即使所有層級(jí)的目錄都不存在脯宿。這些文件夾都會(huì)創(chuàng)建念颈。比如我們事先并沒有在D盤創(chuàng)建upload和2017等這些文件夾。

  • kdir只能用于父級(jí)目錄已經(jīng)存在的情況下使用嗅绰,在已存在的父級(jí)目錄下再新建一級(jí)舍肠。只能一級(jí)!比如File("D:\upload\2017\04")窘面。且D:\upload\2017是已經(jīng)存在的翠语。父級(jí) 目錄存且只新建一級(jí)。故file.makedir()返回true成功創(chuàng)建财边。

    但是File("D:\upload\2017\04\15")且D:\upload\2017存在肌括,但不存在15文件夾。所以這里是想新建兩級(jí)目錄酣难。因?yàn)楦讣?jí)目錄不存在所以創(chuàng)建失敗返回false谍夭。

多文件同時(shí)上傳

同時(shí)上傳多個(gè)文件,無非是多寫幾個(gè)<input type="file" name="photo">這樣的標(biāo)簽憨募,提交的時(shí)候一并上傳紧索,同時(shí)上傳的文件不過是被分成了幾段而已。由于List<FileItem> list = upload.parseRequest(request);返回的是全部的FileItem的菜谣,所以后臺(tái)處理的代碼不用變珠漂。這里用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 創(chuàng)建出想要添加的行
        var tr = document.createElement("tr");
        // this表示當(dāng)前標(biāo)簽尾膊,這里是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的父節(jié)點(diǎn)可能并不是table,所以下面的寫法兼容了兩種情況
        lastRow.parentNode.insertBefore(tr, lastRow);
    }
    //參數(shù): 要?jiǎng)h除行中的刪除按鈕對(duì)象冈敛,button的父節(jié)點(diǎn)是td待笑,td父節(jié)點(diǎn)是tr,tr的父節(jié)點(diǎn)不一定是table抓谴。不過沒關(guān)系暮蹂,用tr的父節(jié)點(diǎn)去刪除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" 表示參數(shù)--%>
  <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基礎(chǔ)(四).md">Android基礎(chǔ)(四).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 {
    // 點(diǎn)擊超鏈接屬于GET提交
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
     // 原則: 凡是響應(yīng)正文中需要輸出內(nèi)容, 一定要設(shè)置content-type頭
        String fileName = request.getParameter("name");
        System.out.println(fileName);
        ServletContext sc = getServletContext();
        // 只會(huì)截取文件后綴名
        String type = sc.getMimeType(fileName);
        response.setContentType(type);
        // 設(shè)置這里讓瀏覽器知道我們下載的文件名癌压,要不默認(rèn)使用Aservlet這個(gè)名稱椎侠,且沒有后綴名.注意這里加了attachment;filename=
        // 文件名可能是中文的,下載時(shí)候可能出現(xiàn)亂碼措拇。要轉(zhuǎn)碼成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 這種就是注解.

  • 注釋:給程序員看的.
  • 注解:給程序看。

使用注解的目的: 其實(shí)將來使用注解目的就是為了代替?zhèn)鹘y(tǒng)配置文件。
比如下面三個(gè)注解

public class Demo1 implements Person {

    @Override
    //向編譯器描述,該方法是被重寫的.
    //幫你檢查,被注解修飾方法是否是被重寫的,如果不是編譯報(bào)錯(cuò)!
    public void eat() {
       
    }
 
    @Deprecated
    //該注解告訴編譯器,被修飾的方法是過時(shí)方法
    public static void show(){
        System.out.println("hello world!");
    }
}


// ***** 另外一個(gè)文件 *****
package annotation;

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

public class Test {
    // 告訴編譯器,不要檢查什么錯(cuò)誤浅悉。

    @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 {

}

上面的內(nèi)容被編譯成.class再反編譯過來就是這樣

interface MyAnnotation extends Annotation
{
}

注解本質(zhì)上就是一個(gè)接口趟据。它擴(kuò)展了java.lang.annotation.Annotation接口;在java中所有注解都是Annotation接口的子接口。

package annoation;

public @interface MyAnnotation {
    //聲明屬性=> 用抽象方法

    //聲明一個(gè)名為name的屬性  類型是String
    String name();

}

自定義注解的使用

package annoation;

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

注解支持的類型

常見的比如

  • 八大基本數(shù)據(jù)類型
  • String
  • Array
  • Enum枚舉
package annoation;

import java.lang.annotation.ElementType;

public @interface MyAnnotation {

    //可以使用default關(guān)鍵字,添加屬性的默認(rèn)值术健。就不用寫 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)

    //數(shù)組屬性在賦值時(shí)使用大括號(hào). => j = { "jack","rose" }
    //如果數(shù)組中只有一個(gè)元素.那么可以忽略大括號(hào) j = "jack"
    public void test() { 
    }
}

注意

如果注解中,必填屬性只有一個(gè). 這個(gè)屬性的名字是"value".那么在賦值時(shí)不需要加屬性的鍵.

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關(guān)鍵字,添加屬性的默認(rèn)值汹碱。就不用寫 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個(gè))
//修飾注解的注解
//RetentionPolicy.SOURCE  注解會(huì)保留到Java源文件這一階段 ,編譯成class文件后就不存在了
//RetentionPolicy.CLASS 注解在源文件和編譯成class文件后也還存在
//RetentionPolicy.RUNTIME 注解在源文件荞估、編譯后的class文件咳促、以及運(yùn)行時(shí)期都存在
//
@Retention(RetentionPolicy.RUNTIME)
//@Target 注解支持加在什么位置
//ElementType.CONSTRUCTOR 構(gòu)造方法
//ElementType.METHOD 方法
// 比如下面,注解可以加勘伺、在構(gòu)造函數(shù)跪腹,方法和類上
@Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.TYPE})

//@Inherited 所加的注解可否被繼承
@Inherited
//@Documented 生成java文檔時(shí)候也保留注解
@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();
}

模擬銀行轉(zhuǎn)賬,注解的作用是:?jiǎn)喂P轉(zhuǎn)賬不得超過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. 獲得注解所在的反射對(duì)象   
        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("單次轉(zhuǎn)賬不能超過" + maxmoney + "元!");
            }

            System.out.println(person + "給" + another + "轉(zhuǎn)了" + money + "元!");
        } else {
            //沒被注解修飾
            throw new RuntimeException("系統(tǒng)異常,不能轉(zhuǎn)賬!");
        }
    }

    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;

    // 靜態(tài)代碼塊冲茸,隨著類的加載而加載,只加載一次
    static {
        try {
            //獲得注解中配置的屬性
            //1 獲得注解所在的反射對(duì)象
            //2 獲得注解的實(shí)現(xiàn)類
            JDBCInfo info = Class.forName("annoation.JDBCUtils").getAnnotation(JDBCInfo.class);
            //3 獲得4個(gè)屬性值
            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("創(chuàng)建連接失斆辶薄轴术!");
        }
        return connection;
    }
    // 釋放資源
    // 參數(shù)可能為空
    // 調(diào)用close要拋出異常,即使出現(xiàn)異常也能關(guān)閉
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來保存并讀取配置文件钦无。對(duì)比一下

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;

    // 靜態(tài)代碼塊逗栽,隨著類的加載而加載,只加載一次
    static {
        try {
            Properties prop = new Properties();
            // load()接收InputStream失暂,所以向上轉(zhuǎn)型
            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("創(chuàng)建連接失敿老荨!");
        }
        return connection;
    }

    // 釋放資源
    // 參數(shù)可能為空
    // 調(diào)用close要拋出異常趣席,即使出現(xiàn)異常也能關(guān)閉
    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());
    }
}


枚舉簡(jiǎn)介

通常描述數(shù)量固定的屬性。比如性別醇蝴,一周從周一到周日宣肚,這些都是固定的 。為了定義這些量悠栓,我們可以這樣寫霉涨。

public class Sex { 
      // 靜態(tài)代碼塊最先執(zhí)行,所以這里定義的時(shí)候不初始化也不會(huì)報(bào)錯(cuò)
    public static final Sex MALE;
    public static final Sex FEMALE;

    static {
        MALE = new Sex();
        FEMALE = new Sex();
    }
      // 或者不用靜態(tài)代碼塊的方法惭适,直接下面這樣
      //  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;
}

測(cè)試一下

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

by @sunhaiyu

2017.4.15

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末笙瑟,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子癞志,更是在濱河造成了極大的恐慌往枷,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,123評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異错洁,居然都是意外死亡秉宿,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,031評(píng)論 2 384
  • 文/潘曉璐 我一進(jìn)店門屯碴,熙熙樓的掌柜王于貴愁眉苦臉地迎上來描睦,“玉大人,你說我怎么就攤上這事导而〕腊龋” “怎么了?”我有些...
    開封第一講書人閱讀 156,723評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵今艺,是天一觀的道長(zhǎng)韵丑。 經(jīng)常有香客問我,道長(zhǎng)洼滚,這世上最難降的妖魔是什么埂息? 我笑而不...
    開封第一講書人閱讀 56,357評(píng)論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮遥巴,結(jié)果婚禮上千康,老公的妹妹穿的比我還像新娘。我一直安慰自己铲掐,他們只是感情好拾弃,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,412評(píng)論 5 384
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著摆霉,像睡著了一般豪椿。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上携栋,一...
    開封第一講書人閱讀 49,760評(píng)論 1 289
  • 那天搭盾,我揣著相機(jī)與錄音,去河邊找鬼婉支。 笑死鸯隅,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的向挖。 我是一名探鬼主播蝌以,決...
    沈念sama閱讀 38,904評(píng)論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼何之!你這毒婦竟也來了跟畅?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,672評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤溶推,失蹤者是張志新(化名)和其女友劉穎徊件,沒想到半個(gè)月后奸攻,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,118評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡庇忌,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,456評(píng)論 2 325
  • 正文 我和宋清朗相戀三年舞箍,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片皆疹。...
    茶點(diǎn)故事閱讀 38,599評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡疏橄,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出略就,到底是詐尸還是另有隱情捎迫,我是刑警寧澤,帶...
    沈念sama閱讀 34,264評(píng)論 4 328
  • 正文 年R本政府宣布表牢,位于F島的核電站窄绒,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏崔兴。R本人自食惡果不足惜彰导,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,857評(píng)論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望敲茄。 院中可真熱鬧位谋,春花似錦、人聲如沸堰燎。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,731評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽秆剪。三九已至赊淑,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間仅讽,已是汗流浹背陶缺。 一陣腳步聲響...
    開封第一講書人閱讀 31,956評(píng)論 1 264
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留洁灵,地道東北人饱岸。 一個(gè)月前我還...
    沈念sama閱讀 46,286評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像处渣,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子蛛砰,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,465評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 171,742評(píng)論 25 707
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理罐栈,服務(wù)發(fā)現(xiàn),斷路器泥畅,智...
    卡卡羅2017閱讀 134,628評(píng)論 18 139
  • 本文章涉及代碼已放到github上annotation-study 1.Annotation為何而來 What:A...
    zlcook閱讀 29,127評(píng)論 15 116
  • Spring Boot 參考指南 介紹 轉(zhuǎn)載自:https://www.gitbook.com/book/qbgb...
    毛宇鵬閱讀 46,773評(píng)論 6 342
  • 網(wǎng)絡(luò)監(jiān)聽用到的類為Reachability.h荠诬,這個(gè)Xcode項(xiàng)目里面是不自帶的,需要從github上面下載,在使...
    TomatosX閱讀 1,040評(píng)論 0 5