Java中注解到底是如何工作的

注解@annotation?

1.什么是注解?

用一个词就可以描述注解,那就是元数据,即一种描述数据的数据。所以,可以说注解就是源代码的元数据。注解仅仅是元数据,和业务逻辑无关,所以当你查看注解类时,发现里面没有任何逻辑处理;

<string name="app_name">AnnotationDemo</string>

这里的"app_name"就是描述数据"AnnotationDemo"的数据,这是在配置文件中写的,注解是在源码中写的,如下所示:

@Override
public String toString() {
    return "This is String Representation of current object.";
}

上面的代码中,我重写了toString()方法并使用了@Override注解。但是,即使我不使用@Override注解标记代码,程序也能够正常执行。

@Override告诉编译器这个方法是一个重写方法(描述方法的元数据),如果父类中不存在该方法,编译器便会报错,提示该方法没有重写父类中的方法。

javadoc中的@author、@version、@param、@return、@deprecated、@hide、@throws、@exception、@see是标记,并不是注解;

2. 注解如何使用

使用注解很简单,根据注解类的@Target所修饰的对象范围,可以在类、方法、变量、参数、包中使用“@+注解类名+[属性值]”的方式使用注解。比如:

@UiThread
private void setTextInOtherThread(@StringRes int resId){
    TextView threadTxtView = (TextView)MainActivity.this.findViewById(R.id.threadTxtViewId);
    threadTxtView.setText(resId);
}
3. 注解的作用
  1. 格式检查:告诉编译器信息,比如被@Override标记的方法如果不是父类的某个方法,IDE会报错;

  2. 减少配置:运行时动态处理,得到注解信息,实现代替配置文件的功能;

  3. 减少重复工作:比如第三方框架xUtils,通过注解@ViewInject减少对findViewById的调用,类似的还有(JUnit、ActiveAndroid等);

4. 为什么要引入注解
  1. XML配置复杂
  2. 与代码紧耦合

注释的优缺点:

​ 1)保存在 class 文件中,降低维护成本。

​ 2)无需工具支持,无需解析。

​ 3)编译期即可验证正确性,查错变得容易。

​ 4)提升开发效率。

5)若要对配置项进行修改,不得不修改 Java 文件,重新编译打包应用。

6)配置项编码在 Java 文件中,可扩展性差。

xml文件的优缺点:

​ 1)xml作为可扩展标记语言最大的优势在于开发者能够为软件量身定制适用的标记,使代码更加通俗易懂。

​ 2)利用xml配置能使软件更具扩展性。例如Spring将class间的依赖配置在xml中,最大限度地提升应用的可扩展性。

​ 3)具有成熟的验证机制确保程序正确性。利用Schema或DTD可以对xml的正确性进行验证,避免了非法的配置导致应用程序出错。

​ 4) 修改配置而无需变动现有程序。

5)需要解析工具或类库的支持。

6)解析xml势必会影响应用程序性能,占用系统资源。

7)配置文件过多导致管理变得困难。

8)编译期无法对其配置项的正确性进行验证,或要查错只能在运行期。

9)IDE无法验证配置项的正确性无能为力。

10)查错变得困难。往往配置的一个手误导致莫名其妙的错误。

11)开发人员不得不同时维护代码和配置文件,开发效率变得低下。

12)配置项与代码间存在潜规则。改变了任何一方都有可能影响另外一方。

但也不是说引入注解就是好的,xml管理依赖项的注入是至关可见的,所以xml和annotation混合是比较好的方案

https://stackoverflow.com/questions/182393/xml-configuration-versus-annotation-based-configuration

https://cloud.tencent.com/developer/article/1448189

假如你想为应用设置很多的常量或参数,这种情况下,XML是一个很好的选择,因为它不会同特定的代码相连。如果你想把某个方法声明为服务,那么使用Annotation会更好一些,因为这种情况下需要注解和方法紧密耦合起来,开发人员也必须认识到这点。

另一个很重要的因素是Annotation定义了一种标准的描述元数据的方式。在这之前,开发人员通常使用他们自己的方式定义元数据。例如,使用标记interfaces,注释,transient关键字等等。每个程序员按照自己的方式定义元数据,而不像Annotation这种标准的方式。

