1. ホーム
  2. java

徹底的なJAVAアノテーション(Annotation):カスタムアノテーション

2022-02-22 13:41:04

I. 基本中の基本:メタアノテーション

アノテーションについて深く学ぶには、自分でアノテーションを定義し、それを使えるようになることが必要です。独自のアノテーションを定義する前に、Javaが提供するメタアノテーションと、それを定義するための関連構文について理解する必要があります。

メタアノテーション

  メタアノテーションの役割は、他のアノテーションの注釈を担当することである。Java 5.0では、他のアノテーションタイプの説明を提供するために使用される4つの標準メタアノテーションタイプが定義されています。
    1.@Target。
    2.@Retention
    3.@Documented,
    4.継承
  これらの型とサポートするクラスは、java.lang.annotation パッケージに含まれています。以下では、それぞれのメタアノテーションの役割と、対応するサブパラメータの使用方法について説明します。

  ターゲット

   @Targetは、Annotationが修正するオブジェクトの範囲を記述します。Annotationはパッケージ、型(クラス、インターフェース、列挙、Annotation型)、型メンバー(メソッド、コンストラクター、メンバー変数、列挙値)、メソッドパラメーター、ローカル変数(ループ変数、キャッチパラメーターなど)に使用することができます。Annotation型の宣言でtargetを使用することで、何を変更するかが明確になる。

  役割 アノテーションのスコープを記述する(記述されているアノテーションが何に使えるか)。

  Fetch値(ElementType)は。

    1. CONSTRUCTOR: コンストラクタを記述するために使用されます。
    2.FIELD:ドメインを記述するために使用します。
    3.LOCAL_VARIABLE: ローカル変数を記述するために使用される
    4.METHOD:メソッドを記述するために使用される
    5.PACKAGE:パッケージを記述するために使用される
    6.PARAMETER:パラメータを記述するために使用する
    7.TYPE: クラス、インターフェース(注釈付きタイプを含む)、または列挙型宣言を記述するために使用されます。

  使用例  

@Target(ElementType.TYPE)
public @interface Table {
    /**
     * Data table name annotation, the default value is the class name
     * @return
     */
    public String tableName() default "className";
}

@Target(ElementType.FIELD)
public @interface NoDBColumn {

}

<イグ

  アノテーションTableはクラス、インターフェース(アノテーションされた型を含む)、enum宣言のアノテーションに使用でき、アノテーションNoDBColumnはクラスのメンバー変数へのアノテーションにのみ使用可能です。

  @保持する。

  クラスファイルにコンパイルされたAnnotationは、仮想マシンによって無視されるかもしれませんが、他のものはクラスがロードされたときに読み込まれます(Annotationが使用中のクラスから分離されているので、これはクラスの実行に影響を与えないことに注意してください)。このメタアノテーションは、アノテーションのライフサイクルを制限するために使用されます。

  内容:アノテーション情報をどのレベルで保存する必要があるかを示し、アノテーションのライフサイクル(記述されているアノテーションが有効である範囲)を記述する。

  値(RetentionPoicy)は以下の通り。

    1. SOURCE:ソースファイル内で有効(=ソースファイル保持)
    2.CLASS:クラスファイル内で有効(=クラスが保存されていること)
    3.RUNTIME:ランタイム時に有効(=ランタイムリザーブド)

  Retentionメタアノテーション型は、java.lang.annotation.RetentionPolicyの列挙型値から値を取る一意の値をメンバーとして持っています。具体的な例としては、以下のようなものがあります。

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Column {
    public String name() default "fieldName";
    public String setFuncName() default "setField";
    public String getFuncName() default "getField"; 
    public boolean defaultDBValue() default false;
}

