GoF之工厂模式

了解GoF

  • 设计模式是一种可以反复利用的解决方案
  • GoF(Gang of Four),中文名——四人组。
    • 《Design Patterns: Elements of Reusable Object-Oriented Software》(即《设计模式》一书),1995年由 Erich Gamma、Richard Helm、Ralph Johnson 和 John Vlissides 合著。这几位作者常被称为”四人组(Gang of Four)”。
  • 常提到的23种设计模式,在这本书都能找到。除了GoF提到的23种设计模式,起始还有其他的设计模式,比如:JavaEE的设计模式(DAO模式、MVC模式等)。
  • GoF中的23种设计模式可以分为三大类:
  • 创建型(5个):解决对象创建问题。
    • 单例模式
    • 工厂方法模式
    • 抽象工厂模式
    • 建造者模式
    • 原型模式
  • 结构型(7个):一些类或对象组合在一起的经典结构。
    • 代理模式
    • 装饰模式
    • 适配器模式
    • 组合模式
    • 享元模式
    • 外观模式
    • 桥接模式
  • 行为型(11个):解决类或对象之间的交互问题。
    • 策略模式
    • 模板方法模式
    • 责任链模式
    • 观察者模式
    • 迭代子模式
    • 命令模式
    • 备忘录模式
    • 状态模式
    • 访问者模式
    • 中介者模式
    • 解释器模式
  • 工厂模式是解决对象创建问题的,所以工厂模式属于创建型设计模式
  • 为什么在这里学习工厂模式呢?
    • 这是因为Spring框架底层使用了大量的工厂模式。

工厂模式的三种形态

  • 第一种:简单工厂模式(Simple Factory):不属于23种设计模式之一。简单工厂模式又叫做:静态工厂方法模式。简单工厂模式是工厂方法模式的一种特殊实现。
  • 第二种:工厂方法模式(Factory Method):是23种设计模式之一。
  • 第三种:抽象工厂模式(Abstract Factory):是23种设计模式之一。

简单工厂模式

  • 简单工厂模式角色包括三个:
    • 抽象产品角色
    • 具体产品角色
    • 工厂类角色
      1
      2
      3
      4
      5
      6
      public abstract class Weapon {
      /**
      * 所有的武器都有攻击行为
      */
      public abstract void attack();
      }
      1
      2
      3
      4
      5
      public class Tank extends Weapon{
      public void attack() {
      System.out.println("坦克开炮!");
      }
      }
      1
      2
      3
      4
      5
      public class Fighter extends Weapon{
      public void attack() {
      System.out.println("战斗机投下原子弹!");
      }
      }
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      public class WeaponFactory {
      public static Weapon get(String weaponType){
      if (weaponType == null || weaponType.trim().length() == 0)
      return null;
      Weapon weapon = null;
      if ("TANK".equals(weaponType)) {
      weapon = new Tank();
      } else if ("FIGHTER".equals(weaponType)) {
      weapon = new Fighter();
      } else if ("DAGGER".equals(weaponType)) {
      weapon = new Dagger();
      } else {
      throw new RuntimeException("不支持该武器!");
      }
      return weapon;
      }
      }

      简单工厂模式的优点:

      • 客户端程序不需要关心对象的创建细节,需要哪个对象时,只需要向工厂索要即可,初步实现了责任的分离。客户端只负责“消费”,工厂负责“生产”。生产和消费分离。

      简单工厂模式的缺点:

      • 工厂类集中了所有产品的创造逻辑,形成一个无所不知的全能类,有人把它叫做上帝类。显然工厂类非常关键,不能出问题,一旦出问题,整个系统瘫痪。
      • 不符合OCP开闭原则。在进行系统扩展时,需要修改工厂类。

      工厂方法模式

  • 工厂方法模式整体与简单工厂类似。既保留了简单工厂模式的优点,同时又解决了简单工厂模式的缺点。

  • 工厂方法模式角色:

    • 抽象产品角色
    • 具体产品角色
    • 抽象工厂角色
    • 具体工厂角色
