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

    技术2025-03-21  40

    目录

    认识注解常见注解限定重写父类方法:@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

    图示: 代码

    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.RUNTIMEVM将在运行期也保留注释,因此可以通过反射机制读取注解的信息

    指定可被修饰元素:@Target

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

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

    它代表着此注解会被javadoc工具提取成文档。

    被子类继承:@Inherited

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

    自定义注解

    注释结构

    点开我们常见的 @Override看看 发现结构只有两个:

    元注解注解体

    测试自定义注解

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

    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(); }

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

    重点来了

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

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

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

    返回值方法及详解<T extends Annotation>getAnnotation(Class annotationClass)T如果存在该元素的指定类型的注释,则返回这些注释,否则返回 null。Annotation[]getAnnotations()返回此元素上存在的所有注释。Annotation[]getDeclaredAnnotations()返回直接存在于此元素上的所有注释。booleanisAnnotationPresent(Class<? extends Annotation> 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, "单击了取消按钮"); } }

    效果

    提问

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

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

    Processed: 0.012, SQL: 9