一、注解的作用
其实在开发中是很少用到自定义注解的,真的做研发之后就会发现自定义的注解是多么的重要以及常用,野心为项目中自己使用了大量的自定义注解,所以想着有必要记录下来,方便自己查看,也可以分享给各位感兴趣的朋友。包 java.lang.annotation 中包含所有定义自定义注解所需用到的原注解和接口。如接口 java.lang.annotation.Annotation 是所有注解继承的接口,并且是自动继承,不需要定义时指定,类似于所有类都自动继承Object。先来说说JAVA中的注解的作用:
1、减少大量的代码臃肿,使得代码更加的简洁耐用
2、生成文档。这是最常见的,也是java 最早提供的注解。常用的有@see @param @return 等
3、通过代码里标识的元数据让编译器能够实现基本的编译检查
二、怎么使用
使用自定义注解首先要明白它的四个标识:Documented,Inherited,Target(作用范围,方法,属性,构造方法等),Retention(生命范围,源代码,class,runtime)
1、@Inherited 允许子类继承父类中的注解
2、@Document 该注解包含在javadoc中,代表会被javac提取成为文档,文档中的内容会因为该注解内的内容不同而不同
3、@Target 表示该注解用于什么地方,可能的值在枚举类 ElemenetType 中
TYPE : 类、接口
METHOD: 方法上
PACKAGE: 包上
PARAMETER: 参数上
FIELD: 用于局部变量上
CONSTRUCTOR: 构造器上
4、@Retention 什么级别保存该注解信息
RetentionPolicy.SOURCE :编译之后的class文件中注解被丢弃,运行期更加不会有相关注解信息。
RetentionPolicy.CLASS : 编译之后的class文件中包含有该注解,但是运行期被丢弃 。
RetentionPolicy.RUNTIME:编译之后class文件中有该注解,并且运行期间不会被丢弃。
三、例子
现在越来越感觉自定义注解的好处,下面就讲解三个例子来说明其是多么的优美
1、为属性设置元数据
自定义注解:
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.FIELD)//作用到局部变量上
@Retention(RetentionPolicy.RUNTIME)//在class文件中保存,并且在运行期间不会丢失
public @interface MetaField {
boolean store() default true;//是否存储
boolean readonly() default true;//是否可读
String name () default "";//属性名称
boolean searchable() default false;//是否可搜索
}
使用:
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
public class Student {
@MetaField(name = "主键ID" , store = true , readonly = false , searchable = true)
private Integer id;
@MetaField(name = "主键ID" , store = true , readonly = true , searchable = true)
private Integer name;
@MetaField(name = "年龄" , store = true , readonly = false , searchable = false)
private Integer age;
@MetaField(name = "性别" , store = false , readonly = false , searchable = false)
private Integer sex;
@MetaField(name = "住址" , store = true , readonly = false , searchable = false)
private Integer address;
public List<SortableField> init (){
List<SortableField> list = new ArrayList<SortableField>();
Class entity = this.getClass();
if(entity!=null){
Field[] fields = entity.getDeclaredFields();
for(Field f : fields){
//获取字段中包含fieldMeta的注解
MetaField meta = f.getAnnotation(MetaField.class);
if(meta!=null){
SortableField sf = new SortableField(meta, f);
list.add(sf);
}
}
}
return list;
}
}
封装自定义属性,方便操作:
aField meta;
private Field field;
private String name;
private Class<?> type;
public SortableField(){}
public SortableField(MetaField meta, Field field) {
super();
this.meta = meta;
this.field = field;
this.name=field.getName();
this.type=field.getType();
}
public SortableField(MetaField meta, String name, Class<?> type) {
super();
this.meta = meta;
this.name = name;
this.type = type;
}
public MetaField getMeta() {
return meta;
}
public void setMeta(MetaField meta) {
this.meta = meta;
}
public Field getField() {
return field;
}
public void setField(Field field) {
this.field = field;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Class<?> getType() {
return type;
}
public void setType(Class<?> type) {
this.type = type;
}
}
使用并获取注解属性值:
import java.util.List;
public class MetaFieldTest {
public static void main(String[] args) {
Student student = new Student();
List<SortableField > sortableFields = student.init();
if(sortableFields != null && sortableFields.size() > 0) {
for(SortableField sortableField : sortableFields) {
System.out.println("是否可读 : " + sortableField.getMeta().readonly());
System.out.println("是否存储 : " + sortableField.getMeta().store());
System.out.println("名称 : " + sortableField.getMeta().name());
System.out.println("是否可搜索 : " + sortableField.getMeta().searchable());
System.out.println();
}
}
}
}
2、标识哪些接口是公共接口
业务需求,我们使用权限:用户《==》角色《==》资源时会出现白名单这个名词,这时候,我们可以使用配置文件或者一个list存储到内存中,但是这样还是不利于我们维护,我们可以使用注解来实现,可读性也比较强。只需要在需要访问的接口上面标识这个接口是一个公共接口即可
自定义注解:
/**
* 定义公共接口使用的
*/
@Target(METHOD)
@Retention(RUNTIME)
@Documented
public @interface PublicMethod {
boolean value() default false;
}
项目启动并初始化容器后获取通过注解获取对应的接口信息:
@Component
public class MyListenerProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
Method[] methods = ReflectionUtils.getAllDeclaredMethods(bean.getClass());
if (methods != null) {
for (Method method : methods) {
//这是公共接口相关注解的扫描
PublicMethod publicMethod = AnnotationUtils.findAnnotation(method, PublicMethod.class);
RequestMapping requestMapping = AnnotationUtils.findAnnotation(method, RequestMapping.class);
if (publicMethod != null) {
System.out.println(method.getName());
boolean result = publicMethod.value();
if(result){
String [] values = requestMapping.value();
if(values != null && values.length > 0){
AnnotationConstant.requestMappingList.add(values[0]);
}
}
}
}
}
return bean;
}
}
我已经在项目自启动时将相关的url地址放到了一个常量中,使用的时候我只需要遍历这个集合就可以判断哪些url是公共的。
使用:
//公共接口
List<String > requestMappingList = AnnotationConstant.requestMappingList;
if(requestMappingList.contains(requestURI)){
obj = jp.proceed();//用户拥有该方法权限时执行方法里面的内容
}
3、代替enum使得代码更简洁,更易于维护
记得我们项目中大量使用了枚举,当时感觉还是可以满足我们的基本需求的,但是后来发现,枚举写的越来越多,虽然可以解决我们的需求,但是为了减少以后的维护成本,只能寻找其他方式获取相关信息。就拿我在项目中的例子来说明,项目中使用到了大量的反射,因为我们的项目是基于模型开发的,所以有这些关系:模型名称----》表名-----》entity-----》mapper----》service,我想要的结果就是在知道其中一个的值,我可以得到它们中任意一个值,我们也知道,如果我只知道模型名称,想要获取对应实体类中的某个属性的值时,我需要知道实体类的全路径(包名+类名),这样再通过反射就可以调他们对应get方法就可以得到属性的名称和返回类型,返回值相关信息。来例子:
自定义注解:
/**
* 模型拓展注解
*/
@Target({ElementType.TYPE})
@Retention(RUNTIME)
@Documented
public @interface ModelExpansion {
String modelName() default "";//模型名称
String tableName() default "";//数据库表名称
String entityName() default "";//实体类名称
String serviceName() default "";//service名称
String mapperName() default "";//mapper名称
}
使用:
@TableName("student_student")
@Data
@Component
@ModelExpansion(modelName = "student.student" , entityName = "com.yuqiinfo.student.entity.Student" , tableName = "student_student" , serviceName = "com.yuqiinfo.student.service.StudentService",mapperName = "com.yuqiinfo.student.mapper.StudentMapper")
public class Student extends BaseModelDto implements Serializable {
/**
* 姓名
*/
private CommonTypeString name = new CommonTypeString("姓名");
/**
* 性别
*/
private CommonTypeString sex=new CommonTypeString("性别");
/**
* 状态
*/
private CommonTypeString state=new CommonTypeString("状态");
/**
* Active
*/
private CommonTypeBoolean active=new CommonTypeBoolean("active");
/**
* 学校
*/
@TableField("school_id")
private CommonTypeM2O school_id=new CommonTypeM2O("学校","student.school");
/**
* 年龄
*/
public Student(){
}
}
主要是实体类上面的自定义注解部分。
在项目启动时,将注解进行扫描,得到保存到内存中:
@Component
public class MyListenerProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
//模型拓展注解扫描
ModelExpansion modelExpansion = AnnotationUtils.findAnnotation(bean.getClass(), ModelExpansion.class);
if(modelExpansion != null){
AnnotationConstant.modelExpansionList.add(modelExpansion);
}
return bean;
}
}
附加一个使用的工具类:
public class ModelExpansionUtil {
/**
* 获取所有的模型名称
* @param modelExpansionList
* @return
*/
public static List<String > getAllModelName(List<ModelExpansion> modelExpansionList){
List<String > list = new ArrayList<>();
if(modelExpansionList != null && modelExpansionList.size() > 0){
for(ModelExpansion modelExpansion: modelExpansionList){
list.add(modelExpansion.modelName());
}
}
return list;
}
/**
* 通过模型模型名称获取对应的注解对象
*/
public static ModelExpansion getByModelName(String modelName , List<ModelExpansion> modelExpansionList){
if(modelExpansionList != null && modelExpansionList.size() > 0){
for(ModelExpansion modelExpansion: modelExpansionList){
if(modelName.equals(modelExpansion.modelName())){
return modelExpansion;
}
}
}
return null;
}
/**
* 通过表名称获取对用的注解对象
*/
public static ModelExpansion getByTableName(String tableName , List<ModelExpansion> modelExpansionList){
if(modelExpansionList != null && modelExpansionList.size() > 0){
for(ModelExpansion modelExpansion: modelExpansionList){
if(tableName.equals(modelExpansion.tableName())){
return modelExpansion;
}
}
}
return null;
}
/**
* 通过Service称获取各个对象
*/
public static ModelExpansion getByServiceName(String serviceName , List<ModelExpansion> modelExpansionList){
if(modelExpansionList != null && modelExpansionList.size() > 0){
for(ModelExpansion modelExpansion: modelExpansionList){
if(serviceName.equals(modelExpansion.serviceName())){
return modelExpansion;
}
}
}
return null;
}
/**
* 通过实体类名称获取各个对象
*/
public static ModelExpansion getByEntityName(String entityName , List<ModelExpansion> modelExpansionList){
if(modelExpansionList != null && modelExpansionList.size() > 0){
for(ModelExpansion modelExpansion: modelExpansionList){
if(entityName.equals(modelExpansion.entityName())){
return modelExpansion;
}
}
}
return null;
}
}
怎么获取:
public void load_views( List<List<Object>> views , Map<String , Object > options) {
Integer uid=(Integer) context.get("uid");
String serviceName = this.getClass().toString();
serviceName = serviceName.substring(serviceName.indexOf(" ") + 1);
String modelName = ModelExpansionUtil.getByServiceName(serviceName ,
AnnotationConstant.modelExpansionList).modelName();
}
四、总结
因为部分代码涉及到公司项目源码问题,所以不易太多的暴漏太多源码,当然,如果有感兴趣的朋友可以添加评论共同讨论。