Java知识扫盲,再深入——Annotation 注解、自定义注解、自定义框架

目录

  • 认识注解
  • 常见注解
    • 限定重写父类方法:@Override
      • 作用:
  • 其他基本注解
    • 标记已过时:@Deprecated
    • 抑制编译器警告:@SuppressWarnings
  • JDK的元注解
    • 存在阶段:@Retention
    • 指定可被修饰元素:@Target
    • 可被javadoc工具提成文档:@Documented
    • 被子类继承:@Inherited
  • 自定义注解
    • 注释结构
    • 测试自定义注解
    • 重点来了
    • 注解+反射案例
      • 只运行有自定义注解方法
      • 注解添加按钮监听“框架”
  • 提问

认识注解

注解是为程序元素设置元数据的。本质上是一个接口。

注解起的是标记作用,可以用来标记包、类、构造器、方法、成员变量、参数、局部变量。

但是,注解不会影响程序代码的执行

如果想让注释在程序中起作用,需要配套的注释工具ATP(Annotation Processing Tool)

常见注解

限定重写父类方法:@Override

学Java的人,对@Override一定不陌生,当我们实现一个接口后,编译器自动添加的方法,上边都有这个注解。如:

public class OverrideTest implements father{

	@Override
	public void m1() {
		// TODO 自动生成的方法存根
		
	}

}
interface father {
	public void m1();
}

作用:

