Annotation(注解) 概述
从 JDK 5.0 开始, Java 增加了对元数据(MetaData) 的支持, 也就是Annotation(注解)。
什么是Annotation,以及注解的作用?三个基本的 Annotation:
- @Override: 限定重写父类方法, 该注解只能用于方法
- @Deprecated: 用于表示某个程序元素(类, 方法等)已过时
- @SuppressWarnings: 抑制编译器警告.
Annotation 其实就是代码里的特殊标记, 它用于替代配置文件,也就是说,传统方式通过配置文件告诉类如何运行,有了注解技术后,开发人员可以通过注解告诉类如何运行。在Java技术里注解的典型应用是:可以通过反射技术去得到类里面的注解,以决定怎么去运行类。
掌握注解技术的要点:
- 如何定义注解
- 如何反射注解,并根据反射的注解信息,决定如何去运行类
注解的应用结构图
自定义 Annotation
定义注解类不能使用class、enum,也不能使用interface,而是使用@interface。
public @interface MyAnn {
}
注解可以作用在:类(接口或枚举)、属性、方法、构造器、包、参数、局部变量,例如:
@MyAnn
public class MyClass {
@MyAnn
private int a;
@MyAnn
public MyClass() {}
@MyAnn
public void fun1() {}
@MyAnn
public void fun2(@MyAnn String s) {
@MyAnn
int n = 10;
}
}
注解属性支持类型:String、基本数据类型、枚举、Class 、其它注解类型、以及这些数据类型相应的一维数组
public @interface MyAnn {
String value(); //String类型
int value1(); //int类型
}
其中value和value1就是属性!你可能会说,它是一个方法!没错,它是一个方法,但我们非要称之为属性,因为把它当做属性更加好理解。
当为注解指定属性后,那么在使用注解时就必须要给属性赋值了,例如:
@MyAnn(value1=100,value="hello")
public class MyClass {
}
注解的属性还可以有默认值,在使用注解时就可以不用给有默认值的属性赋值了。但没有给出默认值的属性还是要赋值的。
package blog.csdn.net.annotation;
/**
* 自定义注解
* @author mChenys
*
*/
public @interface MyAnn {
String value() default "张三";
int value1();
}
package blog.csdn.net.test;
import blog.csdn.net.annotation.MyAnn;
@MyAnn(value="张三", value1=20)
public class Test1 {
}
@MyAnn(value1=20) //有默认值的可以省略
class Test1_1 {
}
@MyAnn("李四" ,value1=20) //报错,注解属性有多个,不能简写
class Test1_2 {
}
在使用注解时,如果注解的属性有且仅有一个,且名字是value,那么赋值的时候可以有3种方式赋值,否则只能采用的书写格式
package blog.csdn.net.annotation;
/**
* 自定义注解
* @author mChenys
*/
public @interface MyAnn2 {
String value() default "张三";
}
package blog.csdn.net.test;
import blog.csdn.net.annotation.MyAnn2;
/**
* 使用注解
* @author mChenys
*
*/
@MyAnn2() //属性value有默认值,可以不传
public class Test2 {
}
@MyAnn2("李四") //属性名是value,可以简写
class Test2_1{
}
@MyAnn2(value="王五") //默认书写格式
class Test2_2{
}
定义和使用注解属性需要注意的事项:
- 注解的属性后面要有一对圆括号,而且圆括号内不能给出东西。就像是无参的方法一样;
- 注解的属性类型只能是:基本类型、String、Enum、Class、以上类型的一维数组类型;
- 注解的属性可以有默认值,例如:int a() default 100;
- 数组的属性默认值:int[] arr() default {1,2,3},这里不能使用new int[]{1,2,3}
- 使用注解时,在给数组属性赋值时的格式:@MyAnn(arr={1,2,3});
JDK的元Annotation
元 Annotation指修饰Annotation的Annotation。JDK中定义了如下元Annotation:
1.@Retention: 它是注解的保留策略,只能用于修饰一个 Annotation 定义, 用于指定该 Annotation 可以保留的域,@Rentention 包含一个 RetentionPolicy 类型的成员变量, 通过这个变量指定域。
- RetentionPolicy.SOURCE: 编译器将把注解记录在java源文件,编译时直接丢弃这种策略的注释
- RetentionPolicy.CLASS: 编译器将把注解记录在 class 文件中. 当运行 Java 程序时, JVM 不会保留注解. 这是默认值
- RetentionPolicy.RUNTIME:编译器将把注释记录内存中的字节码. 当运行 Java 程序时, JVM 会保留注解. 程序可以通过反射获取该注释.
如果希望注解被反射,那么注解就要保留到运行时,而不是源代码或类文件上。
2.@Target:指定注解用于修饰类的哪个成员. @Target 包含了一个名为 value,类型为ElementType的成员变量,ElementType参数包括:
- ElementType.CONSTRUCTOR: 构造器声明
- ElementType.FIELD: 成员变量、对象、属性(包括enum实例)
- ElementType.LOCAL_VARIABLE: 局部变量声明
- ElementType.METHOD: 方法声明
- ElementType.PACKAGE: 包声明
- ElementType.PARAMETER: 参数声明
- ElementType.TYPE: 类、接口(包括注解类型)或enum声明
3.@Documented: 用于指定被该元 Annotation 修饰的 Annotation 类将被 javadoc 工具提取成文档.
4.@Inherited: 被它修饰的 Annotation 将具有继承性.如果某个类使用了被 @Inherited 修饰的 Annotation, 则其子类将自动具有该注解
例如表示只能用于类、接口、方法上的注解书写格式如下:
package blog.csdn.net.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Target;
@Target({ElementType.TYPE,ElementType.METHOD}) //多个使用类型,用{}包起来
public @interface MyAnn4 {
}
提取 Annotation 信息
JDK 5.0 在 java.lang.reflect 包下新增了 AnnotatedElement 接口, 该接口表示目前正在此 VM 中运行的程序的一个已注释元素。该接口允许反射性地读取注释.
当一个 Annotation 类型被定义为运行时 Annotation 后, 该注释才是运行时可见, 当 class 文件被载入时保存在 class 文件中的 Annotation 才会被虚拟机读取
程序可以调用 AnnotatedElement对象的如下方法来访问 Annotation 信息
通过demo演示读取注解的值
定义注解
package blog.csdn.net.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)//保留到内存中
@Target({ ElementType.TYPE, ElementType.METHOD,ElementType.PARAMETER }) // 多个使用类型,用{}包起来
public @interface MyAnn4 {
String value() default "张三";
int a() default 10;
int b() default 10;
}
使用注解
package blog.csdn.net.test;
import blog.csdn.net.annotation.MyAnn4;
@MyAnn4("李四") //将注解使用在类上
public class MyClass {
//将注解使用在方法参数上
public int getSun1(@MyAnn4(a = 100) int a, @MyAnn4(b = 100) int b) {
return a + b;
}
//将注解使用在方法上
@MyAnn4(a = 200, b = 200)
public int getSun2(int a, int b) {
return a + b;
}
}
获取注解
package blog.csdn.net.test;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import org.junit.Test;
import blog.csdn.net.annotation.MyAnn4;
public class Test4 {
public static void main(String[] args) throws Exception {
Class<MyClass> clazz = MyClass.class;
// 判断类上面有没有注解
if (clazz.isAnnotationPresent(MyAnn4.class)) {
MyAnn4 an4 = (MyAnn4) clazz.getAnnotation(MyAnn4.class);
System.out.println("获取MyClass类上的注解默认属性value:" + an4.value());
System.out.println("获取MyClass类上的注解默认属性a:" + an4.a());
System.out.println("获取MyClass类上的注解默认属性b:" + an4.b());
}
// 获取getSun1方法
Method method1 = clazz.getMethod("getSun1", int.class, int.class);
// 获取getSun1方法的参数
Parameter[] param1 = method1.getParameters();
// 获取参数上定义的注解
int a = 0, b = 0;
if (param1[0].isAnnotationPresent(MyAnn4.class)) {
a = param1[0].getAnnotation(MyAnn4.class).a();
}
if (param1[1].isAnnotationPresent(MyAnn4.class)) {
b = param1[1].getAnnotation(MyAnn4.class).b();
}
int sum1 = (int) method1.invoke(clazz.newInstance(), a, b);
System.out.println("调用getSun1方法的结果:" + sum1);
// 获取getSun2方法
Method method2 = clazz.getMethod("getSun2", int.class, int.class);
// 判断方法上是否有注解
if (method2.isAnnotationPresent(MyAnn4.class)) {
MyAnn4 myAnn4 = method2.getAnnotation(MyAnn4.class);
int sum2 = (int) method2.invoke(clazz.newInstance(), myAnn4.a(), myAnn4.b());
System.out.println("调用getSun2方法的结果:" + sum2);
}
}
}
运行结果:
Servlet3.0新特性概述
Servlete3.0的主要新特性如下三部分:
- 使用@WebServlet、@WebFilter、@WebListener三个注解来替代web.xml文件中的Servlet、Filter、Listener的配置;
- Servlet异步处理:当Servlet处理比较费时的问题时,这会让客户感觉到很卡。当使用异常处理时可以把已经处理好的内容先一步响应给客户端浏览器,然后使用另一个线程来完成费时的操作,也就是把内容一部分一部分的显示出来;
- 使用@MultipartConfig支持上传组件:不用再使用fileupload等第三方的上传组件,使用Servlet3.0的上传组件会更方便。
演示@WebServlet注解:
package blog.csdn.net.web3;
import java.io.IOException;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebInitParam;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@WebServlet(urlPatterns = "/a", initParams = {
@WebInitParam(name = "paramName", value = "paramValue") }, loadOnStartup = 1)
public class ServletA extends HttpServlet {
@Override
public void init(ServletConfig config) throws ServletException {
super.init(config);
System.out.println("获取初始参数:"+config.getInitParameter("paramName"));
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
req.setCharacterEncoding("utf-8");
resp.setContentType("text/html;charset=utf-8");
resp.getWriter().print("Hello World!");
}
}
由于配置了loadOnStartup,服务器启动的时候会输出
访问http://localhost:8080/annotation/a会打开ServletA
演示@WebFilter:
package blog.csdn.net.web3;
import java.io.IOException;
import javax.servlet.DispatcherType;
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.annotation.WebFilter;
@WebFilter(urlPatterns = { "/a", "/a/b/c", "/*" }, dispatcherTypes = { DispatcherType.REQUEST, DispatcherType.FORWARD })
public class FilterA implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
System.out.println("start filter");
chain.doFilter(request, response);
System.out.println("end filter");
}
@Override
public void destroy() {
}
}
访问http://localhost:8080/annotation/a时,控制台输出的结果:
演示@WebListener:
package blog.csdn.net.web3;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;
@WebListener()
public class AListener implements ServletContextListener {
public void contextDestroyed(ServletContextEvent arg0) {
System.out.println("服务器关闭了");
}
public void contextInitialized(ServletContextEvent arg0) {
System.out.println("服务器启动了");
}
}
启动和关闭服务器的输出结果: