写在前面:最近在学springmvc,发现了注解功能强大,就来理解一下原理是咋回事
@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()));
}
}
所谓元注解,就是加在注解上的注解。常用的有@Documented、@Target、@Retention
例子: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
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
((ParameterizedType)this.getClass().getGenericSuperclass()),这里的this.getClass()是获取的是继承了BaseDao
其实我们会发现真正用到注解的地方只有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);
}
}
写在后面:大多数情况下,框架把注解实现和读取注解的程序隐藏,除非阅读源码否则根本看不到,光顾着使用注解就会忘记它背后的原理了。