告诉编译器,下边的方法是重写\实现的方法,让编译器检查有没有拼写错误。好处是:避免低级错误有的编译器里边 o、O、0 还有 1、I、l 基本没区别,当我们想重写父类方法,却因为这种“小”问题出错,而且这种“小”问题,排错非常非常难!!(教训过于惨痛,不举例说明了。。。

其他基本注解

标记已过时:@Deprecated

图示:
Java知识扫盲,再深入——Annotation 注解、自定义注解、自定义框架_第1张图片
代码

public class DeprecatedTest {

	public static void main(String[] args) {
		// 使用过时方法会被警告
		new Apple().info();
	}
}

class Apple{
	// 定义 info 方法,并设置过时
	@Deprecated
	public void info() {
		System.out.println("Apple 的info方法");
	}
}

抑制编译器警告:@SuppressWarnings

图示:
效果对比
代码

import java.util.ArrayList;
import java.util.List;

public class SuppressWarningsTest {

	// 抑制没有进行类型检查操作的警告
	@SuppressWarnings(value="unchecked")
	public static void main(String[] args) {
		// 使用 generics(泛型) 时忽略没有指定相应的类型
		@SuppressWarnings("rawtypes")
		List myList = new ArrayList();
		myList.add("");
	}
	
}

JDK的元注解

存在阶段:@Retention

value取值 含义
RetentionPolicy.SOURCE 注解将被编译器丢弃
RetentionPolicy.CLASS 注解在class文件中可用,但会被VM丢弃
RetentionPolicy.RUNTIME VM将在运行期也保留注释,因此可以通过反射机制读取注解的信息

指定可被修饰元素:@Target

value取值 含义
ElemenetType.CONSTRUCTOR 构造器声明
ElemenetType.FIELD 域声明(包括 enum 实例)
ElemenetType.LOCAL_VARIABLE 局部变量声明
ElemenetType.METHOD 方法声明
ElemenetType.PACKAGE 包声明
ElemenetType.PARAMETER 参数声明
ElemenetType.TYPE 类,接口(包括注解类型)或enum声明

可被javadoc工具提成文档:@Documented

它代表着此注解会被javadoc工具提取成文档。
Java知识扫盲,再深入——Annotation 注解、自定义注解、自定义框架_第2张图片

被子类继承:@Inherited

允许子类继承父类中的注解。

自定义注解

注释结构

点开我们常见的 @Override看看
Java知识扫盲,再深入——Annotation 注解、自定义注解、自定义框架_第3张图片
发现结构只有两个:

  1. 元注解
  2. 注解体

测试自定义注解

我们模仿上边的注解,写个注解试试。

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface MyOverridn {

}

好像有点看不太懂,反编译一下,看看具体是什么。

反编译后生成的代码:

public class annotation.test.OverrideTest implements annotation.test.father {
  public annotation.test.OverrideTest();
  public void m1();
}

这下就可以看出来了。本质上就是一个类,去实现了一个接口

重点来了

我们上边已经说过,注解就是一个标签,那么我们这样做个 标签,它又不会影响代码执行,那注解有什么用?

想让注解发挥更大的作用,需要用到反射!

反射可以拿到注解标记的元素,方法如下。

返回值 方法及详解
getAnnotation(Class annotationClass)
T 如果存在该元素的指定类型的注释,则返回这些注释,否则返回 null。
Annotation[] getAnnotations()
返回此元素上存在的所有注释。
Annotation[] getDeclaredAnnotations()
返回直接存在于此元素上的所有注释。
boolean isAnnotationPresent(Class annotationClass)
如果指定类型的注释存在于此元素上,则返回 true,否则返回 false。

注解+反射案例

学过框架或者反射的人,应该都听“过框架就是注解+反射”、“反射是框架的灵魂”这些话,下边咱们就做2个小“框架”,来练练手。

只运行有自定义注解方法

一个类的方法有很多,平常咱们想用哪个就调那个,现在,咱们要用自定义注解去标识一下,然后就让这些被标示的方法跑起来。并且记录一下异常出现的次数。
Testable——自定义注解

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

// 指定 Testable 注释可以保留多久
@Retention(RetentionPolicy.RUNTIME)
// 指定 Testable 注释能修饰的目标(只能是方法)
@Target(ElementType.METHOD)
public @interface Testable {
	
}

MyTest——有需要运行方法的类

public class MyTest {
	
	@Testable
	public static void m1() {

	}

	public static void m2() {

	}
	@Testable
	public static void m3() {
		throw new RuntimeException("Boom");
	}

	public static void m4() {

	}
	@Testable
	public static void m5() {

	}

	public static void m6() {

	}
	@Testable
	public static void m7() {
		throw new RuntimeException("Crash");
	}

	public static void m8() {

	}
}

TestProcessor——使用反射加载被标识方法,并运行的核心列

import java.lang.reflect.Method;

public class TestProcessor {

	public static void process(Class<MyTest> class1) throws SecurityException, ClassNotFoundException {
		int passed = 0;
		int failed = 0;
		for (Method m : class1.getMethods()) {
			if (m.isAnnotationPresent(Testable.class)) {
				try {
					m.invoke(null);
					passed++;
				} catch (Exception e) {
					System.out.println("方法"+m+"运行失败,异常:"+e.getCause()+"\n");
					failed++;
				}
			}
		}
		System.out.println("共运行了:"+(passed+failed)+"个方法,其中\n"+
					"失败了:"+failed+"个,\n"+
					"成功了:"+passed+"个,\n"
				);
	}
}

RunTests——调用核心类

public class RunTests {
	public static void main(String[] args) throws Exception {
		TestProcessor.process(MyTest.class);
	}
}

运行结果

方法public static void annotation.useAnnotation.MyTest.m7()运行失败,异常:java.lang.RuntimeException: Crash

方法public static void annotation.useAnnotation.MyTest.m3()运行失败,异常:java.lang.RuntimeException: Boom

共运行了:4个方法,其中
失败了:2个,
成功了:2个,

注解添加按钮监听“框架”

学习JavaSE,做小游戏,相信按钮监听让很多人心烦,每个按钮都需要一个个添加监听,一个个写事件处理,不如只用一个注解,给添加得了
ActionListenerFor——自定义注解

import java.awt.event.ActionListener;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ActionListenerFor {
	// 定义一个成员变量,用于保存监听器实现类
	Class<? extends ActionListener> listener();
}

ActionListenerInstaller——核心处理类

import java.awt.event.ActionListener;
import java.lang.reflect.Field;

import javax.swing.AbstractButton;

public class ActionListenerInstaller {
	// 处理注释的方法,其中 obj 是包含注释的对象
	public static void processAnnotations(Object obj) {
		try {
			// 获取 obj 对象的类
			Class cl = obj.getClass();
			// 获取指定 obj 对象的所有成员变量,并遍历每个成员变量
			for (Field f : cl.getDeclaredFields()) {
				// 将成员变量设置为可自由访问
				f.setAccessible(true);
				// 获取成员变量上 ActionListenerFor 类型的Annotation
				ActionListenerFor a = f.getAnnotation(ActionListenerFor.class);
				// 获取成员变量 f 的值
				Object fObj = f.get(obj);
				// 如果 f 是 AbstractButton 的实例,且 a 不为 null
				if(a != null && fObj != null && fObj instanceof AbstractButton) {
					// 获取 a 注解里的 listener 源数据(它是一个监听类)
					Class<? extends ActionListener > listennerClazz = a.listener();
					// 使用反射创建 listener 类的对象
					ActionListener al = listennerClazz.newInstance();
					AbstractButton ab = (AbstractButton)fObj;
					// 为 ab 按钮添加事件监听器
					ab.addActionListener(al);
				}
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

AnnotationTest——需要添加按钮的类(以及按钮点击事件的处理类)

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JPanel;

public class AnnotationTest {

	private JFrame mainWin = new JFrame("使用注解绑定事件监听器");
	// 使用注解为 ok 按钮绑定事件监听
	@ActionListenerFor(listener=OkListener.class)
	private JButton ok = new JButton("确定");
	// 使用注解为cancel 按钮绑定事件监听
	@ActionListenerFor(listener=CancelListener.class)
	private JButton cancel = new JButton("取消");
	
	public void init() {
		// 初始化界面
		JPanel jp = new JPanel();
		jp.add(ok);
		jp.add(cancel);
		mainWin.add(jp);
		ActionListenerInstaller.processAnnotations(this);
		mainWin.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		mainWin.pack();
		mainWin.setVisible(true);
	}
	public static void main(String[] args) {
		new AnnotationTest().init();
	}
}
// 定义 ok 按钮的事件监听器实现类
class OkListener implements ActionListener{
	@Override
	public void actionPerformed(ActionEvent e) {
		JOptionPane.showMessageDialog(null, "单击了确认按钮");
	}
}
// 定义 cancel 按钮的事件监听器实现类
class CancelListener implements ActionListener{
	@Override
	public void actionPerformed(ActionEvent e) {
		JOptionPane.showMessageDialog(null, "单击了取消按钮");
	}
}

效果
Java知识扫盲,再深入——Annotation 注解、自定义注解、自定义框架_第4张图片

提问

学到这里,有些问题,在你心里边应该有个大致答案了。
比如:
Spring的注解@Table——怎么把对象映射到数据库的数据表??
SpringMVC的注解@Autowired——怎么实现自动注入??

让你自己实现类似功能,你是否能实现?

你可能感兴趣的:(Java知识扫盲)