美文网首页
第2章:spring 依赖

第2章:spring 依赖

作者: 小聪明李良才 | 来源:发表于2017-11-09 17:54 被阅读39次

    第2章:spring 依赖

    标签(空格分隔): JavaEE开发的颠覆者SpringBoot实战


    spring中声明Bean的属性和构造函数参数有两种方法:

    • <property/>元素
    • <constructor-arg/>元素

    另外在声明具体的值上,我们可以是 Straight values(primitives, Strings),也可以使idref元素,或者是对其他bean的指向,下面分别举例子:

    Straight values

    <property/>元素的value属性可以直接是一个字符串,然后通过 Spring 的conversion service 进行转换,看例子:

    <bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
            <!-- results in a setDriverClassName(String) call -->
            <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
            <property name="url" value="jdbc:mysql://localhost:3306/mydb"/>
            <property name="username" value="root"/>
            <property name="password" value="masterkaoli"/>
    </bean>
    

    另外除了能将String转换原始类型,还可以转换 properties 值,看下面的例子的:

    <bean id="mappings"
            class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
    
            <!-- typed as a java.util.Properties -->
            <property name="properties">
                    <value>
                            jdbc.driver.className=com.mysql.jdbc.Driver
                            jdbc.url=jdbc:mysql://localhost:3306/mydb
                    </value>
            </property>
    </bean>
    

    value中的值会转化为java.util.Properties

    idref 元素

    看使用方式:

    <bean id="theTargetBean" class="..."/>
    
    <bean id="theClientBean" class="...">
            <property name="targetName">
                    <idref bean="theTargetBean"/>
            </property>
    </bean>
    

    这种通过<idref/>指定的方式,可以方便IoC容器在部署的时候就去检查所依赖的Bean是否存在的,上面的方式和下面的声明是一样的功能,缺点就是不能检查value值是否存在,只能在运行时看的。

    <bean id="theTargetBean" class="..." />
    
    <bean id="client" class="...">
            <property name="targetName" value="theTargetBean"/>
    </bean>
    

    ref 元素

    ref vs idref 区别是啥呢?

    idref 只是一个字符串,而 ref 则是注入对象,看示例:

    <bean id="a" class="com.wisely.ref.A"/>
        <bean id="b" class="com.wisely.ref.B"/>
    
        <bean id="hello" class="com.wisely.ref.Hello">
            <constructor-arg ref="a"/>
            <constructor-arg>
                <idref bean="b"/>
            </constructor-arg>
            <property name="name" value="Hello" />
            <property name="age" value="12"/>
        </bean>
    

    此处构造函数一个是 ref,一个是 idref,再看代码:

    public class Hello {
        private String name;
        private int age;
        private String b;
        private A a;
        public Hello(A a, String b) {
            this.a = a;
            this.b = b;
        }
    }
    

    此处b传入的就只是一个字符串"b"。

    inner beans

    我们可以在<property/>,<constructor-arg/>中通过<bean/>标签来创建内部的bean,看例子:

    <bean id="outer" class="...">
            <!-- instead of using a reference to a target bean, simply define the target bean inline -->
            <property name="target">
                    <bean class="com.example.Person"> <!-- this is the inner bean -->
                            <property name="name" value="Fiona Apple"/>
                            <property name="age" value="25"/>
                    </bean>
            </property>
    </bean>
    

    Collections

    Collections 标签有:<list/>, <set/>, <map/>, <props/>,分别代表:List, Set, Map, Properties。来看一个具体的例子:

    <bean id="moreComplexObject" class="example.ComplexObject">
            <!-- results in a setAdminEmails(java.util.Properties) call -->
            <property name="adminEmails">
                    <props>
                            <prop key="administrator">administrator@example.org</prop>
                            <prop key="support">support@example.org</prop>
                            <prop key="development">development@example.org</prop>
                    </props>
            </property>
            <!-- results in a setSomeList(java.util.List) call -->
            <property name="someList">
                    <list>
                            <value>a list element followed by a reference</value>
                            <ref bean="myDataSource" />
                    </list>
            </property>
            <!-- results in a setSomeMap(java.util.Map) call -->
            <property name="someMap">
                    <map>
                            <entry key="an entry" value="just some string"/>
                            <entry key ="a ref" value-ref="myDataSource"/>
                    </map>
            </property>
            <!-- results in a setSomeSet(java.util.Set) call -->
            <property name="someSet">
                    <set>
                            <value>just some string</value>
                            <ref bean="myDataSource" />
                    </set>
            </property>
    </bean>
    

    Null 和 empty string values

    我们可以在xml中通过""和null来表示空字符串和null值。

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

    beans 延迟初始化

    IoC对于单例的Beans默认是在创建ApplicationContext中的过程中就初始化了,有时候我们希望只有当请求这个Bean的时候才去初始化,这个时候可以在xml中通过配置lazy-init="true"来实现,如下:

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

    但是如果依赖于这个lazy Bean的其他Bean是需要在ApplicationContext中初始化的,则即使设置了lazy-init,仍会在ApplicationContext创建的时候初始化。
    另一种设置所有beans延迟加载的方式如下:

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

    bean 自动装配

    自动转配(Autowiring collaborators)机制能有效的减少xml的配置,ApplicationContext会自动帮你将依赖注入。

    Autowiring 的模式有4种:

    模式 说明
    no 默认不进行自动装配,这样在大型项目中能更好的对项目进行控制
    byName 属性名字和Bean名字一致
    byType 按属性类型装配,如果存在多个统一类型的Bean,则报错
    constructor 和 byType 一致

    方法注入

    一般 IoC 容器中管理的Bean都是单例的,如果我们有一个单例的Bean的属性是一个非单例的Bean,那会存在单例Bean只会创建一次,意味着属性也只会注入一次,但是我们希望每次这个属性Bean都是新的,这个时候怎么办呢?

    一个做法是实现ApplicationContextAware接口,我们主动通过getBean方法去获取Bean,代码如下:

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

    上面代码的问题就是侵入式,所有就有了下面的method injection技术,直接上代码:

    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="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>
    

    上面的技术是通过CGLIB来做的,简单说就是动态创建了一个proxy来覆写方法。

    Bean scopes

    目前spring常用的scope有6个,分别介绍下,下面是第一个:singleton scope。

    singleton scope

    在IoC容器中只保存一个Bean


    singleton scopesingleton scope

    此处和GoF提的单例模式的区别是:单例模式是一个ClassLoader都只有一个,而此处是每个IoC容器一个Bean。

    注意:默认 scope 是 singleton scope

    prototype scope

    使用 singleton scope 还是 prototype scope 原则是:有状态的bean使用 prototype,无状态的使用 singleton。

    prototype scopeprototype scope

    每次请求bean都是生成一个新的bean,这就意味着如果一个 singleton scope 的如果依赖于一个 prototype scope 的bean,那这个 prototype 只会生成一次,因此需要用到之前的method injection技术。

    Request, session, application, and WebSocket scopes

    这些 scope 只能在 web 应用中使用,IoC容器例如:XmlWebApplicationContext,当不在这种容器中的时候,会抛出异常。

    request scope
    request scope 定义的bean在每次http请求的时候都会重新创建。
    session scope
    request scope定义的bean在每个 http session 请求的时候都会重新创建。 **application scope** 作为 ServletContext 的属性存在,类似于 singleton scope,不同在于 singleton scope 是每个ApplicationContext`一个。

    不同 scope bean 之间的依赖关系

    我们不应该将生命周期短的bean注入到生命周期长的bean中,看配置:

    <bean id="userPreferences" class="com.foo.UserPreferences" scope="session"/>
    
    <bean id="userManager" class="com.foo.UserManager">
            <property name="userPreferences" ref="userPreferences"/>
    </bean>
    

    此时 userManager 是长时间存在的,一旦去访问 userPreferences 的,此时 userPreferences 会销毁掉,会出问题,那解决办法就是通过代理的方式。

    <bean id="userPreferences" class="com.foo.UserPreferences" scope="session">
            <aop:scoped-proxy/>
    </bean>
    
    <bean id="userManager" class="com.foo.UserManager">
            <property name="userPreferences" ref="userPreferences"/>
    </bean>
    

    Bean 生命周期

    在整个spring中控制Bean生命周期的方法有:

    1. 实现spring的 InitializingBeanDisposableBean方法
    2. 自定义 init()destroy()方法
    3. @PostConstruct@PreDestroy 注释

    然后如果这些都存在的话,其执行的一个顺序是:

    1. 带有@PostConstruct的方法
    2. 实现的方法 afterPropertiesSet()
    3. 用户自定义的init方法
      destroy的时候的顺序是:
    4. 带有@PreDestroy的方法
    5. 实现接口DisposableBeandestroy()方法
    6. 用户自定义的destroy()方法

    下一篇将会详细如何使用java注释来代替xml的方式。

    更好的阅读体验可以看 https://www.zybuluo.com/zhuanxu/note/944455

    相关文章

      网友评论

          本文标题:第2章:spring 依赖

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