5. 常见注解

@Override

属于标记注解,不需要设置属性值;只能添加在方法的前面,用于标记该方法是复写的父类中的某个方法,如果在父类没有的方法前面加上@Override注解,编译器会报错:

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}

@Deprecated

属于标记注解,不需要设置属性值;可以对构造方法、变量、方法、包、参数标记,告知用户和编译器被标记的内容已不建议被使用,如果被使用,编译器会报警告,但不会报错,程序也能正常运行:

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.CONSTRUCTOR, ElementType.FIELD, ElementType.LOCAL_VARIABLE, ElementType.METHOD, ElementType.PACKAGE, ElementType.PARAMETER, ElementType.TYPE})
public @interface Deprecated {
}

@SuppressWarnings

可以对构造方法、变量、方法、包、参数标记,用于告知编译器忽略指定的警告,不用再编译完成后出现警告信息:

@Target({ElementType.TYPE, ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.CONSTRUCTOR, ElementType.LOCAL_VARIABLE})
@Retention(RetentionPolicy.SOURCE)
public @interface SuppressWarnings {
    String[] value();
}

@TargetApi

可以对接口、方法、构造方法标记,如果在应用中指定minSdkVersion为8,但有地方需要使用API 11中的方法,为了避免编译器报错,在调用API11中方法的接口、方法或者构造方法前面加上@Target(11),这样该方法就可以使用<=11的API接口了。虽然这样能够避免编译器报错,但在运行时需要注意,不能在API低于11的设备中使用该方法,否则会crash(可以获取程序运行设备的API版本来判断是否调用该方法):

@Target({TYPE, METHOD, CONSTRUCTOR})
@Retention(RetentionPolicy.CLASS)
public @interface TargetApi {
    /**
     * This sets the target api level for the type..
     */
    int value();
}

@SuppressLint

和@Target的功能差不多,但使用范围更广,主要用于避免在lint检查时报错:

@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.CLASS)
public @interface SuppressLint {
    /**
     * The set of warnings (identified by the lint issue id) that should be
     * ignored by lint. It is not an error to specify an unrecognized name.
     */
    String[] value();
}

Android也有一些注解@UiThread、@MainThread、@WorkerThread、@BinderThread…

6. 注解是如何实现的

注解只是个标记,里面的属性就是键值对,通过反射可以获取值,然后根据值进行各种处理

Java的annotation没有行为,只能有数据,实际上就是一组键值对而已。通过解析(parse)Class文件就能把一个annotation需要的键值对都找出来。

  • 有一个接口
  • 有一组键值对,它里面的数组能支持前面那个接口的功能

注解是通过sun.reflect.annotation.AnnotationParser.annotationForMap()方法通过JDK的动态代理完成实例化的。

public class AnnotationParser {

    public static Annotation annotationForMap(Class<? extends Annotation> type, Map<String, Object> memberValues) {
        return (Annotation) Proxy.newProxyInstance(type.getClassLoader(), new Class[]{type}, new AnnotationInvocationHandler(type, memberValues));
    }
}

这个AnnotationInvocationHandler是一个实现了InvocationHandler的类,所以很明显这个其实就是动态代理了。
嗯,注解本质是个接口,然后运行期间获取到的到底是什么,就是个接口的代理对象。然后说 @xxx 就能自动运行某个方法之类的具体实现,其实这类东西都依赖于一个容器或者工厂的东西

class AnnotationInvocationHandler implements InvocationHandler, Serializable {

    private static final long serialVersionUID = 6182022883658399397L;

    // 注解class
    private final Class<? extends Annotation> type;
    // 成员方法返回值集合
    private final Map<String, Object> memberValues;

    AnnotationInvocationHandler(Class<? extends Annotation> type, Map<String, Object> memberValues) {
        Class[] interfaces = type.getInterfaces();
        // 注解校验
        if (type.isAnnotation() && interfaces.length == 1 && interfaces[0] == Annotation.class) {
            this.type = type;
            this.memberValues = memberValues;
        } else {
            throw new AnnotationFormatError("Attempt to create proxy for a non-annotation type.");
        }
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 方法名
        String member = method.getName();
        // 方法参数
        Class<?>[] paramTypes = method.getParameterTypes();
        // equals()方法
        if (member.equals("equals") && paramTypes.length == 1 && paramTypes[0] == Object.class) {
            return equalsImpl(args[0]);
        }
        // 除equals()外, 注解的其它方法都不带参数
        if (paramTypes.length != 0) {
            throw new AssertionError("Too many parameters for an annotation method");
        }
        // toString()方法
        if (member.equals("toString")) {
            return toStringImpl();
        }
        // hashCode()方法
        if (member.equals("hashCode")) {
            return hashCodeImpl();
        }
        // annotationType()方法
        if (member.equals("annotationType")) {
            return type;
        }
        // 以下是注解自定义的方法
        // 注解方法的返回值
        Object result = memberValues.get(member);
        // 注解方法的返回值为null, 抛出异常
        if (result == null) {
            throw new IncompleteAnnotationException(type, member);
        }
        // 结果异常
        if (result instanceof ExceptionProxy) {
            throw ((ExceptionProxy) result).generateException();
        }
        // 克隆并返回数组结果
        if (result.getClass().isArray() && Array.getLength(result) != 0) {
            result = cloneArray(result);
        }
        // 返回结果
        return result;
    }

}
7. Java8新增类型注解

在 Java8 之前的版本中,只能允许在声明式前使用注解。而在 Java8 版本中,注解可以被用在任何使用 Type 的地方,例如:初始化对象时 (new),对象类型转化时,使用 implements 表达式时,或者使用 throws 表达式时。

//初始化对象时
String myString = new @NotNull String();

//对象类型转化时
myString = (@NonNull String) str;

//使用 implements 表达式时
class MyList<T> implements @ReadOnly List<@ReadOnly T>{
    ...
}
 //使用 throws 表达式时
public void validateValues() throws @Critical ValidationFailedException{
    ...
}

定义一个类型的方法与普通的注解类似,只需要指定TargetElementType.TYPE_PARAMETER或者ElementType.TYPE_USE,或者同时指定这两个Target

@Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE})
public  @interface MyAnnotation {
    ...
}

Java8 通过引入类型,使得开发者可以在更多的地方使用注解,从而能够更全面地对代码进行分析以及进行更强的类型检查

8. 四种元注解

注解类会被@interface标记;

注解类的顶部会被@Documented@Retention@Target@Inherited这四个注解标记(@Documented@Inherited可选,@Retention@Target必须要有)

@Target:

**作用:**用于描述注解的使用范围,即被描述的注解可以用在什么地方;

取值:

​ 1)CONSTRUCTOR:构造器;

​ 2)FIELD:实例;

​ 3)LOCAL_VARIABLE:局部变量;

​ 4)METHOD:方法;

​ 5)PACKAGE:包;

​ 6)PARAMETER:参数;

​ 7)TYPE:类、接口(包括注解类型) 或enum声明。

/**
 * 实体注解接口
 */
@Target(value = {ElementType.TYPE})
@Retention(value = RetentionPolicy.RUNTIME)
public @interface Entity {
    /***
     * 实体默认firstLevelCache属性为false
     * @return boolean
     */
    boolean firstLevelCache() default false;
    /***
     * 实体默认secondLevelCache属性为false
     * @return boolean
     */
    boolean secondLevelCache() default true;
    /***
     * 表名默认为空
     * @return String
     */
    String tableName() default "";
    /***
     * 默认以""分割注解
     */
    String split() default "";
}

@Retention:

**作用:**表示需要在什么级别保存该注解信息,用于描述注解的生命周期,即被描述的注解在什么范围内有效;

取值:

​ 1)SOURCE:在源文件中有效,即源文件保留;

​ 2)CLASS:在class文件中有效,即class保留;

​ 3)RUNTIME:在运行时有效,即运行时保留;

/***
 * 字段注解接口
 */
@Target(value = {ElementType.FIELD})//注解可以被添加在实例上
@Retention(value = RetentionPolicy.RUNTIME)//注解保存在JVM运行时刻,能够在运行时刻通过反射API来获取到注解的信息
public @interface Column {
    String name();//注解的name属性
}

@Documented:

