第2章:spring 依赖

第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 scope
singleton scope

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

注意:默认 scope 是 singleton scope

prototype scope

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

prototype scope
prototype 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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,924评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,902评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 107,716评论 0 239
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,783评论 0 203
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,166评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,510评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,784评论 2 311
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,476评论 0 196
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,196评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,459评论 2 243
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,978评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,321评论 2 252
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,964评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,046评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,803评论 0 193
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,530评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,420评论 2 265

推荐阅读更多精彩内容