对java注解的理解

写在前面:最近在学springmvc,发现了注解功能强大,就来理解一下原理是咋回事


文章目录

  • 格式
  • 类别
  • 使用位置
  • 作用
  • 级别
  • 注解的本质
  • 反射注解信息
  • 元注解
  • 注解属性的数据类型
  • 特性
  • 自定义Junit框架
  • 山寨版JPA实现


格式

@Target
@Retention
@Documented
public @interface Name {
    String method() default "default value";
}

类别

注解分为自定义注解、JDK内置注解和第三方注解(框架),自定义注解一般要我们自己定义、使用、并写程序读取,而JDK内置注解和第三方注解我们只要使用,定义和读取都交给它们

使用位置

注解常常出现在类、方法、成员变量、形参等位置

作用

是元数据,描述数据的数据,为计算机提供代码的描述,比如程序只要读到加了@Test的方法,就知道该方法是待测试方法

级别

注解和类、接口、枚举是同一级别的。

注解的本质

本质是一个接口,java编译@interface时变成interface,而且自动继承了Annotation,所以注解本质是个接口,那么自然可以在里面写方法,在注解中的方法被称为属性,使用注解时可以给属性赋值,当没有赋值时,属性将使用默认值。

反射注解信息

程序可以通过反射读取注解,Class、Method、Field对象都有个getAnnotation(),可以获取各自位置的注解信息
例子如下:在main方法中获取@RequestMapping注解信息"/HelloController"

@Controller
@RequestMapping("/HelloController")
public class HelloController {

    //真实访问地址 : 项目名/HelloController/hello
    @RequestMapping("/hello")
    public String sayHello(Model model){
        //向模型中添加属性msg与值,可以在JSP页面中取出并渲染
        model.addAttribute("msg","hello,SpringMVC");
        //web-inf/jsp/hello.jsp
        return "hello";
    }

    public static void main(String[] args) {
        // 获取类上的注解
        Class<HelloController> clazz = HelloController.class;
        RequestMapping c =  clazz.getAnnotation(RequestMapping.class);
        System.out.println(Arrays.toString(c.value()));
    }
}

结果显示:
对java注解的理解_第1张图片

元注解

所谓元注解,就是加在注解上的注解。常用的有@Documented、@Target、@Retention

  1. @Documented
    用于制作文档
  2. @Target
    限定该注解的使用位置,常用的有ElementType.TYPE,ElementType.METHOD
  3. @Retention
    注解的保留策略,保留策略有三种SOURCE/ClASS/RUNTIME,要想被反射读取,保留策略只能用RUNTIME,即运行时仍可读取

例子:springMVC的@Controller的源码,我们可以看到元注解有
@Target
@Retention
@Documented
@Component

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Controller {
    @AliasFor(
        annotation = Component.class
    )
    String value() default "";
}

注解属性的数据类型

  • 八种基本数据类型byte、short、int、long、float、double、char、boolean
  • String
  • 枚举
  • Class
  • 注解类型
  • 以上类型的一维数组

特性

  • 如果注解的属性只有一个,且叫value,那么使用该注解时,可以不用指定属性名,因为默认就是给value赋值
  • 如果数组的元素只有一个,可以省略{}

自定义Junit框架

MyTest注解

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

建个dao类用于测试

package com.yyz.dao;

import com.yyz.annotation.MyTest;

public class UserDao {
    @MyTest
    public void Save() {
        System.out.println("save...");
    }

    @MyTest
    public void Delete() {
        System.out.println("delete...");
    }
}

类Junit实现测试:

import com.yyz.annotation.MyTest;
import com.yyz.dao.UserDao;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;

public class TestMain {
    public static void main(String[] args) throws IllegalAccessException, InstantiationException, InvocationTargetException {
        // 1.先找到测试类的字节码:EmployeeDAOTest
        Class<UserDao> clazz = UserDao.class;
        Object obj = clazz.newInstance();
        // 2.获取EmployeeDAOTest类中所有的公共方法
        Method[] methods = clazz.getMethods();
		// 3.迭代出每一个Method对象判断哪些方法上使用@MyTest注解
        List<Method> myTestList = new ArrayList<>();
        for (Method method : methods) {
            if(method.isAnnotationPresent(MyTest.class)){
                //存储使用了@MyTest注解的方法对象
                myTestList.add(method);
            }
        }
        // 执行方法测试
        for (Method testMethod : myTestList) {
            // 测试方法
            testMethod.invoke(obj);
        }
    }
}

