java基础进阶五-注解

注解

注解,和反射一样,是Java中最重要却最容易被人遗忘的知识点。哪怕Spring、SpringMVC、SpringBoot等框架中充满了注解,我们还是选择性地忽视它。很多人不明白它是怎么起作用的,甚至有人把它和注释混淆…工作中也只是机械性地在Controller上加@RequestMapping。是的,我们太习以为常了,以至于觉得它理所应当就是如此。

注解概述
格式
public @interface 注解名称{
	属性列表;
}
分类
  1. 自定义注解:就是我们自己写的注解。如@UserLog
  2. JDK内置注解:@Override
  3. 第三方框架:springmvc中的@Controller等
作用

如果说注释是给人看的,那么注解就是给程序看的,它就像是一个标签,贴在一个字段,类或者方法上,它的目的是为当前读取该注解的程序提供判断依据及少量附加信息。比如程序只要读到加了@Test的方法,就知道该方法是待测试方法。

注解的本质

@interface和interface从名字上看非常相似,我猜注解的本质是一个接口(当然,这是瞎猜)。

为了验证这个猜测,我们做个实验。先按上面的格式写一个注解(暂时不附加属性):
java基础进阶五-注解_第1张图片
编译后得到字节码文件:通过XJad工具反编译MyAnnotation.class:
java基础进阶五-注解_第2张图片
我们发现,@interface变成了interface,而且自动继承了Annotation:
java基础进阶五-注解_第3张图片
既然确实是个接口,那么我们自然可以在里面写方法
java基础进阶五-注解_第4张图片
得到class文件后反编译
java基础进阶五-注解_第5张图片
虽说注解的本质是接口,但是仍然有很多怪异的地方,比如使用注解时,我们竟然可以给getValue()赋值:

@MyAnnotation(getValue = "annotation on class")
public class Demo {

    @MyAnnotation(getValue = "annotation on field")
    public String name;

    @MyAnnotation(getValue = "annotation on method")
    public void hello() {}

}

所以在注解里,类似于String getValue()这种,被称为“属性”,而给属性赋值显然听起来好接受多了。

反射注解信息

上面说过,注解就像一个标签,是贴在程序代码上供另一个程序读取的。
要牢记,只要用到注解,必然有三角关系:
● 定义注解
● 使用注解
● 读取注解

反射获取注解信息:

public class AnnotationTest {
    public static void main(String[] args) throws Exception {
        // 获取类上的注解
        Class<Demo> clazz = Demo.class;
        MyAnnotation annotationOnClass = clazz.getAnnotation(MyAnnotation.class);
        System.out.println(annotationOnClass.getValue());

        // 获取成员变量上的注解
        Field name = clazz.getField("name");
        MyAnnotation annotationOnField = name.getAnnotation(MyAnnotation.class);
        System.out.println(annotationOnField.getValue());

        // 获取hello方法上的注解
        Method hello = clazz.getMethod("hello", (Class<?>[]) null);
        MyAnnotation annotationOnMethod = hello.getAnnotation(MyAnnotation.class);
        System.out.println(annotationOnMethod.getValue());

        // 获取defaultMethod方法上的注解
        Method defaultMethod = clazz.getMethod("defaultMethod", (Class<?>[]) null);
        MyAnnotation annotationOnDefaultMethod = defaultMethod.getAnnotation(MyAnnotation.class);
        System.out.println(annotationOnDefaultMethod.getValue());

    }
}

Class、Method、Field对象都有个getAnnotation()方法,可以获取各自位置上的注解信息,但这样程序运行的时候会报错,这是因为注解有所谓的保留策略,控制自己可以保留到哪个阶段。保留策略也是通过注解实现的,它属于元注解,也叫做元数据。

元注解

元注解就是加在注解上的注解,常用的就是:
● @Documented:用于制作文档,不是很重要,忽略便是
● @Target:加在注解上,限定该注解的使用位置。不写的话,好像默认各个位置都是可以的。
●@Retention:注解的保留策略
java基础进阶五-注解_第6张图片
注解的保留策略有三种:SOURCE/ClASS/RUNTIME
java基础进阶五-注解_第7张图片
一般来说,普通开发者使用注解的时机都是运行时,比如反射读取注解(也有类似Lombok这类编译期注解)。既然反射是运行时调用,那就要求注解的信息必须保留到虚拟机将.class文件加载到内存为止。如果你需要反射读取注解,却把保留策略设置为RetentionPolicy.SOURCE、RetentionPolicy.CLASS,那就读取不到了。

