(持续更新ing… …反射常用API&JDK动态代理)
Java注解
什么是注解?
百度百科对注解的定义如下:注解(Annotation),也叫元数据。一种代码级别的说明。
它是JDK1.5及以后版本引入的一个特性,注解 @interface 与类class、接口 interface、枚举 enum是在同一个层次。
它可以声明在包、类、字段、方法、局部变量、方法参数等的前面,用来对这些元素进行说明,注释。
Annotation接口中有这么一句话 "The common interface extended by all annotation types. "。也就是所有的注解都继承于 java.lang.annotation.Annotation
接口。
这句话有点抽象,但是却说出了注解的本质。我们随便看一个JDK内置注解的定义:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
上面代码是@Override注解的定义,其实他本质上是
public interface Override extends Annotation{
}
注解的本质就是一个继承了Annotation接口的接口。
而注解在编译完成后,也是字节码,所以,它在JVM中也是Class的实例。如:Class<? extends Annotation>
。
注解的目的是用来描述数据,更确切地讲,是用代码的方式来描述数据。
注解从使用方法和用途上,可以分为三种?
- Java自带的标准注解
包括@Override、@Deprecated、@SuppressWarnings等,使用这些注解后编译器就会进行检查。 - 元注解
元注解是用于定义注解的注解,包括@Retention、@Target、@Inherited、@Documented、@Repeatable 等。
元注解也是Java自带的标准注解,只不过用于修饰注解,比较特殊。 - 自定义注解
用户可以根据自己的需求定义注解。
系统注解有哪些?JDK内置注解
标准注解(3+1)
包括@Override、@Deprecated、@SuppressWarnings等,使用这些注解后编译器就会进行检查。
@Override:检查该方法是否重写方法,如果发现其父类,或者是引用的接口中并没有该方法时,会报编译错误。
@Deprecated:标记过时方法,如果使用该方法,会报编译警告。
@SuppressWarnings:指示编译器去忽略注解中声明的警告。
@FunctionalInterface:Java8支持,标识一个匿名函数或者函数式接口。
@Override
如果试图使用 @Override 标记一个实际上并没有覆写父类的方法时,java 编译器会告警。
public class Parent {
public void test() {
}
}
public class Child extends Parent{
@Override
public void test() {
super.test();
}
}
@Deprecated
用于标明被修饰的类或类成员、类方法已经废弃、过时,不建议使用。
@Deprecated
class TestClass {
// do something
}
@SuppressWarnings
用于关闭对类、方法、成员编译时产生的特定警告。
- 抑制单类型的警告
@SuppressWarnings("unchecked") public void addItems(String item){ @SuppressWarnings("rawtypes") List items = new ArrayList(); items.add(item); }
- 抑制多类型的警告
@SuppressWarnings(value={"unchecked", "rawtypes"}) public void addItems(String item){ List items = new ArrayList(); items.add(item); }
- 抑制所有类型的警告
@SuppressWarnings("all") public void addItems(String item){ List items = new ArrayList(); items.add(item); }
@SuppressWarnings 注解的常见参数值的简单说明:
- deprecation:使用了不赞成使用的类或方法时的警告。
- unchecked:执行了未检查的转换时的警告,例如当使用集合时没有用泛型 (Generics) 来指定集合保存的类型。
- fallthrough:当 Switch 程序块直接通往下一种情况而没有 Break 时的警告。
- path:在类路径、源文件路径等中有不存在的路径时的警告。
- serial:当在可序列化的类上缺少 serialVersionUID 定义时的警告。
- finally:任何 finally 子句不能正常完成时的警告。
- all:关于以上所有情况的警告。
@FunctionalInterface
用于指示被修饰的接口是函数式接口,在JDK8 引入。
@FunctionalInterface
public interface UserService {
/**
* 抽象方法
*/
void getUser(Long userId);
/**
* 默认方法,可以用多个默认方法
*/
default void setUser() {
}
/**
* 静态方法
*/
static void saveUser() {
}
/**
* 覆盖Object中的equals方法
*/
@Override
boolean equals(Object obj);
}
函数式接口(Functional Interface)就是一个有且仅有一个抽象方法,但是可以有多个非抽象方法的接口。
元注解(5+2)
元注解是负责对其它注解进行说明的注解,自定义注解时可以使用元注解。
Java5定义了4个注解,分别是@Documented
、@Target
、@Retention
和@Inherited
。Java8又增加了@Repeatable
和 @Native
两个注解。这些注解都可以在java.lang.annotation
包中找到。
@Documented
@Documented 是一个标记注解,没有成员变量。用@Documented注解修饰的注解类会被 JavaDoc工具提取成文档。默认情况下,JavaDoc是不包括注解的,但如果声明注解时指定了@Documented,就会被JavaDoc之类的工具处理,所以注解类型信息就会被包括在生成的帮助文档中。
@Documented
@Target({ ElementType.TYPE, ElementType.METHOD })
public @interface MyDocumented {
public String value() default "这是@Documented注解";
}
public class DocumentedTest {
/**
* 测试document
*/
@MyDocumented
public String test() {
return "Java教程";
}
}
然后打开Java文件所在的目录,分别输入如下两条命令行:
javac MyDocumented.java DocumentedTest.java
javadoc -d doc MyDocumented.java DocumentedTest.java
运行成功后,打开生成的帮助文档,可以看到在类和方法上都保留了MyDocument的注解信息。如下所示:
@Target
@Target 注解用来指定一个注解的使用范围,即被@Target修饰的注解可以用在什么地方。@Target 注解有一个成员变量(value)用来设置适用目标,value是java.lang.annotation.ElementType
枚举类型的数组,下表为 ElementType 常用的枚举常量。
TYPE:用于类、接口(包括注解类型)或 enum 声明
FIELD:用于成员变量(包括枚举常量)
METHOD:用于方法
LOCAL_VARIABLE:用于局部变量
PACKAGE:用于包
TYPE_PARAMETER:用于类型参数(JDK1.8新增)
自定义一个 MyTarget 注解,使用范围为方法,代码如下所示。
@Target({ ElementType.METHOD })
public @interface MyTarget {
}
public class MyTargetTest {
@MyTarget
private String name;
}
如上代码第 8 行会编译错误,错误信息为:'@MyTarget' not applicable to field
。提示此位置不允许使用注解@MyTarget,@MyTarget不能修饰成员变量,只能修饰方法。
@Retention
Retention 用于描述注解的生命周期,也就是该注解被保留的时间长短。@Retention 注解中的成员变量(value)用来设置保留策略,value 是java.lang.annotation.RetentionPolicy
枚举类型,RetentionPolicy 有 3 个枚举常量,如下所示。
SOURCE:在源文件中有效(即源文件保留)
CLASS:在 class 文件中有效(即 class 保留)
RUNTIME:在运行时有效(即运行时保留)
生命周期大小排序为 SOURCE < CLASS < RUNTIME,前者能使用的地方后者一定也能使用。如果需要在运行时去动态获取注解信息,那只能用 RUNTIME 注解;如果要在编译时进行一些预处理操作,比如生成一些辅助代码(如 ButterKnife),就用 CLASS 注解;如果只是做一些检查性的操作,比如 @Override 和 @SuppressWarnings,则可选用 SOURCE 注解。
@Inherited
@Inherited 是一个标记注解,用来指定该注解可以被继承。使用 @Inherited 注解的 Class 类,表示这个注解可以被用于该 Class 类的子类。就是说如果某个类使用了被 @Inherited 修饰的注解,则其子类将自动具有该注解。
测试代码如下:
@Target({ ElementType.TYPE })
@Inherited
@Retention(RetentionPolicy.RUNTIME)
public @interface MyInherited {
}
@MyInherited
public class MyInheritedTestA {
public static void main(String[] args) {
System.out.println(MyInheritedTestA.class.getAnnotation(MyInherited.class));
System.out.println(MyInheritedTestB.class.getAnnotation(MyInherited.class));
System.out.println(MyInheritedTestC.class.getAnnotation(MyInherited.class));
}
}
public class MyInheritedTestB extends MyInheritedTestA{
}
public class MyInheritedTestC extends MyInheritedTestB{
}
运行结果:
@com.aiz.annotation.MyInherited()
@com.aiz.annotation.MyInherited()
@com.aiz.annotation.MyInherited()
@Repeatable
@Repeatable 注解是 Java 8 新增加的,它允许在相同的程序元素中重复注解,在需要对同一种注解多次使用时,往往需要借助 @Repeatable 注解。Java 8 版本以前,同一个程序元素前最多只能有一个相同类型的注解,如果需要在同一个元素前使用多个相同类型的注解,则必须使用注解“容器”。
Java 8 之前的做法:
public @interface Roles {
Role[] roles();
}
public @interface Roles {
Role[] value();
}
public class RoleTest {
@Roles(roles = {@Role(roleName = "程序员"), @Role(roleName = "教师")})
public String doString(){
return "Java教程";
}
}
Java 8 之后增加了重复注解,使用方式如下:
public @interface Roles {
Role[] value();
}
@Repeatable(Roles.class)
public @interface Role {
String roleName();
}
public class RoleTest {
@Role(roleName = "程序员")
@Role(roleName = "教师")
public String doString(){
return "Java教程";
}
}
不同的地方是,创建重复注解 Role 时加上了 @Repeatable 注解,指向存储注解 Roles,这样在使用时就可以直接重复使用 Role 注解。从上面例子看出,使用 @Repeatable 注解更符合常规思维,可读性强一点。
两种方法获得的效果相同。重复注解只是一种简化写法,这种简化写法是一种假象,多个重复注解其实会被作为“容器”注解的 value 成员的数组元素处理。
@Native
使用 @Native 注解修饰成员变量,则表示这个变量可以被本地代码引用,常常被代码生成工具使用。对于 @Native 注解不常使用,了解即可。
参考:https://blog.csdn.net/Hell_potato777/article/details/126808559
为什么要用注解?
1、检查
2、约束
3、灵活
4、方便简洁
5、让静态语言拥有动态机制
注解和XML的异同:
使用Annotation之前(甚至在使用之后),XML被广泛的应用于描述元数据。不知何时开始一些应用开发人员和架构师发现XML的维护越来越糟糕了。他们希望使用一些和代码紧耦合的东西,而不是像XML那样和代码是松耦合的(在某些情况下甚至是完全分离的)代码描述。如果你在Google中搜索“XML vs. annotations”,会看到许多关于这个问题的辩论。最有趣的是XML配置其实就是为了分离代码和配置而引入的。上述两种观点可能会让你很疑惑,两者观点似乎构成了一种循环,但各有利弊。下面我们通过一个例子来理解这两者的区别。
假如你想为应用设置很多的常量或参数,这种情况下,XML是一个很好的选择,因为它不会同特定的代码相连。如果你想把某个方法声明为服务,那么使用Annotation会更好一些,因为这种情况下需要注解和方法紧密耦合起来,开发人员也必须认识到这点。
另一个很重要的因素是Annotation定义了一种标准的描述元数据的方式。在这之前,开发人员通常使用他们自己的方式定义元数据。例如,使用标记interfaces,注释,transient关键字等等。每个程序员按照自己的方式定义元数据,而不像Annotation这种标准的方式。
目前,许多框架将XML和Annotation两种方式结合使用,平衡两者之间的利弊。
参考:https://www.cnblogs.com/chanshuyi/p/annotation_serial_01_why_annotation.html
注解应用场景?
注解的保留级别不同,对注解的使用自然存在不同场景。
级别1:源码——APT,Annotation Parse/Process Tools
在编译期能够获取注解与注解声明的类,包括类中的所有成员信息,一般用于生成额外的辅助类。
级别2:字节码——字节码增强
在编译出Class后,通过修改Class数据以实现修改代码逻辑目的。对于是否需要修改的区分或者修改为不同逻辑的判断可以使用注解。
级别3:运行时——反射
在程序运行期间,通过反射技术动态获取注解与其元素,从而完成不同的逻辑判定。
自定义注解?
创建自定义注解
使用 @interface 自定义注解时,自动继承了java.lang.annotation.Annotation
接口,由编译程序自动完成其他细节。在定义注解时,不能继承其他的注解或接口。@interface 用来声明一个注解,其中的每一个方法实际上是声明了一个配置参数。方法的名称就是参数的名称,返回值类型就是参数的类型(返回值类型只能是 基本类型、Class、String、enum)。可以通过 default 来声明参数的默认值。
自定义注解格式:
public @interface 注解名{注解体}
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Inherited
public @interface HelloAnnotation {
String value() default "hi,ZhangYao!";
}
开发注解时,元素的类型只能是:所有基本数据类型(int,float,boolean,byte,double,char,long,short)、String类型、Class类型、enum类型、Annotation类、以上所有类型的数组。
使用自定义注解
注解开发出来本身是不会产生功能的,它必需要被其它的元素”使用”,哪些元素可以使用注解呢?类、属性、方法、局部变量、包… …
当然,你开发注解时,是需要事先指定此注解将来要用在哪些元素上的。你需要使用元注解
来限定。
没有元素的注解叫标记注解
;只有1个元素的注解,一般来说,都会把元素名取为value,叫做单值注解
;有多个元素的注解,就是完整注解
。
根据你要用的注解的API,查看此注解有哪些元素?
根据元素的类型,使用不同的格式,如:
- String 直接使用 “” 括起来
- 基本类型 直接使用 字面量
- 枚举类型 直接使用 枚举常量值
- 注解类型 使用 @注解名
- 数组类型 使用 {} 括起来,多个元素之间使用, 号隔开
public class HelloAnnotationClient {
@HelloAnnotation(value = "Simple Custom Annotation Example")
public void sayHello() {
System.out.println("Inside sayHello method..");
}
}
测试自定义注解
要让注解产生作用,要编写注解的解析/处理程序,这个程序就叫APT,Annotation Parse/Process Tools
。
如何开发APT?? 基于反射
API:
java.lang.reflect.AnnotatedElement 接口
方法:
Annotation[] getDeclaredAnnotations(); //返回直接存在于此元素上的所有注释。与此接口中的其他方法不同,该方法将忽略继承的注释。(如果没有注释直接存在于此元素上,则返回长度为零的一个数组。)该方法的调用者可以随意修改返回的数组;这不会对其他调用者返回的数组产生任何影响。
Annotation[] getAnnotations(); //包含从父类继承的
boolean isAnnotationPresent(Class<? extends Annotation> c);// 判断该程序元素上是否包含指定类型的注解,存在则返回true,否则返回false.
<T extends Annotation> T getAnnotation(Class<T> c); // 返回改程序元素上存在的、指定类型的注解,如果该类型注解不存在,则返回null。
java.lang.annotation.Annotation 接口
Class<? extends Annotaion> annotationType();
测试上面我们自定义的@HelloAnnotation。
/**
* 自定义注解测试
*/
public class HelloAnnotationTest {
public static void main(String[] args)
throws InvocationTargetException, IllegalAccessException, NoSuchMethodException {
HelloAnnotationClient helloAnnotationClient = new HelloAnnotationClient();
Method method = helloAnnotationClient.getClass().getMethod("sayHello", null);
if (method.isAnnotationPresent(HelloAnnotation.class)) {
HelloAnnotation helloAnnotation = method.getAnnotation(HelloAnnotation.class);
//Get value of custom annotation
System.out.println("Value : " + helloAnnotation.value());
//Invoke sayHello method
method.invoke(helloAnnotationClient);
}
}
}
输出结果:
Value : Simple Custom Annotation Example
Inside sayHello method..
Java反射
什么是反射?
百度百科对反射的定义:Java的反射(reflection)机制是指在程序的运行状态中,可以构造任意一个类的对象,可以了解任意一个对象所属的类,可以了解任意一个类的成员变量和方法,可以调用任意一个对象的属性和方法。这种动态获取程序信息以及动态调用对象的功能称为Java语言的反射机制。反射被视为动态语言的关键。
Java反射的原理
要想解剖一个类,必须先要获取到该类的字节码文件对象。而解剖使用的就是Class类中的方法。所以先要获取到每一个字节码文件对应的Class类型的对象。
反射就是把java类中的各种成分映射成一个个的Java对象。
例如:一个类有:成员变量、方法、构造方法、包等等信息,利用反射技术可以对一个类进行解剖,把一个个组成部分映射成一个个对象。(其实:一个类中这些成员方法、构造方法、在加入类中都有一个类来描述)
加载的时候:Class对象的由来是将 .class 文件读入内存,并为之创建一个Class对象。
Class类
Class 类的实例表示正在运行的 Java 应用程序中的类和接口。也就是jvm中有N多的实例每个类都有该Class对象。(包括基本数据类型)
Class 没有公共构造方法。Class 对象是在加载类时由 Java 虚拟机以及通过调用类加载器中的defineClass方法自动构造的。也就是这不需要我们自己去处理创建,JVM已经帮我们创建好了。
我们知道Spring框架可以帮我们创建和管理对象。需要对象时,我们无需自己手动new对象,直接从Spring提供的容器中的Beans获取即可。Beans底层其实就是一个Map<String,Object>,最终通过getBean(“user”)来获取。而这其中最核心的实现就是利用反射技术。
Bean
1、Java面向对象,对象有方法和属性,那么就需要对象实例来调用方法和属性(即实例化);
2、凡是有方法或属性的类都需要实例化,这样才能具象化去使用这些方法和属性;
3、规律:凡是子类及带有方法或属性的类都要加上注册Bean到Spring IoC的注解;(@Component、@Repository、@ Controller 、@Service、@Configration)
4、把Bean理解为类的代理或代言人(实际上确实是通过反射、代理来实现的),这样它就能代表类拥有该拥有的东西了。
5、在Spring中,你标识一个@符号,那么Spring就会来看看,并且从这里拿到一个Bean(注册)或者给出一个Bean(使用)
参考:https://blog.csdn.net/qq_51515673/article/details/124830558
反射API
获取类对应的字节码的对象
获取某个类型在JVM中的Class实例,有如下三种方法:
-
通过类名.class 来获取
Class<Teacher> c = Teacher.class;
-
通过对象来获取
Teacher t1 = new Teacher(); Class<Teacher> c = t1.getClass();
-
通过Class中的一个静态方法 forName
Class<Teacher> c = Class.forName("Teacher全限定名 ");
常用API
API:
java.lang
\- Class
\- Package
java.lang.reflect 包
\- Array 针对数类型的反射工具类
\- Modifier 修饰符
\- AccessibleObject
\- Constructor 构造
\- Method 方法
\- Field 属性
//获取包名、类名
clazz.getPackage().getName()//包名
clazz.getSimpleName()//类名
clazz.getName()//完整类名
//获取成员变量定义信息
getFields()//获取所有公开的成员变量,包括继承变量
getDeclaredFields()//获取本类定义的成员变量,包括私有,但不包括继承的变量
getField(变量名)
getDeclaredField(变量名)
//获取构造方法定义信息
getConstructor(参数类型列表)//获取公开的构造方法
getConstructors()//获取所有的公开的构造方法
getDeclaredConstructors()//获取所有的构造方法,包括私有
getDeclaredConstructor(int.class,String.class)
//获取方法定义信息
getMethods()//获取所有可见的方法,包括继承的方法
getMethod(方法名,参数类型列表)
getDeclaredMethods()//获取本类定义的的方法,包括私有,不包括继承的方法
getDeclaredMethod(方法名,int.class,String.class)
//反射新建实例
clazz.newInstance();//执行无参构造创建对象
clazz.newInstance(222,"Java");//执行有参构造创建对象
clazz.getConstructor(int.class,String.class)//获取构造方法
//反射调用成员变量
clazz.getDeclaredField(变量名);//获取变量
clazz.setAccessible(true);//使私有成员允许访问
f.set(实例,值);//为指定实例的变量赋值,静态变量,第一参数给null
f.get(实例);//访问指定实例变量的值,静态变量,第一参数给null
//反射调用成员方法
Method m = Clazz.getDeclaredMethod(方法名,参数类型列表);
m.setAccessible(true);//使私有方法允许被调用
m.invoke(实例,参数数据);//让指定实例来执行该方法
//例如:
public class A {
void ma() { ... }
void ma(int a, int b) { ... }
void mb(double a, double b) { ... }
}
Class<A> c = A.class;
Method m1 = c.getDeclaredMethod("ma",Integer.class, Integer.TYPE);
Method m2 = c.getDeclaredMethod("ma");
反射应用场景
1、配合注解的使用
使用注解的实现原理它的底层实现就是java反射。
2、编辑基础框架
有一句话这么说来着:反射机制是很多Java框架的基石,经典的就是在xml文件或者properties里面写好了配置,然后在Java类里面解析xml或properties里面的内容,得到一个字符串,然后用反射机制,根据这个字符串获得某个类的Class实例,这样就可以动态配置一些东西,spring,Hibernate底层都有类似的实现。
3、动态加载配置类
其他在编码阶段不知道那个类名,要在运行期从配置文件读取类名配置。
这段代码想必大家肯定都有写过,这个数据库的连接驱动类就是编译的时候不知道你到底是用的mysql,oracle还是其他数据库,而是由运行期动态加载的。
// 1.加载驱动程序
Class.forName("com.mysql.jdbc.Driver");
// 2.获得数据库的连接
Connection conn = DriverManager.getConnection(URL, NAME, PASSWORD);
// 3.通过数据库的连接操作数据库,实现增删改查
Statement stmt = conn.createStatement();
ResultSet rs = stmt
.executeQuery("select user_name,age from user");
while (rs.next()) {// 如果对象中有数据,就会循环打印出来
System.out.println(rs.getString("user_name") + ","
+ rs.getInt("age"));
}
以上介绍了反射的应用场景,程序猿开发业务代码中应尽量少用反射,一个是代码可读性不是特别好,第二是反射需要运行期JVM去重新解析性能上也没有直接使用好,唯一比较合理的地方是业务中需要用到AOP可以大大简化业务代码建议使用。
JDK动态代理
我们知道 Spring 主要有两大思想,一个是 IOC,一个就是 AOP。对于 IOC 容器中 bean 的创建就是使用了 Java 的反射技术;而对于 Spring 的核心 AOP 来说,我们知道 AOP 的实现原理之一就是 JDK 的动态代理机制,而 JDK 的动态代理本质就是利用了反射技术来实现的。
JDK 的动态代理机制中,有两个重要的类和接口,一个是 InvocationHandler 接口、一个是 Proxy 类,这一个类和接口是实现我们动态代理所必须用到的。
Proxy 类
Proxy 类来自于 java.lang.reflect 包下,它就是用来动态创建一个代理对象的类,它提供了许多的方法,但是我们用的最多的就是 newProxyInstance() 这个方法。
Object proxy = Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
- loader:一个 ClassLoader 类加载器对象,定义了由哪个 ClassLoader 对象来对生成的代理对象进行加载
- interfaces:一个 Interface 对象的数组,表示的是我将要给我需要代理的对象提供一组什么接口,如果我提供了一组接口给它,那么这个代理对象就宣称实现了该接口(多态),这样我代理对象就能调用这组接口中的方法了
- h:一个 InvocationHandler 对象,表示的是当我这个动态代理对象在调用方法的时候,会关联到哪一个 InvocationHandler 对象上。
通过Proxy.newProxyInstance() 创建的代理对象是在 JVM 运行时动态生成的一个对象 ,它并不是我们的 InvocationHandler 类型,也不是我们定义的那组接口的类型,而是在运行是动态生成的一个对象。
InvocationHandler 接口
InvocationHandler 接口也来自于 java.lang.reflect 包下。每一个动态代理类都必须要实现 InvocationHandler 这个接口,并且每个代理类的实例都关联到了一个 handler,当我们通过代理对象调用一个方法的时候,这个方法的调用就会被转发为由 InvocationHandler 这个接口的 invoke() 方法来进行调用。
package java.lang.reflect;
public interface InvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
}
- proxy:指代我们所代理的那个真实对象
- method:指代的是我们所要调用真实对象的某个方法的 Method 对象
- args:指代的是调用真实对象某个方法时接受的参数
参考:https://blog.csdn.net/weixin_38192427/article/details/120347850