Thking in java(第四版)-查缺补漏(第20章)

背景

继续查缺补漏,加油。

注解(也被称为元数据)为我们在代码中添加信息提供了一种形式化的方法,使我们可以在稍后某个时刻

非常方便地使用这些数据。注解使得我们能够以将由编译器来测试和验证的格式,存储有关程序的额外信

息。可以用来生成描述符文件,甚至或是新的类定义,并且有助于减轻编写“样板”代码的负担。

1.内置注解:

(1)@Override :表示当前的方法定义将覆盖超类中的方法。如果你不小心拼写错误,或方法签名对不上被

覆盖的方法,编译器就会发出错误提示。

(2)@Deprecated :如果使用了注解为它的元素,编译器就会发出警告信息。表示这个方法以后要被删除,

就好不要用。

(3)@SuppressWarnings :关闭当前的编译器警告。

这些注解定义在java.lang中。当你创建描述符性质的类或接口时,一旦其中包含了重复性的工作,那就可以

考虑使用注解来简化与自动化该过程。

扫描二维码关注公众号,回复: 3754725 查看本文章

2.基本语法

(1)注解的使用:

import tools.atunit.*;
public class Testable {
	public void execute(){
		System.out.println("Executing...");
	}
	@Test void testExecute(){	execute();}
}

(2)定义注解:注解也会被编译成class文件

import java.lang.annotation.*;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Test {}

@Target用来定义注解将应用于什么地方,例如方法或域;@Retention用来定义该注解在哪一个级别可用,在

源代码SOURSE,类文件CLASS,或者运行时RUNTIME。

在注解中,一般会包含一些元素以表示某些值。当分析处理注解时,程序或工具可以利用这些值。可以为注解

的元素指定默认值。没有元素的注解被称为标记注解。就像上面的@Test

我们可以用注解来跟踪一个项目中的用例:

package annotations;
import java.lang.annotation.*;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface UseCase{
	public int id();
	public String description() default "no description";
}

--------------------------------------------------------
package annotations;
import java.util.*;
public class PasswordUtils {
	@UseCase(id=47,description="Passwords must contian at least one numeric")
	public boolean validataPassword(String password){
		return (password.matches("\\w*\\d\\w*"));
	}
	@UseCase(id=48)
	public String encryptPassword(String password){
		return new StringBuilder(password).reverse().toString();
	}
	@UseCase(id=49,description="New passwords can't equal previously used ones")
	public boolean checkForNewPassword(List<String> prevPasswords,String password){
		return !prevPasswords.contains(password);
	}
}

3.元注解

元注解专职负责注解其他的注解:

 4.编写注解处理器

利用反射机制的API,javax.annotation.processing和javax.lang.model来构建注解处理器。apt工具在

下个版本将会被删除。

例如:

package annotations;
import java.lang.reflect.*;
import java.util.*;
public class UseCaseTracker {
	public static void trackUseCases(List<Integer> useCases,Class<?> cl){
		for(Method m:cl.getDeclaredMethods()){
			UseCase uc=m.getAnnotation(UseCase.class);
			if(uc!=null){
				System.out.println("Found Use Case :"+uc.id()+" "+uc.description());
				useCases.remove(new Integer(uc.id()));
			}
		}
		for(int i:useCases){
			System.out.println("Warning:Missing use case-"+i);
		}
	}
	public static void main(String[] args){
		List<Integer> useCases=new ArrayList<Integer>();
		Collections.addAll(useCases, 47,48,49,50);
		trackUseCases(useCases,PasswordUtils.class);
	}
}

getAnnotation()返回指定类型的注解对象,没有则返回null。

注解元素可用的类型有:所有基本类型,String,class,enum,Annotation,以上类型的数组。

注解也可以作为元素的类型,注解可以嵌套。

5.默认值限制

对于非基本类型的元素,无论是在源代码中声明时,或是在注解接口中定义默认值,都不能以null作为其值。

因此我们经常用空字符串或是负数,表示某个元素不存在。

6.生成外部文件

假设你希望提供一些基本的对象/关系映射功能,能够自动生成数据库表,用以存储javabean对象,可以使用注解

,你可以把所有信息都保存在javabean源文件中。 例如:

下面是一个注解定义,作用是让注解处理器,生成一个数据库表,name()元素提供表名:

package annotations;
import java.lang.annotation.*;
@Target(ElementType.TYPE) //Applies to classes only
@Retention(RetentionPolicy.RUNTIME)
public @interface DBTable{
	public String name() default "";
}

下面是修饰javabean域的注解:

package annotations;
import java.lang.annotation.*;
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Constraints{
	boolean primaryKey() default false;
	boolean allowNull() default true;
	boolean unique() default false;
}
---------------------------------------
package annotations;
import java.lang.annotation.*;
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SQLString{
	int value() default 0;
	String name() default "";
	Constraints constraints() default @Constraints;
}
---------------------------------------
package annotations;
import java.lang.annotation.*;
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SQLInteger{
	String name() default "";
	Constraints constraints() default @Constraints;
}

上面注解中SQLInteger和SQLString利用了注解的嵌套。

下面是一个bean的定义,而且应用了上面的注解:

package annotations;
@DBTable(name="MEMBER")
public class Member {
	@SQLString(30) String firstName;
	@SQLString(50) String lastName;
	@SQLInteger Integer age;
	@SQLString(value=30,constraints=@Constraints(primaryKey=true)) String handle;
	static int memberCount;
	public String getHandle(){	return handle;}
	public String getFirstName(){	return firstName;}
	public String getLastName(){	return lastName;}
	public String toString(){	return handle;}
	public Integer getAge(){	return age;}
}

快捷方式:如果注解中定义了名为value的元素,并且在应用注解的时候,该元素是唯一需要赋值的元素,

那就不用使用名-值对的这种语法,而是在括号中给出值就可以了。

注解不支持继承。

实现处理器:检查一个类文件上的数据库注解,并生成用来创建数据库的SQL命令。

package annotations;
import java.lang.annotation.*;
import java.lang.reflect.*;
import java.util.*;
public class TableCreator {
	public static void main(String[] args)throws Exception{
		if(args.length<1){
			System.out.println("arguments: annotated classes");
			System.exit(0);
		}
		for(String className:args){
			Class<?> cl=Class.forName(className);
			DBTable dbTable=cl.getAnnotation(DBTable.class);
			if(dbTable==null){
				System.out.println("No DBTable annotations in class"+className);
				continue;
			}
			String tableName=dbTable.name();
			//if the name is empty,use the Class name:
			if(tableName.length()<1)
				tableName=cl.getName().toUpperCase();
			List<String> columnDefs=new ArrayList<String>();
			for(Field field:cl.getDeclaredFields()){
				String columnName=null;
				Annotation[] anns=field.getDeclaredAnnotations();
				if(anns.length<1)
					continue; //Not a db table column
				if(anns[0] instanceof SQLInteger){
					SQLInteger sInt=(SQLInteger) anns[0];
					//Use field name if name not specified
					if(sInt.name().length()<1)
						columnName=field.getName().toUpperCase();
					else
						columnName=sInt.name();
					columnDefs.add(columnName +"INT"+getConstraints(sInt.constraints()));
				}
				
				if(anns[0] instanceof SQLString){
					SQLString sString=(SQLString) anns[0];
					//Use field name if name not specified.
					if(sString.name().length()<1)
						columnName=field.getName().toUpperCase();
					else
						columnName=sString.name();
					columnDefs.add(columnName 
                  +"VARCHAR("+sString.value()+")"+getConstraints(sString.constraints()));
				}
			StringBuilder createCommand=new StringBuilder("CREATE TABLE "+tableName+ (");
				for(String columnDef:columnDefs)
					createCommand.append("\n    "+columnDef+",");
				//Remove trailing comma
				String tableCreate=
                        createCommand.substring(0,createCommand.length()-1)+");";
				System.out.println("Table Create SQL for "+className +" is 
                                   :\n"+tableCreate);
			}
		}
	}
	private static String getConstraints(Constraints con){
		String constraints="";
		if(!con.allowNull())
			constraints+=" NOT NULL";
		if(con.primaryKey())
			constraints+=" PRIMARY KEY";
		if(con.unique())
			constraints+=" UNIQUE";
		return constraints;
	}
}

7.访问者设计模式

一个访问者会遍历某个数据结构或一个对象的集合,对其中的每一个对象执行一个操作。该数据结构无需有序,而

你对每个对象执行的操作,都是特定于此对象的类型。这就将操作与对象解耦,也就是说,当你添加新的操作,而

无需向类的定义中添加方法。

8.JUnit-基于注解的单元测试

单元测试是对类中的每个方法提供一个或多个测试的一种实践。

下面是一个简单的应用:详细的方法看官网。JUnit官网

1---------------------------------------------
package annotations;
public class AtUnitExample1 {
	public String methodOne(){
		return "This is methodOne" ;
	}
	public int methodTwo(){
		System.out.println("This is methodTwo");
		return 2;
	}
}
2--------------------------------------------------
package annotations;
import org.junit.*;
import static org.junit.Assert.*;

public class AtUnitTest {
	String message="This is ";
	AtUnitExample1 ae=new AtUnitExample1();
	@Test
	public void testPrintMessage(){
		assertEquals(message,ae.methodOne());
	}
}
3--------------------------------------------------
package annotations;
import org.junit.runner.*;
import org.junit.runner.notification.Failure;
public class AtUnitRun {
	public static void main(String[] args){
		Result result=JUnitCore.runClasses(AtUnitTest.class);
		for(Failure failure:result.getFailures())
			System.out.println(failure.toString());
		
	}
}

第一个是要测试的类,第二个是测试方法,第三个是运行测试。

测试方法必须以test为前缀。

如果并非必须把测试方法嵌入到原本的类中,可以利用继承或组合生成一个非嵌入式的测试。上面的例子用了组合。

9.移除测试代码

Javassist工具类库将字节码工程带入了一个可行的领域。

例如:详细的用法看官网。javassist官网

ClassPool cPool=ClassPool.getDefault();
CtClass ctClass=cPool.get(cName);
for(CtMethod method:ctClass.getDeclaredMethods()){
	MethodInfo mi=method.getMethodInfo();
	AnnotationsAttribute attr= 
                    (AnnotationsAttribute)mi.getAttribute(AnnotationsAttribute.visibleTag);
	if(attr==null)continue;
	for(Annotation ann:attr.getAnnotations()){
		if(ann.getTypeName().startsWith("annotations")){
			print(ctClass.getName()+" Method: "+mi.getName()+" "+ann);
			if(remove){
				ctClass.removeMethod(method);
				modified=true;
			}
		}
	}
}
//Fields are not removed in this version(see text).
if(modified)
    ctClass.toBytecode(new DataOutputStream(new FileOutputStream(cFile)));
ctClass.detach();

ClassPool是一种全景,记录了你正在修改的系统中的所有的类,并能够保证所有类在修改后的一致性。

CtClass包含的是类对象的字节码,可以通过它取得类有关的信息。

猜你喜欢

转载自blog.csdn.net/a614528195/article/details/82589866