<イグ

   ColumnアノテーションのRetentionPolicyプロパティは値RUTIMEを持っているので、アノテーションプロセッサはリフレクションを通じてアノテーションのプロパティ値を取得し、いくつかの実行時ロジックを行うことができる。

  @Documented:

  Documentedは、アノテーションされたプログラムのメンバーのためのパブリックAPIとして使用されるべきで、したがってjavadocなどのツールでドキュメント化することができる他のタイプのアノテーションを記述するために使用されます。

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Column {
    public String name() default "fieldName";
    public String setFuncName() default "setField";
    public String getFuncName() default "getField"; 
    public boolean defaultDBValue() default false;
}

<イグ

  @継承される。

  Inheritedメタアノテーションは、@Inheritedが特定のアノテーションタイプが継承されることを示すマークアップアノテーションです。Inheritedで修飾されたアノテーションタイプがクラスに使用されると、そのクラスのサブクラスにもそのアノテーションが使用されることになります。

  注:@Inheritedアノテーションタイプは、アノテーションクラスのサブクラスから継承されます。クラスは、それが実装しているインターフェースからアノテーションを継承せず、メソッドは、それがオーバーライドするメソッドからアノテーションを継承しない。

  Reflection APIは、@InheritedアノテーションタイプでアノテーションされたアノテーションのRetentionがRetentionPolicyである場合に、この継承を強化します。java.lang.reflectを使って@Inheritedアノテーションタイプのアノテーションを照会すると、 リフレクションコードチェックは仕事をします: 指定されたアノテーションタイプが見つかるか、 クラス継承構造のトップレベルに到達するまで、 クラスとその親クラスをチェックします。

コード例です。

/**
 * 
 * @author peida
 *
 */
@Inherited
public @interface Greeting {
    public enum FontColor{ BULE,RED,GREEN};
    String name();
    FontColor fontColor() default FontColor.GREEN;
}

II. 基本的なこと カスタムアノテーション

@interface カスタムアノテーションを使用すると、java.lang.annotation.Annotation インターフェイスが自動的に継承され、残りの詳細についてはコンパイラが自動的に処理します。アノテーションを定義する場合、他のアノテーションやインターフェイスを継承することはできません。各メソッドが実際に設定パラメータを宣言するアノテーションを宣言する場合は、@interfaceを使用します。メソッド名がパラメータ名、戻り値の型がパラメータの型になります(戻り値の型はbasic, Class, String, enumのみ)。パラメータのデフォルト値は、defaultを介して宣言することができます。

  アノテーションの形式を定義します。
  public @interface annotation name {definition body}.

  アノテーションパラメータでサポートされているデータ型。

    1. すべての基本データ型(int, float, boolean, byte, double, char, long, short)
    2.文字列型
    3.クラス型
    4.enum型
    5. アノテーションの種類
    6. 上記すべての型の配列

  Annotation型の内部にパラメータを設定する方法。 
  まず、アクセス修飾子はpublicかdefaultしか使えません。例えば、String value();では、このメソッドはデフォルトの型であるdefaulに設定されています。   
  次に、パラメータメンバには、byte, short, char, int, long, float, double, booleanとString, Enum, Class, annotationsなどの基本データ型と、これらの型の配列しか使えません。例えば、String value();は、パラメータメンバがStringである場合。  
  第三に、パラメータが1つしかない場合は、パラメータ名を"value"とし、その後に括弧を付けるとよい。例 次のFruitNameアノテーションの例では、パラメータ・メンバが1つだけです。

  簡単なカスタムアノテーションとアノテーションの使用例です。

package annotation;

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

/**
 * fruit name annotation
 * @author peida
 *
 */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface FruitName {
    String value() default "";
}
package annotation;

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

/**
 * fruit color annotation
 * @author peida
 *
 */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface FruitColor {
    /**
     * Color enumeration
     * @author peida
     *
     */
    public enum Color{ BULE,RED,GREEN};
    
    /**
     * color property
     * @return
     */
    Color fruitColor() default Color.GREEN;

}
package annotation;

import annotation.FruitColor.Color;

public class Apple {
    
    @FruitName("Apple")
    private String appleName;
    
