Java基礎增強-(註解,動態代理,類加載器,servlet3.0新特性)

1.   Annotation註解html

1.1.  Annotation概述

Annotation是JDK 5.0之後提供對元數據的支持,能夠在編譯、加載和運行時被讀取,並執行相應的處理。所謂Annotation就是提供了一種爲程序元素設置元數據的方法,可用於修飾包、類、構造器、方法、成員變量、參數和局部變量的聲明,這些信息被存儲在Annotation的「name=value」對中。java

Annotation能被用來爲程序元素(類、方法、成員變量等)設置元數據,好比一段代碼的做者或者告訴編譯器禁止一些特殊的錯誤,不會影響代碼的執行。mysql

1.2.  基本Annotation

在Java中提供了3個基本Annotation的用法,使用Annotation時要在其前面增長@符號,並把該Annotation看成一個修飾符使用,用於修飾它支持的程序元素。這3個基本Annotation都定義在java.lang包下,能夠經過查看API文檔來了解。web

  • @Override:限定重寫父類方法。

@Override就是用來指定方法覆載的,它能夠強制一個子類必須覆蓋父類的方法。sql

 

public class Fruit {
    public void info(){
        System.out.println("這是一個水果,想吃嗎?");
    }
}
public class Apple extends Fruit {
    @Override
    public void info() {
        System.out.println("這不只是一個水果,它是蘋果.");
    }

 

 

若是Apple類的info()方法名寫成了inf()的話,編譯器會報錯。值得注意的是,@Override只能修飾方法,不能修飾其餘程序元素。數據庫

  • @Deprecated:標示已過期。

@Deprecated用於表示某個程序元素已過期,當其餘程序使用已過期的類、方法時,編譯器將會給出警告。設計模式

 

public class Fruit {
    @Deprecated
    public void info(){
        System.out.println("這是一個水果,想吃嗎?");
    }
}
public class DeprecatedTest {
    public static void main(String[] args) {
        // 使用info()方法時將會出現劃線,表示該方法已過期.
        new Fruit().info();
    }
}

 

  • @SuppressWarnings:抑制編譯器警告。

@SuppressWarnings表示被該Annotation修飾的代碼取消顯示指定的編譯器警告。數組

 

public class SuppressWarningsTest {
    public static void main(String[] args) {
        @SuppressWarnings("rawtypes")
        /*
         * List集合在定義時,沒有指定泛型類型.
         *  * 默認狀況下,出現編譯器警告.
         *  * 使用@SuppressWarnings註釋後,取消警告信息.
         */
        List list = new ArrayList();
    }
}

 關於jdk的註解@SuppressWarnings詳解緩存

@SuppressWarnings
J2SE 提供的一個批註或者註解。該批註的做用是給編譯器一條指令,告訴它對被批註的代碼元素內部的某些警告保持靜默,即忽略這些警告信息。安全

在日常的編碼過程當中,咱們常用到的是unchecked,serial這些。

@SuppressWarnings()中可傳入一個字符串數組,數組中列出須要忽略的狀況。
若是傳入多種狀況,這幾種狀況的處理同時執行。例如:

@SuppressWarnings({"unchecked","serial"})
public void test(){
  //
}


如果只忽略一種狀況的話,就能夠寫成這樣

@SuppressWarnings("unchecked")
public void test(){
  //
}


如下是主要的幾種狀況:
關鍵字 用途
deprecation  使用了已過期或者不推薦使用的類或方法時的警告
unchecked  執行了未檢查的轉換時的警告,例如當使用集合時沒有用泛型 (Generics) 來指定集合保存的類型
fallthrough  當 Switch 程序塊直接通往下一種狀況而沒有 Break 時的警告
path  在類路徑、源文件路徑等中有不存在的路徑時的警告
serial  當在可序列化的類上缺乏 serialVersionUID 定義時的警告
all  關於以上全部狀況的警告

 

1.3.  自定義Annotation

自定義一個Annotation類型使用@interface關鍵字,定義一個新的Annotation類型與定義一個接口很是像(只是多了一個@符號)。

 

// 自定義一個Annotation類型
public @interface Test {
}

 

在自定義一個Annotation類型一般能夠用於修飾程序中的類、方法、變量、接口等。通常狀況下,使用Annotation會在代碼以前使用。

 

// 自定義Annotation類型定義在類上.
@Test
public class AnnotationTest {
    // 自定義Annotation類型定義在成員變量上.
    @Test
    private int i;
    // 自定義Annotation類型定義在構造函數上.
    @Test
    public AnnotationTest(){}
    // 自定義Annotation類型定義在方法上.
    @Test
    // 自定義Annotation類型定義在方法參數上.
    public void fun(@Test String str){
        // 自定義Annotation類型定義在變量上.
        @Test
        int z;
    }
}

 

1.4.  Annotation屬性

自定義Annotation不只能夠是這種簡單形式,還能夠包含成員變量。自定義的Annotation的成員變量以無形參的方法形式來聲明,其方法名和返回值定義了該成員變量的名字和類型。

 

/**
 * 自定義帶有username和password屬性的Annotation
 * */
public @interface UserInfo {
    String username();
    String password();
}

 

使用帶有屬性的自定義Annotation時,必須使用其屬性指定值,不然會報錯。

 

@UserInfo(username="zhangwuji",password="123")
public class UserInfoTest {
}

 

自定義Annotation不只能夠設置屬性,還能夠爲屬性設置默認值,使用default關鍵字。

/**
 * 自定義帶有username和password屬性的Annotation
*  * 爲username屬性設置默認值.
 * @author 金雲龍
 */
public @interface UserInfo {
    String username() default "zhangwuji";
    String password();
}

 

若是爲自定義Annotation的屬性設置了默認值,則在使用時能夠不爲該屬性指定值(使用默認值)。也能夠在使用該Annotation時爲其屬性指定值,則默認值不會起做用。

自定義Annotation中具備名爲value的屬性,在使用該Annotation時若是隻使用value屬性的話,能夠不寫屬性名直接指定值。

 

@UserInfo("jiaozhu")
public class UserInfoTest {
}

 

Annotation的屬性類型只能是基本類型、String、Enum、Class及上述類型的一維數組類型。

1.5.  @Target註解

@Target修飾自定義Annotation,指定該自定義Annotation能夠用於修飾哪些程序單元,例如方法、成員變量等。@Target註解包含一個ElementType類型的value屬性,該屬性值只能是以下幾個:

