首页 > 应用开发 > Java > 死磕java底层(三)—反射、动态代理和注解
1.1反射
反射是指程序可以访问,检测,修改它本身状态或行为的一种能力。
1.2java的反射机制
java的反射机制是指在程序运行状态中,给定任意一个类,都可以获取到这个类的属性和方法;给定任意一个对象都可以调用这个对象的属性和方法,这种动态的获取类的信息和调用对象的方法的功能称之为java的反射机制。 一言以蔽之:反射机制可以让你在程序运行时,拿到任意一个类的属性和方法并调用它。
1.3java反射的主要功能
运行时构造一个类的对象;
运行时获取一个类所具有的的成员变量和方法;
运行时调用任意一个对象的方法;
生成动态代理; 其实反射最主要的功能我觉得是与框架搭配使用。
1.4java类类型
想要理解反射首先需要知道Class这个类,它的全称是java.lang.Class类。java是面向对象的语言,讲究万物皆对象,即使强大到一个类,它依然是另一个类(Class类)的对象,换句话说,普通类是Class类的对象,即Class是所有类的类(There is a class named Class)。 对于普通的对象,我们一般会这样创建:
Code code1 = new Code();
上面说了,所有的类都是Class的对象,那么如何表示呢,可不可以通过如下方式呢:
Class c = new Class();
但是我们查看Class的源码时,是这样写的:
private
Class
(
ClassLoader loader
)
{
classLoader
= loader
;
}
可以看到构造器是私有的,只有JVM才可以调用这个构造函数创建Class的对象,因此不可以像普通类一样new一个Class对象,虽然我们不能new一个Class对象,但是却可以通过已有的类得到一个Class对象,共有三种方式,如下:
Class c1
= Test
.class
; 这说明任何一个类都有一个隐含的静态成员变量
class,这种方式是通过获取类的静态成员变量
class得到的
Class c2
= test
.
getClass
(
)
;
test是Test类的一个对象,这种方式是通过一个类的对象的getClass
(
)方法获得的
Class c3
= Class
.
forName
(
"com.catchu.me.reflect.Test"
)
; 这种方法是Class类调用forName方法,通过一个类的全量限定名获得
这里,c1、c2、c3都是Class的对象,他们是完全一样的,而且有个学名,叫做Test的类类型(class type)。 这里就让人奇怪了,前面不是说Test是Class的对象吗,而c1、c2、c3也是Class的对象,那么Test和c1、c2、c3不就一样了吗?为什么还叫Test什么类类型?这里不要纠结于它们是否相同,只要理解类类型是干什么的就好了,顾名思义,类类型就是类的类型,也就是描述一个类是什么,都有哪些东西,所以我们可以通过类类型知道一个类的属性和方法,并且可以调用一个类的属性和方法,这就是反射的基础。 示例代码:
public
class
Test
{
//java学习交流:737251827 进入可领取学习资源及对十年开发经验大佬提问,免费解答!
public
static
void
main
(String
[
] args
) throws ClassNotFoundException
{
Class
<Test
> class1
= Test
.class
;
System
.out
.
println
(
"类名1:"
+class1
.
getName
(
)
)
;
Test Test
=
new
Test
(
)
;
Class
<
?
extends
Test
> class2
= Test
.
getClass
(
)
;
System
.out
.
println
(
"类名2:"
+class2
.
getName
(
)
)
;
Class
<
?
> class3
= Class
.
forName
(
"com.catchu.me.reflect.Test"
)
;
System
.out
.
println
(
"类名3:"
+class3
.
getName
(
)
)
;
if
(class1
==class2
)
{
System
.out
.
println
(
"class1==class2"
)
;
}
if
(class1
==class3
)
{
System
.out
.
println
(
"class1==class3"
)
;
}
}
}
输出结果:
类名
1:com
.catchu
.me
.reflect
.Test
类名
2:com
.catchu
.me
.reflect
.Test
类名
3:com
.catchu
.me
.reflect
.Test
class1
==class2
class1
==class3
2.反射的操作
java的反射操作主要是用到了java.lang.Class类和java.lang.reflect反射包下的类,上面说到我们已经可以拿到一个类的Class信息,根据这个Class我们就可以使用某些方法来操作(获取)类的以下信息:
2.1操作构造函数
万物皆对象,类的构造函数是java.lang.reflect.Constructor类的对象,通过Class的下列方法可以获取构造函数对象:
public Constructor
<
T
>
getDeclaredConstructor
(Class
<
?
>
... parameterTypes
)
// 获得该类所有的构造器,不包括其父类的构造器
public Constructor
<
T
>
getConstructor
(Class
<
?
>
... parameterTypes
)
// 获得该类所有public构造器,包括父类
//具体
Constructor
<
?
>
[
] allConstructors
= class1
.
getDeclaredConstructors
(
)
;
//获取class对象的所有声明构造函数
Constructor
<
?
>
[
] publicConstructors
= class1
.
getConstructors
(
)
;
//获取class对象public构造函数
Constructor
<
?
> constructor
= class1
.
getDeclaredConstructor
(String
.class
)
;
//获取指定声明构造函数(局部变量是一个字符串类型的)
Constructor publicConstructor
= class1
.
getConstructor
(String
.class
)
;
//获取指定声明的public构造函数
测试代码如下:
public
class
TestConstructor
{
public
static
void
main
(String
[
] args
) throws Exception
{
Class
<
?
> personClass
= Class
.
forName
(
"com.catchu.me.reflect.Person"
)
;
//获取所有的构造函数,包括私有的,不包括父类的
Constructor
<
?
>
[
] allConstructors
= personClass
.
getDeclaredConstructors
(
)
;
//获取所有公有的构造函数,包括父类的
Constructor
<
?
>
[
] publicConstructors
= personClass
.
getConstructors
(
)
;
System
.out
.
println
(
"遍历之后的构造函数:"
)
;
for
(Constructor c1
: allConstructors
)
{
System
.out
.
println
(c1
)
;
}
Constructor
<
?
> c2
= personClass
.
getDeclaredConstructor
(String
.class
)
;
c2
.
setAccessible
(
true
)
;
//设置是否可访问,因为该构造器是private的,所以要手动设置允许访问,如果构造器是public的就不用设置
Object person
= c2
.
newInstance
(
"尿床"
)
;
//使用反射创建Person类的对象,并传入参数
System
.out
.
println
(person
.
toString
(
)
)
;
}
}
Person类如下,为测出效果包含一个私有构造函数:
public
class
Person
{
private int age
;
private String name
;
public
Person
(
)
{
}
private
Person
(
String name
)
{
this
.name
= name
;
}
public
Person
(
int age
,String name
)
{
this
.age
= age
;
this
.name
= name
;
}
//省略set/get方法
@Override
public String
toString
(
)
{
return
"Person{"
+
"age="
+ age
+
", name='"
+ name
+
'\''
+
'}'
;
}
}
测试结果如下:
遍历之后的构造函数:
遍历之后的构造函数:
public com
.catchu
.me
.reflect
.
Person
(int
,java
.lang
.String
)
private com
.catchu
.me
.reflect
.
Person
(java
.lang
.String
)
public com
.catchu
.me
.reflect
.
Person
(
)
Person
{age
=
0
, name
=
'尿床'
}
由上面可以看到我们在获得某个类的Class类类型之后,可以通过反射包中的方法获取到这个类的构造函数,进而可以创建该类的对象。
2.2操作成员变量
万物皆对象,类的成员变量是java.lang.reflect.Field类的对象,通过Class类的以下方法可以获取某个类的成员变量,值得一提的是变量是包含两部分的,变量类型和变量名:
public Field
getDeclaredField
(String name
)
// 获得该类自身声明的所有变量,不包括其父类的变量
public Field
getField
(String name
)
// 获得该类自所有的public成员变量,包括其父类变量
//具体实现
Field
[
] allFields
= class1
.
getDeclaredFields
(
)
;
//获取class对象的所有属性
Field
[
] publicFields
= class1
.
getFields
(
)
;
//获取class对象的public属性
Field ageField
= class1
.
getDeclaredField
(
"age"
)
;
//获取class指定属性
Field desField
= class1
.
getField
(
"des"
)
;
//获取class指定的public属性
示例代码如下:
public
class
TestField
{
public
static
void
main
(String
[
] args
) throws Exception
{
Class
<Person
> personClass
= Person
.class
;
//获取所有的成员变量,包含私有的
Field
[
] allFields
= personClass
.
getDeclaredFields
(
)
;
//获取所有公有的成员变量,包含父类的
Field
[
] publicFields
= personClass
.
getFields
(
)
;
System
.out
.
println
(
"所有的成员变量:"
)
;
for
(Field f
: allFields
)
{
System
.out
.
println
(f
)
;
}
//获取某个变量的值
//创建对象的实例
Constructor
<Person
> c
= personClass
.
getDeclaredConstructor
(String
.class
)
;
c
.
setAccessible
(
true
)
;
//因为该构造函数时私有的,需要在这里设置成可访问的
Person person
= c
.
newInstance
(
"尿床"
)
;
//获取变量name对象
Field field
= personClass
.
getDeclaredField
(
"name"
)
;
field
.
setAccessible
(
true
)
;
//因为变量name是私有的,需要在这里设置成可访问的
//注意对比下面这两行,官方对field.get(Object obj)方法的解释是返回对象obj字段field的值
Object value
= field
.
get
(person
)
;
//String name = person.getName();
System
.out
.
println
(
"获取的变量的值是:"
+value
)
;
}
}
输出结果如下:
所有的成员变量:
private int com
.catchu
.me
.reflect
.Person
.age
private java
.lang
.String com
.catchu
.me
.reflect
.Person
.name
获取的变量的值是:尿床
这里要注意field.get(person)方法,我们根据对象获取属性的常规方法是通过:String name = person.getName(),反射中可以通过:字段.get(对象),这也是获取对象的某个字段,有点类似于invoke方法。
2.3操作成员方法
万物皆对象,类的成员方法是java.lang.reflect.Method的对象,通过java.lang.Class类的以下方法可以获取到类的成员方法,通过方法类Method提供的一些方法,又可以调用获取到的成员方法。
public Method
getDeclaredMethod
(String name
, Class
<
?
>
... parameterTypes
)
// 得到该类所有的方法,不包括父类的
public Method
getMethod
(String name
, Class
<
?
>
... parameterTypes
)
// 得到该类所有的public方法,包括父类的
//具体使用
Method
[
] methods
= class1
.
getDeclaredMethods
(
)
;
//获取class对象的所有声明方法
Method
[
] allMethods
= class1
.
getMethods
(
)
;
//获取class对象的所有public方法 包括父类的方法
Method method
= class1
.
getMethod
(
"info"
, String
.class
)
;
//返回此class1对应的public修饰的方法名是info的,包含一个String类型变量的方法
Method declaredMethod
= class1
.
getDeclaredMethod
(
"info"
, String
.class
)
;
//返回此Class对象对应类的、带指定形参列表的方法
测试代码如下:
public
class
TestMethod
{
public
static
void
main
(String
[
] args
) throws Exception
{
Person person
=
new
Person
(
)
;
Class
<
?
extends
Person
> personClass
= person
.
getClass
(
)
;
Method
[
] allMethods
= personClass
.
getDeclaredMethods
(
)
;
Method
[
] publicMethods
= personClass
.
getMethods
(
)
;
System
.out
.
println
(
"遍历所有的方法:"
)
;
for
(Method m
: allMethods
)
{
System
.out
.
println
(m
)
;
}
//下面是测试通过反射调用函数
//通过反射创建实例对象,默认调无参构造函数
Person person2
= personClass
.
newInstance
(
)
;
//获取要调用的方法,要调用study方法,包含int和String参数,注意int和Integer在这有区别
Method method
= personClass
.
getMethod
(
"study"
, int
.class
, String
.class
)
;
Object o
= method
.
invoke
(person2
,
18
,
"尿床"
)
;
}
}
测试结果:
遍历所有的方法:
public java
.lang
.String com
.catchu
.me
.reflect
.Person
.
toString
(
)
public java
.lang
.String com
.catchu
.me
.reflect
.Person
.
getName
(
)
public
void com
.catchu
.me
.reflect
.Person
.
setName
(java
.lang
.String
)
public
void com
.catchu
.me
.reflect
.Person
.
study
(int
,java
.lang
.String
)
public int com
.catchu
.me
.reflect
.Person
.
getAge
(
)
public
void com
.catchu
.me
.reflect
.Person
.
setAge
(int
)
我叫尿床
,我今年
18
,我在学习反射
注意:Object o = method.invoke(person2, 18, "尿床");就是调用person2对象的method方法,格式是:方法名.invoke(对象,参数),类似于获取成员变量值时的get方法。 由上面可以看出反射的强大:通过反射我们可以获取到类类型,通过Class类型我们可以获取到构造函数,进而实例化new出一个对象;通过反射我们可以获取到成员变量和成员方法,通过实例出的对象又可以获取到这些成员变量的值或调用成员方法。这才只是反射的一部分,通过反射我们还可以判断类,变量,方法,是否包含某些特定注解,还可以通过反射来动态代理去调用其它方法,跟注解和动态代理挂起勾会有无限的想象空间,比如spring框架,底层就是通过这些原理。下面在说几个反射常用的API,最后会介绍反射跟注解和动态代理的结合使用。
2.4其它方法
注解中常用的方法:
Annotation
[
] annotations
=
(Annotation
[
]
) class1
.
getAnnotations
(
)
;
//获取class对象的所有注解
Annotation annotation
=
(Annotation
) class1
.
getAnnotation
(Deprecated
.class
)
;
//获取class对象指定注解
Type genericSuperclass
= class1
.
getGenericSuperclass
(
)
;
//获取class对象的直接超类的
Type Type
[
] interfaceTypes
= class1
.
getGenericInterfaces
(
)
;
//获取class对象的所有接口的type集合
获取Class对象其它信息的方法:
boolean isPrimitive
= class1
.
isPrimitive
(
)
;
//判断是否是基础类型
boolean isArray
= class1
.
isArray
(
)
;
//判断是否是集合类
boolean isAnnotation
= class1
.
isAnnotation
(
)
;
//判断是否是注解类
boolean isInterface
= class1
.
isInterface
(
)
;
//判断是否是接口类
boolean isEnum
= class1
.
isEnum
(
)
;
//判断是否是枚举类
boolean isAnonymousClass
= class1
.
isAnonymousClass
(
)
;
//判断是否是匿名内部类
boolean isAnnotationPresent
= class1
.
isAnnotationPresent
(Deprecated
.class
)
;
//判断是否被某个注解类修饰
String className
= class1
.
getName
(
)
;
//获取class名字 包含包名路径
Package aPackage
= class1
.
getPackage
(
)
;
//获取class的包信息
String simpleName
= class1
.
getSimpleName
(
)
;
//获取class类名
int modifiers
= class1
.
getModifiers
(
)
;
//获取class访问权限
Class
<
?
>
[
] declaredClasses
= class1
.
getDeclaredClasses
(
)
;
//内部类
Class
<
?
> declaringClass
= class1
.
getDeclaringClass
(
)
;
//外部类
ClassLoader ClassLoader
= class1
.
getClassLoader
(
) 返回类加载器
getSuperclass
(
):获取某类所有的父类
getInterfaces
(
):获取某类所有实现的接口
3.动态代理
代理的操作是通过java.lang.reflect.Proxy 类中实现的,通过Proxy的newProxyInstance()方法可以创建一个代理对象,如下:
public
static Object
newProxyInstance
(
ClassLoader loader
,类
<
?
>
[
] interfaces
,InvocationHandler h
)
不要看到这里面一大坨晦涩的屎代码就害怕,这里面是有技巧的,其实都是模板,需要什么,我们传什么过去就可以了。可以看到需要三个参数,类加载器,接口和调用处理者。我们在上面已经能拿到Class类了,使用class.getClassLoader就可以获取类加载器,使用class.getgetInterfaces()可以获取所有的接口,那现在要写的不就是新建一个InvocationHandler对象了吗?事实上,我们动态代理的核心代码也就是在这里面写的。我上面说的模板,其实就是下面这几步:
书写代理类和代理方法,在代理方法中实现代理Proxy.newProxyInstance();
代理中需要的参数分别为:被代理的类的类加载器class.getClassLoader(),被代理类的所有实现接口new Class[] { Interface.class },句柄方法new InvocationHandler();
在句柄方法中重写invoke方法,invoke方法的输入有3个参数Object proxy(代理类对象), Method method(被代理类的方法),Object[] args(被代理类方法的传入参数),在这个方法中,我们可以定制化的写我们的业务;
获取代理类,强转成被代理的接口;
最后,我们可以像没被代理一样,调用接口的任何方法,方法被调用后,方法名和参数列表将被传入代理类的invoke方法中,进行新业务的逻辑流程。 看下面的示例代码: 接口PersonInterface:
public
interface
PersonInterface
{
void
doSomething
(
)
;
void
saySomething
(
)
;
}
接口的实现类:
public
class
PersonImpl
implements
PersonInterface
{
@Override
public
void
doSomething
(
)
{
System
.out
.
println
(
"人类在做事"
)
;
}
@Override
public
void
saySomething
(
)
{
System
.out
.
println
(
"人类在说话"
)
;
}
}
代理类:
/**
* @author 尿床
*/
public
class
PersonProxy
{
public
static
void
main
(
String
[
] args
)
{
final PersonImpl person
=
new
PersonImpl
(
)
;
PersonInterface proxyPerson
=
(PersonInterface
) Proxy
.
newProxyInstance
(PersonImpl
.class
.
getClassLoader
(
)
,
PersonImpl
.class
.
getInterfaces
(
)
,
new
InvocationHandler
(
)
{
//在下面的invoke方法里面写我们的业务
@Override
public Object
invoke
(Object proxy
, Method method
, Object
[
] args
) throws Throwable
{
if
(method
.
getName
(
)
==
"doSomething"
)
{
person
.
doSomething
(
)
;
System
.out
.
println
(
"通过常规方法调用了实现类"
)
;
}
else
{
method
.
invoke
(person
,args
)
;
System
.out
.
println
(
"通过反射机制调用了实现类"
)
;
}
return
null
;
}
}
)
;
proxyPerson
.
doSomething
(
)
;
proxyPerson
.
saySomething
(
)
;
}
}
执行结果如下:
人类在做事
通过常规方法调用了实现类
人类在说话
通过反射机制调用了实现类
在我们通过proxyPerson.doSomething()调用的时候,其实不是立马进入实现类的doSomething方法,而是带着方法名,参数进入到了我们的代理方法invoke里面,在这里面我进行了一次判断,如果等于"doSomething"就使用常规方法调用,否则使用反射的方法调用。这样看似还是平时的调用,但是每次执行都要走我们的代理方法里面,我们可以在这里面做些“手脚”,加入我们的业务处理。 可以看下面另一个示例,比如天猫一件衣服正常卖50,现在你是我的vip用户,可以给你打折扣10块,其它业务都是相同的,只有这里便宜了10,重新服务提供者就很麻烦,用代理可以解决这个问题。 接口SaleService:
public
interface
SaleService
{
//根据尺码返回衣服的大小
int
clothes
(String size
)
;
}
接口实现类SaleServiceImpl:
public
class
SaleServiceImpl
implements
SaleService
{
@Override
public int
clothes
(
String size
)
{
System
.out
.
println
(
"衣服大小"
+size
)
;
//模拟从数据库取衣服价格
return
50
;
}
}
普通无折扣的调用测试:
/**
* @author 尿床
* @Description 普通用户
*/
public
class
OrdinaryCustom
{
public
static
void
main
(
String
[
] args
)
{
SaleService saleService
=
new
SaleServiceImpl
(
)
;
int money
= saleService
.
clothes
(
"XXl"
)
;
System
.out
.
println
(
"价格是:"
+money
)
;
}
}
输出结果:
衣服大小XXl
价格是:
50
代理类ProxySale:
/**
* @author 尿床
* @Description 代理类
*/
public
class
ProxySale
{
//对接口方法进行代理
public
static
<
T
>
T
getProxy
(
final int discount
, final Class
<SaleServiceImpl
> implementClasses
, Class
<SaleService
> interfaceClasses
)
{
return
(
T
)Proxy
.
newProxyInstance
(implementClasses
.
getClassLoader
(
)
,
implementClasses
.
getInterfaces
(
)
,
new
InvocationHandler
(
)
{
@Override
public Object
invoke
(Object proxy
, Method method
, Object
[
] args
) throws Throwable
{
//调用原始对象的方法,获取未打折之前的价格
int price
=
(int
) method
.
invoke
(implementClasses
.
newInstance
(
)
, args
)
;
return price
-discount
;
}
}
)
;
}
}
vip用户测试类VipCustom:
/**
* @author 尿床
* @Description Vip用户,有打折优惠
*/
public
class
VipCustom
{
public
static
void
main
(
String
[
] args
)
{
//vip用户,打10元折扣
int discount
=
10
;
SaleService saleService
= ProxySale
.
getProxy
(discount
, SaleServiceImpl
.class
, SaleService
.class
)
;
int money
= saleService
.
clothes
(
"xxl"
)
;
System
.out
.
println
(
"价格是:"
+money
)
;
}
}
输出结果是:
衣服大小xxl
价格是:
40
可以看到,在未修改服务提供者的情况下,我们在代理类里面做了手脚,结果符合预期。
4.注解
4.1概念及作用
概念
注解即元数据,就是源代码的元数据
注解在代码中添加信息提供了一种形式化的方法,可以在后续中更方便的 使用这些数据
Annotation是一种应用于类、方法、参数、变量、构造器及包声明中的特殊修饰符。它是一种由JSR-175标准选择用来描述元数据的一种工具。
作用
生成文档
跟踪代码依赖性,实现替代配置文件功能,减少配置。如Spring中的一些注解
在编译时进行格式检查,如@Override等
每当你创建描述符性质的类或者接口时,一旦其中包含重复性的工作,就可以考虑使用注解来简化与自动化该过程。
4.2java注解
什么是java注解? 在java语法中,使用@符号作为开头,并在@后面紧跟注解名。被运用于类,接口,方法和字段之上,java中的注解包是java.lang.annotation,例如:
@Override
void
myMethod
(
)
{
...
...
}
这其中@Override就是注解。这个注解的作用也就是告诉编译器,myMethod()方法覆盖了父类中的myMethod()方法。
java中内置的注解 java中有三个内置的注解:
@Override
:表示当前的方法定义将覆盖超类中的方法,如果出现错误,编译器就会报错。
@Deprecated
:如果使用此注解,编译器会出现警告信息。
@SuppressWarnings
:忽略编译器的警告信息。
元注解 元注解的作用就是负责注解其他注解(自定义注解的时候用到的)。Java5.0定义了4个标准的meta-annotation类型,它们被用来提供对其它 annotation类型作说明。 Java5.0定义的4个元注解:
@Target
@Retention
@Documented
@Inherited
java8加了两个新注解,后续我会讲到。
@Target
@Target说明了Annotation所修饰的对象范围:Annotation可被用于 packages、types(类、接口、枚举、Annotation类型)、类型成员(方法、构造方法、成员变量、枚举值)、方法参数和本地变量(如循环变量、catch参数)。在Annotation类型的声明中使用了target可更加明晰其修饰的目标。
作用:用于描述注解的使用范围(即:被描述的注解可以用在什么地方) 取值(ElementType)有:
类型
用途
CONSTRUCTOR
用于描述构造器
FIELD
用于描述域
LOCAL_VARIABLE
用于描述局部变量
METHOD
用于描述方法
PACKAGE
用于描述包
PARAMETER
用于描述参数
TYPE
用于描述类、接口(包括注解类型) 或enum声明
比如定义下面一个注解,它就只能用在方法上,因为已经限定了它是方法级别的注解,如果用在类或者其它上面,编译阶段就会报错:
@
Target
(
{ElementType
.
METHOD
}
)
public @
interface
MyMethodAnnotation
{
}
测试类MyClass:
//@MyMethodAnnotation 报错,方法级别注解不能注在类头上
public
class
MyClass
{
@MyMethodAnnotation
public
void
myTestMethod
(
)
{
//
}
}
@Retention
@Retention定义了该Annotation被保留的时间长短:某些Annotation仅出现在源代码中,而被编译器丢弃;而另一些却被编译在class文件中;编译在class文件中的Annotation可能会被虚拟机忽略,而另一些在class被装载时将被读取(请注意并不影响class的执行,因为Annotation与class在使用上是被分离的)。使用这个meta-Annotation可以对 Annotation的“生命周期”限制。
作用:表示需要在什么级别保存该注释信息,用于描述注解的生命周期(即:被描述的注解在什么范围内有效) 取值(RetentionPoicy)有:
类型
用途
说明
SOURCE
在源文件中有效(即源文件保留)
仅出现在源代码中,而被编译器丢弃
CLASS
在class文件中有效(即class保留)
被编译在class文件中
RUNTIME
在运行时有效(即运行时保留)
编译在class文件中
示例:
@
Target
(
{ElementType
.
TYPE
}
)
//用在描述类、接口或enum
@
Retention
(RetentionPolicy
.
RUNTIME
)
//运行时有效
public @
interface
MyClassAnnotation
{
String
value
(
)
;
//这个MyClassAnnotation注解有个value属性,将来可以设置/获取值
}
@Documented
@Documented用于描述其它类型的annotation应该被作为被标注的程序成员的公共API,因此可以被例如javadoc此类的工具文档化。Documented是一个标记注解,没有成员。
作用:将注解包含在javadoc中
java
.lang
.annotation
.Documented
@Documented
public @
interface
MyCustomAnnotation
{
//Annotation body}
@Inherited
是一个标记注解,阐述了某个被标注的类型是被继承的,使用了@Inherited修饰的annotation类型被用于一个class,则这个annotation将被用于该class的子类,@Inherited annotation类型是被标注过的class的子类所继承。类并不从实现的接口继承annotation,方法不从它所重载的方法继承annotation,当@Inherited annotation类型标注的annotation的Retention是RetentionPolicy.RUNTIME,则反射API增强了这种继承性。如果我们使用java.lang.reflect去查询一个@Inherited annotation类型的annotation时,反射代码检查将展开工作:检查class和其父类,直到发现指定的annotation类型被发现,或者到达类继承结构的顶层。
作用:允许子类继承父类中的注解 示例,这里的MyParentClass 使用的注解标注了@Inherited,所以子类可以继承这个注解信息:
java
.lang
.annotation
.Inherited
@Inherited
public @
interface
MyCustomAnnotation
{
}
@MyCustomAnnotation
public
class
MyParentClass
{
...
}
public
class
MyChildClass
extends
MyParentClass
{
...
}
4.3自定义注解
格式
public @
interface 注解名
{
定义体
}
注解参数的可支持数据类型:
所有基本数据类型(int,float,double,boolean,byte,char,long,short)
String 类型
Class类型
enum类型
Annotation类型
以上所有类型的数组
规则
修饰符只能是public 或默认(default)
参数成员只能用基本类型byte,short,int,long,float,double,boolean八种基本类型和String,Enum,Class,annotations及这些类型的数组
如果只有一个参数成员,最好将名称设为”value”
注解元素必须有确定的值,可以在注解中定义默认值,也可以使用注解时指定,非基本类型的值不可为null,常使用空字符串或0作默认值
在表现一个元素存在或缺失的状态时,定义一下特殊值来表示,如空字符串或负值
示例:
@
Target
(ElementType
.
FIELD
)
@
Retention
(value
=RetentionPolicy
.
RUNTIME
)
@Documented
public @
interface
MyFieldAnnotation
{
int
id
(
)
default
0
;
String
name
(
)
default
""
;
}
定义了一个用在字段上的,运行时有效的名为MyFieldAnnotation的注解,它有两个属性,int类型的id(id后面记得带括号)默认值是0,还有一个String类型的name,默认值是""。
4.4注解的操作—注解处理类库
在上面我们已经知道了怎么自定义一个注解了,但是光定义没用啊,重要的是我要使用它,使用的方法也很简单,最上面讲反射的时候也提到过几个这样的方法了,比如:class1.isAnnotation(),其实他们统统是java.lang.reflect包下的AnnotatedElement接口里面的方法,这个接口主要有以下几个实现类:
Class:类定义
Constructor:构造器定义
Field:累的成员变量定义
Method:类的方法定义
Package:类的包定义 java.lang.reflect 包下主要包含一些实现反射功能的工具类,实际上,java.lang.reflect 包所有提供的反射API扩充了读取运行时Annotation信息的能力。当一个Annotation类型被定义为运行时的Annotation后,该注解才能是运行时可见,当class文件被装载时被保存在class文件中的Annotation才会被虚拟机读取。 AnnotatedElement 接口是所有程序元素(Class、Method和Constructor)的父接口,所以程序通过反射获取了某个类的AnnotatedElement对象之后,程序就可以调用该对象的如下四个个方法来访问Annotation信息:
方法1: T getAnnotation(Class annotationClass): 返回程序元素上存在的、指定类型的注解,如果该类型注解不存在,则返回null。
方法2:Annotation[] getAnnotations():返回该程序元素上存在的所有注解。
方法3:boolean is AnnotationPresent(Class<?extends Annotation> annotationClass):判断该程序元素上是否包含指定类型的注解,存在则返回true,否则返回false.
方法4:Annotation[] getDeclaredAnnotations():返回直接存在于此元素上的所有注释。与此接口中的其他方法不同,该方法将忽略继承的注释。(如果没有注释直接存在于此元素上,则返回长度为零的一个数组。)该方法的调用者可以随意修改返回的数组;这不会对其他调用者返回的数组产生任何影响。 总结:定义注解用的是java.lang.annotation.Annotation,操作注解是用java.lang.reflect.AnnotatedElement
测试代码CustomClassAnnotation如下:
/**
* @author 尿床
* @Description 自定义类注解
*/
@
Target
(ElementType
.
TYPE
)
//作用在类,枚举或接口上
@
Retention
(RetentionPolicy
.
RUNTIME
)
//运行时有效
@Documented
//文档可见
public @
interface
CustomClassAnnotation
{
String
value
(
)
;
//获取注解名称
}
FruitName类如下:
/**
*java学习交流:737251827 进入可领取学习资源及对十年开发经验大佬提问,免费解答!
* @author 尿床
* @Description 字段注解(字符串类型的)
*/
@
Target
(ElementType
.
FIELD
)
//用在字段上
@
Retention
(RetentionPolicy
.
RUNTIME
)
@Documented
public @
interface
FruitName
{
String
name
(
)
default
""
;
}
FruitColor类如下:
/**
* @author 尿床
* @Description 字段注解(枚举类型的)
*/
@
Target
(ElementType
.
FIELD
)
@
Retention
(RetentionPolicy
.
RUNTIME
)
@Documented
public @
interface
FruitColor
{
//颜色枚举
enum Color
{
BLUE
,
RED
,
GREEN
}
;
//颜色属性
Color
color
(
)
default Color
.
RED
;
}
Fruit实体类如下:
/**
* @author 尿床
* @Description Fruit实体类
*/
@
CustomClassAnnotation
(value
=
"fruit"
)
public
class
Fruit
{
@
FruitName
(name
=
"apple"
)
private String name
;
@
FruitColor
(color
= FruitColor
.Color
.
RED
)
private String color
;
}
测试类TestAnnotation如下:
/**
* @author 尿床
* @Description 测试类
*/
public
class
TestAnnotation
{
public
static
void
main
(
String
[
] args
)
{
Class
<Fruit
> clazz
= Fruit
.class
;
//反射获取Class对象
CustomClassAnnotation annotation
= clazz
.
getAnnotation
(CustomClassAnnotation
.class
)
;
//拿到Fruit类的注解
if
(
null
!=annotation
&&
"fruit"
.
equals
(annotation
.
value
(
)
)
)
{
System
.out
.
println
(
"Fruit类的注解名是======"
+annotation
.
value
(
)
)
;
//获取所有的属性遍历,拿到每一个属性的值
Field
[
] allFields
= clazz
.
getDeclaredFields
(
)
;
for
(Field field
: allFields
)
{
if
(field
.
isAnnotationPresent
(FruitName
.class
)
)
{
//判断是否存在FruitName注解
FruitName fruitName
= field
.
getAnnotation
(FruitName
.class
)
;
System
.out
.
println
(
"水果名称====="
+fruitName
.
name
(
)
)
;
}
if
(field
.
isAnnotationPresent
(FruitColor
.class
)
)
{
FruitColor fruitColor
= field
.
getAnnotation
(FruitColor
.class
)
;
System
.out
.
println
(
"水果颜色====="
+fruitColor
.
color
(
)
)
;
}
}
}
else
{
System
.out
.
println
(
"注解值不对,请检查"
)
;
}
}
}
总结:通过注解可以获取到类名,接口名,方法名,属性名,再搭配反射可以动态的生成对象,再搭配动态代理,去动态的去调用某些方法,这基本上也就是spring框架底层实现原理的一部分了。 注:本文一些内容引用自http://t.cn/RK8ci8w ,原作者反射和注解写的确实不错,我对部分内容进行了完善并重写了部分示例;动态代理部分我加的,考虑着把反射,代理,注解放在一块来说。
来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/70010294/viewspace-2845824/ ,如需转载,请注明出处,否则将追究法律责任。