**作用:**用于描述其它类型的annotation应该被作为被标注的程序成员的公共API,因此可以被例如javadoc此类的工具文档化。

**取值:**它属于标记注解,没有成员;

@Documented
@Retention(CLASS)
@Target({METHOD,CONSTRUCTOR,TYPE})
public @interface UiThread {
}

@Inherited:

**作用:**用于描述某个被标注的类型是可被继承的。如果一个使用了@Inherited修饰的annotation类型被用于一个class,则这个annotation将被用于该class的子类。

**取值:**它属于标记注解,没有成员;

示例:

@Inherited  
public @interface Greeting {  
    public enum FontColor{ BULE,RED,GREEN};  
    String name();  
    FontColor fontColor() default FontColor.GREEN;  
} 
9. 如何编写自定义注解
元注解
public @interface 注解名{
    定义体;
}

**ToDo.java:**注解类

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@interface Todo {
    public enum Priority {LOW, MEDIUM, HIGH}
    public enum Status {STARTED, NOT_STARTED}    
    String author() default "Yash";
    Priority priority() default Priority.LOW;
    Status status() default Status.NOT_STARTED;
}

**BusinessLogic:**使用注解的类

public class BusinessLogic {
    public BusinessLogic() {
        super();
    }
    
    public void compltedMethod() {
        System.out.println("This method is complete");
    }    
    
    @Todo(priority = Todo.Priority.HIGH)
    public void notYetStartedMethod() {
        // No Code Written yet
    }
    
    @Todo(priority = Todo.Priority.MEDIUM, author = "Uday", status = Todo.Status.STARTED)
    public void incompleteMethod1() {
        //Some business logic is written
        //But its not complete yet
    }

    @Todo(priority = Todo.Priority.LOW, status = Todo.Status.STARTED )
    public void incompleteMethod2() {
        //Some business logic is written
        //But its not complete yet
    }
}

**TodoReport.java:**解析注解信息

public class TodoReport {
    public TodoReport() {
        super();
    }

    public static void main(String[] args) {
        getTodoReportForBusinessLogic();
    }

    /**
     * 解析使用注解的类,获取通过注解设置的属性
     */
    private static void getTodoReportForBusinessLogic() {
        Class businessLogicClass = BusinessLogic.class;
        for(Method method : businessLogicClass.getMethods()) {
            Todo todoAnnotation = (Todo)method.getAnnotation(Todo.class);
            if(todoAnnotation != null) {
                System.out.println(" Method Name : " + method.getName());
                System.out.println(" Author : " + todoAnnotation.author());
                System.out.println(" Priority : " + todoAnnotation.priority());
                System.out.println(" Status : " + todoAnnotation.status());
                System.out.println(" --------------------------- ");
            }
        }
    }
}
10. 注解的应用
10.1 SpringBoot组合注解
@Api(tags = "自定义组合注解", description = "组合注解优化代码")
@StandardResult
@RequestMapping("/Ccww")
@Controller
@ResponseBody
public class CombinationController{
}

@SpringBootApplication

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = {
		@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
		@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {

	@AliasFor(annotation = EnableAutoConfiguration.class)
	Class<?>[] exclude() default {};
	

	@AliasFor(annotation = EnableAutoConfiguration.class)
	String[] excludeName() default {};
	

	@AliasFor(annotation = ComponentScan.class, attribute = "basePackages")
	String[] scanBasePackages() default {};
	
	@AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses")
	Class<?>[] scanBasePackageClasses() default {};

}

声明@Inherited注解,声明了它的类的子类是可以继承它的

声明@SpringBootConfiguration注解,可为类标注为配置类

声明@EnableAutoConfiguration注解,声明了它的类会默认开启自动配置

声明@ComponentScan注解,同时是@ComponentScan注解的容器。我们发现scanBasePackagesscanBasePackageClasses两个注解属性上面同样声明了@AliasFor注解,分别指向了@ComponentScan注解的basePackages注解属性和basePackageClasses属性。

声明了exclude()为排除特定的自动配置类以及excludeName()排除特定的自动配置类名称

最后,@AliasFor的作用是什么呢?

  • 用到注解 属性上,表示两个属性互相为别名,互相为别名的属性值必须相同,若设置成不同,则会报错
  • 注解是可以继承的,但是注解是不能继承父注解的属性的,也就是说,我在类扫描的时候,拿到的注解的属性值,依然是父注解的属性值,而不是你定义的注解的属性值,所以此时可以在子注解对应的属性上加上@AliasFor
package com.timwang.annotation;

import io.swagger.annotations.Api;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import java.lang.annotation.*;

/**
 * @author wangjun
 * @date 2019-12-13
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Controller
@ResponseBody
@RequestMapping
@Api
public @interface StandardResultRestControllerApi {
    /**
     * 定义映射路径URL
     */
    @AliasFor(annotation = RequestMapping.class, value = "path")
    String[] value() default {};

    /**
     *定义spring类名称
     */
    @AliasFor(annotation = Controller.class, value = "value")
    String name() default "";
    /**
     *定义Api类tags属性
     */
    @AliasFor(annotation = Api.class, attribute = "tags")
    String[] tags() default "";

    /**
     *定义Api类description属性
     */
    @AliasFor(annotation = Api.class, attribute = "description")
    String description() default "";
}
10.2 监控方法执行耗时

