方法注入(Method Injection)

 

Method injection更容易理解的翻译方式是通过方法注入。先不说什么是method injection,先通过一个问题(从官方文档翻译而来),引入method injection。

提出问题

一个单例bean A,如果依赖非单例的bean B(B被注入到A中);对于A来说,B就是一个单例的bean。因为不论使用构造器、setter或工厂方法注入,只会注入一次,现在想要的结果就是A对B的每次使用,都希望新实例化一个B。

解决方式一:放弃一部分IoC,与Spring耦合(不推荐使用)

就拿上面的问题来说,如果A依赖于Spring容器(A保存一个容器的引用),A每次使用B时,都通过容器的getBean("B")来获取一个新的实例。

public class CommandManager {

    private ApplicationContext applicationContext;

    public Object process(Map commandState) {
        // grab a new instance of the appropriate Command
        Command command = createCommand();
        // set the state on the (hopefully brand new) Command instance
        command.setState(commandState);
        return command.execute();
    }

    protected Command createCommand() {
        // notice the Spring API dependency!
        return this.applicationContext.getBean("command", Command.class);
    }

}

看这个例子,每次CommandManager调用process的时候,都会调用creatCommand,也就是说从容器中取一个非单例的bean。

到此为止,上面的例子还不能运行成功,因为还没有给CommandMananger注入applicationContext;这又是一个问题,对于applicationContext的注入,不应该放到容器外(通过其他类)完成,所以,Spring提供了一个ApplicationContextAware接口

public interface ApplicationContextAware {

    void setApplicationContext(ApplicationContext applicationContext) throws BeansException;
}

官方对于这个接口的描述:

When an ApplicationContext creates an object instance that implements the org.springframework.context.ApplicationContextAware interface, the instance is provided with a reference to that ApplicationContext.

也就是说,当容器初始化一个实现了ApplicationContextAware接口的bean时,会把容器的引用,通过接口中的setter方法注入到bean中。

所以,完整的例子如下:

public class CommandManager implements ApplicationContextAware {

    private ApplicationContext applicationContext;

    public Object process(Map commandState) {
        // grab a new instance of the appropriate Command
        Command command = createCommand();
        // set the state on the (hopefully brand new) Command instance
        command.setState(commandState);
        return command.execute();
    }

    protected Command createCommand() {
        // notice the Spring API dependency!
        return this.applicationContext.getBean("command", Command.class);
    }

    public void setApplicationContext(
            ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
}  

这样的方式是不推荐的:

  1. 与Spring容器产生了耦合
  2. 如果没有源文件(没办法实现接口),这样的方式无法解决问题

解决方式二:Lookup Method Injection

Lookup method injection is the ability of the container to override methods on container-managed beans and return the lookup result for another named bean in the container.

上面是官方文档的解释,也就是说,容器通过重写一个bean的方法,返回另一个bean(被依赖的非单例bean)。

The Spring Framework implements this method injection by using bytecode generation from the CGLIB library to dynamically generate a subclass that overrides the method.

Spring框架通过CGLIB(代码生成库)动态地生成重写了方法的字节码。

修改上面的例子,现在,要把createCommand()方法交给容器重写

public abstract class CommandManager {

    public Object process(Object commandState) {
        // grab a new instance of the appropriate Command interface
        Command command = createCommand();
        // set the state on the (hopefully brand new) Command instance
        command.setState(commandState);
        return command.execute();
    }

    // okay... but where is the implementation of this method?
    protected abstract Command createCommand();
}

配置元数据如下:<lookup-method/>指出要重写的方法以及方法返回的bean。

<!-- a stateful bean deployed as a prototype (non-singleton) -->
<bean id="myCommand" class="fiona.apple.AsyncCommand" scope="prototype">
    <!-- inject dependencies here as required -->
</bean>

<!-- commandProcessor uses statefulCommandHelper -->
<bean id="commandManager" class="fiona.apple.CommandManager">
    <lookup-method name="createCommand" bean="myCommand"/>
</bean>   

这个配置的含意:Spring框架重写createCommand()方法,这个方法返回myCommand。

注:如果上面的myCommand这个bean是单例bean,则createCommand每次返回同一个对象。(一般没人这样做)

被重写的方法的签名必须符合下面的要求:

<public|protected> [abstract] <return-type> theMethodName(no-arguments);

上面的例子是一个抽象方法,被重写的方法可以是具体的方法。

除了使用XML配置,还可以使用注解的方式

public abstract class CommandManager {

    public Object process(Object commandState) {
        Command command = createCommand();
        command.setState(commandState);
        return command.execute();
    }

    @Lookup("myCommand")
    protected abstract Command createCommand();
}

Lookup("myCommand")指出这个方法要被重写,返回id/name为myCommand的bean。

除了使用id/name指定要返回的bean,Spring还可以根据方法的返回类型自动匹配bean:

public abstract class CommandManager {

    public Object process(Object commandState) {
        MyCommand command = createCommand();
        command.setState(commandState);
        return command.execute();
    }

    @Lookup
    protected abstract MyCommand createCommand();
}

要重写的方法是createCommand,它的返回值类型为MyCommand,所以,会返回容器中类型为MyCommand的bean。

总结

Method Injection,通过方法注入依赖。就像上面的Lookup method,通过这个方法得到另一个bean的引用。

一个bean可以使用Lookup Method来得到它依赖的bean,从而不用字段来保存另一个bean的引用。

发布了213 篇原创文章 · 获赞 116 · 访问量 8万+

猜你喜欢

转载自blog.csdn.net/qq2071114140/article/details/104284268