大多数情况下,三角关系中我们只负责使用注解,无需定义和执行,框架会将注解类和读取注解的程序隐藏起来,除非阅读源码,否则根本看不到。平时见不到定义和读取的过程,光顾着使用注解,久而久之很多人就忘了注解如何起作用了!
java基础进阶五-注解_第8张图片

注解案例:
  1. 山寨Junit
代码结构

java基础进阶五-注解_第9张图片

案例代码
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface MyBefore {
}

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface MyTest {
}

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface MyAfter {
}

山寨junit

public class Junit {
    //山赛Junit

    @MyBefore
    public void init(){
        System.out.println("mybefore");
    }

    @MyTest
    public void test(){
        System.out.println("mytest");
    }

    @MyAfter
    public void destory(){
        System.out.println("myafger");
    }
}

读取注解

public class MyJunitFrameWork {

    //读取注解
    public static void main(String[] args) throws Exception {
        Class<Junit> aClass = Junit.class;
        Object obj = aClass.newInstance();

        //获取所有的方法
        Method[] methods = aClass.getMethods();
        //迭代出每一个Method对象,判断哪些方法上使用了@MyBefore/@MyAfter/@MyTest注解
        ArrayList<Method> myBefore = new ArrayList<>();
        ArrayList<Method> myAfter = new ArrayList<>();
        ArrayList<Method> myTest = new ArrayList<>();
        for (Method method : methods) {
            if (method.isAnnotationPresent(MyBefore.class)){
                myBefore.add(method);
            }else if (method.isAnnotationPresent(MyAfter.class)){
                myAfter.add(method);
            }else if (method.isAnnotationPresent(MyTest.class)){
                myTest.add(method);
            }
        }
        //执行方法测试
        for (Method method : myTest) {
            //before
            for (Method method1 : myBefore) {
                method1.invoke(obj);
            }
            //test
            method.invoke(obj);
            //after
            for (Method method1 : myAfter) {
                method1.invoke(obj);
            }
        }

    }
}

输出结果
java基础进阶五-注解_第10张图片
2. 山寨JPA

要写山寨JPA需要两个技能:注解+反射。
注解已经学过了,反射其实还有一个进阶内容,之前那篇反射文章里没有提到,放在这里补充。至于是什么内容,一两句话说不清楚。慢慢来吧。

首先,要跟大家介绍泛型中几个定义(记住最后一个):
● ArrayList中的E称为类型参数变量
● ArrayList中的Integer称为实际类型参数
● 整个ArrayList称为泛型类型
● 整个ArrayList称为参数化的类型ParameterizedType

接下来看问题:

class A<T>{
    public A(){
       /*
        我想在这里获得子类B、C传递的实际类型参数的Class对象
        class java.lang.String/class java.lang.Integer
       */
    }
}

class B extends A<String>{
}

class C extends A<Integer>{
}

可能你会想了,直接T.class不就行了吗。明确一点,这样是不行的

是不是有点懵了,那就一步一步来

父类中的this是谁?

看代码:

public class Test {
	public static void main(String[] args) {
		new B();
	}
}

class A<T>{
	public A(){
        // this是谁?A还是B?
		Class clazz = this.getClass();
		System.out.println(clazz.getName());
	}
}

class B extends A<String>{
}

根据之前学习到的,这个this是B,这样我们就迈出了第一步,在父类中获得子类的Class对象

如果根据子类class获取父类的class?

分析:

class A<T>{
	public A(){
        //clazz是B.class
		Class clazz = this.getClass();
	}
}
class B extends A<String>{
}

我们在泛型父类中获得了子类Class对象,而我们要等到泛型父类中泛型的Class对象,先不说泛型的class对象,我们怎么通过子类Class对象获得父类Class对象?
查阅API文档,我们发现有这么个方法:
java基础进阶五-注解_第11张图片
Generic Super Class,直译就是“带泛型的父类”。也就是说调用getGenericSuperclass()就会返回泛型父类的Class对象。这非常符合我们的情况,因为Class A确实是泛型类。试着打印一下:
java基础进阶五-注解_第12张图片
上面已经证明了通过子类Class对象可以获取父类Class对象,接下来我们尝试如何获取带有实际类型参数的父类class,虽然genericSuperclass是Type接收的,但可以看出实际类型为ParameterizedTypeImpl:
java基础进阶五-注解_第13张图片
这里我们不去关心Type、ParameterizedType还有Class之间的继承关系,总之以我们多年的编码经验,子类的方法总是更多,所以毫不犹豫地向下转型:

