欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

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

程序员文章站 2024-03-17 17:40:40
...

背景

继续查缺补漏,加油。

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

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

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

1.内置注解:

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

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

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

就好不要用。

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

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

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

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.元注解

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

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

 4.编写注解处理器

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

下个版本将会被删除。

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

例如:

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,aaa@qq.com(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包含的是类对象的字节码,可以通过它取得类有关的信息。