annotation:注解,代表程序的一些特殊的功能,注解是由虚拟机进行处理的。
JDK中提供的常用的注解:
# @Override :描述子类重写父类的方法:
* JDK1.5版本中,该注解只能应用在类的继承上.
* JDK1.6版本中,该注解可以应用在类的实现上(实现接口).
# @SuppressWarnings :压制警告,参数是all是压制所有的警告.
# @Deprecated :描述方法过时,标记方法过时.
自定义注解:使用@interface
关键声明
格式:public @interface 注解名称{ 内容 }
注解的属性定义:
# 注解定义时需加上`()`,如:
@interface Anno {
int a();
boolean b() default false;
}
# 定义属性时:可以使用default关键字声明属性的默认值
# 注解的属性的数据类型:
1. 基本数据类型:primitive type
2. String类型:String
3. Class类型:Class
4. 注解类型:annotation
5. 枚举类型:enumeration
6. 以上类型的一维数组:
# 特殊的属性名称:value
* 如果使用注解的时候,只出现了value属性,value属性可以省略,但当出现多个属性时,value属性名不能省略
注解的存在阶段:
# Java类的状态:源代码阶段-->Class阶段-->运行阶段
源代码阶段 Class阶段 运行阶段
.java源文件 --编译--> .class --加载--> JVM中运行
# 自定义的注解只存在与源代码阶段,编译后就不存在了,可以通过JDK提供的元注解来增大自定义注解的存在阶段:
1. 源代码阶段:@Retention(value = RetentionPolicy.SOURCE)
2. Class阶段:@Retention(value = RetentionPolicy.CLASS)
3. 运行阶段:@Retention(value = RetentionPolicy.RUNTIME)
案例:自定义注解完成简单的注解单元测试的功能
package com.annotation;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/** * 自定义注解 MyTest.java */
@Retention(value=RetentionPolicy.RUNTIME) // 设置自定义注解运行阶段
public @interface MyTest {
}
/** * 运行的核心类 CoreRunner.java * 和单元测试的原理相同,均需要一个程序运行的主方法 * 获取要进行单元测试的类的Class对象,通过反射获取该类的所有的方法 * 遍历所有的方法,如果方法上有注解,则通过反射执行该方法即可 */
public class CoreRunner {
public static void main(String[] args) throws Exception {
/** * 获得测试类的Class. * 获得Class中的所有的方法. * 遍历每个方法,查看每个方法上是否有MyTest注解. * 有MyTest注解,这个方法就执行. */
// 1.获得测试类的Class:
Class clazz = AnnotationDemo1.class;
// 2.获得Class中的所有的方法:规定了测试的方法必须是public
Method[] methods = clazz.getMethods();
// 3.遍历每个方法:
for(Method method:methods){
// 获取当前的方法是否含有MyTest注解
boolean flag = method.isAnnotationPresent(MyTest.class);
if(flag){
// 说明方法上有MyTest注解,执行方法
method.invoke(clazz.newInstance(), null);
}
}
}
}
Servlet3.0和Servlet2.5的区别:
# Servlet3.0和Servlet2.5相比提供了三个特性:
1. 注解开发:可以取代web.xml的配置文件
* @WebServlet:Servlet的注解
* @WebListener:监听器的注解
* @WebFilter:过滤的注解
2. 文件上传:没有提供获取上传的文件名等相关的API
3. 异步请求:本质是开启新的线程进行处理请求
注解开发的原理:
// Servlet2.5中web.xml配置文件配置Servlet
<servlet>
<servlet-name>servlet名称</servlet-name>
<servlet-class>servlet的完全限定名</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>servlet名称</servlet-name>
<url-pattern>servlet的访问路径</url-pattern>
</servlet-mapping>
// Servlet3.0中使用注解
@WebServlet(urlPatterns="Servlet的访问路径") 在定义Servlet时添加注解,相当于是配置文件的中访问路径
public class Servlet名称 extends HttpServlet {...}
# 文件上传的要素: 三个条件必须满足
1. 表单的提交的方式必须是POST. (由于Get方式有数据大小的限制,不使用get方式上传数据)
2. 表单中需要有文件上传的表单元素:这个元素必须有name属性和值:<input type="file" name="upload">
3. 表单的enctype属性的值必须是multipart/form-data.
(enctype的默认值是:"application/x-www-form-urlencoded",该默认值在后台中只能获取文件的名称,无法获取文件内容)
# 文件上传技术: Servlet2.5不支持文件上传
* Servlet3.0 :比2.5版本增加的功能:注解开发,文件上传,异步请求.
* JSPSmartUpload :嵌入到JSP中完成文件上传.主要用于(jsp+javabean开发模式)的.
* FileUpload :Apache的文件上传组件.
* Struts2 :底层是FileUpload.
注意事项(重点):
# form表单的设为enctype="multipart/form-data"后,Servlet中getParameter()等获取参数的方法无效,需要在Servlet上添加 @MultipartConfig 注解
# 上传的文件的信息不能通过getParameter()等获取参数的方法获取,需要使用getPart()方法进行操作
文件上传存在的问题:
# 文件的重名问题:
解决方法:
1. 使用UUID生成随机的字符串作为文件名,数据库中存储的是文件原始名称和真实文件存储路径的映射;
2. 使用时间戳+原始文件名作为存储的文件名,数据库中存储的是文件原始名称和真实文件存储路径的映射.
# 同一目录下存储文件过多:
解决方法:
1. 按时间分:一个月一个目录,一个星期一个目录,一天一个目录
2. 按数量分:一个目录下存5000个文件,创建一个新的目录,再去存放.
3. 按用户分:为每个用户创建一个单独目录 存放文件.
4. 按目录分离算法分:
* 使用唯一文件名.hashCode(); -- 得到一个代表当前这个文件的int类型值.
* int类型占4个字节32位.可以让hashCode值&0xf; 得到一个int值,用这个int值作为一级目录.
* 让hashCode右移4位 &0xf ;得到一个int值,用这个int值作为二级目录.依次类推.
案例实现:
<!--fileupload.jsp-->
<% @ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>文件上传</title>
</head>
<body>
<h1>文件上传</h1>
<form action="${ pageContext.request.contextPath }/FileUploadServlet" method="post" enctype="multipart/form-data">
<table border="1">
<tr>
<td>文件描述</td>
<td>
<textarea rows="4" cols="20" name="filedesc"></textarea>
</td>
</tr>
<tr>
<td>文件上传</td>
<td>
<input type="file" name="upload" />
</td>
</tr>
<tr>
<td colspan="2">
<input type="submit" value="上传"/>
</td>
</tr>
</table>
</form>
</body>
</html>
package com.itheima.servlet;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import javax.servlet.ServletException;
import javax.servlet.ServletInputStream;
import javax.servlet.annotation.MultipartConfig;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.Part;
import com.itheima.utils.UpLoadUtils;
/** * 文件上传的Servlet FileUploadServlet.java */
@WebServlet(urlPatterns="/FileUploadServlet")
@MultipartConfig // 该注解是将传统的获取参数的方法进行封装
public class FileUploadServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doPost(request, response);
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 设置request缓冲区的编码,防止中文数据乱码
request.setCharacterEncoding("UTF-8");
// 获取文件的描述信息
String filedesc = request.getParameter("filedesc");
// 获取代表文件部分的对象
Part part = request.getPart("upload");
// 获取文件大小
// int size = part.getSize();
// 获取表单中该文件的input标签的name属性值
// String name = part.getName();
// Servlet3.0没有提供获取上传文件名的方法,需要从part的头信息中进行截取
// 获取请求头的Content-Disposition字段值
// 获取的文件的格式是:form-data; name="upload"; filename="文件名"
String header = part.getHeader("Content-Disposition");
// 分割头信息获取文件名
int lindex = header.lastIndexOf("\""); // 获取最后一个双引号的索引
int findex = header.substring(0, lindex).lastIndexOf("\"");
String filename = header.substring(findex + 1, lindex);
// 定义在服务器存储文件的根目录
String realPath = request.getServletContext().getRealPath("/upload");
File file = new File(realPath);
// 存储文件的目录不存在则创建目录
if(!file.exists()) {
file.mkdir();
}
// 获取存储时的文件名,防止文件的重名
String realFileName = UpLoadUtils.setFileName(filename);
// 获取文件存储路径,防止同一目录下文件过多,使用目录分离算法建立存储目录
String storePath = UpLoadUtils.getPath();
// 根据文件存储的根目录和存储路径获取存储的完全路径
file = new File(realPath+storePath);
// 如果文件存储的目录不存在则创建
if(!file.exists()) {
file.mkdirs();
}
// 获取要存储文件的对象
file = new File(file,realFileName);
// 存储文件,使用IO流读写文件,上传文件信息存储在part中
// 是从part中获取字节输入流对象,不是获取request中的输入流对象
InputStream is = part.getInputStream();
OutputStream os = new FileOutputStream(file);
// 读取文件并保存到指定目录
byte[] bys = new byte[1024];
int len = 0;
while((len = is.read(bys)) != -1) {
os.write(bys, 0, len);
}
// 释放资源
os.close();
// is.close(); 输入流资源是从part中获取的,可以不用释放,part会进行管理
// 给出提示
response.setContentType("text/html;charset=UTF-8");
response.getWriter().println("<h1>文件上传成功</h1>");
}
}
package com.itheima.utils;
import java.text.SimpleDateFormat;
import java.util.Date;
/** * 文件上传的工具类 UpLoadUtils.java */
public class UpLoadUtils {
/** * 根据文件名称获取文件保存的目录 * 按照时间进行分离目录 * 存储路径格式:yyyy-MM-dd/HH-mm-ss * @param filename 文件名 * @return 返回文件的存储路径 */
public static String getPath() {
// 获取当前日期
Date date = new Date();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd#HH-mm-ss");
String format = sdf.format(date);
return "/"+format.split("#")[0]+"/"+format.split("#")[1];
}
/** * 根据时间生成文件的名称 * 防止文件重名 * 新文件名格式:yyyy_MM_dd_HH_mm_ss_SS-原始文件名 * @param filename 文件名称 * @return 返回文件的新的名称 */
public static String setFileName(String filename) {
Date date = new Date();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy_MM_dd_HH_mm_ss_SS");
String format = sdf.format(date);
return format+"-"+filename;
}
}
代理模式:是java开发的设计模式,其特点是代理类与委托类有同样的接口,代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类,以及委托类处理消息后再次处理消息等。代理类与委托类之间通常会存在关联关系,一个代理类的对象与一个委托类的对象关联,代理类的对象本身并不真正实现服务,而是通过调用自定义类的相关方法,实现对委托类的代理访问。 按照代理的创建时期,代理类可以分为两种。
静态代理:由程序员创建或特定工具自动生成源代码,再对其编译。在程序运行前,代理类的.class文件就已经存在了。
动态代理:动态代理类与普通类的唯一区别就是其字节码是由 JVM 在运行时动态生成的而非预存在于任何一个 .class 文件中。
Java的JDK中提供创建动态代理类API,当时只能创建针对接口的动态代理类。创建动态代理的要求是:被代理的类实现了接口。
1. Java.lang.reflect.Proxy:动态代理类,提供一组静态方法为一组接口动态的生成子类对象和代理类
方法 | 说明 |
---|---|
InvocationHandler getInvocationHandler(Object proxy) | 用于获取指定代理对象所关联的调用处理器 |
Class getProxyClass(ClassLoader loader,Class… interfaces) | 用于获取指定类加载器和一组接口上的动态代理类对象 |
static boolean isProxyClass(Class cl) | 用于判断指定类对象是不是动态代理对象 |
Object newProxyInstance(ClassLoader loader,Class[] interfaces,InvocationHandler h) | 用于为指定类加载器和一组接口生成动态代理对象,并自定义调用处理器 |
2. java.lang.reflect.InvocationHandler:调用处理器接口,实现invoke方法,用于实现对于被代理类的代理访问
// 该接口就只有一个方法,该方法是用户处理被代理类的访问控制
/** * 该方法负责集中处理动态代理类上的所有方法调用(就是对被代理类的访问进行控制) * proxy:代理类实例 * method:被调用的方法对象(就是被代理类中的方法) * args:调用该方法的参数 */
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
import java.io.IOException;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
/** * 统一的字符集编码过滤器 * @ClassName: CharacterEncodingFilter * @Description:(统一的字符集编码过滤器) */
public class CharacterEncodingFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
// 1.获取被代理类的对象
final HttpServletRequest hsrequest = (HttpServletRequest) request;
// 2.创建动态代理对象
HttpServletRequest myrequest = (HttpServletRequest) Proxy.newProxyInstance(hsrequest.getClass().getClassLoader(), hsrequest.getClass().getInterfaces(), new InvocationHandler() {
@Override
// 3.实现invoke方法,实现对被代理类对象的访问的控制
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
// 对request对象的getParameter方法的get和post方法获取参数进行字符集编码的处理
// 获取方法的名称
String methodName = method.getName();
// 当方法名时getParameter时,进行增强
if("getParameter".equals(methodName)) {
// 获取请求的方式
String reqMethod = hsrequest.getMethod();
// 当是get方式请求
if("get".equalsIgnoreCase(reqMethod)) {
// get方式需将其中的值取出,进行编码转换
// 先调用原始的方法获取其中的值,然后进行编码的转换
String value = (String) method.invoke(hsrequest, args);
return new String(value.getBytes("ISO-8859-1"),"UTF-8");
} else if("post".equalsIgnoreCase(reqMethod)) {
// 当请求是post方式,post方式只需设置request的缓冲区的编码即可
hsrequest.setCharacterEncoding("UTF-8");
// 原始调用原始的获取参数的方法
return method.invoke(hsrequest, args);
}
}
// 针对request中其余的方法执行原有的调用即可
return method.invoke(hsrequest, args);
}
});
// 4.将请求放行,注意放行的是代理对象
chain.doFilter(myrequest, response);
}
@Override
public void destroy() {
}
}
// 字符集过滤器需要在项目的web.xml中进行配置
<!-- 配置字符集过滤器 -->
<filter>
<filter-name>CharacterEncodingFilter</filter-name>
<filter-class>com.filter.CharacterEncodingFilter</filter-class>
</filter>
<!-- 配置过滤器的映射 -->
<filter-mapping>
<filter-name>CharacterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern> </filter-mapping>
java.lang.ClassLoader:类加载器,将类的字节码加载到JVM中并为创建类对象,然后该类才能被使用。Proxy类与普通类的唯一区别就是其字节码是由 JVM 在运行时动态生成的而非预存在于任何一个 .class 文件中。每次生成动态代理类对象时都需要指定一个类加载器对象:newProxyInstance()方法第一个参数。
# Java中类加载器分为三类:加载器间是层级关系,不是子父类关系
1. 引导类加载器:负责加载本地环境的JDK中/jre/lib/rt.jar,该包是Java的核心api
|
2. 扩展类加载器:负责加载本地环境中JDK中/jre/lib/ext/\*.jar,该包是Java的扩展API
|
3. 应用类加载器:负责加载自定义类路径下所有的class文件
# Java中为保证类只被加载一次:使用的是类加载器的全盘委托机制
# 全盘委托机制原理:所有的类加载都要先经过`应用类加载器`,`应用类加载器`获得所有的class文件后不进行操作直接向`扩展类加载器`传递这些class文件,`扩展类加载器`拿到所有的class文件后不进行操作直接向`引导类加载器`传递这些class文件;`引导类加载器`获取所有的class文件后查找其中属于自己负责加载的class文件进行加载,将剩余的class文件传递给`扩展类加载器`,`扩展类加载器`拿到class文件后只加载属于自己负责加载的class文件进行加载,将剩余的class文件传递给`应用类加载器`,`应用类加载器`获取剩余的class文件进行全部的加载。这种机制保证了每个class文件只被加载一次。