假如,我们需要监控某些方法的执行,最原始的办法就是在方法执行的开头和结尾分别记录时间,最后计算前后的时间差即可,但是这些代码与核心业务无关,且大量重复、分散在各处,维护起来也困难。这时我们可以使用Spring AOP来统计方法的执行耗时,同时我们也可以使用注解的方式来实现,更自由灵活。

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 自定义'统计方法耗时'并打印日志的注解.
 * @author tim.wang 
 * @date 2019-12-13
 */
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
@Documented
public @interface CostTime {

    /**
     * 执行超过某毫秒数时数则打印'warn'级别的日志,默认 0ms,即默认都打印.
     * @return 毫秒数
     */
    long value() default 0;

}

然后,书写监控所标注有@CostTime注解的方法代理类:

import java.lang.reflect.Method;

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * 被标注为'@CostTime'注解的方法执行耗时的代理方法.
 * <p>实现了cglib中的`MethodInterceptor`的方法拦截接口.</p>
 */
public class CostTimeProxy implements MethodInterceptor {

    private static final Logger log = LoggerFactory.getLogger(CostTimeProxy.class);

    private Enhancer enhancer = new Enhancer();

    /**
     * 获取代理类.
     * @param cls 代理类的class
     * @return 代理类实例
     */
    public Object getProxy(Class cls) {
        enhancer.setSuperclass(cls);
        enhancer.setCallback(this);
        return enhancer.create();
    }

    /**
     * 拦截方法,判断是否有'@CostTime'的注解,如果有则拦截执行.
     *
     * @param o 对象
     * @param method 方法
     * @param args 参数
     * @param methodProxy 代理方法
     * @return 对象
     * @throws Throwable 问题
     */
    @Override
    public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        // 判断该方法上是否有 CostTime 注解
        if (!method.isAnnotationPresent(CostTime.class)) {
            return methodProxy.invokeSuper(o, args);
        }
        // 获取注解信息
        CostTime costTime = method.getAnnotation(CostTime.class);
        long limitTime = costTime.value();

        // 记录方法执行前后的耗时时间,并做差,判断是否需要打印方法执行耗时
        long startTime = System.currentTimeMillis();
        Object result = methodProxy.invokeSuper(o, args);
        long diffTime = System.currentTimeMillis() - startTime;
        if (limitTime <= 0 || (diffTime >= limitTime)) {
            String methodName = method.getName();
            // 打印耗时的信息
            log.warn("【CostTime监控】通过注解监控方法'{}'的执行耗时为:{}", methodName, diffTime);
        }
        return result;
    }

}

接着,可以写一些业务类及方法,这里就以A类为例:

package com.timwang.annotation;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * @author wangjun
 * @date 2019-12-13
 */
public class CostTimeClass {
    private static final Logger log = LoggerFactory.getLogger(CostTimeClass.class);

    /**
     * 始终打印方法执行耗时的方法.
     */
    @CostTime
    public void doSomeThing() {
        log.info("执行A类中doSomeThing()方法!");
    }

