第一章 反射+注解
第一章 反射+注解
文章目录
前言
本文主要记录了反射
和注解
相关的知识,看完本文你将理解框架的思想,甚至可以写一个简单的框架(适合有基础的初学者
)
一、反射
1.1 反射的前置知识
我们简单概述一下Java代码执行过程:.class文件经过类加载器加载到内存生成一个大的Class对象,这个大的Class对象里面可以获取到有getFields
、getMethods()
、getConstructors
方法等。Jvm通过这个Class对象构造Person()对象。所以我们可不可以不通过new 对象的方式直接在内存中构建Person对象呢?当然可以,反射就是一种方式!
1.2 反射创建类对象
其实通过反射创建Class对象,就是创建上面的内存中的大Class对象
Class.forName(“类全名”)//这种是基于字节码文件--->写配置文件中常见这种写法下面例子有
Person.class //这种是基于Class对象--->对象传参
new Person().getClass() //这种是基于Person对象--->多用于有Person对象之后
值得注意的是上面的三种方法得到的Class对象都是一样的!
1.3 得到类对象之后
我刚刚学的时候一直搞不懂得到Class对象之后能够做什么呢?不知道你有没有这样的想法,其实Class对象中的方法主要分成四类对应着我们可以获得:Person对象的属性
,Person对象的构造方法
,Person对象的普通方法
,Person对象的类名
。我们下面介绍一下这些分类的基本API方法
Fields
//得到person对象的公共Fields
public Field[] getFields()
public Field getField(String name)
//得到person对象的所有Fields
public Field[] getDeclaredFields()
public Field getDeclaredField(String name)
Constructors
//得到person对象的公共Constructors
public Constructor<?>[] getConstructors()
public Constructor<T> getConstructor(Class<?>... parameterTypes)
//得到person对象的所有Constructors
public Constructor<?>[] getDeclaredConstructors()
public Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes)
methods
//得到person对象的公共Methods
public Method[] getMethods()
public Method getMethod(String name, Class<?>... parameterTypes)
//得到person对象中的所有的methods
public Method[] getDeclaredMethods()
public Method getDeclaredMethod(String name, Class<?>... parameterTypes)
名字
//得到类的全类名
public String getName()
//得到类名
public String getSimpleName()
介绍完API之后我们想一下我们获得Fields之后我们会想做什么呢?是不是就是获取值get()和设置值set()
public Object get(Object obj)
public void set(Object obj, Object value)
我们获得Constructors之后想要做什么呢?是不是就是要创建实例newInstance()
public T newInstance(Object ... initargs)
得到无参构造器对应使用无参构造创建对象,使用几参构造器使用几参构造创建对象。
我们获得方法之后是不是就要执行invoke()
public Object invoke(Object obj, Object... args)
这样分类来记忆是不是简单多了,这里我需要重点提一下:我们既然可以获得所有的属性和方法,那么肯定包含私有属性和方法那么是不是意味着我们可以操作Person对象里面的私有属性和方法了呢?答案是yes,不过我们需要调用里面的setAccess下面举例说明一下:
Person类
public class Person {
private String name;
private int age;
public Person() {
}
public Person(String name,int age){
this.name = name ;
this.age = age;
}
public void run(){
System.out.println("我跑的很快");
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
测试类
public class Refelct01 {
public static void main(String[] args) throws Exception {
Class<Person> pc = Person.class;
Field name = pc.getDeclaredField("name");
Person person = new Person("张三",2);
name.setAccessible(true);
name.set(person,"李四");
System.out.println("张三的名字变成了"+person.getName());
}
}
name.setAccessible(true);//注意我们如果不加这行代码,那么程序会抛出
java.lang.IllegalAccessException
异常,因为name属性是私有的,加入这行代码便是暴力反射
。是不是感觉暴力反射也没有那么难搞了呢。
1.4 反射的优点
如果你已经看到了这里,那么你一定很好奇?为什么这么麻烦咱们直接new Person().getName()不好吗,我先告诉你反射的最大得优点就是我们可以解耦
写写框架
。
假设我们在UserController层里面调用UserService类的对象,正常情况下我们需要在UserController层里面 new UserService()得到userService对象,但是使用Spring框架的时候我们只需要将UserService对象注入到UserController里面就可以了。
好了到这个时候你把思路捋顺一下:我们想一下当项目投产之后这个UserService感觉并不合适因为我们项目的主要用户群体是StudentService,那么这个时候你是不是要在UserController里面重新new StudentService呢?但是在Spring框架中我们只需要将UserService替换成StudentService即可!上面我们简单的描述了为什么框架可以解耦,现在我们用一个例子来写一个简单的框架。
1.5 反射实现框架
这个框架的作用就是可以将我们写的任意一个类中的方法执行:
首先我们来写一个pro.properties
文件
className=com.xxx.reflect.Person
methodName=run
然后我们准备一个person类
public class Person {
private String name;
private int age;
public Person() {
}
public Person(String name,int age){
this.name = name ;
this.age = age;
}
public void run(){
System.out.println("我跑的很快");
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
然后我们编写测试代码
public class Reflect02 {
public static void main(String[] args) throws Exception {
ResourceBundle resource = ResourceBundle.getBundle("pro");//获得pro.properties文件
String className = resource.getString("className");//得到pro文件中的className
String methodname = resource.getString("methodName");//得到pro文件中的methodName
Class<?> clazz = Class.forName(className);//通过反射获得Class对象
Method method = clazz.getMethod(methodname);//通过反射获得run方法
Object person = clazz.newInstance();//通过反射创建对象
method.invoke(person);//执行person中的run方法
}
}
我们试想一下如果将Reflect02作为一个框架打成一个jar包,那么使用者是不是只要咋pro.properties里面加上他们自己写的类,那么他们的类就是可以运行的了呢?怎么样,你看你现在已经可以写出来一个框架了是不是很厉害!
二、注解
2.1 自定义注解
注解格式
public @interface read{ }
注解实质
public interface read extends java.lang.annotation.Annotation{}
我们可以看到其实自定义注解就是一个接口而已,接口中属性是没有意义的,只能定义抽象方法,抽象方法又可以省略 public abstract 那么我们注解是不是就可以写成这样的了:
public @interface read {
String name ();
int age();
}
注解属性
注解的属性其实就是抽象方法的方法名(我之前学习的时候总是记不住注解的属性怎么定义的,现在发现他是一个接口里面定义的是抽象方法是不是简单多了
)
- 属性的返回值只能是
基本类型
、String
、注解
、enum
、注解
、以上类型的数组
- 如果属性使用default默认赋值,可以不进行赋值
- 如果属性名是value,并且只有一个属性可以将value省略
元注解
简单的理解就是放在注解上面的注解
@Target
:描述注解作用的位置@Target(ElementType.TYPE)、@Target(ElementType.METHOD)@Target(ElementType.FIELD)
@Retention
描述注解被保留的阶段@Retention(RetentionPolicy.RUNTIME) (JVM可以读到注解,自定义注解一般都是RUNTIME)@Retention(RetentionPolicy.RUNTIME)
其他元注解用到时不懂直接百度,不要背
那么我们现在就自定义一个自己的注解把
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.CLASS)
public @interface read {
String className ();
String methodName();
}
2.2 解析元注解
我们将上面的使用配置文件反射的方式转换为使用注解的方式
@Read(className="com.xxx.reflect.Person",methodName="run")
public class Reflect02 {
public static void main(String[] args) throws Exception {
Read annotation = Reflect02.class.getAnnotation(Read.class);
String className = annotation.className();
String methodName = annotation.methodName();
Class<?> clazz = Class.forName(className);//通过反射获得Class对象
Method method = clazz.getMethod(methodName);//通过反射获得run方法
Object person = clazz.newInstance();//通过反射创建对象
method.invoke(person);//执行person中的run方法
}
}
2.3 简单框架
这个框架的作用是创建一个check注解,在Caculator的方法里面部分加上@Check注解,加上Check注解的可以执行响应的代码,并且代码出现异常可以抛出异常,没有加check注解的不能被检查
Check注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Check {
}
Caculator类
public class Caculator {
@Check
public void add(){
System.out.println("1+0="+(1+0));
}
@Check
public void sub(){
System.out.println("1-0="+(1-0));
}
@Check
public void div() {
System.out.println("1/0="+(1/0));
}
}
TestCheck
public class TestCheck {
public static void main(String[] args) {
Class<Caculator> clazz = Caculator.class;//获取到Calculator类目的是得到方法上面的注解
Method[] methods = clazz.getMethods();//获取到Calculator类目的是得到方法
Caculator caculator = null;
try {
caculator = clazz.newInstance();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
for(Method method : methods) {
Check annotation = method.getAnnotation(Check.class);//判断方法上面时候有check注解,如果有注解的话我们才会将Caculator里面的方法执行
if(annotation!=null) {
try {
method.invoke(caculator);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}
总结
本篇文章主要简述了java基础里面比较难理解的注解和反射
的知识,相信大家看完这篇文章已经可以一个简单的框架demo
了,继续加油!
如果有问题请评论中指出,希望大家共同进步
:simle