山寨版JPA实现

@Table注解,用来告诉程序这个POJO和数据库哪张表对应

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

javaBean类,也可以叫entity类,这里用User举例

@Table("t_user")
public class User {
	private String name;
	private Integer age;

	public User(String name, Integer age) {
		this.name = name;
		this.age = age;
	}

	public String getName() {

		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public Integer getAge() {
		return age;
	}

	public void setAge(Integer age) {
		this.age = age;
	}

}

BaseDao,测试类,我们一般springMVC框架时创建Dao类首先是创建Dao的接口再去实现DaoImpl,其实这样做是过于麻烦的,我们就会想到把Dao类做泛型,实现如下:

public class BaseDao<T> {

	private static BasicDataSource datasource = new BasicDataSource();

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

	//得到jdbcTemplate
	private JdbcTemplate jdbcTemplate = new JdbcTemplate(datasource);
	//泛型参数的Class对象
	private Class<T> beanClass;

	public BaseDao() {
		//得到泛型参数的Class对象,假设是User.class
		beanClass = (Class) ((ParameterizedType) this.getClass()
				.getGenericSuperclass())
				.getActualTypeArguments()[0];
	}

	public void add(T bean) {
		//得到User对象的所有字段
		Field[] declaredFields = beanClass.getDeclaredFields();

		//拼接sql语句,【表名从User类Table注解中获取】
		String sql = "insert into "
				+ beanClass.getAnnotation(Table.class).value() 
				+ " values(";
		for (int i = 0; i < declaredFields.length; i++) {
			sql += "?";
			if (i < declaredFields.length - 1) {
				sql += ",";
			}
		}
		sql += ")";

		//获得bean字段的值(要插入的记录)
		ArrayList<Object> paramList = new ArrayList<>();
		for (int i = 0; i < declaredFields.length; i++) {
			try {
				declaredFields[i].setAccessible(true);
				Object o = declaredFields[i].get(bean);
				paramList.add(o);
			} catch (IllegalAccessException e) {
				e.printStackTrace();
			}
		}
		int size = paramList.size();
		Object[] params = paramList.toArray(new Object[size]);

		//传入sql语句模板和模板所需的参数,插入User
		int num = jdbcTemplate.update(sql, params);
		System.out.println(num);
	}
}

难点在这里,是泛型的知识
Class beanClass = (Class) ((ParameterizedType)this.getClass().getGenericSuperclass()).getActualTypeArguments()[0];

((ParameterizedType)this.getClass().getGenericSuperclass()),这里的this.getClass()是获取的是继承了BaseDao的子类, 用getGenericSuperclass()获得泛型父类的Class对象,但返回值类型是Type,用向下转型(ParameterizedType)转成ParameterizedType类型,什么是ParameterizedType类型?举个例子:ArrayList就是ParameterizedType类型,为什么要向下转型?因为向下转型后才能再用getActualTypeArguments()获取泛型参数,如ArrayList用getActualTypeArguments()就是Integer,getActualTypeArguments() 返回值类型是Type[],值是参数列表, 最后转型成Class,获取子类继承时传来的泛型的Class对象。

其实我们会发现真正用到注解的地方只有beanClass.getAnnotation(Table.class).value()获取数据表的名字,重头还是泛型和反射机制。

UserDao类继承BaseDao

public class UserDao extends BaseDao<User> {
	@Override
	public void add(User bean) {
		super.add(bean);
	}
}

测试类

public class TestUserDao {
	public static void main(String[] args) {
		UserDao userDao = new UserDao();
		User user = new User("hst", 20);
		userDao.add(user);
	}
}

写在后面:大多数情况下,框架把注解实现和读取注解的程序隐藏,除非阅读源码否则根本看不到,光顾着使用注解就会忘记它背后的原理了。

你可能感兴趣的:(对java注解的理解)