码农小汪-spring框架学习之3-spring Method injection(方法注入 ) Lookup method injection

方法注入 单例依赖非单例

一般情况,容器中的大部分的 bean 都是单例的。当单例 bean 依赖另一个单例bean,或者一个非单例 bean 依赖另个非单例 bean 是,通常是将另一个 bean定义成其他 bean 的属性。当 bean 的生命周期不同时,那么问题来了。假设单例 bean A 依赖非单例 bean(prototype) B,也许会在每个方法里都需要 B。容器之创建了一个单例 bean A,因此只有一次将 B 注入的机会。 A 调用 B,需要很多 B 的实例 ,但是容器不会这么干

解决办法是放弃一些 IoC 控制反转。令 A 实现接口 ApplicationContextAware,You can make bean A aware of the container by implementing the ApplicationContextAware interface此时 A 能够感知容器,即获取 ApplicationContext,每次当 A 调用 B 时,调用容器的 getBean(“B”)方法用以创建 B 的实例。

// a class that uses a stateful Command-style class to perform some processing
package fiona.apple;

// Spring-API imports
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;

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

并不推荐上面的做法,因为业务代码耦合了 Spring 框架。方法注入,是SpringIoc 容器的高级特性,能够简洁的满足此场景。

查找式方法注入 这个是个高级的用法

查找式是指,容器为了覆盖它所管理的 bean 的方法,在容器范围内查找一个bean 作为返回结果。通常是查找一个原型(prototype)bean,就像是上面章节中提到过的场景。 Srping 框架,使用 CGLIB 类盖方法。
Note:For this dynamic subclassing to work, the class that the Spring container will subclass cannot be final, and the method to be overridden cannot be final either. Also, testing a class that has an abstract method requires you to subclass the class yourself and to supply a stub implementation of the abstract method. Finally, objects that have been the target of method injection cannot be serialized. As of Spring 3.2 it is no longer necessary to add CGLIB to your classpath, because CGLIB classes are repackaged under org.springframework and distributed within the spring-core JAR. This is done both for convenience as well as to avoid potential conflicts with other projects that use differing versions of CGLIB.()不需要导入这个包,被集成进来了!

看CommandManager类在前面的代码片段,你看到 Spring容器将动态覆盖的实现createCommand()方法。 你的CommandManager类没有任何Spring依赖项,如 中看到的例子:

package fiona.apple;

// no more Spring imports!

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

这样子就可以调用了,特别的爱,给特别的你….

<!-- a stateful bean deployed as a prototype (non-singleton) -->
<bean id="command" 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="command"/>
</bean>

bean标识为commandManager调用自己的方法createCommand()当它需要的一个新实例命令bean。 你必须小心部署 的commandbean作为 prototype,如果这实际上是必要的。 如果它是部署 作为一个单例,同样的的实例command每次返回bean。

Bean scopes

Stylemenas
单例 singleton(Default) Scopes a single bean definition to a single object instance per Spring IoC container.
prototype 原型范围一个bean定义任意数量的对象实例。
request 请求Scopes a single bean definition to the lifecycle of a single HTTP request; that is, each HTTP request has its own instance of a bean created off the back of a single bean definition. Only valid in the context of a web-aware Spring ApplicationContext.
sessionScopes a single bean definition to the lifecycle of an HTTP Session. Only valid in the context of a web-aware Spring ApplicationContext.
global sessionScopes a single bean definition to the lifecycle of a global HTTP Session. Typically only valid when used in a portlet context. Only valid in the context of a web-aware Spring ApplicationContext.

一般情况下,我们使用原型模式设计的比较多,单例模式也不错

Request, session, and global session scopes

The request, session, and global session scopes are only available if you use a web-aware Spring ApplicationContext implementation (such as XmlWebApplicationContext). If you use these scopes with regular Spring IoC containers such as the ClassPathXmlApplicationContext, you get an IllegalStateException complaining about an unknown bean scope.
在SPring自己的MVC下使用才是有效的
Initial web configuration 如何配置要根据具体的 Servlet 环境
若使用 Spring Web MVC 访问这些作用域 bean,实际上是使用Spring DispatcherServlet 类或者 DispatcherPortlet 类处理 request,则无需特别配置: DispatcherServlet 和 DispatcherPortlet 已经暴露了所有的相关状态。

作用域beans 作为依赖向

Spring IoC 容器不仅管理 bean 的实例化,也负责组装(或者依赖)。如果想将 HTTP request 作用域 bean 注入给其他 bean,就得给作用域 bean(request或者 session)注入一个 AOP 代理用来替换作用域 bean。通过注入一个代理对象暴露于作用域 bean 相同的的接口,他是代理对象也能从相关作用域( request 或者 session)中检索到真正的被代理对象,并委派方法调用实际对象的方法。

The configuration in the following example is only one line, but it is important to understand the “why” as well as the “how” behind it. 简单,但是要知道怎么去弄

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd">

    <!-- an HTTP Session-scoped bean exposed as a proxy -->
    <bean id="userPreferences" class="com.foo.UserPreferences" scope="session">
        <!-- instructs the container to proxy the surrounding bean -->
        <aop:scoped-proxy/>//一个session类型的
    </bean>

    <!-- a singleton-scoped bean injected with a proxy to the above bean -->
    <bean id="userService" class="com.foo.SimpleUserService">
        <!-- a reference to the proxied userPreferences bean -->
        <property name="userPreferences" ref="userPreferences"/>
    </bean>
</beans>

为啥子要使用代理呢?因为我们的注入的依赖项是一个session类型的,而我们的Service是一个单列,不可能session重开始到结束就只有一个涩。这样不就是矛盾了嘛。虽然我们可以通过方法注入解决问题。代理也是一种方法和手段

Choosing the type of proxy to create

By default, when the Spring container creates a proxy for a bean that is marked up with the element, a CGLIB-based class proxy is created. 通常情况下是使用CGLIB来作为代理的,没有使用jdk自带的

CGLIB 代理只会拦截 public 方法调用。非 public 方法不会“呼叫转移”给实际的作用域 bean。
JDK interface-based 代理,设置元素 proxy-target-class 属性的值为 false 即可。使用标准 JDK 接口代理好处是无需引入第三方 jar 包

<!-- DefaultUserPreferences implements the UserPreferences interface -->
<bean id="userPreferences" class="com.foo.DefaultUserPreferences" scope="session">
    <aop:scoped-proxy proxy-target-class="false"/>
</bean>
<bean id="userManager" class="com.foo.UserManager">
    <property name="userPreferences" ref="userPreferences"/>
</bean>

自定义作用域

感觉这个没有啥子特别的需要
bean是可扩展的范围界定机制;你可以定义你自己的 范围,甚至重新定义现有的范围,尽管后者被认为是不好的做法 和你不能覆盖内置的singleton和prototype范围。
you need to implement the org.springframework.beans.factory.config.Scope interface,需要去实现这个接口
Scope 接口共有 4 个方法用于从作用域获取对象、从作用域删除对象、销毁对象、
session 作用域的实现,该方法返
回 session-scoped 会话作用域 bean(若不存在,方法创建该 bean 的实例,并绑定到 session 会话中,用于引用,然后返回该对象)

Object get(String name, ObjectFactory objectFactory)

下面的方法作用是从作用域中删除对象。以 session 作用域实现为例,方法内删除对象后,会返回该对象,但是若找不到指定对象,则会返回 null

Object remove(String name)

下面的方法作用是注册销毁回调函数,销毁是指对象销毁或者是作用域内对象销毁The following method registers the callbacks the scope should execute when it is destroyed or when the specified object in the scope is destroyed.
这个回调函数是非常熟悉的吧~

void registerDestructionCallback(String name, Runnable destructionCallback)

下面的方法,用于获取作用域会话标识。每个作用域的标识都不一样。比如,session 作用域的实现中,标识就是 session 标识(应该是指 sessionId 吧)

String getConversationId()

使用自定义作用域 you need to make the Spring container aware of your new scope(s). The following method is the central method to register a new Scope with the Spring container:

void registerScope(String scopeName, Scope scope);

此方法声明在ConfigurableBeanFactory 接口中,该在大部分 ApplicationContext 具体实现中都是可用的
registerScope(..)方法第一个参数是作用域名称,该名称具有唯一性。比如Spring 容器内置的作用域 singleton 和 prototype。第二个参数是自定义作用域实现的实例,就是你想注册的、使用的那个自定义作用域。

下面的 SimpleThreadScope 作用域,是 Spring 内置的,但是默认并未注册到容器中。 你自定义的作用域实现,应该也使用相同的代码来注册。

Scope threadScope = new SimpleThreadScope();
beanFactory.registerScope("thread", threadScope);
<bean id="..." class="..." scope="thread">

这个是线程单一的,看源码就知道了

public class SimpleThreadScope implements Scope {

    private static final Log logger = LogFactory.getLog(SimpleThreadScope.class);

    private final ThreadLocal<Map<String, Object>> threadScope =
            new NamedThreadLocal<Map<String, Object>>("SimpleThreadScope") {
                @Override
                protected Map<String, Object> initialValue() {
                    return new HashMap<String, Object>();
                }
            };


    @Override
    public Object get(String name, ObjectFactory<?> objectFactory) {
        Map<String, Object> scope = this.threadScope.get();
        Object object = scope.get(name);
        if (object == null) {
            object = objectFactory.getObject();
            scope.put(name, object);
        }
        return object;
    }

    @Override
    public Object remove(String name) {
        Map<String, Object> scope = this.threadScope.get();
        return scope.remove(name);
    }

    @Override
    public void registerDestructionCallback(String name, Runnable callback) {
        logger.warn("SimpleThreadScope does not support destruction callbacks. " +
                "Consider using RequestScope in a web environment.");
    }

    @Override
    public Object resolveContextualObject(String key) {
        return null;
    }

    @Override
    public String getConversationId() {
        return Thread.currentThread().getName();
    }

}

自定义作用域的实现,不局限于编程式注册。也可以使用 CustomScopeConfigurer类声明式注册,这个比较的简单方便,解耦

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd">

    <bean class="org.springframework.beans.factory.config.CustomScopeConfigurer">
        <property name="scopes">
            <map>
                <entry key="thread">
                    <bean class="org.springframework.context.support.SimpleThreadScope"/>
                </entry>
            </map>
        </property>
    </bean>

    <bean id="bar" class="x.y.Bar" scope="thread">
        <property name="name" value="Rick"/>
        <aop:scoped-proxy/>
    </bean>

    <bean id="foo" class="x.y.Foo">
        <property name="bar" ref="bar"/>
    </bean>

</beans>
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值