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(); } }
代理模式是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();
类加载器就加载字节码文件(.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); } } }); } }