每天一个知识点——Java反射

91 阅读6分钟

参考文章:

Java 反射机制详解 | JavaGuide

IoC & AOP详解(快速搞懂) | JavaGuide

Spring IoC有什么好处呢?

【Java 】 如何通过 反射 获取 注解信息 ?-阿里云开发者社区

1.什么是Java反射?

        Java 反射(Reflection)是 Java 语言提供的一种强大的机制,允许程序在运行时动态地访问和操作类、方法、字段等信息。通过反射,程序可以在运行时检查类的结构、创建对象、调用方法、访问字段等,而无需在编译时知道具体的类名和方法名。

2.反射的应用场景

        我们平时写业务代码可能很少直接跟 Java 的反射(Reflection)打交道。但你可能没意识到,你天天都在享受反射带来的便利!很多流行的框架,比如 Spring/Spring Boot、MyBatis 等,底层都大量运用了反射机制,这才让它们能够那么灵活和强大。在这里结合Spring的核心特性来看看反射是如何工作的。

2.1 注解

        Annotation (注解) 是 Java5 开始引入的新特性,可以看作是一种特殊的注释,主要用于修饰类、方法或者变量,提供某些信息供程序在编译或者运行时使用。

        注解本身只是个“标记”,得有人去读这个标记才知道要做什么。反射就是那个“读取器”。框架通过反射检查类、方法、字段上有没有特定的注解,然后根据注解信息执行相应的逻辑。比如,看到 @Value,就用反射读取注解内容,去配置文件找对应的值,再用反射把值设置给字段。

反射在注解中的作用

  1. 获取注解信息

    • 反射提供了isAnnotationPresentgetAnnotationgetAnnotations等方法,用于检查一个类、方法或字段是否被特定的注解标记,并获取这些注解的实例。
    • 例如,Class.getAnnotation(Class<T> annotationClass)可以获取类上指定类型的注解,Method.getAnnotation(Class<T> annotationClass)可以获取方法上指定类型的注解。
  2. 处理注解

    • 通过反射,可以在运行时动态地检查和处理注解。例如,Spring框架通过反射机制来解析类上的@Controller@Service等注解,从而实现依赖注入和Bean的管理。
  3. 动态代理

    • 在某些情况下,注解的处理还涉及到动态代理。例如,当注解被用于定义接口的行为时,可以通过动态代理来实现这些行为。

2.2 依赖注入与控制反转(IOC)

控制反转(IoC)

IoC (Inversion of Control )即控制反转/反转控制。它是一种思想不是一个技术实现。描述的是:Java 开发领域对象的创建以及管理的问题。

例如:现有类 A 依赖于类 B

  • 传统的开发方式 :往往是在类 A 中手动通过 new 关键字来 new 一个 B 的对象出来
  • 使用 IoC 思想的开发方式 :不通过 new 关键字来创建对象,而是通过 IoC 容器(Spring 框架) 来帮助我们实例化对象。我们需要哪个对象,直接从 IoC 容器里面去取即可。

在这里我们可以思考一下为什么要使用IOC的思想?

        现在我们有一个servicelmpl的实现类,此时需要调用serviceMapper,如果使用传统的开发方式,需要去手动new一个serviceMapper对象,由于不能new接口类,我们要不就new一个它的实现类,要不就重写它的方法,但是如果此时遇到了一个新的需求,需要开发新的方法,需要修改servicelmpl中 new 的对象,如果有很多地方都用了serviceMapper,修改起来就非常麻烦了。

——现在明白了为什么要把控制权交给Ioc

我们可以思考一下如果只是为了方便创建对象为什么不直接使用单例模式? (早期的spring是基于xml配置文件的,其实配置起来也很麻烦,如果我们自己利用单例模式实现,似乎更方便)

        

        当我们要在类A中使用类B时,如果使用原始的方法,会造成A和B的耦合,但如果我们把控制权交给Ioc容器,此时A不直接依赖于类B,降低了A和B之间的耦合度。

把控制权交给Ioc以后,那我们要使用类又怎么向IoC容器获取呢?

最基本的方式

ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
DataSource dataSource = (DataSource) ctx.getBean("dataSource");
System.out.println(dataSource);

依赖注入(DI)获取

        依赖注入通常使用三种方式实现,构造器注入,setter注入和接口注入。反射主要用于构造器注入和setter注入。构造器注入依赖于类构造器的反射,而setter注入主要依赖于对类setter方法的反射。

        依赖注入(DI,Dependency Injection)确实是实现控制反转(IoC,Inversion of Control)的一种方式,它的核心思想就是将实例变量(依赖对象)传入到一个对象中去,而不是让对象自己去创建或查找这些依赖对象。这种方式可以显著减少对象之间的耦合度,提高代码的灵活性和可维护性。

依赖注入是通过反射实现

1.接口注入
@Component
public class FooService {
    @Autowired
    private FooFormatter fooFormatter;
}

2.setter方法注入
public class FooService {
    private FooFormatter fooFormatter;
    @Autowired
    public void setFormatter(FooFormatter fooFormatter) {
        this.fooFormatter = fooFormatter;
    }
}

3.构造函数注入
public class FooService {
    private FooFormatter fooFormatter;
    @Autowired
    public FooService(FooFormatter fooFormatter) {
        this.fooFormatter = fooFormatter;
    }
}

总结

就是先将创建对象的控制权交给IoC容器,在需要创建对象的时候使用依赖注入。

Ioc解决了什么问题?

  1. 对象之间的耦合度或者说依赖程度降低;
  2. 资源变的容易管理;比如你用 Spring 容器提供的话很容易就可以实现一个单例。

2.3 动态代理与 AOP

想在调用某个方法前后自动加点料(比如打日志、开事务、做权限检查)?AOP(面向切面编程)就是干这个的,而动态代理是实现 AOP 的常用手段。JDK 自带的动态代理(Proxy 和 InvocationHandler)就离不开反射。代理对象在内部调用真实对象的方法时,就是通过反射的 Method.invoke 来完成的。

public class DebugInvocationHandler implements InvocationHandler {
    /**
     * 代理类中的真实对象
     */
    private final Object target;

    public DebugInvocationHandler(Object target) {
        this.target = target;
    }


    public Object invoke(Object proxy, Method method, Object[] args) throws InvocationTargetException, IllegalAccessException {
        System.out.println("before method " + method.getName());
        Object result = method.invoke(target, args);
        System.out.println("after method " + method.getName());
        return result;
    }
}

2.4 对象关系映射(ORM

像 MyBatis、Hibernate 这种框架,能帮你把数据库查出来的一行行数据,自动变成一个个 Java 对象。它是怎么知道数据库字段对应哪个 Java 属性的?还是靠反射。它通过反射获取 Java 类的属性列表,然后把查询结果按名字或配置对应起来,再用反射调用 setter 或直接修改字段值。反过来,保存对象到数据库时,也是用反射读取属性值来拼 SQL。