本文主要是介绍Java基础增强(三):Servlet3.0、动态代理、类加载器,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
一、Servlet3.0新特性
1、Servlet3.0 新特性概述
Servlet3.0 的主要新特性如下三部分:
- 使用 @WebServlet、WebFilter、WebListner 三个注解来代替 web.xml 文件中的 Servlet、Filter、Listner 的配置;
- Servlet 异步处理:当 Servlet 处理比较费时的问题时,这会让客户感到很卡。当使用异常处理时可以把已经处理好的内容先一步响应给客户端浏览器,然后使用另一个线程来完成费时的操作,也就是内容一部分一部分的显示出来;
- 上传组件:不用再使用 fileupload 等第三方的上传组件,使用 Servlet3.0 的上传组件会更方便。
2、@WebServlet、@WebFilter、@WebListner
@WebServlet(urlpatterns = {"/AServlet"},initParams = {@WebInitParam(name="paramName",value="paramValue")},loadOnStartUp=1
)
public class AServlet extends HttpServlet{public void init(ServletConfig config)throws ServletException{System.out.print(config.getInitParameter("paramName"));}public void doGet(HttpServletRequest request,HttpServletResopnse response)throws ServletException,IOException{request.setCharactorEncoding("utf-8");response.setContentType("text/html;charset=utf-8");response.getWriter().print("hello");}
}
@WebFilter(urlpattern={"/*"},dispacherTypes={DispacherType.REQUEST,Dispacher.FORWARD}
)
public class AFilter implements Filter{public void destory(){}public void doFilter(HttpServletRequest request,HttpServletResponse response,FilterChain chain)throws ServletException,IOException{System.out.println("start filter!");chain.doFilter(request,response);System.out.println("end filter!");}
}
@WebListner()
public class AListner imlements ServletContextListner{public void contextDestoryed(ServletContextEvent event){System.out.println("服务器关闭了!");}public void contextInialized(ServletContextEvent event){System.out.println("服务器开启了!");}
}
3、Servlet 异步处理
Servlet 异步处理就是让 Servlet 在处理费时的请求时不要阻塞,而是一部分一部分的显示。
也就是说,在使用 Servlet 异步处理之后,页面可以一部分一部分的显示数据,而不是一直卡,等待响应结束后一起显示。
在使用异步处理之前,银锭要在 @WebServlet 注解中给出 asyncSupported=true ,不然默认 Servlet 是不支持异步处理的。如果存在过滤器,也需要设置 @Webfilter 的asynSupported=true 。
@WebServlet(urlpattern={"/AServlet"},asyncSupperted=true)
public class AServlet extends HttpServlet(){...}
注意:响应类型必须是 text/html,所以 response.setContentType("text/html;charsetutf-8")
使用异步处理大致可以分为两步:
- Servlet 正常响应数据;
- Servlet 异常响应数据;
在 Servlet 正常响应数据时,没什么可说的,可通知 response.getWriter().print() 来向客户端输出,但输出后要使用 response.getWriter().flush() 刷新,不然数据只是在缓存区中,不能像客户端发送数据的。
异步响应数据需要使用 request.startAsync() 方法获取 AsyncContext 对象。然后调用 AsyncContext 对象的 start() 方法启动异步响应,start() 方法需要一个 Runnable 类型的参数,在 Runnable 的 run() 方法中给出异步响应的代码。
AsyncContext ac = request.startAsyncContext(request,response);
ac.start(new Runnable(){...})
注意在异步处理线程中使用 response 响应后,要使用 response.getWriter().flush() 来刷新流,不然数据是不能响应到客户端浏览器的。
AasyncContext.start(new Runnable() {public void run() {for(char i = 'a'; i <= 'z'; i++) {try {Thread.sleep(100);asyncContext.getResponse().getWriter().print(i + " ");asyncContext.getResponse().getWriter().flush();} catch (Exception e) {e.printStackTrace();}}asyncContext.complete();}
});
Tmocat 需要知道异步响应是否结束,如果响应不结束,虽然客户端会看到响应的数据,但是鼠标上有个圈圈不停地转,表示响应还没有结束。Tomcat 会等待超时为止,这个超时的时间可以通过 AsyncContext 类的 getTimeout() 方法获取,Tomcat 默认为 20000 毫秒。当然也可以通过 setTimeOut() 方法设置,以毫秒为单位。
如果异步线程已经结束了响应,那么可以在异步线程中调用 AsyncContext.comlete() 方法,这样 Tomcat 就知道异步线程已经结束工作了。
@WebServlet(urlPatterns = {"/AServlet"}, asyncSupported=true)
public class AServlet extends HttpServlet {public void doGet(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {response.setContentType("text/html;charset=utf-8");PrintWriter out = response.getWriter();out.println("Servlet begin <br>");[给出这么之输出的原因是可爱的IE,如果你的响应内容太少时,IE是不会在浏览器上显示数据的。]out.println("Servlet begin <br>");out.println("Servlet begin <br>");out.println("Servlet begin <br>");out.println("Servlet begin <br>");out.println("Servlet begin <br>");out.println("Servlet begin <br>");out.println("Servlet begin <br>");out.println("Servlet begin <br>");out.println("Servlet begin <br>");out.println("Servlet begin <br>");out.println("Servlet begin <br>");out.println("Servlet begin <br>");out.println("Servlet begin <br>");out.println("Servlet begin <br>");out.flush();[要刷新流]final AsyncContext asyncContext = request.startAsync(request, response);[获取异步上下文对象]asyncContext.setTimeout(1000 * 20);[设置超时时间为20秒]asyncContext.start[启动异步线程](new Runnable() {public void run() {try {Thread.sleep(1000);asyncContext.getResponse().getWriter().print("马上开始" + "<br/>");asyncContext.getResponse().getWriter().flush();Thread.sleep(2000);} catch (Exception e1) {}for(char i = 'a'; i <= 'z'; i++) {try {Thread.sleep(100);asyncContext.getResponse().getWriter().print(i + " ");asyncContext.getResponse().getWriter().flush();[每次响应后都要刷新一次流,这样会看到在浏览器上一个字母一个字母的出现]} catch (Exception e) {e.printStackTrace();}}asyncContext.complete();[告诉Tomcat异步线程已经完成。这样Tomcat就会结束响应。]}});// asyncContext.start(businessHandleThread);// 也可以用这种方法启动异步线程out.println("Servlet end <br>");}
}
4、上传文件
Servlet3.0 提供了文件上传的处理方案。只需要在 Servlet 上添加 @MultipartConfig 注解即可。
@WebServlet(urlpattern={"/UploadServlet"});
@MultipartConfig(maxFileSize=1024);
public class UploadServlet extends HttpServlet(){...};
当然也可以为 @MultipartConfig 注解指定属性值,它有四个属性:
- int FileSizeThreshold:指定缓存的大小,当超出这个大小后,文件会保存到磁盘上;
- String location:指定临时文件的目录;
- long maxFileSize:指定上传单个文件的大小限制,如果上传的文件超出了这个大小,那么就会抛出异常;
- long maxRequestSize:指定整个表单的大小限制;
当在 Servlet 上使用了 @MultipartCongfig 注解后,那么就可以使用 request.getPart("filename") 来获取<input:file> 的内容,其中 part 表示一个文件表单项。
<form action="/a1/UploadServlet" method="post" enctype="multipart/form-data">用户名:<input type="text" name="username"></br>照 片:<input type="file" name="file"></br><input type="submit" vlue="提交">
</form>
@WebServlet(urlpattern={"/UploadServlet"})
@MultipartConfig(maxFileSize=1024*1024)
public class UploadServlet extends HttpServlet{public void doPost(HttpServletRequest request,HttpServletResopnse response)throws ServletException,IOException{request.setCharactorEncoding("utf-8");response.setContentType("text/html;charset=utf-8");String username = request.getParameter("username");response.getWriter().print("size:"+username+"</br>");Part part = request.getPart("file");response.getWriter().print("size:"+part.getSize()+"</br>");
<pre name="code" class="html"> response.getWriter().print("type:"+part.getContentType()+"</br>");
response.getWriter().print("name:"+part.getName+"</br>");String name = part.getHeader("content-disposition");String fileNameTmp = name.substring(name.indexOf("filename=")+10);String filename = fileNameTmp.substring(fileNameTmp.indexOf("\""));String savePath = this.getServletContext().getRealPath("/uploads");part.write(savePath+"/"+filename);
}
}
二、动态代理
1、学习动态dialing的目的
动态代理技术都是在框架中使用,例如 Struts1、Struts2、Spring 和 Hibernate 中都使用了动态代理技术。如果你不想自己写框架技术,那么基本上是用不上动态代理技术的。
我们学习动态代理技术的目的是为了更好的理解框架的内部原理,也就是说将来我们学习框架打基础。
动态代理技术有点小难度,而且明白了动态代理技术可能一时半会儿也想不到它适合在什么情况下使用。这些东西都会在学习框架过程中渐渐明白。
2、运行时实现指定的接口
想实现某个接口,你需要写一个类,然后在类名的后面给出 " implements " xxx 接口。这才是实现某个接口:
public interface MyInterface {void fun1();void fun2();
}public class MyInterfaceImpl implements MyInterface {public void fun1() {System.out.println("fun1()");}public void fun2() {System.out.println("fun2()");}
}
上面的代码对我们来说没有什么新鲜感,我们要说的是动态代理技术可以通过一个方法调用就可以生成一个对指定接口的实现类对象。
Class[] clazz = {MyInterface.class};
MyInterface mi = (MyInterface)Proxy.newProxyInstance(loader,clazz,h);
上面代码中,Proxy 类的静态方法 newProxyInstance() 方法生成了一个对象,这个对象实现了 clazz 数组中指定的接口。没错,返回值 mi 是 MyInterface 接口的实现类。你不要问这个类是哪个类,你只需要知道 mi 是 MyInterface 接口的实现类就可以了。你现在也不用去管 loader 和 h 这两个参数是什么东东,你只需要知道 Proxy 类的静态方法 newProxyInstance() 方法返回的方法是实现了指定接口的实现类,甚至你都没有看见实现类的代码。
动态代理就是在运行时生成一个类,这个类会实现你指定的一组接口,而这个类没有 .java 文件,是在运行时产生的,你也不用去关心它是什么类,你只需要知道它实现了哪些接口即可。
3、newProxyInstance() 方法的参数
Proxy 类的 newInstance() 方法有三个参数:
- ClassLoader loader:它是类加载器类型,通过 MyInterface.class.getClassLoader() 就可以获取到 ClassLoader 对象;
- Class[] interfaces:指定 newProxyInstance() 方法返回的对象要实现哪些接口,可以指定多个接口;
- InvocationHandler h:它是最重要的参数!是一个接口,名字为调用处理器!其实无论你调用代理对象的什么方法,都是都是在调用 InvocationHeadler 的 invoke() 方法;
public static void main(String[] args) {Class[] cs = {MyInterface.class};ClassLoader loader = MyInterface.class.getClassLoader();InvocationHandler h = new InvocationHandler() {public Object invoke(Object proxy, Method method, Object[] args)throws Throwable {System.out.println("无论你调用代理对象的什么方法,其实都是在调用invoke()...");return null;}};MyInterface mi = (MyInterface)Proxy.newProxyInstance(loader, cs, h);mi.fun1();mi.fun2();
}
InvocationHeadler 接口只有一个方法,即 invoke() 方法。它是代理对象所有方法的唯一实现,也就是所,无论你调用代理对象的哪个方法,其实都是在调用 InvocationHeadler 的 invoke() 方法。
4、InvocationHeadler 的 invoke() 方法
InvocationHeadler 的 invoke() 方法的参数有三个:
- Object proxy:代理对象,也就是 Proxy.newProxyInstance() 方法返回的对象,通常我们用不上它;
- Method method:表示当前被调用方法的反射对象,例如mi.fun(),那么 method 就是 fun() 方法的反射对象;
- Object args:表示当前被调用方法的参数,当然 mi.fun() 这个调用是没有参数的,所以 args 是一个零长度数组;
最后要说的是 invoke() 方法的返回值为 Object 类型,它表示当前被调用的方法的返回值,当然 mi.fun() 方法是没有返回值的,所以 invoke() 返回的就必须是 null 。
5、动态代理的用途
动态代理的用途和装饰模式很相似,就是为了对某个对象进行增强,所有使用装饰者模式的案例都可以使用动态代理来代替。
下面我们用一个例子来说明动态代理的用途。
我们来写一个 Waiter 接口,它只有一个 server() 方法。MyWaiter 是 Waiter 接口的实现类。
public interface Waiter {public void serve();
}
public class MyWaiter implements Waiter {public void serve() {System.out.println("服务...");}
}
现在我们要对 MyWaiter 对象进行增强,要让它在服务之前和服务之后添加礼貌用语,即在服务之前说”您好!“,在服务之后说”再见!“。
public class MainApp1 {public static void main(String[] args) {ClassLoader loader = MainApp1.class.getClassLoader();Class[] cs = {Waiter.class};Waiter target = new MyWaiter();MyInvocationHandler h = new MyInvocationHandler(target);Waiter waiter = (Waiter)Proxy.newProxyInstance(loader, cs, h);waiter.serve();}
}class MyInvocationHandler implements InvocationHandler {public Waiter target;public MyInvocationHandler(Waiter target) {this.target = target;}public Object invoke(Object proxy, Method method, Object[] args)throws Throwable {System.out.println("您好!");Object result = method.invoke(target, args);System.out.println("很高兴为您服务!");return result;}
}
三、类加载器
1、什么是类加载器
类加载器就是用来加载类的东西。类加载器也是一个类:ClassLoader。
类加载器可以被加载到内存中,是通过类加载器完成的。Java提供了三种类加载器,分别是:
- bootstrap classloader:引导类加载器,加载 rt.jar 中的类;
- sun.misc.Launcher$ExtClassLoader:扩展类加载器,加载 lib/ext 目录下的类;
- sun.misc.Launcher$AppClassLoader:系统类加载器,加载 LASSPATH 下的类,即我们自己写的类,以及第三方提供的类。
通常情况下,Java 中所有类都是通过这三个类加载器加载的。
类加载器之间存在上下级关系,系统类加载器的上级是扩展类加载器,而扩展类加载器的上级是引导类加载器。
2、JVM 眼中的相同的类
在 JVM 中,不可能存在一个类被加载两次的情况!一个类如果已经被加载了,当再次尝试加载这个类时,类加载器会先去查找这个类是否已经被加载过了,如果已经被加载过了,就不会再去加载了。
但是,如果同一个类使用不同的类加载器去加载,是可以出现多次加载的情况的!也就是说,在 JVM 眼中,相同的类需要有相同的 class 文件,以及相同的类加载器。当一个 class 文件,被不同的类加载器加载了,JVM 会认识这是两个不同的类,这会在 JVM 中出现两个相同的 class 对象!甚至会出现类型转换异常。
3、类加载器的代理模式
当系统类加载器去加载一个类时,它首先会让上级去加载,即让扩展类加载器去加载类,扩展类加载器也会让它的上级引导类加载器去加载类。如果上级没有加载成功,那么再由自己去加载!
例如我们自己写的 Person 类,一定是存放在 CLASSPATH 中,那么一定是由系统类加载器来加载。当系统类加载器来加载类时,它首先把加载的任务交给扩展类加载器,如果扩展类加载器加载成功了,那么系统类加载器就不会再加载、这就是代理模式了!
相同的道理,扩展类加载器也会把加载类的任务交给它的上级,即引导类加载器,引导类加载器加载成功,那么引导类加载器也就不会再去加载了。引导类加载器是用 c 语言写的,是 JVM 的一部分,它是最上层的类加载器了,所以它就没有上层了。它只负责去加载“内部人”,即JDK 中的类,但我们知道 Person 类不是我们自己写的类,所以加载失败。
当扩展类加载器发现上级不能加载类,它就开始加载工作了,它加载的是 lib\ext 目录下的 jar 文件,当然它也会加载失败,所以最终还是由系统类加载器在 CLASSPATH 中去加载 Person,最终由系统类加载器加载到了 Person 类。
代理模式保证了 JDK 中的类一定是由引导类加载器加载的!这就不会出现多个版本的类,这也是代理模式的好处!
4、自定义类加载器(了解)
我们也可以通过继承 ClassLoader 类来完成自定义类加载器,自定义类加载器的目的一般是为了加载网上的类,因为这会让 class 在网络中传输,为了安全,那么 class 一定是需要加密的,所以需要自定义的类加载器来加载(自定义的类加载器需要做解密工作)。
ClassLoader 加载类都是通过 loadClass() 方法来完成的,loadClass() 方法的工作流程如下:
- 调用 findLoadedClass() 方法查看该类是否已经被加载过了,如果该类没有加载过,那么这个方法返回 null;
- 判断 findLoadedClass() 方法返回的是否为 null,如果不是 null 那么直接返回,这可以避免同一个类被加载两次;
- 如果 findLoadedClass() 方法放回的是null,那么就启动代理模式(委托机制),即调用上级的 loadClass() 方法,获取上级的方法是 getParent() ,当然上级可能还有上级,这个动作就一直向上走;
- 如果 getParent().loadClass() 返回的不是 null,这说明上级加载成功了,那么就加载结果;
- 如果上级返回的是 null,这说明需要自己动手了,这时 loadClass() 方法会调用本类的 findClass() 方法来加载类;
- 这说明我们只需要重写 ClassLoader 的 findClass() 方法,这就可以了。
通过上面的分析,我们知道要自定义一个类加载器,只需要继承 ClassLoader 类,然后重写它的 findClass() 方法即可。那么在 findClass() 中我们需要完成那些工作呢?
- 找到 class 文件,把它加载到一个 byte[] 中;
- 调用 difineClass() 方法,把 byte[] 传递给这个方法即可。
public class FileSystemClassLoader extends ClassLoader {private String classpath[这是它的地盘!因为类加载器都是片警,它需要有自己的地盘!];public FileSystemClassLoader() {}public FileSystemClassLoader[创建类加载器时,就要为其指定负责的地盘!它只会到这里去查找类!](String classpath) {this.classpath = classpath;}@Overridepublic Class<?> findClass(String name) throws ClassNotFoundException {try {byte[] datas = getClassData(name);[通过类名称找到.class文件,把文件加载到一个字节数组中。]if(datas [如果返回的字节数组为null,说明没有找到这个类,抛出异常]== null) {throw new ClassNotFoundException("类没有找到:" + name);}return this.defineClass[它可以把字节数组变成Class对象!defineClass()是ClassLoader的方法,它的作用是把字节数组变成Class对象!](name, datas, 0, datas.length);} catch (IOException e) {e.printStackTrace();throw new ClassNotFoundException("类找不到:" + name);}}private byte[] getClassData(String name) throws IOException {name = name.replace(".", "\\") + ".class";File classFile = new File(classpath, name);return FileUtils[commons-io.jar中的类].readFileToByteArray(classFile);}
}
ClassLoader loader = new FileSystemClassLoader("F:\\classpath");
Class clazz = loader.loadClass("cn.itcast.utils.CommonUtils");
Method method = clazz.getMethod("md5", String.class);
String result = (String) method.invoke(null, "qdmmy6");
System.out.println(result);
5、Tomcat 的类加载器
Tomcat 会为每个项目提供一个类加载器,Tomcat 提供的类加载器负责加载自己项目下的类,即 WEB-INF\lib 和 WEB-INF\classes 下的类。但 Tomcat 提供的类加载器不会使用传统的代理模式,而是自己去加载,如果加载不到,再使用代理模式。
Tomcat 提供的类加载器有这样一个好处,就是可以使自己项目下的类优先加载。
这篇关于Java基础增强(三):Servlet3.0、动态代理、类加载器的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!