1. Java 注解
JDK5.0之后引入的特性。
注解Annotation
,用于描述元数据的修饰符,包括类、成员变量、构造方法、成员方法、方法参数和声明。
示例:
1 2 3 4 5 6
| public class UserDaoImpl implements UserDao { @Override 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
规定自定义注解的作用区域(默认全区域)
1 2 3 4 5 6 7
| public @interface Target { ElementType[] value(); }
@Target(ElementType.METHOD) public @interface 自定义注解名 { }
|
当前定义的注解可以应用的范围:
- TYPE, 类、接口、枚举的类型定义上
- FIELD, 成员变量上
- METHOD, 成员方法上
- PARAMETER, 方法参数上
- CONSTRUCTOR, 构造方法上
- LOCAL_VARIABLE, 局部变量上
- ANNOTATION_TYPE, 注解类型上
- PACKAGE, 包定义语句上
- TYPE_PARAMETER, 类型参数声明
- TYPE_USE, 使用一个类型
@Retention
规定自定义注解的存活策略(一般情况下设置RUNTIME)
1 2 3 4 5 6 7
| public @interface Retention { RetentionPolicy value(); }
@Retention(RetentionPolicy.RUNTIME) public @interface 自定义注解名 { }
|
当前定义的注解可以存活的方式:
- SOURCE, 仅仅停留在源码中,编译时即除去
- CLASS, 保留到编译后的字节码中,运行时无法获取注解信息
- RUNTIME, 保留到运行时,通过反射机制可以获取注解信息
@Documented
文档标记注解(无参数)
将注解中的内容包含到Javadoc中。
1
| public @interface Documented { }
|
@Inherited
可随修饰类被继承的标记注解(无参数)
例如B继承了A,A添加了注解,那么B也会继承同样的注解。
1
| public @interface Inherited { }
|
2. 自定义注解
格式:(default 默认值可省略)
1 2 3 4 5 6
| @Retention(RetentionPolicy.RUNTIME) public @interface 自定义注解名 { 数据类型 属性名1() default 默认值1; 数据类型 属性名2() default 默认值2; 数据类型 属性名3()[] default {元素值1, 元素值2}; }
|
eg:
1 2 3 4 5 6
| @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对象
● 获取测试类的成员方法
● 判断方法上是否有自定义注解
● 执行添加了注解的方法
自定义注解:
1 2 3
| @Retention(RetentionPolicy.RUNTIME) public @interface MyTest { }
|
使用自定义注解修饰方法:
1 2 3 4 5
| class Test01 { @MyTest public void t1() { System.out.println("t1 t1..."); } public void t2() { System.out.println("t2 t2..."); } }
|
调用执行自定义注解修饰的方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| public class TestMyTest { public static void main(String[] args) { Class<Test01> clazz = Test01.class; Method[] methods = clazz.getMethods(); Arrays.stream(methods).forEach(method -> { boolean flag = method.isAnnotationPresent(MyTest.class); if (flag) { try { method.invoke(clazz.newInstance()); } catch (Exception e) { e.printStackTrace(); } } }); } }
|
运行结果:
3.2 获取注解参数信息用于数据库初始化(完整注解)
步骤:
1.自定义注解@JDBCInfo
2.在DBUtils工具类上使用@JDBCInfo
3.在DBUtils工具类的静态代码中获取注解中的属性(反射读取注解信息)
自定义注解:
1 2 3 4 5 6 7
| @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工具类中获取注解默认值信息(覆盖了密码值):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| @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 properties = new Properties(); InputStream is = DBUtils.class.getClassLoader().getResourceAsStream("database.properties"); try { properties.load(is); } catch (Exception e) { e.printStackTrace(); } DRIVERCLASS = properties.getProperty("driver"); URL = properties.getProperty("url"); USERNAME = properties.getProperty("username"); PASSWORD = properties.getProperty("password"); } }
public static void loadDriver() throws Exception { Class.forName(DRIVERCLASS); }
public static Connection getConnection() throws Exception { return DriverManager.getConnection(URL, USERNAME, PASSWORD); } }
|
测试类调用:
1 2 3 4 5
| public class TestJDBCInfo { public static void main(String[] args) throws Exception { new DBUtils(); } }
|
运行结果:
4. 注解+反射+动态代理设计模式 实现【日志记录】
① 自定义注解:
1 2
| @Retention(RetentionPolicy.RUNTIME) public @interface SysLog { }
|
② UserDao接口:
1 2 3 4 5 6 7 8
| public interface UserDao { @SysLog void add(); @SysLog void del(); void upd(); void get(); }
|
③ UserDaoImpl实现类:
1 2 3 4 5 6 7 8 9 10 11 12 13
| public class UserDaoImpl implements UserDao { @Override public void add() { System.out.println("add method executed..."); }
@Override public void del() { System.out.println("del method executed..."); }
@Override public void upd() { System.out.println("upd method executed..."); }
@Override public void get() { System.out.println("get method executed..."); } }
|
④ 动态代理设计模式 - 测试类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| public class TestSysLog { public static void main(String[] args) { UserDao userDao = new UserDaoImpl(); UserDao p = (UserDao) Proxy.newProxyInstance( userDao.getClass().getClassLoader(), userDao.getClass().getInterfaces(), (proxy, method, argss) -> { boolean present = method.isAnnotationPresent(SysLog.class); if (present) { String className = UserDao.class.getName(); String methodName = method.getName(); String currTimeStr = new SimpleDateFormat("yyyy/MM/dd hh:mm:ss").format(new Date()); System.out.println("[" + currTimeStr + "]<" + className + "> " + methodName + "():"); } return method.invoke(userDao); } ); p.add(); p.del(); p.upd(); p.get(); } }
|
运行结果:
5. 常用注解说明
5.1 JDK 内置注解
@Override
方法覆盖注解
判断子类中的方法是否是一个父类方法的重写方法。
@Deprecated
过时标记注解
标记一个类/方法/变量是否是过时的,调用时会有删除线。
@SupressWarning("all")
压制警告注解
可以消除编译器/开发工具烦人的警告的提示,一般给 all 即可。
@FunctionaInterface
函数式接口注解
该方法只有1个公开抽象方法,用于 Lambda 表达式简写匿名内部类。
@SafeVarargs
安全的可变参数注解
jdk1.7引入的适用于可变参数与泛型能够更好结合的一个注解,也就是说如果你认为你的方法或者构造方法是类型安全的,那么你也就可以使用 @SafeVarargs 来跳过@SuppressWarnings(“unchecked”) 检查。
5.2 Junit 单元测试注解
@Test
单元测试注解
该注解的方法被强制public且无参,可以直接运行被改注解修饰的方法,底层还是 main 方法运行(通过反射执行)。
@Ignore
忽略测试注解
暂时不运行某些测试方法\测试类,可以在方法前加上这个注解。(不建议经常这么做)
@BeforeClass
优先测试注解
在测试类里所有用例运行之前,运行一次这个方法。例如创建数据库连接、读取文件等。
还有其他的,比较简单,暂不赘述,随用随查即可。
5.3 Servlet 配置注解
@WebServlet
web资源配置注解
Servlet3.0+版本支持,可以替代web.xml配置。1 2 3 4 5 6 7 8 9 10
| @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.4 其他框架注解
——更多第三方注解后续汇总…
6. xml与注解优劣对比
xml
优点:
- 降低耦合,使容易扩展
- 对象之间的关系一目了然
- xml配置文件比注解功能齐全
缺点:
- 配置文件配置工作量相对注解要大
注解
优点:
- 在class文件中,可以降低维护成本
- 提高开发效率
缺点:
- 如果对注解文件(Annotation)进行修改,需要重新编译整个工程
- 业务类之间的关系不如xml配置那样一目了然
- 程序中过多的注解,对于代码的简洁度有一定影响
- 注解功能没有xml配置文件齐全