1. Annotation註解html
Annotation是JDK 5.0之後提供對元數據的支持,能夠在編譯、加載和運行時被讀取,並執行相應的處理。所謂Annotation就是提供了一種爲程序元素設置元數據的方法,可用於修飾包、類、構造器、方法、成員變量、參數和局部變量的聲明,這些信息被存儲在Annotation的「name=value」對中。java
Annotation能被用來爲程序元素(類、方法、成員變量等)設置元數據,好比一段代碼的做者或者告訴編譯器禁止一些特殊的錯誤,不會影響代碼的執行。mysql
在Java中提供了3個基本Annotation的用法,使用Annotation時要在其前面增長@符號,並把該Annotation看成一個修飾符使用,用於修飾它支持的程序元素。這3個基本Annotation都定義在java.lang包下,能夠經過查看API文檔來了解。web
@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用於表示某個程序元素已過期,當其餘程序使用已過期的類、方法時,編譯器將會給出警告。設計模式
public class Fruit { @Deprecated public void info(){ System.out.println("這是一個水果,想吃嗎?"); } } public class DeprecatedTest { public static void main(String[] args) { // 使用info()方法時將會出現劃線,表示該方法已過期. new Fruit().info(); } }
@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 關於以上全部狀況的警告
自定義一個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; } }
自定義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及上述類型的一維數組類型。
@Target修飾自定義Annotation,指定該自定義Annotation能夠用於修飾哪些程序單元,例如方法、成員變量等。@Target註解包含一個ElementType類型的value屬性,該屬性值只能是以下幾個:
如下是@Target註解的源碼和ElementType的源碼:
@Target(ElementType.ANNOTATION_TYPE) public @interface Target { ElementType[] value(); } public enum ElementType { TYPE,FIELD,METHOD,PARAMETER,CONSTRUCTOR,LOCAL_VARIABLE, ANNOTATION_TYPE, PACKAGE }
@Retention修飾自定義Annotation,指定自定義Annotation的生命週期。@Retention包含一個RetentionPolicy類型的value屬性,該屬性值只能是以下幾個:
如下是@Retention註解的源碼和RetentionPolicy的源碼:
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.ANNOTATION_TYPE) public @interface Retention { RetentionPolicy value(); } public enum RetentionPolicy { SOURCE, CLASS, RUNTIME }
使用Annotation修飾了類、方法、成員變量等以後,這些Annotation不會本身生效,必須經過相應程序提取並處理Annotation信息。Java提供的Annotation接口是全部註解的父接口,在JDK 5.0新增長AnnotatedElement接口,該接口提供讀取運行時Annotation的方法。只有當自定義的Annotation使用了@Retention(RetentionPolicy.RUNTIME)時,該Annotation纔會在運行可見,JVM才能讀取保存在class文件的Annotation信息。
如下是AnnotatedElement接口提供的方法API:
方法摘要 |
||
|
|
|
|
||
|
||
|
|
實際獲取某類使用的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()); } }
使用JDBC鏈接MySQL數據庫時,須要driverClassName、url、username和password四個參數。而以前的作法是將這四個參數寫入一個配置文件,在JDBCUtils工具類中讀取配置文件。目前能夠將四個參數定義爲一個註解,在JDBCUtils工具類中經過反射獲取對應註解定義的四個參數內容。具體作法以下:
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface JDBCInfo { String driverClassName(); String url(); String username(); String password(); }
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; } }
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. 動態代理
代理模式是Java設計模式中的一種,其特徵爲代理類與委託類有一樣的接口,代理類主要負責爲委託類預處理消息、過濾消息、把消息轉發給委託類,以及過後處理消息等。代理類與委託類之間一般存在關聯關係,一個代理類的對象與一個委託類的對象關聯,代理類的對象自己並不真正實現業務,而是經過調用委託類對象的相關方法來提供具體業務。
在Java中的java.lang.reflect包下提供了一個Proxy類和一個InvocationHandler接口,經過這個類和接口能夠生成JDK動態代理或動態代理對象。
按照代理的建立時間不一樣,能夠分爲兩種:
動態代理的實現原理有些相似於過濾器的實現原理,但有所不一樣。動態代理的代理類與委託類之間的關係更像是明星與經紀人之間的關係,也就是說,若是你想找某個明星演出的話,並非找他本人,而是找到他的經紀人就能夠了。動態代理的實現過程很相似於這個過程,具體請看下圖:
Proxy類是Java的java.lang.reflect包下提供的,該類用於建立動態代理類和代理對象的靜態方法,它也是全部動態代理類的父類。若是在程序中爲一個或多個接口動態地生成實現類,就能夠用Proxy類來建立動態代理類;若是須要爲一個或多個接口動態地建立實例,也可使用Proxy類來建立動態代理實例。
方法摘要 |
|
|
|
|
|
|
|
|
|
InvocationHandler接口提供了invoke()方法,用於替換代理對象的每個方法。真實業務類能夠經過代理類對象調用InvocationHandler接口提供的invoke()方法,來替代調用委託類的真實方法。
如下是InvocationHandler的API內容:
方法摘要 |
|
|
² 參數proxy:表示代理類對象,也就是Proxy.newProxyInstance()方法返回的對象,一般用不上。
² 參數method:表示當前被調用方法的反射對象,
² 參數args:表示調用目標方法時傳入的實參。
利用Java提供的Proxy類和InvocationHandler接口來生成動態代理類或動態代理對象,具體實現步驟以下:
public interface Person { void sayMe(); void sayHello(String name); }
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("張無忌"); } }
經過Java提供的Proxy類和InvocationHandler接口生成的動態代理類,能夠阻止調用委託類的方法、過濾參數及修改對應方法的返回值等做用。實現業務接口方法的實現類即委託類,具體操做以下:
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("張無忌"); } }
目前已經掌握註解和動態代理的內容,下面利用註解和動態代理來完成權限控制的功能。首先,完成基本業務的功能,具體以下操做:
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);
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;
}
}
<%@ 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>
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); } }
<%@ 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>
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("查看圖書成功..."); } }
<?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>
完成到目前爲止,已經將一個圖書操做(增刪改查)的功能完成。下面利用註解和動態代理來完成權限控制的功能,具體操做以下:
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) @Inherited public @interface PrivilegeInfo { String value(); }
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; } }
BookService service = ServiceFactory.getBookService();
3. 類加載器
類加載器就加載字節碼文件(.class)
類加載器負責加載全部的類,系統爲全部被載入內存中的類生成一個java.lang.Class實例。一旦一個類被加入JVM中,同一個類就不會被再次加入了。正如一個對象有一個惟一的標識同樣,一個載入JVM的類也有一個惟一的標識。在Java中,一個類用其全限定類名(包括包名和類名)做爲標識;但在JVM中,一個類用其全限定類名和其類加載器做爲其惟一標識。
例如如下案例,在JVM中兩個同名的Person類是徹底不一樣的,之間也互不兼容,由於類加載器不一樣。
上述狀況轉換成代碼以下:
public class Person { private Person instance; public void setPerson(Object instance) { this.instance = (Person) instance; } }
@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(); } }
當JVM啓動時,會造成由三個類加載器組成的初始類加載器層次結構。
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提供的,因此不能獲取。
JVM的類加載器機制主要有以下三種:
JVM中除根類加載器以外的全部類加載器都是ClassLoader子類的實例,能夠經過擴展ClassLoader的子類,並重寫該ClassLoader提供的方法來實現自定義的類加載器。
ClassLoader類具備以下兩個關鍵方法:
因爲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";
}
}
對於運行在 Java EE™容器中的 Web 應用來講,類加載器的實現方式與通常的 Java 應用有所不一樣。不一樣的 Web 容器的實現方式也會有所不一樣。以 Apache Tomcat 來講,每一個 Web 應用都有一個對應的類加載器實例。該類加載器也使用代理模式,所不一樣的是它是首先嚐試去加載某個類,若是找不到再代理給父類加載器。這與通常類加載器的順序是相反的。這是 Java Servlet 規範中的推薦作法,其目的是使得 Web 應用本身的類的優先級高於 Web 容器提供的類。這種代理模式的一個例外是:Java 核心庫的類是不在查找範圍以內的。這也是爲了保證 Java 核心庫的類型安全。
絕大多數狀況下,Web 應用的開發人員不須要考慮與類加載器相關的細節。下面給出幾條簡單的原則:
須要注意的是,若是使用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>
@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); } }
@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() {} }
@WebListener public class ListenerTest implements HttpSessionListener { @Override public void sessionCreated(HttpSessionEvent arg0) {} @Override public void sessionDestroyed(HttpSessionEvent arg0) {} }
Servlet 3.0的另外一個新特性就是提供了處理文件上傳的功能,使用Servlet 3.0的內容實現文件上傳須要如下幾個內容:
Optional Element Summary |
|
|
|
|
|
|
|
|
|
Method Summary |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
利用Servlet 3.0實現文件上傳的功能,具體實現代碼以下:
<%@ 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>
@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); } }
所謂異步處理就是指數據發送方發送數據給數據接收方後,沒必要等待數據接收方做出響應,能夠繼續發送下一個數據的通訊方式。
Servlet 3.0版本提供了相關異步處理的功能,具體實現方式以下:
根據上述步驟,經過一個代碼實例演示。
@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); } } }); } }