注解(Annotation)
1、注解的理解:
-
Annotation 其实就是代码里的特殊标记, 这些标记可以在编译, 类加载, 运行时被读取(与注释的区别), 并执行相应的处理。通过使用 Annotation,程序员可以在不改变原逻辑的情况下, 在源文件中嵌入一些补充信息。
-
Annotation可以像修饰符一样使用,可以用来修饰包、类、构造器、方法、成员变量、参数、局部变量的声明,这些信息被保存在Annotation的“name = value”对中。
-
在JavaSE中,注解的使用目的比较简单,例如标记过时的功能,忽略警告等。在JavaEE/Android 中注解占据了更重要的角色,例如用来配置应用程序的任何切面,代替JavaEE旧版中所遗留的繁冗 代码和XML配置等。
2、内置注解:
@Override:定义在java.lang.Override中,此注解只设用于修饰方法,表示这个方法打算重写父类中的另一个方法,并且可以检查是否符合重写的规范
@Deprecated:定义在java.lang.Deprecated中,此注解可以修饰方法、属性、类,表示不鼓励程序员使用这种元素,通常是因为它很危险或者存在更好的选择
@SuppressWarnings:定义在java.lang.SuppressWarnings中,用来抑制编译时的警告信息。
3、注解的使用:
使用 Annotation时要在其前面增加@,并把该 Annotation当成个修饰符使用。用于修饰它支持的程序元素
示例一:生成文档相关的注解
- @author标明开发该类模块的作者,多个作者之间使用,分割
- @version标明该类模块的版本
- @see参考转向,也就是相关主题
- @since从哪个版本开始增加的
- @param对方法中某参数的说明,如果没有参数就不能写
- @return对方法返回值的说明,如果方法的返回值类型是void就不能写
- @exception对方法可能抛出的异常进行说明,如果方法没有用 throws显式抛出的异常就不能写
- 其中 @param @return和@exception这三个标记都是只用于方法的。
/**
* @author bruce
* @project_name JavaSenior
* @package_name com.bruce.java
* @create 2020-08-30 13:58
*/
public class AnnotationTest {
/**
*程序的主方法
* @param args 传入命令行参数
*/
public static void main(String[] args) {
}
/**
* 求圆形面积
* @param radius 所求面积的半径
* @return 面积值
*/
public static double getArea(double radius){
return Math.PI * radius * radius;
}
}
示例二:在编译时进行格式检查(JDK内置的个基本注解)
public class AnnotationTest{
public static void mian (String [] args){
@SuppressWarning("unused")
int a = 0;
}
@Deprecated
public void print(){
System.out.print("过时的方法");
}
@Override
public String toString(){
return "重写的toString方法";
}
}
示例三:跟踪代码依赖性,实现替代配置文件功能
在使用Spring框架时会大量用到注解驱动开发。
3、元注解
对现有的注解进行解释说明的注解。
- Retention:指定所修饰的 Annotation 的生命周期:SOURCE\CLASS(默认行为)\RUNTIME只有声明为RUNTIME生命周期的注解,才能通过反射获取。
- Target:用于指定被修饰的 Annotation 能用于修饰哪些程序元素
- Documented:表示所修饰的注解在被javadoc解析时,保留下来。
- Inherited:被它修饰的 Annotation 将具继承性。
4、自定义注解
- 注解声明为:@interface
- 内部定义成员,通常使用value表示
- 可以指定成员的默认值,使用default定义
- 如果自定义注解没成员,表明是一个标识作用。
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE,TYPE_PARAMETER,TYPE_USE})
public @interface MyAnnotation {
String value() default "hello";
}
- 如果注解有成员,在使用注解时,需要指明成员的值。
- 自定义注解必须配上注解的信息处理流程(使用反射)才意义。
- 自定义注解通常都会指明两个元注解:Retention、Target
反射(Reflection)
1、反射的概括
-
Reflection(反射)是被视为动态语言的关键,反射机制允许程序在执行期借助于Reflection API取得任何类的内部信息,并能直接操作任意对象的内部属性及方法。
-
加载完类之后,在堆内存的方法区中就产生了一个Class类型的对象(一个类只有一个Class对象),这个对象就包含了完整的类的结构信息。我们可以通过这个对象看到类的结构。这个对象就像一面镜子,透过这个镜子看到类的结构,所以,我们形象的称之为:反射。
通常的方式:引入需要的“包类”名称---->通过“new”实例化---->获得实例化对象
反射的方式:实例化对象---->getClass()方法---->得到完整的“包类”名称
反射机制提供的功能
-
在运行时判断任意一个对象所属的类
-
在运行时构造任意一个类的对象
-
在运行时判断任意一个类所具有的成员变量和方法
-
在运行时获取泛型信息
-
在运行时调用任意一个对象的成员变量和方法
-
在运行时处理注解
-
生成动态代理
2、Class类
1 、Class类简述
在Java中,每个class都有一个相应的Class对象。也就是说,当我们编写一个类,编译完成后,在生成的.class文件中,就会产生一个Class对象,用于表示这个类的类型信息。
-
Class实例对应着加载到内存中的运行时类(不是对象)
-
在Object类中定义了public final Class getClass(),此方法将被所有子类继承,方法返回值的类型是一个Class类,此类是Java反射的源头,实际上所谓反射从程序的运行结果来看也很好理解,即可以通过对象反射求出类的名称。
-
对象使用反射后可以得到的信息:某个类的属性、方法和构造器、某个类到底实现了哪些接口。对于每个类而言,JRE都为其保留一个不变的Class类型的对象。一个Class对象包含了特定某个结构( class/interface/enum/annotation/primitive type/void/[])的有关信息。
-
Class本身也是一个类
-
Class对象只能由系统建立对象
-
一个加载的类在JVM中只会有一个Class实例
-
一个Class对象对应的是一个加载到JVM中的一个.class文件
-
每个类的实例都会记得自己是由哪个Class实例所生成
-
通过Class可以完整地得到一个类中的所有被加载的结构
-
Class类是Reflection的根源,针对任何你想动态加载、运行的类,唯有先获得相应的Class对象
2、类的加载过程:
-
程序经过javac.exe命令以后,会生成一个或多个字节码文件(.class结尾)。接着我们使用java.exe命令对某个字节码文件进行解释运行。相当于将某个字节码文件加载到内存中。此过程就称为类的加载。加载到内存中的类,我们就称为运行时类,此运行时类,就作为Class的一个实例。
-
换句话说,Class的实例就对应着一个运行时类。
-
加载到内存中的运行时类,会缓存一定的时间。在此时间之内,我们可以通过不同的方式来获取此运行时类。
3、Class类的常用方法
4、获取Class实例的几种方式
-
已知具体的类,通过类的class属性获取,该方法最为安全可靠,程序性能最高 比如:
Class clazz = String.class;这种用的比较少,因为不能动态获取。还不如直接new
-
已知某个类的实例,调用该实例的getclass()方法获取Class对象 比如:Class clazz=person.getclass();
-
已知一个类的全类名,且该类在类路径下,可通过Class类的静态方法forName()获取,可能抛出
ClassNotFoundException
(比较常用) 比如:Class clazz = Class.forName(String classPath)
//体现动态性 -
用类加载器
ClassLoader cl = this.getclass().getClassLoader();
Class clazz = cl.loadClass("类的全类名");(少用)
5、Class实例可以代表的结构
-
class:外部类,成员(成员内部类,静态内部类),局部内部类,匿名内部类
-
interface:接口
-
[]:数组
-
enum:枚举
-
annotation:注解@interface
-
primitive type:基本数据类型
-
void
万事万物皆对象
3、反射的应用
1、创建运行时类的对象
@Test
public void test1() throws Exception {
//方式一
Class<Person> clazz1 = Person.class;
//方式二
Class<Person> clazz2 = (Class<Person>) Class.forName("cn.bruce.java.Person");
Person person1 = clazz1.newInstance();
Person person2 = clazz2.newInstance();
System.out.println(person1);
System.out.println(person2);
}
newInstance():调用此方法,创建对应的运行时类的对象。内部调用了运行时类的空参的构造器。
要想此方法正常的创建运行时类的对象,要求:
-
运行时类必须提供空参的构造器
-
空参的构造器的访问权限得够。通常,设置为public。
javabean中要求提供一个public的空参构造器。原因:
-
便于通过反射,创建运行时类的对象
-
便于子类继承此运行时类时,默认调用super()时,保证父类此构造器
2、获取运行时类的完整结构(了解)
我们可以通过反射,然后创建运行时类对象,获取对应的运行时类中所有的属性、方法、构造器、父类、接口、父类的泛型、包、注解、异常等
获取属性Fieled:
-
public Field[] getDeclaredFields(),返回此Class对象所表示的类或接口的全部Field
-
public Field[] getFields(),返回此Class对象所表示的类及其所有父类或接口的public的Field。
-
Field方法中
-
public int getModifiers()以整数形式返回此Field的修饰符(比如0代表缺省,1代表public,2代表private等)
-
public Class getType()得到Field的属性类型
-
public String getName()返回Field的名称。
-
获取方法Method
-
public Method[] getDeclaredMethods(),返回此Class对象所表示的类或接口的全部方法
-
public Method[] getMethods(),返回此Class对象所表示的类及其所有父类或接口的public的方法
-
Method类中:
-
public Class getReturnType()取得全部的返回值
-
public Class [] getParameterTypes()取得全部的参数
-
public int getModifiers()取得修饰符
-
public Class [] getEXceptionTypes()取得异常信息
-
实现的全部接口
public Class [] getInterfaces() 确定此对象所表示的类或接口实现的接口。
所继承的父类
public Class getSuperclass() 返回表示此Class所表示的实体(类、接口、基本类型)的父类的Class。
全部的构造器:
-
public Constructor [] getConstructors(),返回此Class对象所表示的类的所有public构造方法。
-
public Constructor [] getDeclaredConstructors(),返回此Class对象表示的类声明的所有构造方法。
-
在Constructor类中:
-
取得修饰符:public int getModifiers();
-
取得方法名称: public String getName();
-
取得参数的类型: public Class getParameterTypes();
-
public class Main {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
//加载类
Class<Book> bClass = (Class<Book>) Class.forName("com.company.Book");
//获取构造器
Constructor<Book> constructor = bClass.getConstructor(String.class, String.class);
//创造实例
Book book = constructor.newInstance("西游记", "师徒四人西天取经");
System.out.println(book);//Book{name='西游记', info='师徒四人西天取经'}
}
}
Annotation相关
get Annotation(Class annotationClass)
get DeclaredAnnotations()
3、调用运行时类的指定结构
调用指定的属性
在反射机制中,可以直接通过Field类操作类中的属性,通过Field类提供的set()和get()方法就可以完成设置和取得属性内容。
-
public Field getField(String name)返回此Class对象表示的类或接口的指定的public的Field。(不常用)
-
public Field getDeclaredField(String name)返回此Class对象表示的类或接口的指定的Field。
在Field中:
-
public Object get(object obj)取得指定对象obj上此 Field的属性内容
-
public void set(Object obj,Object value)设置指定对象obj上此Field的属性内容
//Person类中有一个私有的name属性
@Test
public void testField() throws Exception {
Class clazz = Person.class;
//创建运行时类的对象
Person p = (Person) clazz.newInstance();
//1. getDeclaredField(String fieldName):获取运行时类中指定变量名的属性
Field name = clazz.getDeclaredField("name");
//2.保证当前属性是可访问的(这样私有的才能访问)
name.setAccessible(true);
//3.获取、设置指定对象的此属性值
name.set(p,"Tom");
System.out.println(name.get(p));
}
调用指定的方法(常用)
通过反射,调用类中的方法,通过Method类完成。步骤:
-
通过Class类的getMethod(String name,Class… parameterTypes)方法取得一个Method对象,后面的可变形参是调用方法参数的类型。
-
之后使用 Object invoke(Object obj, Object[] args)进行调用,并向方法中传递要设置的ob对象的参数信息
注意:
-
Object对应原方法的返回值,若原方法无返回值,此时返回null
-
若原方法若为静态方法,此时形参Object obj可为null,因为静态的方法不需要知道具体的哪个对象。
-
若原方法形参列表为空,则Object[] args为null
-
若原方法声明为private,则需要在调用此invoke()方法前,显式调用方法对象的setAccessible(true)方法,将可访问private的方法。
//Person类中有两个方法:
//private String show(String s);
//private static void showDesc();
@Test
public void testMethod() throws Exception {
Class<Person> clazz = Person.class;
//创建运行时类的对象
Person person = clazz.newInstance();
/*
1.获取指定的某个方法
getDeclaredMethod():参数1 :指明获取的方法的名称 参数2:指明获取的方法的形参列表
*/
Method show = clazz.getDeclaredMethod("show", String.class);
//2.保证当前方法是可访问的
show.setAccessible(true);
/*
3. 调用方法的invoke():参数1:方法的调用者 参数2:给方法形参赋值的实参
invoke()的返回值即为对应类中调用的方法的返回值。
*/
Object returnValue = show.invoke(person, "CHN");
System.out.println(returnValue);
*************如何调用静态方法*****************
Method showDesc = clazz.getDeclaredMethod("showDesc");
showDesc.setAccessible(true);
Object returnVal = showDesc.invoke(Person.class);//括号里面也可填写null
System.out.println(returnVal);//如果调用的运行时类中的方法没有返回值,则此invoke()返回null
}
4、获取注解的信息
@Retention(RetentionPolicy.RUNTIME)//这样才能被反射获取
@Target(ElementType.METHOD)//只能用于方法
public @interface MyAnnotation {
String username();
String password();
}
public class Main {
@MyAnnotation(username = "admin",password = "123")
public void f(){
}
//获取上面这个方法的注解信息
public static void main(String[] args) throws Exception {
//想要拿到这个注解需要得到方法,得到方法的前提是需要得到类
Class c = Class.forName("Test.Main");//写全类名
//获取f方法
Method f = c.getDeclaredMethod("f");
//判断这个方法是否有注解
if(f.isAnnotationPresent(MyAnnotation.class)){
//得到注解
MyAnnotation annotation = f.getAnnotation(MyAnnotation.class);
System.out.println(annotation.username());
System.out.println(annotation.password());
}
}
}
5、反射来读取配置文件信息,动态创建对象
配置文件
className=zz.Student//全类名
methodName=sleep
要创建的类
public class Person {
public void eat() {
System.out.println("开饭啦");
}
}
public class Student {
public void sleep() {
System.out.println("学生睡觉");
}
}
反射代码
package zz;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.util.Properties;
public class Main {
public static void main(String[] args) throws Exception {
//创建properties对象
Properties pro = new Properties();
//获取class目录下的配置文件
//先找到类加载器,再通过类加载器来寻找
ClassLoader classLoader = Main.class.getClassLoader();
//获取这个文件对应的字节流
InputStream inStream = classLoader.getResourceAsStream("pro.properties");
pro.load(inStream);
//获取配置文件中定义的数据
String className = pro.getProperty("className");
String methodName = pro.getProperty("methodName");
//加载该类进内存
Class cls = Class.forName(className);
//创建对象
Object obj = cls.newInstance();
//获取对象的方法
Method method = cls.getMethod(methodName);
//调用方法
method.invoke(obj);
}
}
可以在不改变代码的情况下,只通过改变配置文件就可以动态的创建对象
6.动态代理,面向切面编程
这里我们想要定义一个实现四则运算的类,并且每次执行方法都要给出输入的参数和运行的结果:
传统的写法:
public interface Cal {
public int add(int num1,int num2);
public int sub(int num1,int num2);
public int mul(int num1,int num2);
public int div(int num1,int num2);
}
public class CalImpl implements Cal {
public int add(int num1, int num2) {
System.out.println("add⽅法的参数是["+num1+","+num2+"]");
int result = num1+num2;
System.out.println("add⽅法的结果是"+result);
return result;
}
public int sub(int num1, int num2) {
System.out.println("sub⽅法的参数是["+num1+","+num2+"]");
int result = num1-num2;
System.out.println("sub⽅法的结果是"+result);
return result;
}
public int mul(int num1, int num2) {
System.out.println("mul⽅法的参数是["+num1+","+num2+"]");
int result = num1*num2;
System.out.println("mul⽅法的结果是"+result);
return result;
}
public int div(int num1, int num2) {
System.out.println("div⽅法的参数是["+num1+","+num2+"]");
int result = num1/num2;
System.out.println("div⽅法的结果是"+result);
return result;
}
}
public class CalImpl implements Cal {
public int add(int num1, int num2) {
int result = num1+num2;
return result;
}
public int sub(int num1, int num2) {
int result = num1-num2;
return result;
}
public int mul(int num1, int num2) {
int result = num1*num2;
return result;
}
public int div(int num1, int num2) {
int result = num1/num2;
return result;
}
}
//帮忙创建动态代理类
public class MyInvocationHandler implements InvocationHandler{
//委托对象
private Object object = null;
//返回代理对象实例
public Object bind(Object object) {
this.object = object;
return Proxy.newProxyInstance(object.getClass().getClassLoader(),object.getClass().getInterfaces(),this);
//object.getClass().getInterfaces(),拿到委托对象的所有接口(代理对象必须完成委托对象的全部功能)
}
//执行的方法
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println(method.getName()+"方法的参数是:"+Arrays.toString(args));
Object result = method.invoke(this.object, args);//还是用委托对象来执行业务方法
System.out.println(method.getName()+"的结果是"+result);
return result;
}
}
public class Test {
public static void main(String[] args) {
Cal cal = new CalImpl();//委托对象
MyInvocationHandler myInvocationHandler = new MyInvocationHandler();
Cal cal1 = (Cal)myInvocationHandler.bind(cal);//动态创建代理对象
cal1.add(1, 2);
cal1.sub(3, 2);
cal1.mul(2, 3);
cal1.div(6, 2);
}
}