  • ElementType.ANNOTATION_TYPE:指定該策略的Annotation只能修飾Annotation。
  • ElementType.CONSTRUCTOR:指定該策略的Annotation只能修飾構造器。
  • ElementType.FIELD:指定該策略的Annotation只能修飾成員變量。
  • ElementType.LOCAL_VARIABLE:指定該策略的Annotation只能修飾局部變量。
  • ElementType.METHOD:指定該策略的Annotation只能修飾方法定義。
  • ElementType.PACKAGE:指定該策略的Annotation只能修飾包定義。
  • ElementType.PARAMETER:指定該策略的Annotation只能修飾參數。
  • ElementType.TYPE:指定該策略的Annotation能夠修飾類、接口或枚舉定義。

如下是@Target註解的源碼和ElementType的源碼:

 

@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
    ElementType[] value();
}
public enum ElementType {
    TYPE,FIELD,METHOD,PARAMETER,CONSTRUCTOR,LOCAL_VARIABLE, ANNOTATION_TYPE, PACKAGE
}

 

1.6.  @Retention註解

@Retention修飾自定義Annotation,指定自定義Annotation的生命週期。@Retention包含一個RetentionPolicy類型的value屬性,該屬性值只能是以下幾個:

  • RetentionPolicy.CLASS:編譯器將把Annotation記錄在class文件中。當運行Java程序時,JVM不可獲取Annotation信息。這時默認值。
  • RetentionPolicy.RUNTIME:編譯器將把Annotation記錄在class文件中。當運行Java程序時,JVM也能夠獲取Annotation信息,程序能夠經過反射獲取該Annotation信息。
  • RetentionPolicy.SOURCE:Annotation只保留在源代碼中,編譯器直接丟棄這種Annotation。

如下是@Retention註解的源碼和RetentionPolicy的源碼:

 

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {
    RetentionPolicy value();
}
public enum RetentionPolicy {
    SOURCE, CLASS, RUNTIME
}

 

1.7.  反射讀取Annotation

使用Annotation修飾了類、方法、成員變量等以後,這些Annotation不會本身生效,必須經過相應程序提取並處理Annotation信息。Java提供的Annotation接口是全部註解的父接口,在JDK 5.0新增長AnnotatedElement接口,該接口提供讀取運行時Annotation的方法。只有當自定義的Annotation使用了@Retention(RetentionPolicy.RUNTIME)時,該Annotation纔會在運行可見,JVM才能讀取保存在class文件的Annotation信息。

如下是AnnotatedElement接口提供的方法API:

方法摘要

<T extends Annotation>
T

getAnnotation(Class<T> annotationClass)
若是存在該元素的指定類型的註釋,則返回這些註釋,不然返回 null。

Annotation[]

getAnnotations()
返回此元素上存在的全部註釋。

Annotation[]

getDeclaredAnnotations()
返回直接存在於此元素上的全部註釋。

boolean

isAnnotationPresent(Class<? extends Annotation> annotationClass)
若是指定類型的註釋存在於此元素上,則返回 true,不然返回 false。

實際獲取某類使用的Annotation信息的方式以下:

public class AnnotatedElementTest {
    public static void main(String[] args) throws Exception {
        // 獲取對應類的Class對象.
        Class<UserInfoTest> clazz = UserInfoTest.class;
        // 獲取對應類方法的Method對象.
        Method method = clazz.getMethod("fun");
        // 獲取類上的註解.
        UserInfo anno1 = clazz.getAnnotation(UserInfo.class);
        // 打印該註解的username屬性值.
        System.out.println(anno1.username());
        // 獲取方法上的註解.
        UserInfo anno2 = method.getAnnotation(UserInfo.class);
        // 打印該註解的username屬性值.
        System.out.println(anno2.password());
    }
}

 

1.8.  註解配置JDBC案例

使用JDBC鏈接MySQL數據庫時,須要driverClassName、url、username和password四個參數。而以前的作法是將這四個參數寫入一個配置文件,在JDBCUtils工具類中讀取配置文件。目前能夠將四個參數定義爲一個註解,在JDBCUtils工具類中經過反射獲取對應註解定義的四個參數內容。具體作法以下:

  • 定義一個Annotation用於定義JDBC鏈接MySQL數據庫所需的四個參數內容。

 

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface JDBCInfo {
    String driverClassName();
    String url();
    String username();
    String password();
}

 

  • 定義JDBCUtils工具類,使用Annotation配置四個參數內容,並使用反射進行讀取。

 