    @FruitColor(fruitColor=Color.)
    private String appleColor;
    
    
    
    
    public void setAppleColor(String appleColor) {
        this.appleColor = appleColor;
    }
    public String getAppleColor() {
        return appleColor;
    }
    
    
    public void setAppleName(String appleName) {
        this.appleName = appleName;
    }
    public String getAppleName() {
        return appleName;
    }
    
    public void displayName(){
        System.out.println("The name of the fruit is: apple");
    }
}

<イグ

 1 package testAnnotation;
 2 
 3 import java.lang.annotation;
 4 import java.lang.annotation;
 RetentionPolicy; 5 import java.lang.annotation;
 6 
 7 @Documented
 8 @Retention(RetentionPolicy.RUNTIME)
 9 public @interface Person{
10 String name();
11 int age();
12 }
 package testAnnotation;
 2 
 3 @Person(name="xingoo",age=25)
 4 public class test3 {
 5 public static void print(Class c){
 6 System.out.println(c.getName());
 7         
 8 // The getAnnotation method of java.lang.Class returns the annotation if it has one. Otherwise return null
 9 Person person = (Person)c.getAnnotation(Person.class);
10         
11 if(person ! = null){
12 System.out.println("name:"+person.name()+" age:"+person.age());
13 }else{
14 System.out.println("person unknown!");
15 }
16 }
17 public static void main(String[] args){
18 test3.print(test3.class);
19 }
20 }

<イグ

testAnnotation.test3
name:xingoo age:25

<イグ


アノテーションされた要素のデフォルト値。

  アノテーション要素は、アノテーションを定義するデフォルト値で指定されるか、アノテーションが使用されるときに明確な値を持つ必要があり、基本型以外のアノテーション要素の値はNULLであってはならない。したがって、デフォルト値として空の文字列または0を使用することが一般的である。この制約により、各アノテーション宣言にはすべての要素が存在し、対応する値を持つため、プロセッサが要素の有無を表現することは困難である。この制約を回避するために、空文字列や負数などの特殊な値を定義して、一度に要素の不在を示す必要があり、これがアノテーションを定義する際の慣例的な用法となっている。

III. カスタムアノテーションの例

     以上がアノテーションの基本ですが、ここではカスタムアノテーションの使用方法について少し説明します。一般的に、アノテーションは反射型パーサーと一緒に動作し、クラスのアノテーション内容を見るために反射機構を使用します。以下のように。

package com.newsee.annotation;

import java.lang.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
Inherited; import java.lang.annotation;
RetentionPolicy; import java.lang.annotation;
Target; import java.lang.annotation;

Target; /**
 * Determine if you are logged in
 *
 */
@Documented
@Target(ElementType.METHOD)
@Inherited
@Retention(RetentionPolicy.RUNTIME)
public @interface LoginVerify {

}

<イグ

package com.newsee.annotation;

import java.io.IOException;
import java.lang.reflect;
import javax.annotation.PostConstruct;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.stereotype.Component;
import org.springframework.util.ClassUtils;
import com.newsee.constant.LoginVerifyMapping;

@Component
public class ScanningLoginVerifyAnnotation {
	private static final String PACKAGE_NAME = "com.newsee.face";

	private static final String RESOURCE_PATTERN = "/*/*.class";

	@PostConstruct
	public void scanning() throws IOException, SecurityException,
			ClassNotFoundException {
		String pattern = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX
				+ ClassUtils.convertClassNameToResourcePath(PACKAGE_NAME)
				+ RESOURCE_PATTERN;
		ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
		Resource[] resources = resourcePatternResolver.getResources(pattern);
		for (Resource resource resource : resources) {
			if (resource.isReadable()) {
				String className = getClassName(resource.getURL().toString());
				Class cls = ScanningRequestCodeAnnotation.class.getClassLoader().loadClass((className));
				for (Method method : cls.getMethods()) {
					LoginVerify requestCode = method.getAnnotation(LoginVerify.class);
					if (requestCode ! = null) {
						</span>LoginVerifyMapping.add(className + ". " + method.getName());
					}
				}
			}
		}
	}

