Java注解
注解自JDK1.5开始被引入,广泛用于应用程序代码中,尤其是Spring中大量使用注解,也促进了注解的流行。
什么是注解?形式上看有点像注释,但注解在程序运行期也可以起到作用,更多时起到标识的作用。从代码角度来看,注解是和类、接口并列的概念,但它不能独立存在,需要依附于类、方法或者属性。
常见注解
| 注解 |
提供方 |
作用 |
| @Override |
JDK |
子类的方法是覆盖父类的方法 |
| @Deprecated |
JDK |
过时的方法,编译器在编译时会给出警告 |
| @SuppressWarnings |
JDK |
去除编译器的警告信息 |
| @Autowired |
Spring |
自动注入Bean |
| @Component |
Spring |
声明Bean交给Spring的Ioc容器管理 |
| @Service/@Controller/@Repository |
Spring |
@Component的细分 |
| @SpringBootApplication |
SpringBoot |
多个注解的组合 |
注解
就像class 定义类,interface 定义接口一样, @interface 用来定义注解。
我们先来看一下@Override的定义,其中@Target和@Retention是用来定义注解的注解,一般成为元注解。
@Target(ElementType.METHOD) @Retention(RetentionPolicy.SOURCE) public @interface Override { }
|
元注解
四个常用的元注解是:@Target、@Retention、@Inherited和@Documented
@Target
注解的作用域,就是注解可以加在哪里,可以多选,形如{ElementType.METHOD, ElementType.FIELD} 。
- ElementType.TYPE - 注解加到类或者接口上
- ElementType.FIELD - 注解加到类的成员变量上
- ElementType.METHOD - 注解加到类的成员方法上
- ElementType.CONSTRUCTOR - 注解加到类的构造函数上
- ElementType.PARAMETER - 注解加到方法的参数上
@Retention
注解的生命周期,就是在什么时候起作用,或者说注解会被带到哪个阶段。
- RetentionPolicy.SOURCE - 只在源代码中起作用
- RetentionPolicy.CLASS - 保留到.class文件中
- RetentionPolicy.RUNTIME - 在运行期仍然起作用(通常我们都用这个)
@Inherited
添加@Inherited元注解说明这个注解可以被子类继承。
@Documented
添加@Documented元注解说明可以被JavaDoc文档化。
上述四个元注解中最常用的是@Target和@Retention。
属性
注解可以拥有属性,虽然下面@Component注解中的value()写法看上去像是一个方法,但实际上它是一个属性,value()里面不允许有参数。注解可以拥有多个属性,可以通过default 给属性设置默认值。
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Component { String value() default ""; }
|
使用
以@Component注解为例,@Target声明为ElementType.TYPE,说明注解的作用域在类上。属性可以在()里面根据属性名进行设置,如果不设置使用default值。
@Component(value="user") public class User { }
|
注意:value是一个特殊属性,value是注解的默认属性,如果只有一个属性并且是value,那么可以写成这样
@Component("user") public class User { }
|
这里没有指定属性名,就使用默认的属性名value。
自定义注解
相信大家对于使用JDK和Spring提供的常见注解都不陌生,下面看看如何自定义注解并使用。
下面以实体类对象为例,我们定义两个注解@Table和@Column。
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface Table { String name() default ""; }
|
@Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) public @interface Column { String name() default ""; }
|
我们定义@Table注解作用域在类上,@Column注解作用域在类的成员变量上,都有一个name属性。我们再来定义一个User实体类,与user表对应,这里省略了get/set方法。
@Table(name = "user") public class User { @Column(name = "user_id") private Long userId; @Column(name = "user_name") private String userName; @Column(name = "user_mobile") private String userMobile; }
|
反射读取注解示例
下面我们看一下自定义的注解如何起作用。我们通过自定义注解自动拼接SQL查询语句,如果字段值不为null就作为查询条件。
protected String query(Object object) throws IllegalAccessException { StringBuffer buffer = new StringBuffer(); Class clazz = object.getClass(); if(!clazz.isAnnotationPresent(Table.class)) { return null; } Table annotationTable = (Table)clazz.getAnnotation(Table.class); buffer.append("select * from "); buffer.append(annotationTable.name()); buffer.append(" where 1=1"); Field[] fieldList = clazz.getDeclaredFields(); for (int i=0; i<fieldList.length; i++) { Field field = fieldList[i]; field.setAccessible(true); Object value = field.get(object); if (null == value) { continue; } if (!field.isAnnotationPresent(Column.class)) { continue; } Column annotationColumn = (Column)field.getAnnotation(Column.class); buffer.append(" and "); buffer.append(annotationColumn.name()); buffer.append("="); if (value instanceof String) { buffer.append("'"); buffer.append(value); buffer.append("'"); } else { buffer.append(value); } } return buffer.toString(); }
|
我们做如下测试
User user = new User(); user.setUserId(1001L); user.setUserName("test"); System.out.println(query(user));
|
输出结果为
select * from user where 1=1 and user_id=1001 and user_name='test'
|
Spring读取注解示例
实战中我们往往使用Spring的IoC容器来管理类对象,我们再来看看如何在Spring中如何读取到注解信息。
首先,User类需要增加@Component注解,加入到Spring的IoC容器中进行管理
@Table(name = "user") @Component("user") public class User { @Column(name = "user_id") private Long userId; @Column(name = "user_name") private String userName; @Column(name = "user_mobile") private String userMobile; }
|
ApplicationContextAware
第一种方法,实现ApplicationContextAware接口。
@Configuration public class MybatisAutoConfiguration implements ApplicationContextAware { protected ApplicationContext context; @Override public void setApplicationContext(ApplicationContext context) throws BeansException { this.context = context; } @PostConstruct public void init() throws Exception { Map<String, Object> beans = context.getBeansWithAnnotation(Table.class); if (beans.size() > 0) { for (Map.Entry<String, Object> entry : beans.entrySet()) { String beanName = entry.getKey(); Object object = entry.getValue(); Table annotationTable = context.findAnnotationOnBean(beanName, Table.class); String tableName = annotationTable.name(); System.out.println("tableName=" + tableName); } } }
|
核心是调用容器context的getBeansWithAnnotation()方法获取所有声明了@Table注解的Bean对象,然后再调用context的findAnnotationOnBean()方法读取@Table注解信息。
BeanPostProcessor
第二种方法,实现BeanPostProcessor接口。
上面的方法虽然也能实现,但最正统的应该是实现BeanPostProcessor接口,它保证在每个Bean创建完成后给我们一个定制化处理的机会。
TODO:使用ApplicationContextAware需要保证执行@PostConstruct时所有对象都已经创建完毕,否则就可能出现遗漏的情况。实战过程中还没有出现过遗漏的情况,原因不清楚。
@Configuration public class BeanPostConfiguration implements BeanPostProcessor { @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { return bean; }
@Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { Class clazz = bean.getClass(); if(!clazz.isAnnotationPresent(Table.class)) { return bean; } Table annotationTable = (Table)clazz.getAnnotation(Table.class); System.out.println(annotationTable.name()); return bean; } }
|
在IoC容器实例化每一个Bean对象以后都会调用postProcessAfterInitialization()方法,所以在这里增加对注解的处理是合适的。引申开来,我们甚至可以进行定制化修改,不返回bean对象,返回其他定制化对象。