Thking in java(第四版)-查缺补漏(第20章)
背景
继续查缺补漏,加油。
注解(也被称为元数据)为我们在代码中添加信息提供了一种形式化的方法,使我们可以在稍后某个时刻
非常方便地使用这些数据。注解使得我们能够以将由编译器来测试和验证的格式,存储有关程序的额外信
息。可以用来生成描述符文件,甚至或是新的类定义,并且有助于减轻编写“样板”代码的负担。
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.元注解
元注解专职负责注解其他的注解:
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,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包含的是类对象的字节码,可以通过它取得类有关的信息。