	private String getClassName(String resourceUrl) {
		String url = resourceUrl.replace("/", ". ");
		url = url.replace("\\\", ". ");
		url = url.split("com.newsee")[1];
		url = url.replace(".class", "");
		return "com.newsee" + url.trim();
	}
}


実行結果

public class LoginVerifyMapping {
	private static Map<String, Boolean> faceFunctionIsNeedLoginVerify = new HashMap<String, Boolean>();

	public static void add(String functionName) {
		faceFunctionIsNeedLoginVerify.put(functionName, Boolean.TRUE);
	}

	public static Boolean getFaceFunctionIsNeedLoginVerify(String functionName) {
		return faceFunctionIsNeedLoginVerify.get(functionName);
	}
}

最後にもう一つ、動作例をご紹介します。

LoginVerifyアノテーションは、アノテーションされたメソッドへのアクセス要求時に、ログイン判定を行うために使用されます。

private ResponseContent handleRequests(RequestContent requestContent) throws ClassNotFoundException,
			NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException,
			InvocationTargetException {
		String requestCode = requestContent.getRequest().getHead().getNWCode();
		String className = RequestCodeMapping.getClassName(requestCode);
		String beanName = RequestCodeMapping.getBeanName(requestCode);
		String functionName = RequestCodeMapping.getFunctionName(requestCode);
		Boolean loginVerify = LoginVerifyMapping.getFaceFunctionIsNeedLoginVerify(className + ". " + functionName);
		if (loginVerify ! = null && loginVerify) {//requires login verification
			boolean isAuthenticated = SecurityUtils.getSubject().isAuthenticated();
			if (!isAuthenticated) {
				String exId=requestContent.getRequest().getHead().getNWExID();
				SystemMobileTokenKeyServiceInter systemMobileTokenKeyServiceInter = (SystemMobileTokenKeyServiceInter) SpringContextUtil
					.getBean("systemMobileTokenKeyServiceInter");
				SystemMobileTokenKey systemMobileTokenKey=systemMobileTokenKeyServiceInter.getByExId(exId);
				if(systemMobileTokenKey==null)
					throw new BaseException(ResponseCodeEnum.NO_LOGIN);
				Date keyTime = systemMobileTokenKey.getKeyTime();
				if (System.currentTimeMillis() - keyTime.getTime() > 1000 * 60 * 60 * 24 * 3)
					throw new BaseException(ResponseCodeEnum.NO_LOGIN);
			}
		}
		if (className == null || beanName == null || functionName == null)
			throw new BaseException(ResponseCodeEnum.REQUEST_CODE_NOT_EXIST);
		Object object = SpringContextUtil.getBean(beanName);
		Class cls = Class.forName(className);
		Method method = cls.getMethod(functionName, RequestContent.class);
		Object response = method.invoke(object, requestContent);
		return (ResponseContent) response;
	}
}


ScanningLoginVerifyAnnotation の scanning() メソッドは、@PostConstruct によって変更され、次のように示されます。 サーバーがServletをロードするときに実行され、サーバーによって一度だけ実行されます。

<スパン ここで、もう少しだけ科学的な話をします。

PostConstruct と @PreDestroy です。この 2 つのアノテーションは、非静的な void() メソッドを変更するために使用されます。以下の2つの方法で記述します。

ポストコンストラクト

Public void someMethod() {}.                                                                       
または

public @PostConstruct void someMethod(){}.

PostConstructはコンストラクタの後、init()メソッドの前に実行され、preDestroy()メソッドはdestroy()メソッドの後に実行されます。

スキャンメソッドは、サーブレットがロードされた後にロードされたすべてのクラスを取得し、その中のメソッドを反復処理し、LoginVerifyアノテーションによって変更されたものがあれば、そのメソッド名を静的マップに入れ、保存します。

package com.newsee.annotation;

import java.io.IOException;
import java.lang.reflect;
import javax.annotation.PostConstruct;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.stereotype.Component;
import org.springframework.util.ClassUtils;
import com.newsee.constant.LoginVerifyMapping;