1
2
3
4
5
6
public abstract class Weapon {
/**
* 所有武器都有攻击行为
*/
public abstract void attack();
}
1
2
3
4
5
6
public class Gun extends Weapon{
@Override
public void attack() {
System.out.println("开枪射击!");
}
}
1
2
3
4
5
6
public class Fighter extends Weapon{
@Override
public void attack() {
System.out.println("战斗机发射核弹!");
}
}
1
2
3
public interface WeaponFactory {
Weapon get();
}
1
2
3
4
5
6
public class GunFactory implements WeaponFactory{
@Override
public Weapon get() {
return new Gun();
}
}
1
2
3
4
5
6
public class FighterFactory implements WeaponFactory{
@Override
public Weapon get() {
return new Fighter();
}
}
1
2
3
4
5
6
7
8
9
10
11
public class Client {
public static void main(String[] args) {
WeaponFactory factory = new GunFactory();
Weapon weapon = factory.get();
weapon.attack();

WeaponFactory factory1 = new FighterFactory();
Weapon weapon1 = factory1.get();
weapon1.attack();
}
}
  • 此时想要扩展产品,只需要新增一个产品类和该产品对应的工厂即可,例如新增匕首类
1
2
3
4
5
6
public class Dagger extends Weapon{
@Override
public void attack() {
System.out.println("砍丫的!");
}
}
1
2
3
4
5
6
public class DaggerFactory implements WeaponFactory{
@Override
public Weapon get() {
return new Dagger();
}
}

工厂方法模式的优点:

  • 一个调用者想创建一个对象,只要知道其名称就可以了。
  • 扩展性高,如果想增加一个产品,只要扩展一个工厂类就可以。
  • 屏蔽产品的具体实现,调用者只关心产品的接口。
    工厂方法模式的缺点:
  • 每次增加一个产品时,都需要增加一个具体类和对象实现工厂,使得系统中类的个数成倍增加,在一定程度上增加了系统的复杂度,同时也增加了系统具体类的依赖。这并不是什么好事。

Bean的实例化方式

Spring为Bean提供了多种实例化方式,通常包括4种方式。(也就是说在Spring中为Bean对象的创建准备了多种方案,目的:使Bean的实例更加灵活)

  • 第一种:通过构造方法实例化
  • 第二种:通过简单工厂模式实例化
  • 第三种:通过BeanFactory实例化
  • 第四种:通过FactoryBean接口实例化

BeanFactory和FactoryBean的区别

  • BeanFactory:是Spring IoC容器的顶级对象,常常被翻译为Bean工厂,用于创建和管理Bean对象。BeanFactory是工厂
  • FactoryBean:是Spring的一个工厂Bean,是一个能够辅助Spring实例化其他Bean对象的一个Bean。

在Spring中,Bean可以分为两类:

  • 第一类:普通Bean
  • 第二类:工厂Bean(记住:工厂Bean也是一种Bean,只不过这种Bean比较特殊,它可以辅助Spring实例化其它Bean对象。)

Bean的生命周期

什么是Bean的生命周期?

Spring就是一个管理Bean对象的工厂。它负责对象的创建,对象的销毁等。
所谓的生命周期就是:对象从创建开始到最终销毁的整个过程。

  • 什么时候创建Bean对象?
  • 创建Bean对象的前后会调用什么方法?
  • Bean对象什么时候销毁?
  • Bean对象的销毁前后调用什么方法?

为什么要了解Bean的生命周期?

生命周期的本质:在哪个时间节点上调用了哪个类的哪个方法。
我们需要充分的了解在这个生命线上,都有哪些特殊的时间节点。
只有我们知道了特殊的时间节点都在哪,才可以准确的确定代码写到哪。
我们可能需要在某个特殊的时间点上执行一段特定的代码,这段代码就可以放到这个节点上。当生命线走到这里的时候,就会被自动调用。