    /**
     * 当方法执行耗时大于等于'50ms'时打印出方法执行耗时.
     */
    @CostTime(50)
    public void doSomeThing2() {
        log.info("执行A类中doSomeThing2()方法!");
    }

}

最后,是用来测试CostTimeTest类某些业务方法执行耗时的测试类:

package com.timwang.annotation;

/**
 * @author wangjun
 * @date 2019-12-13
 */
public class CostTimeTest {
    /** A类的全局实例. */
    private static CostTimeClass a;

    static {
        CostTimeProxy aproxy = new CostTimeProxy();
        a = (CostTimeClass) aproxy.getProxy(CostTimeClass.class);
    }

    /**
     * main 方法.
     *
     * @param args 数组参数
     */
    public static void main(String[] args) {
        a.doSomeThing();
        a.doSomeThing2();
    }
}

10.3 APT工具类
/**是否存在对应 Annotation 对象*/
 public boolean isAnnotationPresent(Class<? extends Annotation> annotationClass) {
       return GenericDeclaration.super.isAnnotationPresent(annotationClass);
   }
 
/**获取 Annotation 对象*/
   public <A extends Annotation> A getAnnotation(Class<A> annotationClass) {
       Objects.requireNonNull(annotationClass);

       return (A) annotationData().annotations.get(annotationClass);
   }
/**获取所有 Annotation 对象数组*/   
public Annotation[] getAnnotations() {
       return AnnotationParser.toArray(annotationData().annotations);
   }
public class test {
   public static void main(String[] args) throws NoSuchMethodException {

        /**
         * 获取类注解属性
         */
        Class<Father> fatherClass = Father.class;
        boolean annotationPresent = fatherClass.isAnnotationPresent(MyTestAnnotation.class);
        if(annotationPresent){
            MyTestAnnotation annotation = fatherClass.getAnnotation(MyTestAnnotation.class);
            System.out.println(annotation.name());
            System.out.println(annotation.age());
        }

        /**
         * 获取方法注解属性
         */
        try {
            Field age = fatherClass.getDeclaredField("age");
            boolean annotationPresent1 = age.isAnnotationPresent(Age.class);
            if(annotationPresent1){
                Age annotation = age.getAnnotation(Age.class);
                System.out.println(annotation.value());
            }

            Method play = PlayGame.class.getDeclaredMethod("play");
            if (play!=null){
                People annotation2 = play.getAnnotation(People.class);
                Game[] value = annotation2.value();
                for (Game game : value) {
                    System.out.println(game.value());
                }
            }
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        }
    }
}

处理提取和处理 Annotation 的代码统称为 APT(Annotation Processing Tool)。processAnnotationMoney方法就可以理解为APT工具类。

/**定义限额注解*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface BankTransferMoney {
    double maxMoney() default 10000;
}
/**转账处理业务类*/
public class BankService {
    /**
     * @param money 转账金额
     */
    @BankTransferMoney(maxMoney = 15000)
    public static void TransferMoney(double money){
        System.out.println(processAnnotationMoney(money));

    }
    private static String processAnnotationMoney(double money) {
        try {
            Method transferMoney = BankService.class.getDeclaredMethod("TransferMoney",double.class);
            boolean annotationPresent = transferMoney.isAnnotationPresent(BankTransferMoney.class);
            if(annotationPresent){
                BankTransferMoney annotation = transferMoney.getAnnotation(BankTransferMoney.class);
                double l = annotation.maxMoney();
                if(money>l){
                   return "转账金额大于限额,转账失败";
                }else {
                    return"转账金额为:"+money+",转账成功";
                }
            }
        } catch ( NoSuchMethodException e) {
            e.printStackTrace();
        }
        return "转账处理失败";
    }
    public static void main(String[] args){
        TransferMoney(10000);
    }
}

10.4 框架层面的统一处理

比如我们有的接口需要认证才能调用,有的不需要,简单的做法就是用配置的方式,将需要认证的接口配置好,然后进行拦截过滤,缺点是需要经常维护配置信息,用注解可以避免这个情况。 可以自定义一个注解,只要加了这个注解我们就对这个接口进行认证拦截操作,接下里详细的讲解下这个功能实现。