public class JpaTest {
    public static void main(String[] args) {
        new B();
    }
}

class A<T> {
    public A() {
        Class<? extends A> subClass = this.getClass();
        // 得到泛型父类
        Type genericSuperclass = subClass.getGenericSuperclass();

        // 本质是ParameterizedTypeImpl,可以向下强转
        ParameterizedType parameterizedTypeSuperclass = (ParameterizedType) genericSuperclass;

        // 强转后可用的方法变多了,比如getActualTypeArguments()可以获取Class A的泛型的实际类型参数
        Type[] actualTypeArguments = parameterizedTypeSuperclass.getActualTypeArguments();

        // 由于A类只有一个泛型,这里可以直接通过actualTypeArguments[0]得到子类传递的实际类型参数
        Class actualTypeArgument = (Class) actualTypeArguments[0];
        System.out.println(actualTypeArgument);
        System.out.println(subClass.getName());
    }
}

class B extends A<String> {
}

class C extends A<Integer> {
}

java基础进阶五-注解_第14张图片
这样我们就能在父类中等到子类继承时传递的泛型的实际类型参数。

第一版JPA
需要使用到的依赖:
java基础进阶五-注解_第15张图片
User

CREATE TABLE `User` (
  `name` varchar(255) DEFAULT NULL COMMENT '名字',
  `age` tinyint(4) DEFAULT NULL COMMENT '年龄'
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
@Data
@AllArgsConstructor
public class User {
    private String name;
    private Integer age;
}

BaseDao

public class BaseDao<T> {

    private static BasicDataSource datasource;
    //静态代码块,设置连接数据库的参数
    static {
        datasource = new BasicDataSource();
        datasource.setDriverClassName("com.mysql.cj.jdbc.Driver");
        datasource.setUrl("jdbc:mysql://localhost:3306/test");
        datasource.setUsername("root");
        datasource.setPassword("root");
    }

    //得到jdbcTemplate
    JdbcTemplate jdbcTemplate = new JdbcTemplate(datasource);

    private Class<T> beanClass;

    /*
    构造器,初始化的时候,获取实际类型参数,比如BaseDao ,那么beanClass就是user.class
     */
    public BaseDao(){
      beanClass=  (Class<T>)((ParameterizedType)this.getClass().getGenericSuperclass()).getActualTypeArguments()[0];
    }

    public void add(T bean){
        //获取user对象的所有字段
        Field[] fields = beanClass.getFields();
        //拼接sql语句
        StringBuffer sb = new StringBuffer();
        sb.append("insert into");
        sb.append(beanClass.getSimpleName());
        sb.append(" values(");
        for (int i=0;i< fields.length;i++){
            sb.append("?");
            if (i< fields.length-1){
                sb.append(",");
            }
        }
        sb.append(")");
        //获取要插入的数据的值
        ArrayList<Object> list = new ArrayList<>();
        try {
            for (Field field : fields) {
                field.setAccessible(true);
                Object o = field.get(bean);
                list.add(o);
            }
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
        int size = list.size();
        Object[] objects = list.toArray(new Object[size]);
        int update = jdbcTemplate.update(sb.toString(), objects);
        System.out.println(update);
    }

UserDao

public class UserDao extends BaseDao<User>{


    @Override
    public void add(User bean) {
        super.add(bean);
    }
}

测试类

public class UserDaoTest {
    public static void main(String[] args) {
        UserDao userDao = new UserDao();
        User user = new User("张三", 18);
        userDao.add(user);
    }
}

看到这里你不仅要问了,这和注解有什么关系呢,一个注解都没有用上!!!!

别急,马上上第二版JPA

  1. 第二版JPA
    给我们的User类加一个Table注解,用来告诉程序这个POJO和数据库哪张表对应
CREATE TABLE `t_jpa_user` (
  `name` varchar(255) DEFAULT NULL COMMENT '名字',
  `age` tinyint(4) DEFAULT NULL COMMENT '年龄'
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

@Table注解

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Table {
    String value();
}

给新user加上注解

@Data
@Table("t_jpa_user")
@AllArgsConstructor
public class User {
    private String name;
    private Integer age;
}

java基础进阶五-注解_第16张图片
java基础进阶五-注解_第17张图片
山寨JPA就完成了。

你可能感兴趣的:(java,spring,spring,boot)