Bean的生命周期之5步

  1. 实例化Bean
  2. Bean属性赋值
  3. 初始化Bean
  4. 使用Bean
  5. 销毁Bean
  • 只有正常关闭spring容器,bean的销毁方法才会被调用。
  • ClassPathXmlApplicationContext类才有close()方法。`
  • 配置文件中的init-method指定初始化方法。destroy-method指定销毁方法。

Bean的生命周期之7步

  1. 实例化Bean
  2. Bean属性赋值
  3. Bean后处理器的before方法
  4. 初始化Bean
  5. Bean后处理器的after方法
  6. 使用Bean
  7. 销毁Bean
  • 编写一个类实现BeanPostProcessor类,并重写before和after方法
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    public class LogBeanPostProcessor implements BeanPostProcessor {
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
    System.out.println("Bean后处理器的before方法执行,即将开始初始化");
    return bean;
    }
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
    System.out.println("Bean后处理器的after方法执行,已完成初始化");
    return bean;
    }
    }

在spring.xml文件中配置的Bean后处理器将作用于当前配置文件中所有的Bean。

在spring.xml文件中配置Bean后处理器

1
2
<!--配置Bean后处理器。这个后处理器将作用于当前配置文件中所有的bean。-->
<bean class="com.powernode.spring6.bean.LogBeanPostProcessor"/>

Bean的生命周期之10步(了解)

  1. 实例化Bean
  2. Bean属性赋值
  3. 检查Bean是否实现了Aware相关接口,并设置相关依赖
  4. Bean后处理器的before方法
  5. 检查Bean是否实现了InitializingBean接口,并调用接口方法
  6. 初始化Bean
  7. Bean后处理器的after方法
  8. 使用Bean
  9. 检查Bean是否实现了DisposableBean接口,并调用接口方法
  10. 销毁Bean

Aware相关的接口包括:BeanNameAwareBeanClassLoaderAwareBeanFactoryAware

  • 当Bean实现了BeanNameAware,Spring会将Bean的名字传递给Bean。
  • 当Bean实现了BeanClassLoaderAware,Spring会将加载该Bean的类加载器传递给Bean。
  • 当Bean实现了BeanFactoryAware,Spring会将Bean工厂对象传递给Bean。
  • InitializingBean的方法早于init-method的执行。
  • DisposableBean的方法早于destroy-method的执行。

对于Bean不同的作用域,Spring有不同的管理方式

  • 单例:Spring精确的知到Bean在何时创建,何时初始化,何时被销毁。
  • 多例:Spring只负责创建,当Bean被创建后,Bean的实例就交给客户端代码管理,Spring不再追踪其生命周期。

将自己new的对象交给Spring管理

  • Spring为我们创建的对象,它的生命周期由Spring管理。我们如何将自己new的对象交给Spring管理呢?

通过@Bean注解

  • 创建一个配置类,在配置类中定义一个方法,该方法返回值为要注册的Bean对象。
  • 在方法上添加@Bean注解。
    1
    2
    3
    4
    5
    6
    7
    @Configuration
    public class MyConfig {
    @Bean
    public MyService myService() {
    return new MyService(); // 手动 new,再交给 Spring
    }
    }

    通过DefaultListableBeanFactory

    可以通过BeanDefinitionRegistryPostProcessorBeanFactoryPostProcessor注册
1
2
public class User{
}

使用ApplicationContext

在运行时获取 ConfigurableApplicationContext,调用其 API 注册:

1
2
ConfigurableApplicationContext context = (ConfigurableApplicationContext) applicationContext;
context.getBeanFactory().registerSingleton("myService", new MyService());

总结

方式优点缺点典型场景
@Bean 注册简单直观,最推荐;支持依赖注入;易于维护需要提前在配置类中写死常规手动 new 对象交给 Spring 管理
BeanFactoryPostProcessor / registerSingleton可在容器启动时动态注册 Bean;灵活性高代码复杂,理解成本高;不常用启动阶段根据条件动态注册 Bean
运行时 ApplicationContext.registerSingleton可以在运行时动态注册;方便临时扩展容器已启动时使用,可能影响一致性运行过程中需要临时创建并托管对象
@Component 扫描完全交给 Spring 扫描管理;简洁不能满足“必须自己手动 new”的需求适合完全由 Spring 托管的类
  • 如果只是单纯要把 new 出来的对象交给 Spring → @Bean 是最佳实践。

  • 如果对象的创建需要 运行时条件 或 外部输入 → 使用 registerSingleton。

  • 如果在做 框架级开发,要批量、动态注册 → 用 ImportBeanDefinitionRegistrar。

Bean的循环依赖

  • A对象中有B属性。B对象中有A属性。这就是循环依赖。我依赖你,你也依赖我。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    class A {
    private B b;
    public void setB(B b) { this.b = b; }
    }

    class B {
    private A a;
    public void setA(A a) { this.a = a; }
    }
  • 此时A依赖BB又依赖A,普通的构造过程就会出现死循环

单例下的set注入产生的循环依赖

  • singleton+Set注入的情况下,循环依赖是没有问题的。Spring可以解决这个问题
    • Spring在创建单例Bean的时候,有一个三级缓存机制,提前曝光正在创建的对象引用,让另一个依赖方能够拿到这个对象的”半成品”(还未完全填充依赖的对象)。
  • 创建过程:
    • Spring开始创建单例A
      • 在实例化A后(还未注入属性),Spring就把ObjectFactory(A)放到singletonFactories中。
    • Spring发现A需要依赖B,开始创建B
    • 创建B时发现B需要依赖A
      • 此时Spring会去singletonFactories中获取A的ObjectFactory,之后调用它生成一个earlySingletonReference(早期引用),这个引用会先放入earlySingletonObjects,然后B再依赖这个引用。
      • 这样B就拿到了一个A的引用(还没有完成依赖注入)。
    • B完成创建后也会放入singletonObjects
    • 回到A的注入阶段,Spring将完整的B注入到A中,此时A彻底完成初始化,重新放入singletonObjects中。
    • 此时A和B全部完成了初始化
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class Husband {
private String name;
private Wife wife;
public void setName(String name)
this.name = name;
public String getName()
return name;
public void setWife(Wife wife)
this.wife = wife;

// toString()方法重写时需要注意:不能直接输出wife,输出wife.getName()。要不然会出现递归导致的栈内存溢出错误。
@Override
public String toString() {
return "Husband{" +
"name='" + name + '\'' +
", wife=" + wife.getName() +
'}';
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class Wife {
private String name;
private Husband husband;
public void setName(String name)
this.name = name;
public String getName()
return name;
public void setHusband(Husband husband)
this.husband = husband;

// toString()方法重写时需要注意:不能直接输出husband,输出husband.getName()。要不然会出现递归导致的栈内存溢出错误。
@Override
public String toString() {
return "Wife{" +
"name='" + name + '\'' +
", husband=" + husband.getName() +
'}';
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

<bean id="husbandBean" class="com.powernode.spring6.bean.Husband" scope="singleton">
<property name="name" value="张三"/>
<property name="wife" ref="wifeBean"/>
</bean>
<bean id="wifeBean" class="com.powernode.spring6.bean.Wife" scope="singleton">
<property name="name" value="小花"/>
<property name="husband" ref="husbandBean"/>
</bean>
</beans>

执行结果:

多例下的set注入产生的循环依赖

同样的循环依赖问题,换为多例模式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

<bean id="husbandBean" class="com.powernode.spring6.bean.Husband" scope="prototype">
<property name="name" value="张三"/>
<property name="wife" ref="wifeBean"/>
</bean>
<bean id="wifeBean" class="com.powernode.spring6.bean.Wife" scope="prototype">
<property name="name" value="小花"/>
<property name="husband" ref="husbandBean"/>
</bean>
</beans>

  • 此时就会出现异常:BeanCurrentlyInCreationException
    1
    2
    3
    4
    5
    Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'husbandBean': Requested bean is currently in creation: Is there an unresolvable circular reference?
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:265)
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199)
    at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveReference(BeanDefinitionValueResolver.java:325)
    ... 44 more
  • 翻译为:创建名为“husbandBean”的bean时出错:请求的bean当前正在创建中:是否存在无法解析的循环引用?
  • 通过测试可以知道,多例模式下的循环依赖,Spring无法解决,会抛出异常。
    • 在全部为Set模式下的循环依赖:
      • 一个单例Bean,一个多例Bean不会产生循环依赖。
      • 两个都为单例Bean也不会产生循环依赖。
      • 只有两个都为多例Bean才会产生循环依赖。

    多例下的构造注入产生的循环依赖

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23

    public class Husband {
    private String name;
    private Wife wife;

    public Husband(String name, Wife wife) {
    this.name = name;
    this.wife = wife;
    }

    // -----------------------分割线--------------------------------
    public String getName() {
    return name;
    }

    @Override
    public String toString() {
    return "Husband{" +
    "name='" + name + '\'' +
    ", wife=" + wife +
    '}';
    }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    public class Wife {
    private String name;
    private Husband husband;

    public Wife(String name, Husband husband) {
    this.name = name;
    this.husband = husband;
    }

    // -------------------------分割线--------------------------------
    public String getName() {
    return name;
    }

    @Override
    public String toString() {
    return "Wife{" +
    "name='" + name + '\'' +
    ", husband=" + husband +
    '}';
    }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="hBean" class="com.powernode.spring6.bean2.Husband" scope="singleton">
    <constructor-arg name="name" value="张三"/>
    <constructor-arg name="wife" ref="wBean"/>
    </bean>

    <bean id="wBean" class="com.powernode.spring6.bean2.Wife" scope="singleton">
    <constructor-arg name="name" value="小花"/>
    <constructor-arg name="husband" ref="hBean"/>
    </bean>
    </beans>
    1
    2
    3
    4
    5
    6
    7
    8
    @Test
    public void testSingletonAndConstructor(){
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring2.xml");
    Husband hBean = applicationContext.getBean("hBean", Husband.class);
    Wife wBean = applicationContext.getBean("wBean", Wife.class);
    System.out.println(hBean);
    System.out.println(wBean);
    }
  • 运行程序后发现和多例模式的set注入一样,会抛出异常:BeanCurrentlyInCreationException
    1
    2
    3
    4
    5
    6
    7
    Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'hBean': Requested bean is currently in creation: Is there an unresolvable circular reference?
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.beforeSingletonCreation(DefaultSingletonBeanRegistry.java:355)
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:227)
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:324)
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199)
    at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveReference(BeanDefinitionValueResolver.java:325)
    ... 56 more

    主要原因是因为通过构造方法注入导致的:因为构造方法注入会导致实例化对象的过程和对象属性赋值的过程没有分离开,必须在一起完成导致的。

    Spring解决循环依赖的机理

    Spring解决循环依赖的根本原因:Spring 在创建单例 Bean 时,利用 三级缓存,通过 提前暴露 Bean 的“半成品”(仅完成实例化,还没完成属性赋值和初始化),把 实例化对象依赖注入 两个阶段拆开执行,从而打破循环依赖。

  • 使用Set注入,即在实例化BEan时,调用无参构造方法来完成。此时可以先不给属性赋值,可以提前将该Bean对象“曝光”给外界。,等到给Bean属性赋值时再调用Set方法完成
  • 两个步骤是完全可以分离开去完成的,并且这两步不要求在同一个时间点上完成。
  • 也就是说,Bean都是单例的,我们可以先把所有的单例Bean实例化出来,放到一个集合当中(我们可以称之为缓存),所有的单例Bean全部实例化完成之后,以后我们再慢慢的调用setter方法给属性赋值。这样就解决了循环依赖的问题。
Spring底层源码

</div>

  • 以上类中包含三个重要的属性:
    • Cache of singleton objects: bean name to bean instance. 单例对象的缓存:key存储bean名称,value存储Bean对象【一级缓存】
    • Cache of early singleton objects: bean name to bean instance. 早期单例对象的缓存:key存储bean名称,value存储早期的Bean对象【二级缓存】
    • Cache of singleton factories: bean name to ObjectFactory. 单例工厂缓存:key存储bean名称,value存储该Bean对应的ObjectFactory对象【三级缓存】
  • 这三个缓存本质上是三个Map集合。
  • 在该类中还有一个方法:addSingletonFactory(),这个方法的作用是:将创建Bean对象的ObjectFactory提前曝光
    </div>
    再看以下源码
    </div>
    Spring会先从一级缓存获取Bean对象,如果一级缓存中没有,则从二级缓存中获取,如果二级缓存中没有,则从三级缓存中获取之前曝光的ObejectFactory对象,通过ObjectFactory对象获取Bean实例,这样就解决了循环依赖的问题。

总结:
Spring只能解决setter方法注入的单例bean之间的循环依赖。 ClassA依赖ClassB,ClassB又依赖ClassA,形成依赖闭环。Spring在创建ClassA对象后,不需要等给属性赋值,直接将其曝光到bean缓存当中。在解析ClassA的属性时,又发现依赖于ClassB,再次去获取ClassB,当解析ClassB的属性时,又发现需要ClassA的属性,但此时的ClassA已经被提前曝光加入了正在创建的bean的缓存中,则无需创建新的的ClassA的实例,直接从缓存中获取即可。从而解决循环依赖问题。

回顾反射

分析方法四要素

  • 首先回顾不使用反射机制调用一个方法需要几个要素参与?
1
2
3
4
5
6
7
8
9
10
11
12
13
public class SystemService {

public void logout(){
System.out.println("退出系统");
}

public boolean login(String username, String password){
if ("admin".equals(username) && "admin123".equals(password)) {
return true;
}
return false;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
public class ReflectTest01 {
public static void main(String[] args) {

// 创建对象
SystemService systemService = new SystemService();

// 调用方法并接收方法的返回值
boolean success = systemService.login("admin", "admin123");

System.out.println(success ? "登录成功" : "登录失败");
}
}

调用一个方法一般需要知道该方法的四个要素

  • 调用哪个对象的方法(systemService)
  • 哪个方法(login)
  • 传什么参数(”admin”, “admin123”)
  • 返回什么值(success)

获取Method

  • 如果想通过反射机制调用一个方法,首先需要获取这个方法,在反射中Method实例代表一个方法,如何获得Method实例呢?
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    public class SystemService {

    public void logout(){
    System.out.println("退出系统");
    }

    public boolean login(String username, String password){
    if ("admin".equals(username) && "admin123".equals(password)) {
    return true;
    }
    return false;
    }

    public boolean login(String password){
    if("110".equals(password)){
    return true;
    }
    return false;
    }
    }
    想要获取这个类的三个方法,首先需要获取这个类Class。
    1
    Class clazz = Class.forName("com.powernode.reflect.SystemService");
    拿到这个类以后,就可以调用getDeclaredMethod()方法来获取方法。
    假设想要获取login(String username, String password)方法
    1
    Method loginMethod = clazz.getDeclaredMethod("login", String.class, String.class);
    获取login(String password)方法
    1
    Method loginMethod = clazz.getDeclaredMethod("login", String.class);
    获取一个方法,告诉Java程序,想要获取的方法名字是什么,该方法的形参类型是什么,这样才能获取到对应的方法,因为方法是支持重载的,所以获取指定方法必须明确方法名和形参类型。
    假如想要获得下面的方法,代码该如何写?
1
2
3
public void setAge(int age){
this.age = age;
}

其中setAge是方法名,int.class是形参的类型。

1
Method setAgeMethod = clazz.getDeclaredMethod("setAge", int.class);

调用Method

  • 获取方法后,如果想调用该方法,就关联到四要素了
    • 调用哪个对象的方法
    • 哪个方法
    • 传什么参数
    • 返回什么值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class SystemService {

public void logout(){
System.out.println("退出系统");
}

public boolean login(String username, String password){
if ("admin".equals(username) && "admin123".equals(password)) {
return true;
}
return false;
}

public boolean login(String password){
if("110".equals(password)){
return true;
}
return false;
}
}

此时要调用方法:login(String username, String password)
第一步:创建对象(四要素之首:调用哪个对象的)

1
2
Class clazz = Class.forName("com.powernode.reflect.SystemService");
Object obj = clazz.newInstance();

第二步:获取方法login(String,String)(四要素之一:哪个方法)

1
Method loginMethod = clazz.getDeclaredMethod("login", String.class, String.class);

第三步:调用方法

1
Object retValue = loginMethod.invoke(obj, "admin", "admin123");

解说四要素:

  • 哪个对象:obj
  • 哪个方法:loginMethod
  • 传什么参数:”admin”, “admin123”
  • 返回什么值:retValue
1
2
3
4
5
6
7
8
9
public class ReflectTest02 {
public static void main(String[] args) throws Exception{
Class clazz = Class.forName("com.powernode.reflect.SystemService");
Object obj = clazz.newInstance();
Method loginMethod = clazz.getDeclaredMethod("login", String.class, String.class);
Object retValue = loginMethod.invoke(obj, "admin", "admin123");
System.out.println(retValue);
}
}

既没有参数,又没有返回值的logout方法怎么调用呢?

1
2
3
4
5
6
7
8
public class ReflectTest03 {
public static void main(String[] args) throws Exception{
Class clazz = Class.forName("com.powernode.reflect.SystemService");
Object obj = clazz.newInstance();
Method logoutMethod = clazz.getDeclaredMethod("logout");
logoutMethod.invoke(obj);
}
}

知道属性名为属性赋值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class User {
private String name;
private int age;

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;
}

@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
  • 知道以下信息:
    • 类名是:com.powernode.reflect.User
    • 该类中有String类型的name属性和int类型的age属性。
    • 另外你也知道该类的设计符合javabean规范。(也就是说属性私有化,对外提供setter和getter方法)
  • 如何通过反射机制给User对象的name属性赋值zhangsan,给age属性赋值20岁?

```java
public class UserTest {
public static void main(String[] args) throws Exception{
// 已知类名
String className = “com.powernode.reflect.User”;
// 已知属性名
String propertyName = “age”;
// 通过反射机制给User对象的age属性赋值20岁
Class<?> clazz = Class.forName(className);
Object obj = clazz.newInstance(); // 创建对象
// 根据属性名获取setter方法名
String setMethodName = “set” + propertyName.toUpperCase().charAt(0) + propertyName.substring(1);
// 获取Method
Method setMethod = clazz.getDeclaredMethod(setMethodName, int.class);
// 调用Method
setMethod.invoke(obj, 20);
System.out.println(obj);
}
}