定义开启认证的注解,作用在方法上,运行时可获取注解信息

/**
 * 开启API权限认证
 * @author yinjihuan
 *
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface EnableAuth {
}

在需要认证的接口上增加注解

@EnableAuth
@GetMapping("/userCollectCityInfo")
public Response getUserCollectCityInfos(HttpServletRequest request) {
    //..
}

在拦截器中进行拦截,拦截需要知道当前请求的接口是不是需要拦截的,我们可以在启动时将所有增加了@EnableAuth的接口信息保存起来,这样在拦截器中就知道哪个接口是需要认证。

初始化需要认证的接口信息代码如下:

package com.timwang.annotation;

import com.google.common.collect.Lists;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

import java.lang.reflect.Method;
import java.util.List;
import java.util.Map;

/**
 * @author wangjun
 * @date 2019-12-13
 */
@Component
@Configuration
public class ApiAuthDataInit implements ApplicationContextAware {
    public static List<String> checkApis = Lists.newArrayList();
    @Override
    public void setApplicationContext(ApplicationContext ctx) throws BeansException {
        Map<String, Object> beanMap = ctx.getBeansWithAnnotation(Controller.class);
        if (beanMap != null) {
            for (Object bean : beanMap.values()) {
                Class<?> clz = bean.getClass();
                Method[] methods = clz.getMethods();
                for (Method method : methods) {
                    if (method.isAnnotationPresent(EnableAuth.class)) {
                        String uri = getApiUri(clz, method);
                        checkApis.add(uri);
                    }
                }
            }
        }
    }
    private String getApiUri(Class<?> clz, Method method) {
        StringBuilder uri = new StringBuilder();
        uri.append(clz.getAnnotation(RequestMapping.class).value()[0]);
        if (method.isAnnotationPresent(RequestMapping.class)) {
            uri.append(method.getAnnotation(RequestMapping.class).value()[0]);
        }
        return uri.toString();
    }
}

实现ApplicationContextAware接口,然后通过getBeansWithAnnotation获取所有接口的bean信息,通过RestController注解来获取,也就是说只要class上增加了RestController注解,这边就都能获取到。

然后通过反射获取bean中所有的方法,如果有增加EnableAuth的话就获取接口的uri存储到map中,这样过滤器中就可以根据map中的值来判断是不是需要进行权限认证了。

http://www.importnew.com/15246.html【】

http://www.yq1012.com/myweb/2230.html【注解是什么,为什么要使用注解】

https://www.idlebrains.org/tutorials/java-tutorials/how-annotations-work-java/【HOW ANNOTATIONS WORK IN JAVA ?】

https://cloud.tencent.com/developer/article/1448189【Java中的注解到底是如何工作的?】

https://juejin.im/post/5a619f886fb9a01c9f5b7e4f【Java中的注解-自定义注解】

https://juejin.im/entry/585fe4e61b69e600562147fa【从头到尾带你玩转注解】

https://blog.csdn.net/lengxingxing_/article/details/65441337【java annotation(注解) 的优点缺点】

https://www.jianshu.com/p/5cac4cb9be54【深入浅出Java注解】

https://blinkfox.github.io/2018/11/08/hou-duan/java/java-zhu-jie-de-li-jie-he-ying-yong/【Java注解的理解和应用】

https://www.cnblogs.com/deng-cc/p/7462577.html【注解的基本认识和元注解】

https://www.zhihu.com/question/47449512/answer/106034220【怎样理解 Java 注解和运用注解编程?】

https://www.race604.com/annotation-processing/【Java注解处理器】

https://zhuanlan.zhihu.com/p/59337421【如何解决代码中if…else 过多的问题】

https://www.maoqitian.com/2019/03/25/java-annotation/【Java注解完全解析回顾】

https://github.com/maoqitian/JavaDemo/tree/master/annotationdemo【Java注解完全解析回顾-demo】

https://zhuanlan.zhihu.com/p/60966151【注解(下)】

https://cloud.tencent.com/developer/article/1101069【注解是什么?

https://www.jianshu.com/p/869ed7037833【Spring中的@AliasFor标签】

https://zhuanlan.zhihu.com/p/21410338【深入浅出Java注解】

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值