写在前面:最近在学springmvc,发现了注解功能强大,就来理解一下原理是咋回事
注解分为自定义注解、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())); } }结果显示:
所谓元注解,就是加在注解上的注解。常用的有@Documented、@Target、@Retention
@Documented 用于制作文档@Target 限定该注解的使用位置,常用的有ElementType.TYPE,ElementType.METHOD@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 ""; }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); } } }@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<T>,测试类,我们一般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<T> beanClass = (Class) ((ParameterizedType)this.getClass().getGenericSuperclass()).getActualTypeArguments()[0];
((ParameterizedType)this.getClass().getGenericSuperclass()),这里的this.getClass()是获取的是继承了BaseDao<T>的子类, 用getGenericSuperclass()获得泛型父类的Class对象,但返回值类型是Type,用向下转型(ParameterizedType)转成ParameterizedType类型,什么是ParameterizedType类型?举个例子:ArrayList<Integer>就是ParameterizedType类型,为什么要向下转型?因为向下转型后才能再用getActualTypeArguments()获取泛型参数,如ArrayList<Integer>用getActualTypeArguments()就是Integer,getActualTypeArguments() 返回值类型是Type[],值是参数列表, 最后转型成Class,获取子类继承时传来的泛型的Class对象。
其实我们会发现真正用到注解的地方只有beanClass.getAnnotation(Table.class).value()获取数据表的名字,重头还是泛型和反射机制。
UserDao类继承BaseDao<T>类
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); } }写在后面:大多数情况下,框架把注解实现和读取注解的程序隐藏,除非阅读源码否则根本看不到,光顾着使用注解就会忘记它背后的原理了。