3

作者: google666s | 来源:发表于2016-09-03 14:56 被阅读43次

    XML配置

    <?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="parent" class="com.example.spring.bean.collection.CollectionMerge">
            <property name="list">
                <list >
                    <value>1</value>
                    <value>2</value>
                </list>
            </property>
        </bean>
        
        <bean id="child" parent="parent" >
            <property name="list">
                <list merge="true">
                    <value>1</value>
                    <value>2</value>
                </list>
            </property>
        </bean>
    </beans>
    

    测试代码

    import org.junit.Test;
    import org.junit.runner.RunWith;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.beans.factory.annotation.Qualifier;
    import org.springframework.test.context.ContextConfiguration;
    import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
    
    @ContextConfiguration
    @RunWith(SpringJUnit4ClassRunner.class)
    public class MapMergeTests {
        
        @Autowired
        @Qualifier("child")
        private CollectionMerge service;
        
        @Test
        public void testSimpleProperties() throws Exception {
            assertEquals(4, service.getList().size());
        }
    }
    
    

    经过动手实验,证明Spring合并对于List类型,并无覆盖。接下来,我们看看其源码实现

    MutablePropertyValues.mergeIfRequired()方法

    private PropertyValue mergeIfRequired(PropertyValue newPv, PropertyValue currentPv) {
        Object value = newPv.getValue();
        //属性值是否可合并类
        if (value instanceof Mergeable) {
            Mergeable mergeable = (Mergeable) value;
            //属性值是否设置了merge=true
            if (mergeable.isMergeEnabled()) {
                //合并当前属性
                Object merged = mergeable.merge(currentPv.getValue());
                return new PropertyValue(newPv.getName(), merged);
            }
        }
        return newPv;
    }
    

    上面代码中Mergeable接口共有5个实现类ManagedList,ManagedArray,ManagedMap,ManagedSet,ManagedProperties

    ManagedList.merge(Object parent)方法

    public List<E> merge(Object parent) {
            //防御性抛异常
            if (!this.mergeEnabled) {
                throw new IllegalStateException("Not allowed to merge when the 'mergeEnabled' property is set to 'false'");
            }
            if (parent == null) {
                return this;
            }
            //不能合并非List类型集合
            if (!(parent instanceof List)) {
                throw new IllegalArgumentException("Cannot merge with object of type [" + parent.getClass() + "]");
            }
            List<E> merged = new ManagedList<E>();
            //注意顺序,先增加父bean中的value值,所以文档中说父集合元素索引位置靠前
            merged.addAll((List) parent);
            merged.addAll(this);
            return merged;
        }
    

    译注:我勒个去,为了找这段代码,洒家差点累吐血。由此可见,译者是非常用心的用生命去翻译文档。

    合并限制
    不能合并不同类型的集合,比如合并MapList(译注:上面的源码中有这样的代码,不知聪明的小读者是否注意到了)。如果你非得这么干,那么就会抛出个异常。merge属性必须指定给父-子继承结构bean中的子bean,如果指定给了父集合则无效,不会产生预期的合并结果。

    强类型集合
    Java5中出现了范型,所以可以给集合使用强类型限制。比如说,声明一个只含有String类型的Collection。若使用Spring 注入一个强类型Collection给一个bean,那么就可以利用Spring的类型转换特性 ,该特性能将给定的值转换成合适的类型值,然后赋值给你的强类型Collection

    public class Foo {
    
        private Map<String, Float> accounts;
    
        public void setAccounts(Map<String, Float> accounts) {
            this.accounts = accounts;
        }
    }
    

    看,飞碟

    <beans>
        <bean id="foo" class="x.y.Foo">
            <property name="accounts">
                <map>
                    <entry key="one" value="9.99"/>
                    <entry key="two" value="2.75"/>
                    <entry key="six" value="3.99"/>
                </map>
            </property>
        </bean>
    </beans>
    

    foobean的accounts属性注入前,强类型集合Map<String,Float>的泛型信息通过反射获取。因此Spring的类型转换机制识别出元素的value类型将会转换为Float9.99,2.75,3.99将会转换成Float类型。

    <h5 id=beans-null-element>Null值和空字串</h5>
    Spring treats empty arguments for properties and the like as empty Strings. The following XML-based configuration metadata snippet sets the email property to the empty String value ("").
    Spring对于属性的空参数转换为空字串。下面的XML片段,设置值email属性为空格字串("")

    <bean class="ExampleBean">
        <property name="email" value=""/>
    </bean>
    

    上面的xml配置相当于这样的java代码

    exampleBean.setEmail("")
    

    <null/>元素处理null值:

    <bean class="ExampleBean">
        <property name="email">
            <null/>
        </property>
    </bean>
    

    上面配置相当于

    exampleBean.setEmail(null)
    

    <h5 id='beans-p-namespace'>XML简写p命名空间</h5>
    p-命名空间能让你使用bean元素属性替代内嵌property/>元素,用来描述属性值或者协作类。

    Spirng支持命名空间扩展配置,命名空间基于XML Schema定义。本章讨论的beans配置格式定义在XML Schema文档中。然而,p命名空间并不是在XSD文件中,而是存在于Spring核心中。

    下面XML片段解释了:1使用了标准XML,第2个使用p-命名空间

    <beans xmlns="http://www.springframework.org/schema/beans"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:p="http://www.springframework.org/schema/p"
        xsi:schemaLocation="http://www.springframework.org/schema/beans
            http://www.springframework.org/schema/beans/spring-beans.xsd">
    
        <bean name="classic" class="com.example.ExampleBean">
            <property name="email" value="foo@bar.com"/>
        </bean>
    
        <bean name="p-namespace" class="com.example.ExampleBean"
            p:email="foo@bar.com"/>
    </beans>
    

    上例中解释了,在bean定义中使用p-namespace设置email 属性。它告诉Spring这里有一个property声明。前面提到过,p-namespace 并不存在schema定义,所以p可以修改为其他名字。
    译注,干活都是译者自己撰写用于验证,而非参考手册原版中的内容,之所以验证,是因为原版E文有看不懂的地方、或者翻译拿不准、或者就是闲来无事、或者就是为了凑篇幅,这些事儿得通过写代码验证搞定了
    up fuck goods上干货

    <beans xmlns="http://www.springframework.org/schema/beans"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:ppp="http://www.springframework.org/schema/p"
        xsi:schemaLocation="http://www.springframework.org/schema/beans
            http://www.springframework.org/schema/beans/spring-beans.xsd">
            
        <bean name="p-namespace" class="com.example.spring.bean.p.PNamespaceBean"
            ppp:email="foo@email.com"/>
    </beans>
    

    注意p命名空间的用法xmlns:ppp="http://www.springframework.org/schema/p"ppp:email="foo@email.com"

    go on fuck goods继续干货

    public class PNamespaceBean {
        private String email;
    
        public String getEmail() {
            return email;
        }
    
        public void setEmail(String email) {
            this.email = email;
        }
        
        
    }
    

    下面样例中,2个bean都引用了同一个bean

    <beans xmlns="http://www.springframework.org/schema/beans"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:p="http://www.springframework.org/schema/p"
        xsi:schemaLocation="http://www.springframework.org/schema/beans
            http://www.springframework.org/schema/beans/spring-beans.xsd">
    
        <!--传统-->
        <bean name="john-classic" class="com.example.Person">
            <property name="name" value="John Doe"/>
            <property name="spouse" ref="jane"/>
        </bean>
    
        <!--时髦-->
        <bean name="john-modern"
            class="com.example.Person"
            p:name="John Doe"
            p:spouse-ref="jane"/>
    
        <!---->
        <bean name="jane" class="com.example.Person">
            <property name="name" value="Jane Doe"/>
        </bean>
    </beans>
    

    样例中2个p-namespace设置属性值,出现了一种新的格式声明引用。第一个bean中,使用了<property name="spouse" ref="jane"/>应用bean jane.第二个bean中,使用了p:spouse-ref="jane"做了相同的好事儿,此时,spouse是属性名,然而-ref表示这不是直接量而是引用另一个bean。

    译注 好事儿,我小时候虽然做好事儿不留名,但是总能被发现,令我非常苦恼。我的妈妈常常揪着我的耳朵问:这又是你干的好事儿吧。

    <h5 id="beans-c-namespace">c-namespace命名空间</h5>

    p-namespace相似,c-namespace,是Spring3.1中新出的,允许行内配置构造参数,而不需使用内嵌的constructor-arg元素

    c:namespace重构构造注入

    <beans xmlns="http://www.springframework.org/schema/beans"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:c="http://www.springframework.org/schema/c"
        xsi:schemaLocation="http://www.springframework.org/schema/beans
            http://www.springframework.org/schema/beans/spring-beans.xsd">
    
        <bean id="bar" class="x.y.Bar"/>
        <bean id="baz" class="x.y.Baz"/>
    
        <!-- traditional declaration -->
        <bean id="foo" class="x.y.Foo">
            <constructor-arg ref="bar"/>
            <constructor-arg ref="baz"/>
            <constructor-arg value="foo@bar.com"/>
        </bean>
    
        <!-- c-namespace declaration -->
        <bean id="foo" class="x.y.Foo" c:bar-ref="bar" c:baz-ref="baz" c:email="foo@bar.com"/>
    
    </beans>
    

    c:namespace和p:使用了相同机制(ref后缀表示引用),通过names设置构造参数。因为它未定义在XSD schema中(但是存在于Spring内核中),所以需要先声明。

    有一些情况比较特殊,不能识别或者看到构造参数(比如无源码且编译时无调试信息),此时可以求助于参数索引:

    <!-- c-namespace index declaration -->
    <bean id="foo" class="x.y.Foo" c:_0-ref="bar" c:_1-ref="baz"/>
    
    注意

    由于XML语法,索引标记需要_开头作为XML属性名称,而不能使用数字开头(尽管某些ID支持)

    在实际中,构造注入(name匹配/类型匹配/索引匹配)是非常高效的,一般情况下,推荐使用 name匹配方式配置。

    <h5 id='beans-compound-property-names'>复合属性</h5>
    在设置bean属性时,可以使用复合或者内嵌属性,组件路径可以有多长写多长,除了最后一个属性,其他属性都不能为null。看下面的bean定义

    <bean id="foo" class="foo.Bar">
        <property name="fred.bob.sammy" value="123" />
    </bean>
    

    bean foo有属性fred,fred有属性bob,bob有属性sammy,最后的sammy属性赋值"123"。在beanfoo构造后,fred属性和bob属性都不能为null否则抛异常NullPointerException

    <h4 id='beans-factory-dependson'>使用depends-on</h4>

    若bean是另个bean的依赖,通常是指该bean是另个bean的属性。在XML中通过<ref/>元素配置实现。然而,bean之间并不全是直接依赖。举个栗子,类中有个静态初始化需要出发,像注册数据库驱动这样的。depends-on属性能强制这些先决条件首先完成执行初始化,然后再去使用它(比如用于注入)。
    下面的样例中,展示了使用depends-on来表达bean之间的依赖关系:

    <bean id="beanOne" class="ExampleBean" depends-on="manager"/>
    <bean id="manager" class="ManagerBean" />
    

    也可以依赖多个bean,为depends-on属性值提供一个bean name列表,用逗号,空白,分号分隔。

    <bean id="beanOne" class="ExampleBean" depends-on="manager,accountDao">
        <property name="manager" ref="manager" />
    </bean>
    
    <bean id="manager" class="ManagerBean" />
    <bean id="accountDao" class="x.y.jdbc.JdbcAccountDao" />
    
    注意

    单例bean中,depends-on属性既可以设定依赖的初始化时机,也可以相应的设定依赖的销毁时机。在bean被销毁之前,bean使用depdnds-on属性定义的依赖bean会首先被销毁。因此depends-on也能控制销毁顺序。

    <h4 id='beans-factory-lazy-init'>延迟初始化</h4>
    ApplicationContext的各种实现默认的初始化处理过程,都是尽早的创建、配置所有的单例bean。通常,这种预先实例化是非常好的,因为在配置的错误或者环境问题立刻就能暴露出来,而不是数小时甚至数天后才发现。若不需要此行为,可以通过设置lazy-initialized延迟加载来阻止预先初始化。lazy-initializedbean告诉Ioc容器,只有在第一次请求的时候采取初始化,而不是在启动容器时初始化。

    在XML中,属性lazy-init控制<bean/>元素的初始化。

    <bean id="lazy" class="com.foo.ExpensiveToCreateBean" lazy-init="true"/>
    <bean name="not.lazy" class="com.foo.AnotherBean"/>
    

    ApplicationContext解析上面的配置,在启动时,不会预先初始化这个标记为lazy的bean,为标记lazy的bean则会立刻初始化。

    如果一个非延迟的单例bean依赖了lazy延迟bean,ApplicationContext会在启动时就创建lazy延迟bean,因为它必须满足单例bean依赖。延迟bean注入给单例bean,就意味着,它不会延迟加载的。

    通过设置<beans/>元素的default-lazy-init属性,可以设置容器级别的延迟加载。看样例:

    <beans default-lazy-init="true">
        <!-- no beans will be pre-instantiated... -->
    </beans>
    

    <h4 id='beans-factory-autowire'>自动装配</h4>
    Spring容器提供自动装配,用于组织bean之间的依赖。可以让Spring,通过检查ApplicationContext内容完成自动注入。自动装配有以下优点:

    • 自动装配能明显减少属性和构造参数的配置。(在这方面,还有其他的机制也能达到此目的,比如bean 模板,在后面的章节里有详细的讲解
    • 自动装配可扩展性非常好。比如,给类增加了依赖,无需修改配置,依赖类就能自动注入。因此,自动装配在开发期非常有用,在代码不稳定时,无需修改编码即可完成切换。

    在XML中,设置<bean/>元素的autowire属性来设定bean的自动装配模式。自动装配有5种模式。可以选择任意一种,来设置bean的装配模式。(译注,这不是废话么,有5中模式,每种都能随便用,假设有一种不能用,那就是4种模式了么

    Table 5.2. 自动装配模式

    模式 解释
    no (默认的)非自动装配。必须使用ref元素定义bean引用。对于大规模系统,推荐使用,明确的指定依赖易于控制,清楚明了。它在某种程度上说明了系统的结构。
    byName 通过属性名称property name自动装配。Spring会查找与需要自动装配的属性同名bean。举个栗子,若在bean定义中设置了by name方式的自动装配,该bean有属性master(当然了,还得有个setMaster(..)写属性方法),Spring会查找一个名叫master的bean,并将它注入给master熟悉。
    byType 若在容器中存在一个bean,且bean类型与设置自动装配bean的属性相同,那么将bean注入给属性。若与属性同类型的bean多于1个,则会抛出我们期待已久的致命异常,也就意味着这个bean也许不适合自动注入。若不存在匹配的bean,啥都不会发生;属性也不会设置,然后就没有然后了。
    constructor Analogous to byType, but applies to constructor arguments. If there is not exactly one bean of the constructor argument type in the container, a fatal error is raised.和byType模式类似,但是应用于构造参数。若在容器中不存在与构造参数类型相同的bean,那么接下来呢,抛异常呗,还能干啥?

    byType或者constructor自动装配模式,可以装配arrays数组和范型集合。 这种个情况
    下,容器内匹配类型的bean才会注入给依赖。若key类型是String,你也能自动装配强类型Maps。一个自动装配的Map,所有 匹配value类型的bean都会注入Map.value,此时,Map的key就是相应的bean的name。

    可以结合自动装配和依赖检查,依赖检查会在装配完成后执行。

    <h5 id='beans-autowired-exceptions'>自动装配的局限和缺点</h5>
    自动装配最好是贯穿整个项目。若不是全部或大部分使用自动装配,而仅仅自动装配一两个bean定义,可能会把开发者搞晕。(译注,最可能的原因是,开发者对自动装配机制不熟悉,或者想不到项目中居然还使用了自动装配模式,当发生问题时,擦的,找都没地方找去,调试信息里只能看到经过横切织入事务代理的proxy)

    总结局限和缺点:

    • 属性中和构造参数明确的依赖设置会覆盖自动装配。不能自动装配所谓的简单属性,比如原始类型,StringsClasses(简单属性数组)。这是源于Spring的设计。
    • 和明确装配相比,自动装配是不确切的。正如上面的列表中提到的,Spring谨慎避免匹配模糊,若真的匹配不正确,则导致错误发生,Spring 管理的对象之间的关系记录也变的不明确了。
    • 对于根据Spring容器生成文档的工具,装配信息将变的无用。
    • 容器内多个bean定义可能会匹配设置为自动装配的setter方法或者构造参数的类型。对于arrays,collections,或者maps,这不是个问题。然而对于期望单一值的依赖,这种歧义将不能随意的解决。如果发现多个类型匹配,将会抛出异常 .

    在后面的场景中,给你几条建议:

    • 放弃自动装配,使用明确装配
    • 避免通过在bean定义中设置autowire-candidate属性为false的方式来设置自动装配,下一章节会讲
    • 通过设置<bean/>袁术的primary属性为true来指定单个bean定义作为主候选bean。
    • 使用基于注解的配置实现更细粒度的控制,参看Section 5.9, “Annotation-based container configuration”.

    <h5 id='beans-factory-autowire-candidate'>排除自动装配bean</h5>
    在每个bean的设置中,你可以排除bean用于自动装配。XML配置中,设置<bean/>元素的autowire-candidate属性为false;容器将不使用该bean自动装配。(包括注解配置,像@Autowired

    • 使用对bean名字进行模式匹配来对自动装配进行限制。其做法是在<beans/>元素的'default-autowire-candidates'属性中进行设置。比如,将自动装配限制在名字以'Repository'结尾的bean,那么可以设置为"Repository“。对于多个匹配模式则可以使用逗号进行分隔。注意,如果在bean定义中的'autowire-candidate'属性显式的设置为'true' 或 'false',那么该容器在自动装配的时候优先采用该属性的设置,而模式匹配将不起作用。译注这一段翻译是从网上copy过来的,我勒个擦,得赶紧睡觉去了*

    这些设置非常有用。但是这些被排除出自动注入的bean是不会自动注入到其他bean,但是它本身是可以被自动注入的。

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

    解决办法是放弃一些IoC控制反转。令A实现接口ApplicationContextAware,此时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容器的高级特性,能够简洁的满足此场景。

    想要了解更多的方法注入,参看此博客

    <h5 id='beans-factory-lookup-method-injection'>查找式方法注入</h5>
    查找式是指,容器为了覆盖它所管理的bean的方法,在容器范围内查找一个bean作为返回结果。通常是查找一个原型(prototype)bean,就像是上面章节中提到过的场景。Srping框架,使用CGLIB类库生成动态子类的字节码技术,覆盖方法。

    注意

    为了能让动态子类能运行,其父类不能是final类,被覆盖的方法也不能是final。还有,你得自己测试父类是否含有abstract方法,如果有,需要你提供默认实现。最后,被方法注入的对象不能序列化。Spring 3.2以后,不需要CGLIB的类路径了,因为CGLIB被打包入了org.springframework 包,和Spring-core 这个jar包在一起了。既是为了方便也是为了避免CGLIB包与应用中用到的CGLIB包冲突。

    来看看前面提到的CommandManager类的代码片段,Spring容器会动态的覆盖createCommand()方法的实现。这样CommandManager类就不会依赖任何Spring API了。下面是修改过后的

    package fiona.apple;
    
    // 不再有 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?
        //okay....但是方法实现在哪里?
        protected abstract Command createCommand();
    }
    

    在含有被注入方法的类中(像CmmandManager类),被注入方法需要使用以下签名

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

    动态生成子类会实现抽象方法。若该方法不是抽象的,动态生成自来则会重写在源类中的方法。配置如下:

    <!-- 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>
    

    commandManager类调用createCommand方法时,动态代理类将会被识别为commandManager返回一个command bean的实例。将commandbean设置成prototype,一定要小心处理。若被设置成了singleton,每次调用将返回同一个commandbean。

    注意

    感兴趣的小读者也找找ServiceLocatorFactoryBean类(在org.springframework.beans.factor.config包)来玩玩。使用ServiceLocatorFactoryBean类的处理手法和另一个类ObjectFactoryCreatingFactoryBean类似,但是ServiceLocatorFactoryBean类与虚拟指定你自己的lookup接口(查找接口),这与Spring指定lookup接口略有不同。详情参看这些类的javadocs

    译注,今天加班晚了点,到家时23:30了,可是今天还没翻。进了家门,打开电脑,翻一小节再说,要不估计睡不着觉了。对于我自己的毅力,我还是有相当的认识的,比如:无论咳的多么严重,都能坚持抽烟,由此可见一斑。以上是玩笑。我的意志力并不强,但是意志薄弱也有意志薄弱的积极的正面的意义,比如我养成了每天翻点东西的习惯,哪怕就是再困、再饿、再累,也得翻译一下,因为要是不翻译的话,我就得跟自己的习惯作斗争了,准确的说是和自己斗争,而我却又没有与自己斗争的念想,我根本打不过我自己,就这样,我又翻了一小节

    <h5 id="beans-factory-arbitrary-method-replacement">任意方法替换</h5>
    还有一种方法注入方式,不如lookup method注入方式好用,可以用其他bean方法实现替换受管理的bean的任意方法。你可以跳过本节,当真的需要时再回来也是可以的。

    在xml配置中,设置replaced-method元素,就可用其他实现来替换已经部署的bean中存在的方法实现。考虑下面的类,有一个我们要重写的方法computeValue

    public class MyValueCalculator {
    
        public String computeValue(String input) {
            // balbalba
        }
    
        // 其他方法...
    
    }
    

    相关文章

      网友评论

          本文标题:3

          本文链接:https://www.haomeiwen.com/subject/qrgkettx.html