Java注解、自定义注解、注解信息获取使用
1. Java 注解
JDK5.0之后引入的特性。
注解Annotation
,用于描述元数据的修饰符,包括类、成员变量、构造方法、成员方法、方法参数和声明。
示例:
public class UserDaoImpl implements UserDao {
@Override /* Hey,快来看!这里有个注解 */
public void add() throws Exception {
System.out.println("add user...");
}
}
1.1 Java 注解作用
注解的作用:
- 生成文档或提供配置信息
- 跟踪代码依赖性
- 编译时进行格式检查
1.2 Java 注解分类
按类型分:
- 标记注解:注解中没有属性可以设置,如 @Override、@Deprecated
- 单值注解:注解中只有一个属性("value="可省略),如 @SuppressWarings{ String[] value; }
- 完整注解:注解中有多个属性,如@WebServlet(name = “MyServlet”, urlPatterns = “/demo01”, initParams = {@WebInitParam(name = “root”, value = “1234”)})
按来源分:
- JDK内置注解
- 第三方注解(框架注解)
- 自定义注解
1.3 Java 元注解 - 注解的注解
定义在自定义注解上
,规定自定义的作用区域
、存活策略
。
@Target
规定自定义注解的作用区域(默认全区域)
public @interface Target {
ElementType[] value();
}
当前定义的注解可以应用的范围:
TYPE, 类、接口、枚举的类型定义上
FIELD, 成员变量上
METHOD, 成员方法上
PARAMETER, 方法参数上
CONSTRUCTOR, 构造方法上
LOCAL_VARIABLE, 局部变量上
ANNOTATION_TYPE, 注解类型上
PACKAGE, 包定义语句上
TYPE_PARAMETER, 类型参数声明
TYPE_USE, 使用一个类型
@Retention
规定自定义注解的存活策略(一般情况下设置RUNTIME)
public @interface Retention {
RetentionPolicy value();
}
当前定义的注解可以存活的方式:
SOURCE, 仅仅停留在源码中,编译时即除去
CLASS, 保留到编译后的字节码中,运行时无法获取注解信息
RUNTIME, 保留到运行时,通过反射机制可以获取注解信息
@Documented
文档标记注解(无参数)
将注解中的内容包含到Javadoc中。
public @interface Documented { }
@Inherited
可随修饰类被继承的标记注解(无参数)
例如B继承了A,A添加了注解,那么B也会继承同样的注解。
public @interface Inherited { }
2. 自定义注解
格式:(default 默认值可省略)
@Retention(RetentionPolicy.RUNTIME)
public @interface 自定义注解名 {
数据类型 属性名1() default 默认值1;
数据类型 属性名2() default 默认值2;
数据类型 属性名3()[] default {元素值1, 元素值2}; // 注解中的数组
}
eg:
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
String username() default "root";
int age() default 18;
String citys()[] default {"北京", "上海", "广州", "深圳"};
}
3. 注解信息获取
核心步骤:——反射机制
① 注解在类/方法上,获取对应的 Class 对象
/ Method 对象
② 判断类/方法上的注解是否存在 .isAnnotationPresent(注解名.class)
③ 获取注解信息 .getAnnotation(注解名.class)
3.1 获取并执行自定义注解修饰的方法(标记注解)
步骤:
1.自定义注解
2.在测试类上使用该自定义注解
3.让自定义注解生效
● 获取测试类的Class对象
● 获取测试类的成员方法
● 判断方法上是否有自定义注解
● 执行添加了注解的方法
自定义注解:
@Retention(RetentionPolicy.RUNTIME)
public @interface MyTest {
}
使用自定义注解修饰方法:
class Test01 {
@MyTest
public void t1() { System.out.println("t1 t1..."); }
public void t2() { System.out.println("t2 t2..."); }
}
调用执行自定义注解修饰的方法:
public class TestMyTest {
public static void main(String[] args) {
/* 使用反射去扫描 Test01 中有哪些方法有 @MyTest注解 */
// 1.获取 Class 对象
Class<Test01> clazz = Test01.class;
// 2.获取类里成员方法(@Test注解的方法被强制public且无参)
Method[] methods = clazz.getMethods();
Arrays.stream(methods).forEach(method -> {
// method 就是单个方法对象
// 3.判断方法上的注解
boolean flag = method.isAnnotationPresent(MyTest.class);
//System.out.println(method.getName() + ":" + flag);
if (flag) {
try {
method.invoke(clazz.newInstance());
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
}
运行结果:
3.2 获取注解参数信息用于数据库初始化(完整注解)
步骤:
1.自定义注解@JDBCInfo
2.在DBUtils工具类上使用@JDBCInfo
3.在DBUtils工具类的静态代码中获取注解中的属性(反射读取注解信息)
自定义注解:
@Retention(RetentionPolicy.RUNTIME)
public @interface JDBCInfo {
String driverClass() default "com.mysql.jdbc.Driver";
String url() default "jdbc:mysql://localhost:3306/demo";
String username() default "root";
String password() default "1234";
}
JDBC工具类中获取注解默认值信息(覆盖了密码值):
@JDBCInfo(password = "123456")
public class DBUtils {
private static final String DRIVERCLASS;
private static final String URL;
private static final String USERNAME;
private static final String PASSWORD;
static {
Class<DBUtils> dbClass = DBUtils.class;
boolean present = dbClass.isAnnotationPresent(JDBCInfo.class);
if (present) {
JDBCInfo jdbcInfo = dbClass.getAnnotation(JDBCInfo.class);
DRIVERCLASS = jdbcInfo.driverClass();
URL = jdbcInfo.url();
USERNAME = jdbcInfo.username();
PASSWORD = jdbcInfo.password();
System.out.println(DRIVERCLASS+":"+URL+":"+USERNAME+":"+PASSWORD);
} else {
// 没有注解从 Properties 中取(临时赋空值了)
DRIVERCLASS = URL = USERNAME = PASSWORD = "";
}
}
public static void loadDriver() throws Exception {
Class.forName(DRIVERCLASS);
}
public static Connection getConnection() throws Exception {
return DriverManager.getConnection(URL, USERNAME, PASSWORD);
}
}
测试类调用:
public class TestJDBCInfo {
public static void main(String[] args) throws Exception {
new DBUtils();
}
}
运行结果:
4. 常用注解说明
4.1 JDK 内置注解
-
@Override
方法覆盖注解
判断子类中的方法是否是一个父类方法的重写方法。 -
@Deprecated
过时标记注解
标记一个类/方法/变量是否是过时的,调用时会有删除线。 -
@SupressWarning("all")
压制警告注解
可以消除编译器/开发工具烦人的警告的提示,一般给 all 即可。 -
@FunctionaInterface
函数式接口注解
该方法只有1个公开抽象方法,用于 Lambda 表达式简写匿名内部类。 -
@SafeVarargs
安全的可变参数注解
jdk1.7引入的适用于可变参数与泛型能够更好结合的一个注解,也就是说如果你认为你的方法或者构造方法是类型安全的,那么你也就可以使用 @SafeVarargs 来跳过@SuppressWarnings(“unchecked”) 检查。
4.2 Junit 单元测试注解
-
@Test
单元测试注解
该注解的方法被强制public且无参,可以直接运行被改注解修饰的方法,底层还是 main 方法运行(通过反射执行)。 -
@Ignore
忽略测试注解
暂时不运行某些测试方法\测试类,可以在方法前加上这个注解。(不建议经常这么做) -
@BeforeClass
优先测试注解
在测试类里所有用例运行之前,运行一次这个方法。例如创建数据库连接、读取文件等。
还有其他的,比较简单,暂不赘述,随用随查即可。
4.3 Servlet 配置注解
@WebServlet
web资源配置注解
Servlet3.0+版本支持,可以替代web.xml配置。
@WebServlet(
name = "TestWebServlet", // 设置 Servlet 的映射名字(通常与类名一致)
/*value = {"/demo", "/web"},*/
urlPatterns = {"/demo01", "/web01"}, // 设置 Servlet 访问资源名(value同urlPatterns)
loadOnStartup = 1, // 设置 Servlet 启动优先级
initParams = { // 设置 Servlet 启动参数
@WebInitParam(name = "username", value = "root"),
@WebInitParam(name = "password", value = "123456"),
}
)
——更多第三方注解后续汇总…
5. 注解+反射+动态代理设计模式 实现【日志记录】
① 自定义注解:
@Retention(RetentionPolicy.RUNTIME)
public @interface SysLog {
String className(); // 记录类名
String methodName(); // 记录方法名
}
② UserDao接口:
public interface UserDao {
@SysLog(className = "com.all.dao.UserDao", methodName = "add")
void add();
@SysLog(className = "com.all.dao.UserDao", methodName = "del")
void del();
void upd();
void sel();
}
③ UserDaoImpl实现类:
public class UserDaoImpl implements UserDao {
@Override
public void add() { System.out.println("add user..."); }
@Override
public void del() { System.out.println("del user..."); }
@Override
public void upd() { System.out.println("upd user..."); }
@Override
public void sel() { System.out.println("sel user..."); }
}
④ 动态代理设计模式 - 测试类:
public class TestSysLogAnno {
public static void main(String[] args) throws Exception {
UserDaoImpl userDao = new UserDaoImpl();
// 动态代理模式 - 测试:
UserDao p = (UserDao) Proxy.newProxyInstance(
userDao.getClass().getClassLoader(),
userDao.getClass().getInterfaces(),
(proxy, method, argss) -> {
Object result = null;
// method即为接口中的方法对象
if (null != method) {
// 1.检查方法上是否有对应注解
boolean present = method.isAnnotationPresent(SysLog.class);
// 2.有注解:执行原有功能,打印日志
if (present) {
result = method.invoke(userDao, argss);
SysLog sysLog = method.getAnnotation(SysLog.class);
String className = sysLog.className();
String methodName = sysLog.methodName();
// 3.拼接日志需要的时间和必要信息
String currTimeStr = new SimpleDateFormat("yyyy/MM/dd hh:mm:ss").format(new Date());
System.out.println("[" + currTimeStr + "]" + className + "." + methodName + "() 方法执行...");
} else {
// 4.没注解:执行原有功能
result = method.invoke(userDao, argss);
}
}
return result;
}
);
p.add();
p.del();
p.upd();
p.sel();
}
运行结果:
6. xml与注解优劣对比
xml
优点:
- 降低耦合,使容易扩展
- 对象之间的关系一目了然
- xml配置文件比注解功能齐全
缺点:
- 配置文件配置工作量相对注解要大
注解
优点:
- 在class文件中,可以降低维护成本
- 提高开发效率
缺点:
- 如果对注解文件(Annotation)进行修改,需要重新编译整个工程
- 业务类之间的关系不如xml配置那样一目了然
- 程序中过多的注解,对于代码的简洁度有一定影响
- 注解功能没有xml配置文件齐全