@Component
public class ScanningLoginVerifyAnnotation {
	private static final String PACKAGE_NAME = "com.newsee.face";

	private static final String RESOURCE_PATTERN = "/*/*.class";

	@PostConstruct
	public void scanning() throws IOException, SecurityException,
			ClassNotFoundException {
		String pattern = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX
				+ ClassUtils.convertClassNameToResourcePath(PACKAGE_NAME)
				+ RESOURCE_PATTERN;
		ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
		Resource[] resources = resourcePatternResolver.getResources(pattern);
		for (Resource resource resource : resources) {
			if (resource.isReadable()) {
				String className = getClassName(resource.getURL().toString());
				Class cls = ScanningRequestCodeAnnotation.class.getClassLoader().loadClass((className));
				for (Method method : cls.getMethods()) {
					LoginVerify requestCode = method.getAnnotation(LoginVerify.class);
					if (requestCode ! = null) {
						</span>LoginVerifyMapping.add(className + ". " + method.getName());
					}
				}
			}
		}
	}

	private String getClassName(String resourceUrl) {
		String url = resourceUrl.replace("/", ". ");
		url = url.replace("\\\", ". ");
		url = url.split("com.newsee")[1];
		url = url.replace(".class", "");
		return "com.newsee" + url.trim();
	}
}


LoginVerifyMappingは、LoginVerifyアノテーションによって変更されるメソッドの名前を保持するものです。

public class LoginVerifyMapping {
	private static Map<String, Boolean> faceFunctionIsNeedLoginVerify = new HashMap<String, Boolean>();

	public static void add(String functionName) {
		faceFunctionIsNeedLoginVerify.put(functionName, Boolean.TRUE);
	}

	public static Boolean getFaceFunctionIsNeedLoginVerify(String functionName) {
		return faceFunctionIsNeedLoginVerify.get(functionName);
	}
}

次のメソッドは、要求されたメソッドが LoginVerifyMappingであれば、ログイン認証が必要です。

private ResponseContent handleRequests(RequestContent requestContent) throws ClassNotFoundException,
			NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException,
			InvocationTargetException {
		String requestCode = requestContent.getRequest().getHead().getNWCode();
		String className = RequestCodeMapping.getClassName(requestCode);
		String beanName = RequestCodeMapping.getBeanName(requestCode);
		String functionName = RequestCodeMapping.getFunctionName(requestCode);
		Boolean loginVerify = LoginVerifyMapping.getFaceFunctionIsNeedLoginVerify(className + ". " + functionName);
		if (loginVerify ! = null && loginVerify) {//requires login verification
			boolean isAuthenticated = SecurityUtils.getSubject().isAuthenticated();
			if (!isAuthenticated) {
				String exId=requestContent.getRequest().getHead().getNWExID();
				SystemMobileTokenKeyServiceInter systemMobileTokenKeyServiceInter = (SystemMobileTokenKeyServiceInter) SpringContextUtil
					.getBean("systemMobileTokenKeyServiceInter");
				SystemMobileTokenKey systemMobileTokenKey=systemMobileTokenKeyServiceInter.getByExId(exId);
				if(systemMobileTokenKey==null)
					throw new BaseException(ResponseCodeEnum.NO_LOGIN);
				Date keyTime = systemMobileTokenKey.getKeyTime();
				if (System.currentTimeMillis() - keyTime.getTime() > 1000 * 60 * 60 * 24 * 3)
					throw new BaseException(ResponseCodeEnum.NO_LOGIN);
			}
		}
		if (className == null || beanName == null || functionName == null)
			throw new BaseException(ResponseCodeEnum.REQUEST_CODE_NOT_EXIST);
		Object object = SpringContextUtil.getBean(beanName);
		Class cls = Class.forName(className);
		Method method = cls.getMethod(functionName, RequestContent.class);
		Object response = method.invoke(object, requestContent);
		return (ResponseContent) response;
	}
}