public class JDBCUtils {
    @JDBCInfo(driverClassName = "com.mysql.jdbc.Driver", url = "jdbc:mysql://localhost:3306/jdbc", username = "root", password = "root")
    public static Connection getConnection() throws Exception {
        // 獲取註解修飾目標對應的反射對象.
        Method method = JDBCUtils.class.getDeclaredMethod("getConnection");
        // 判斷是否存在目前註解
if (method.isAnnotationPresent(JDBCInfo.class)) {
            // 獲取註解信息
            JDBCInfo jdbcInfo = method.getAnnotation(JDBCInfo.class);
            // 讀取註解屬性信息
            String driverClassName = jdbcInfo.driverClassName();
            String url = jdbcInfo.url();
            String username = jdbcInfo.username();
            String password = jdbcInfo.password();
            // Class類加載驅動
            Class.forName(driverClassName);
            // 返回鏈接對象
            return DriverManager.getConnection(url, username, password);
        }
        return null;
    }
}

 

 

  • 編寫一個測試類用於測試JDBCUtils工具類是否正確。

 

public class JDBCTest {
    public static void main(String[] args) throws Exception {
        Connection conn = JDBCUtils.getConnection();
        String sql = "select * from products";
        PreparedStatement statement = conn.prepareStatement(sql);
        ResultSet rs = statement.executeQuery();

        while (rs.next()) {
            System.out.println(rs.getString("name") + "," + rs.getDouble("price"));
        }

        rs.close();
        statement.close();
        conn.close();
    }
}

 

2.   動態代理
  1. 2.   

2.1.  動態代理概述

代理模式是Java設計模式中的一種,其特徵爲代理類與委託類有一樣的接口,代理類主要負責爲委託類預處理消息、過濾消息、把消息轉發給委託類,以及過後處理消息等。代理類與委託類之間一般存在關聯關係,一個代理類的對象與一個委託類的對象關聯,代理類的對象自己並不真正實現業務,而是經過調用委託類對象的相關方法來提供具體業務。

在Java中的java.lang.reflect包下提供了一個Proxy類和一個InvocationHandler接口,經過這個類和接口能夠生成JDK動態代理或動態代理對象。

按照代理的建立時間不一樣,能夠分爲兩種:

  • 靜態代理:手動建立,再對其編譯。在程序運行前,代理類的.class文件就已經存在。
  • 動態代理:在程序運行時,經過反射機制動態建立而成。

2.2.  動態代理原理

動態代理的實現原理有些相似於過濾器的實現原理,但有所不一樣。動態代理的代理類與委託類之間的關係更像是明星與經紀人之間的關係,也就是說,若是你想找某個明星演出的話,並非找他本人,而是找到他的經紀人就能夠了。動態代理的實現過程很相似於這個過程,具體請看下圖:

 

 

2.3.  Proxy代理類

Proxy類是Java的java.lang.reflect包下提供的,該類用於建立動態代理類和代理對象的靜態方法,它也是全部動態代理類的父類。若是在程序中爲一個或多個接口動態地生成實現類,就能夠用Proxy類來建立動態代理類;若是須要爲一個或多個接口動態地建立實例,也可使用Proxy類來建立動態代理實例。

方法摘要

static InvocationHandler

getInvocationHandler(Object proxy)
返回指定代理實例的調用處理程序。

static Class<?>

getProxyClass(ClassLoader loader, Class<?>... interfaces)
返回代理類的 java.lang.Class 對象,並向其提供類加載器和接口數組。

static boolean

isProxyClass(Class<?> cl)
當且僅當指定的類經過 getProxyClass 方法或 newProxyInstance 方法動態生成爲代理類時,返回 true。

static Object

newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
返回一個指定接口的代理類實例,該接口能夠將方法調用指派到指定的調用處理程序。

  • static Class<?> getProxyClass(ClassLoader loader, Class<?>... interfaces):建立一個動態代理類所對應的Class對象,該代理類將實現interfaces所指定的多個接口。第一個ClassLoader參數指定生成動態代理類的類加載器。
  • static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h):直接建立一個動態代理對象,該代理對象的實現類實現了interfaces指定的系列接口,執行代理對象的每一個方法時都會被替換執行InvocationHandler對象的invoke()方法。

2.4.  InvocationHandler

InvocationHandler接口提供了invoke()方法,用於替換代理對象的每個方法。真實業務類能夠經過代理類對象調用InvocationHandler接口提供的invoke()方法,來替代調用委託類的真實方法。

如下是InvocationHandler的API內容:

方法摘要

Object

invoke(Object proxy, Method method, Object[] args)
在代理實例上處理方法調用並返回結果。

  • Object invoke(Object proxy, Method method, Object[] args):在代理實例上處理方法調用並返回結果。在與方法關聯的代理實例上調用方法時,將在調用處理程序上調用此方法。

²  參數proxy:表示代理類對象,也就是Proxy.newProxyInstance()方法返回的對象,一般用不上。

²  參數method:表示當前被調用方法的反射對象,

²  參數args:表示調用目標方法時傳入的實參。

2.5.  實現動態代理

利用Java提供的Proxy類和InvocationHandler接口來生成動態代理類或動態代理對象,具體實現步驟以下:

  • 定義一個業務接口,該接口提供具體業務方法的定義。

 

public interface Person {
    void sayMe();
    void sayHello(String name);
}

 

  • 定義一個InvocationHandler接口的實現類,並重寫invoke()方法。

 

public class MyInvocationHandler implements InvocationHandler {
    /**
     * 執行動態代理對象的全部方法時,都會被替換成執行下面的invoke()方法.
     *  * 參數proxy:表明動態代理對象.
*  * 參數method:表明正在執行的方法.
     *  * 參數args:表明調用目標方法時傳入的實參.
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable {
        System.out.println("---正在執行的方法: "+method);
        if(args == null){
            System.out.println("當前調用的方法沒有參數.");
        }else{
            System.out.println("當前調用的方法須要傳入的實參爲:");
            for (Object val : args) {
                System.out.println(val);
            }
        }
        return null;
    }
}

 

 

  • 編寫一個用於測試動態代理的測試類。

 

public class ProxyTest {
    public static void main(String[] args) {
        // 建立一個InvocationHandler對象
        InvocationHandler handler = new MyInvocationHandler();
        // 經過Proxy類使用指定的InvocationHandler來生成動態代理對象
        Person p = (Person)Proxy.newProxyInstance(Person.class.getClassLoader(), new Class[]{Person.class}, handler);
        // 調用動態代理對象的業務方法
        p.sayMe();
        p.sayHello("張無忌");
    }
}

 

2.6.  動態代理的做用

經過Java提供的Proxy類和InvocationHandler接口生成的動態代理類,能夠阻止調用委託類的方法、過濾參數及修改對應方法的返回值等做用。實現業務接口方法的實現類即委託類,具體操做以下:

  • 建立一個實現類,實現Person接口,並重寫業務方法。

 

public class Fanbingbing implements Person {
    @Override
    public void sayMe() {
        System.out.println("我真的是范冰冰哦!");
    }

   @Override

 
   

   public String sayHello(String name) {

 
   

      System.out.println("你好:"+name+",我等你好久了...");

 
   

      return "我終於見到范冰冰啦!";

 
   

   }

 
   

}

 

 

 

  • 編寫一個用於測試動態代理的測試類。

 

public class FanbingbingTest {
    public static void main(String[] args) {
        Person p = (Person) Proxy.newProxyInstance(
                Person.class.getClassLoader(),
                Fanbingbing.class.getInterfaces(), new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method,
                            Object[] args) throws Throwable {
                        // 經過method的getName()方法獲取業務方法名,進行阻止.
                        if (method.getName().equals("sayMe")) {
                            System.out.println("你想多了,哪那麼容易見到啊!");
                            return null;
                        }
                        // 經過args獲取實參,進行修改
                        if(method.getName().equals("sayHello")){
                            String name = (String)args[0];
                            method.invoke(Class.forName("app.java.proxy.Fanbingbing").newInstance(), "某局長");
                        }
                        // 修改返回值
                        if(method.getName().equals("sayHello")){
                            return "都是假的!";
                        }
                        return null;
                    }
                });
        p.sayMe();
        p.sayHello("張無忌");
    }
}

 

2.7.  權限控制案例

目前已經掌握註解和動態代理的內容,下面利用註解和動態代理來完成權限控制的功能。首先,完成基本業務的功能,具體以下操做:

  • 在MySQL數據庫中建立相關數據庫表及初始化必要數據記錄。

 

CREATE TABLE userinfo(
  id INT PRIMARY KEY AUTO_INCREMENT,
  username VARCHAR(40),
  PASSWORD VARCHAR(40)
);

INSERT INTO userinfo VALUES(NULL,'zhangwuji','123');
INSERT INTO userinfo VALUES(NULL,'zhouzhiruo','123');
INSERT INTO userinfo VALUES(NULL,'zhaomin','123');

CREATE TABLE PRIVILEGES(
   id INT PRIMARY KEY AUTO_INCREMENT,
   NAME VARCHAR(40)
);

INSERT INTO PRIVILEGES VALUES(NULL,'添加圖書');
INSERT INTO PRIVILEGES VALUES(NULL,'修改圖書');
INSERT INTO PRIVILEGES VALUES(NULL,'查看圖書');
INSERT INTO PRIVILEGES VALUES(NULL,'刪除圖書');

CREATE TABLE userprivilege(
   user_id INT ,
   privilege_id INT,
   FOREIGN KEY(user_id) REFERENCES userinfo(id),
   FOREIGN KEY(privilege_id) REFERENCES PRIVILEGES(id),
   PRIMARY KEY(user_id,privilege_id)
);

INSERT INTO userprivilege VALUES(1,1);
INSERT INTO userprivilege VALUES(1,2);
INSERT INTO userprivilege VALUES(1,3);

 

  • 建立一個JavaBean用於封裝用戶信息。

 

public class User {
    private int id;
    private String username;
    private String password;
    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    public String getUsername() {
        return username;
    }
    public void setUsername(String username) {
        this.username = username;

}

   public String getPassword() {

      return password;

   }

   public void setPassword(String password) {

      this.password = password;

   }

}

 

 

  • 建立一個JSP頁面用於用戶登陸功能的顯示。
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
  <head>
    <title>My JSP 'login.jsp' starting page</title>
  </head>
  <body>
    <form action="../login" method="post" >
        用戶名 <input type="text" name="username" /> 
        密碼 <input type="password" name="password"/>
        <input type="submit" value="登錄" />
    </form>
  </body>
</html>
  • 建立一個Servlet用於處理用戶登陸邏輯。
public class LoginServlet extends HttpServlet {
    public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        try {
            User user = new User();
            BeanUtils.populate(user, request.getParameterMap());
            QueryRunner runner = new QueryRunner(JDBCUtils.getDataSource());
            String sql = "select * from userinfo where username = ? and password = ?";
            User existUser = runner.query(sql, new BeanHandler<User>(User.class), user.getUsername(), user.getPassword());
            if (existUser == null) {
                response.sendRedirect("/21_java/proxy/login.jsp");
            } else {
                request.getSession().setAttribute("user", existUser);
                response.sendRedirect("/21_java/proxy/book.jsp");
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doGet(request, response);
    }
}

 

  • 建立一個JSP頁面用於登陸成功的功能列表顯示
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
  <head>
    <title>My JSP 'book.jsp' starting page</title>
  </head>
  <body>
    <h1>圖書管理</h1>
    <h2>${empty user?'未登錄':user.username }</h2>
    <a href="../book?operate=add">添加圖書</a>
    <a href="../book?operate=del">刪除圖書</a>
    <a href="../book?operate=edit">修改圖書</a>
    <a href="../book?operate=search">查看圖書</a>
  </body>
</html>

 

  • 建立一個Servlet用於對具體業務的操做邏輯處理。
public class BookServlet extends HttpServlet {
    public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        String operate = request.getParameter("operate");
        User user = (User) request.getSession().getAttribute("user");
        BookService service = new BookServiceImpl();
        
        if("add".equals(operate)){
            service.addBook(user);
        }else if("del".equals(operate)){
            service.delBook(user);
        }else if("edit".equals(operate)){
            service.editBook(user);
        }else if("search".equals(operate)){
            service.searchBook(user);
        }
    }
    public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doGet(request, response);
    }
}

 

  • 建立一個業務接口和業務實現類用於處理具體業務流程。
public interface BookService {
    public void addBook(User user);
    public void delBook(User user);
    public void editBook(User user);
    public void searchBook(User user);
}

 

public class BookServiceImpl implements BookService {
    public void addBook(User user){
        System.out.println("添加圖書成功...");
    }
    public void delBook(User user){
        System.out.println("刪除圖書成功...");
    }
    public void editBook(User user){
        System.out.println("修改圖書成功...");
    }
    public void searchBook(User user){
        System.out.println("查看圖書成功...");
    }
}
  • 配置Web工程的web.xml文件。
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="3.0" 
    xmlns="http://java.sun.com/xml/ns/javaee" 
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee 
    http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">
  <display-name></display-name>
  <servlet>
    <servlet-name>LoginServlet</servlet-name>
    <servlet-class>app.java.proxy.demo.LoginServlet</servlet-class>
  </servlet>
  <servlet>
    <servlet-name>BookServlet</servlet-name>
    <servlet-class>app.java.proxy.demo.BookServlet</servlet-class>
  </servlet>
  <servlet-mapping>
    <servlet-name>LoginServlet</servlet-name>
    <url-pattern>/login</url-pattern>
  </servlet-mapping>
  <servlet-mapping>
    <servlet-name>BookServlet</servlet-name>
    <url-pattern>/book</url-pattern>
  </servlet-mapping>    
  <welcome-file-list>
    <welcome-file>index.jsp</welcome-file>
  </welcome-file-list>
</web-app>

完成到目前爲止,已經將一個圖書操做(增刪改查)的功能完成。下面利用註解和動態代理來完成權限控制的功能,具體操做以下:

  • 建立一個Annotation註解用於配置權限。
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.METHOD)
    @Inherited
    public @interface PrivilegeInfo {
        String value();
    }
  • 利用Annotation註解爲業務接口及實現類的方法配置權限。
    public interface BookService {
        @PrivilegeInfo("添加圖書")
        public void addBook(User user);
        @PrivilegeInfo("刪除圖書")
        public void delBook(User user);
        @PrivilegeInfo("修改圖書")
        public void editBook(User user);
        @PrivilegeInfo("查看圖書")
        public void searchBook(User user);
    }
  • 建立一個業務工廠類用於獲取對應業務類實例對象。
    public class ServiceFactory {
        public static BookService getBookService(){
            // 獲取真實業務實現類對象
            final BookService service = new BookServiceImpl();
            // 經過Proxy代理類獲取對應業務實現類的代理對象
            BookService serviceProxy = (BookService) Proxy.newProxyInstance(service.getClass().getClassLoader(), service.getClass().getInterfaces(), new InvocationHandler() {
                @Override
                public Object invoke(Object proxy, Method method, Object[] args)
                        throws Throwable {
                    // 判斷對應業務實現類是否存在註解信息
                    if (method.isAnnotationPresent(PrivilegeInfo.class)) {
                        // 獲取註解修飾對象
                        PrivilegeInfo info = method.getAnnotation(PrivilegeInfo.class);
                        // 獲取註解修飾屬性值(當前業務方法的權限)
                        String needPrivilege = info.value();
                        // 獲取當前用戶信息
                        User existUser = (User) args[0];
                        // 查詢當前用戶的全部權限
                        QueryRunner queryRunner = new QueryRunner(JDBCUtils.getDataSource());
                        String sql = "select privileges.name from privileges,userprivilege where privileges.id = userprivilege.privilege_id and userprivilege.user_id = ?";
                        List<Object> privilegeList = queryRunner.query(sql,new ColumnListHandler("name"), existUser.getId());
                        // 判斷當前用戶是否具備權限
                        for (Object userPrivilege : privilegeList) {
                            if (needPrivilege.equals(userPrivilege)) {
                                // 具備權限
                                return method.invoke(service, args);
                            }
                        }
                        // 沒有權限
                        throw new RuntimeException("權限不足,沒法訪問");
                    }
                    // 不須要權限
                    return method.invoke(service, args);
                }
            });
            return serviceProxy;
        }
    }
  • 修改用於對具體業務的操做邏輯處理的Servlet代碼。
    BookService service = ServiceFactory.getBookService();

 

3.   類加載器

3.0.什麼是類加載器,做用是什麼?

類加載器就加載字節碼文件(.class)

3.1.  類加載器概述

類加載器負責加載全部的類,系統爲全部被載入內存中的類生成一個java.lang.Class實例。一旦一個類被加入JVM中,同一個類就不會被再次加入了。正如一個對象有一個惟一的標識同樣,一個載入JVM的類也有一個惟一的標識。在Java中,一個類用其全限定類名(包括包名和類名)做爲標識;但在JVM中,一個類用其全限定類名和其類加載器做爲其惟一標識。

例如如下案例,在JVM中兩個同名的Person類是徹底不一樣的,之間也互不兼容,由於類加載器不一樣。

 

 

上述狀況轉換成代碼以下:

  • 定義一個Person類。

 

public class Person {
    private Person instance; 
    public void setPerson(Object instance) { 
        this.instance = (Person) instance; 
    } 
}

 

  • 編寫一個測試方法,用於測試經過兩個不一樣類加載器加載Person類獲得不一樣的Class實例。
    @Test
    public void ClassIdentityTest() throws Exception{
        String classDataRootPath = "WebRoot\\WEB-INF\\classes"; 
        FileSystemClassLoader fscl1 = new FileSystemClassLoader(classDataRootPath); 
        FileSystemClassLoader fscl2 = new FileSystemClassLoader(classDataRootPath); 
        String className = "app.java.classloader.Person";
        try { 
            Class<?> class1 = fscl1.findClass(className); 
            Object obj1 = class1.newInstance(); 
            Class<?> class2 = fscl2.findClass(className); 
            Object obj2 = class2.newInstance(); 
            Method setSampleMethod = class1.getMethod("setPerson", java.lang.Object.class); 
            setSampleMethod.invoke(obj1, obj2); 
        } catch (Exception e) { 
            e.printStackTrace(); 
        } 
    }

 

  • 上述代碼運行後,報以下錯誤,表示雖然兩個對象的類名稱相同,可是因爲經過不一樣類加載器獲得Class實例,JVM不會認爲是相同的。

 

 

3.2.  類加載器分類

當JVM啓動時,會造成由三個類加載器組成的初始類加載器層次結構。

  • Bootstrap ClassLoader:根類加載器。
  • Extension ClassLoader:擴展類加載器。
  • System ClassLoader:系統類加載器。

Bootstrap ClassLoader被稱爲引導(或原始或根)類加載器,它負責加載Java的核心類。在JVM中,當執行java.exe命令時,使用-Xbootclasspath選項或使用-D選項指定sun.boot.class.path系統屬性值能夠指定加載附加的類。根類加載器很是特殊,它並非java.lang.ClassLoader的子類,而是由JVM自身實現的。

 

    @Test
    public void BootstrapTest(){
        // 獲取根類加載器所加載的所有URL數組
        URL[] urls = sun.misc.Launcher.getBootstrapClassPath().getURLs();
        // 遍歷輸出根類加載器加載的所有URL
        for (int i = 0; i < urls.length; i++) {
            System.out.println(urls[i].toExternalForm());
        }
    }

 

Extension ClassLoader被稱爲擴展類加載器,它負責加載JRE的擴展目錄(%JAVA_HOME%/jre/lib/ext或由java.ext.dirs系統屬性指定的目錄)中的JAR包的類。經過這種方式能夠爲Java擴展核心類之外的新功能,只要把本身開發的類打包成JAR文件,而後放入%JAVA_HOME%/jre/lib/ext路徑便可。

 

    @Test
    public void ExtensionTest() throws Exception{
        // 位於jre/lib/ext/dnsns.jar
        DNSNameService dnsNameService = new DNSNameService();
        System.out.println(dnsNameService.getClass().getClassLoader());
    }

 

System ClassLoader被稱爲系統或應用類加載器,它負責在JVM啓動時加載來自java命令的-classpath選項、java.class.path系統屬性,或CLASSPATH環境變量所指定的JAR包和類徑路。程序能夠經過ClassLoader的靜態方法getSystemClassLoader()來獲取系統類加載器。若是沒有特別指定,則用戶自定義的類加載器都以系統類加載器做爲父加載器。

 

    @Test
    public void SystemTest() throws Exception{
        // 獲取當前類的實例對象
        ClassLoaderTest classLoaderTest = new ClassLoaderTest();
        System.out.println(classLoaderTest.getClass().getClassLoader());
    }

 

如下是JVM中4種類加載器的層次結構:

 

 

 

    @Test
    public void ClassLoaderTreeTest() throws Exception{
        // 獲取當前類的類加載器
        ClassLoader classLoader = ClassLoaderTest.class.getClassLoader();
        // 判斷類加載器是否爲空
        while (classLoader != null) {
            // 打印當前類加載器的名稱
            System.out.print(classLoader.getClass().getName()+"->");
            // 獲取當前類加載器的父級
            classLoader = classLoader.getParent();
        }
        System.out.println(classLoader);
    }

 

上述代碼中能夠看出JVM中類加載器的層次結構。最後輸出的是null的願意是由於根類加載器並非Java提供的,而是JVM提供的,因此不能獲取。

3.3.  類加載機制

JVM的類加載器機制主要有以下三種:

  • 全盤負責。所謂全盤負責,就是當一個類加載器負責加載某個Class時,該Class所依賴的和引用的其餘Class也將由該類加載器負責載入,除非顯式使用另一個類加載器來載入。
  • 父類委託。所謂父類委託,則是先讓父類加載器視圖加載該Class,只有在父類加載器沒法加載該類時才嘗試從本身的類路徑中加載該類。
  • 緩存機制。緩存機制將會保證全部加載過的Class都會被緩存,當程序中須要使用某個Class時,類加載器先從緩存區中搜索該Class,只有當緩存區中不存在該Class對象時,系統纔會讀取該類對應的二進制數據,並將其轉換成Class對象,存入緩存區中。這也是爲何修改Class後,必須從新啓動JVM,程序所作的修改纔會生效的緣由。

3.4.  自定義類加載器

JVM中除根類加載器以外的全部類加載器都是ClassLoader子類的實例,能夠經過擴展ClassLoader的子類,並重寫該ClassLoader提供的方法來實現自定義的類加載器。

ClassLoader類具備以下兩個關鍵方法:

  • loadClass(String name, boolean resolve):該方法爲ClassLoader的入口點,根據指定名稱來加載類,系統就是調用ClassLoader的該方法來獲取指定類對應的Class對象。
  • findClass(String name):根據指定名稱來查找類。

因爲loadClass()方法的執行步驟爲1)利用findLoadedClass()方法來檢查是否已經加載類。2)在父類加載器調用loadClass()方法。3)調用findClass()方法查找類。重寫findClass()方法能夠避免默認類加載器的父類委託和緩衝機制,這裏推薦重寫findClass()方法。

下面來實現一個文件系統類加載器,用於加載存儲在文件系統上的Java字節代碼。

public class FileSystemClassLoader extends ClassLoader {

    private String rootDir;

    public FileSystemClassLoader(String rootDir) {
        this.rootDir = rootDir;
    }

    protected Class<?> findClass(String name) throws ClassNotFoundException {
        byte[] classData = getClassData(name);
        if (classData == null) {
            throw new ClassNotFoundException();
        }
        else {
            return defineClass(name, classData, 0, classData.length);
        }
    }

    private byte[] getClassData(String className) {
        String path = classNameToPath(className);
        try {
            InputStream ins = new FileInputStream(path);
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            int bufferSize = 4096;
            byte[] buffer = new byte[bufferSize];
            int bytesNumRead = 0;
            while ((bytesNumRead = ins.read(buffer)) != -1) {
                baos.write(buffer, 0, bytesNumRead);
            }

        return baos.toByteArray();

 
   

      } catch (IOException e) {

 
   

        e.printStackTrace();

 
   

      }

 
   

      return null;

 
   

   }

 
   

 

 
   

   private String classNameToPath(String className) {

 
   

      return rootDir + File.separatorChar

 
   

           + className.replace('.', File.separatorChar) + ".class";

 
   

   }

 
   

}

 

 

 

3.5.  Tomcat類加載器

對於運行在 Java EE™容器中的 Web 應用來講,類加載器的實現方式與通常的 Java 應用有所不一樣。不一樣的 Web 容器的實現方式也會有所不一樣。以 Apache Tomcat 來講,每一個 Web 應用都有一個對應的類加載器實例。該類加載器也使用代理模式,所不一樣的是它是首先嚐試去加載某個類,若是找不到再代理給父類加載器。這與通常類加載器的順序是相反的。這是 Java Servlet 規範中的推薦作法,其目的是使得 Web 應用本身的類的優先級高於 Web 容器提供的類。這種代理模式的一個例外是:Java 核心庫的類是不在查找範圍以內的。這也是爲了保證 Java 核心庫的類型安全。

絕大多數狀況下,Web 應用的開發人員不須要考慮與類加載器相關的細節。下面給出幾條簡單的原則:

  • 每一個 Web 應用本身的 Java 類文件和使用的庫的 jar 包,分別放在WEB-INF/classes和WEB-INF/lib目錄下面。
  • 多個應用共享的 Java 類文件和 jar 包,分別放在 Web 容器指定的由全部 Web 應用共享的目錄下面。
  • 當出現找不到類的錯誤時,檢查當前類的類加載器和當前線程的上下文類加載器是否正確。
4.   Servlet3.0新特性
  1. 4.   

4.1.  註解替代配置文件

須要注意的是,若是使用Servlet 3.0版本的話:首先Tomcat服務器必須使用7.0版本以上的(老版本不提供3.0版本),其次Eclipse建立Web工程時選擇3.0版本。

Servlet 3.0版本容許使用註解方式來替代web.xml文件中配置Servlet、Filter和Listener的信息,首先回憶一下web.xml文件是如何配置Servlet、Filter和Listener的:

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="3.0" 
    xmlns="http://java.sun.com/xml/ns/javaee" 
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee 
    http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">
  <!-- 配置Servlet -->
  <servlet>
    <servlet-name>ServletTest</servlet-name>
    <servlet-class>app.java.servlet.ServletTest</servlet-class>
    <init-param>
        <param-name>longestory</param-name>
        <param-value>龍哥有話說</param-value>
    </init-param>
  </servlet>
  <servlet-mapping>
    <servlet-name>ServletTest</servlet-name>
    <url-pattern>/*</url-pattern>
  </servlet-mapping>
  <!-- 配置Filter -->
  <filter>
      <filter-name>FilterTest</filter-name>
      <filter-class>app.java.servlet.FilterTest</filter-class>
      <init-param>
        <param-name>longestory</param-name>
        <param-value>龍哥有話說</param-value>
    </init-param>
  </filter>
  <filter-mapping>
      <filter-name>FilterTest</filter-name>
      <url-pattern>/*</url-pattern>
  </filter-mapping>
  <!-- 配置Listener -->
  <listener>
      <listener-class>app.java.servlet.ListenerTest</listener-class>
  </listener>
</web-app>

 

  • 使用註解替代web.xml文件配置Servlet。

 

@WebServlet(urlPatterns="/*",initParams={@WebInitParam(name="longestory",value="龍哥有話說")},loadOnStartup=0)
public class ServletTest extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {
        super.doGet(req, resp);
    }
}

 

  • 使用註解替代web.xml文件配置Filter。

 

@WebFilter(urlPatterns="/*",initParams={@WebInitParam(name="longestory",value="龍哥有話說")})
public class FilterTest implements Filter {
    @Override
    public void init(FilterConfig arg0) throws ServletException {}
    @Override
    public void doFilter(ServletRequest arg0, ServletResponse arg1,
            FilterChain arg2) throws IOException, ServletException {
    }
    @Override
    public void destroy() {}
}

 

  • 使用註解替代web.xml文件配置Listener。

 

@WebListener
public class ListenerTest implements HttpSessionListener {
    @Override
    public void sessionCreated(HttpSessionEvent arg0) {}
    @Override
    public void sessionDestroyed(HttpSessionEvent arg0) {}
}

 

4.2.  Serlvet文件上傳

Servlet 3.0的另外一個新特性就是提供了處理文件上傳的功能,使用Servlet 3.0的內容實現文件上傳須要如下幾個內容:

  • 在處理文件上傳的Servlet上增長@MultipartConfig註解,表示當前Servlet符合MIME類型的multipart/form-data。

Optional Element Summary

int

fileSizeThreshold
The size threshold after which the file will be written to disk

java.lang.String

location
The directory location where files will be stored

long

maxFileSize
The maximum size allowed for uploaded files.

long

maxRequestSize
The maximum size allowed for multipart/form-data requests

  • Part接口,表示容許接收MIME類型是multipart/form-data類型的POST請求。

Method Summary

void

delete()
Deletes the underlying storage for a file item, including deleting any associated temporary disk file.

java.lang.String

getContentType()
Gets the content type of this part.

java.lang.String

getHeader(java.lang.String name)
Returns the value of the specified mime header as a String.

java.util.Collection<java.lang.String>

getHeaderNames()
Gets the header names of this Part.

java.util.Collection<java.lang.String>

getHeaders(java.lang.String name)
Gets the values of the Part header with the given name.

java.io.InputStream

getInputStream()
Gets the content of this part as an InputStream

java.lang.String

getName()
Gets the name of this part

long

getSize()
Returns the size of this fille.

void

write(java.lang.String fileName)
A convenience method to write this uploaded item to disk.

利用Servlet 3.0實現文件上傳的功能,具體實現代碼以下:

  • 建立一個JSP頁面用於文件上傳顯示。
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
  <head>
    <title>My JSP 'index.jsp' starting page</title>
  </head>
  <body>
    <form action="uploadFile" method="post" enctype="multipart/form-data">
        文件描述:<input type="text" name="filetext"><br>
        <input type="file" name="upload"><br>
        <input type="submit" value="上傳">
    </form>
  </body>
</html>

 

  • 建立一個Servlet用於處理文件上傳邏輯。
  • @WebServlet(urlPatterns="/uploadFile")
    @MultipartConfig(
    fileSizeThreshold = 10 * 1024,//緩存大小,當上傳的文件超出這個大小時會生成臨時文件
    location = "/temp",// 存放臨時文件的目錄
    maxFileSize = 5 * 1024,// 單個文件大小限制
    maxRequestSize = 10 * 1024// 整個請求大小限制
    )
    public class UploadFileServlet extends HttpServlet {
        @Override
        public void doPost(HttpServletRequest req, HttpServletResponse resp)
                throws ServletException, IOException {
            // 處理請求中文亂碼
            req.setCharacterEncoding("utf-8");
            // 接收上傳文件的描述內容
            String filetext = req.getParameter("filetext");
            // 接收上傳文件的內容
            Part part = req.getPart("upload");
            // 獲取上傳文件的真實名稱
            String cd = part.getHeader("Content-Disposition");
            int index = cd.indexOf("filename=\"") + 10;
            String filename = cd.substring(index, cd.length() - 1);
            // 讀取上傳目錄的絕對路徑
            String path = getServletContext().getRealPath("/upload");
            // 將上傳文件進行保存
            part.write(path + "/" + filename);
        }
        @Override
        public void doGet(HttpServletRequest req, HttpServletResponse resp)
                throws ServletException, IOException {
            doPost(req, resp);
        }
    }

     

 

 

4.3.  Servlet異步處理

所謂異步處理就是指數據發送方發送數據給數據接收方後,沒必要等待數據接收方做出響應,能夠繼續發送下一個數據的通訊方式。

Servlet 3.0版本提供了相關異步處理的功能,具體實現方式以下:

  • 須要使用註解在對應Servlet配置asyncSupported=true,表示當前Servlet支持異步。
  • 經過Request對象的startAsync(Request, Response)方法獲取異步上下文對象。
  • 經過異步上下文對象調用start(new Runnable(){})方法開始異步處理,Runnable類的run()方法提供具體異步的邏輯代碼。

根據上述步驟,經過一個代碼實例演示。

 

@WebServlet(urlPatterns="/async",asyncSupported=true)
public class AsyncServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, final HttpServletResponse resp)
            throws ServletException, IOException {
        // 設置響應類型及編碼格式
        resp.setContentType("text/html;charset=utf-8");
        resp.getWriter().print("立刻開始:<br/>");
        // 刷新緩衝區
        resp.getWriter().flush();
        // 獲取異步上下文對象
        final AsyncContext ac = req.startAsync(req, resp);
        // 開始異步處理
        ac.start(new Runnable() {
            @Override
            public void run() {
                try {
                    for (char ch = 'A'; ch <= 'Z'; ch++) {
                        resp.getWriter().print(ch);
                        resp.getWriter().flush();
                        Thread.sleep(250);
                    }
                    // 表示異步處理完畢
                    ac.complete();
                } catch (Exception e) {
                    throw new RuntimeException(e);
                }
            }
        });
    }
}

 

【當你用心寫完每一篇博客以後,你會發現它比你用代碼實現功能更有成就感!】
相關文章
相關標籤/搜索