spring(一)Bean的定义和使用

学习资源来自:https://www.tutorialspoint.com/spring/spring_hello_world_example.htm
第一部分:建一个完整的spring项目
1.创建一个maven项目, 通过ClassPathXmlApplicationContext手动加载Bean并用getBean手动调用Bean,
2.pom.xml 加入下面的dependencies部分,并点击IDEA窗口右边的刷新按钮启动maven下载依赖包spring-webmvc.jar

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>springlearning</groupId>
    <artifactId>springlearning-1</artifactId>
    <version>1.0-SNAPSHOT</version>

    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>4.1.6.RELEASE</version>
        </dependency>
    </dependencies>
</project>

点击刷新按钮下载jar包:


image.png

3.编写HelloWorld.java和MainApp.java
//注意把src目录标志成source root(右击src目录,选择mark directory as -> source root), 这样加载的时候直接写Beans.xml就可以在当前目录下(source root)被找到

/**
 * Created by vickyy on 12/25/2018.
 */
public class HelloWorld {
    private String message;

    public void setMessage(String message){
        this.message  = message;
    }
    public void getMessage(){
        System.out.println("Your Message : " + message);
    }
}
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * Created by vickyy on 12/25/2018.
 */
public class MainApp {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml"); //注意Beans.xml要在src下面,并且把src标志成source root, 这样Beans.xml就可以在当前目录下(source root)被找到
        HelloWorld obj = (HelloWorld) context.getBean("helloWorld");
        obj.getMessage();
    }
}

4.编写Beans.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-3.0.xsd">

    <bean id = "helloWorld" class = "HelloWorld"> <--编译完后HelloWorld.class和Beans.xml在同一个目录target/classes/下面-->
        <property name = "message" value = "Hello World!!!"/> <--这里有点像给HelloWorld的属性message重新赋值-->
    </bean>

</beans>

5.运行


image.png
可以看到编译完后Beans.xml 和两个class文件都被放到同一个地方

第二部分 接下来详细说下Beans.xml
4.1 Bean的定义:这里要用<bean> 来定义一个个bean, 这也是spring前期比较麻烦的地方,bean里必须包含最基本的id 和 class, 前者用于之后的搜索查询,后者指定类的路径或者类名,可以不用加.java,但必须要能够在路径下被找到。
4.2Bean的scope https://www.tutorialspoint.com/spring/spring_bean_scopes.htm, scope可以有很多种,
比较常用的是singleton和prototype, 前者表示Spring IoC container只会为这个bean创建一个实例,后者表示只要有请求这个bean的地方Spring IoC container每次都会返回一个新的实例:
<bean id = "helloWorld" class = "HelloWorld" scope="singleton">,
<bean id = "helloWorld" class = "HelloWorld" scope="prototype">, 详见scope测试截图。
4.3<property name = "message" value = "xxx" > 指的是这里可以为bean里的属性赋值,当你运行的时候,输出的信息来自这里。

接下来这个例子来验证下scope=singleton和scope=prototype的不同:
HelloWorld.java的代码不变,但是我们要修改下Beans.xml里helloWorld的scope,并且要修改下MainApp.java
Beans.xml改成这样:

<bean id="helloWorld" class="HelloWorld" scope="singleton">
</bean>

MainApp.java改成这样,加了一个HelloWorld的实例objB,此时bean scope 用的是singleton,spring ioc container只会返回一个实例,也就是说即使objB请求再生出一个HelloWorld, ioc也只会返回前面生成的objA:

public class MainApp {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");
        HelloWorld objA = (HelloWorld) context.getBean("helloWorld");
        objA.setMessage("I am object A");
        objA.getMessage();

        HelloWorld objB = (HelloWorld) context.getBean("helloWorld");
        objB.getMessage();
    }
}

结果:
Your Message : I am object A
Your Message : I am object A

接下来把Beans.xml改成这样:

    <bean id="helloWorld" class="HelloWorld" scope="prototype">
    </bean>

由于是prototype, 因此objB是一个新的实例,不与objA共用实例,所以再运行下MainApp.java,结果是:
Your Message : I am object A
Your Message : null
4.4 Bean的生命周期,有很多,但是这里只讲设置bean的init方法和destroy方法
为什么会有这两个属性呢?想下,Bean的初始化之前,可能要做一些额外的工作来让Bean可以注册入IOC 以供使用,同样的道理,Bean在被销毁之前,可能也需要做一些回收的工作。
虽然可以通过实现接口org.springframework.beans.factory.InitializingBean 来定义一个bean的初始化方法,通过实现接口org.springframework.beans.factory.DisposableBean来定义一个bean的终结方法,但是这里我只讲通过Beans.xml的配置来实现。
初始化,用init-method属性来指定,销毁,用destroy-method属性来指定。注意,destroy-method的定义只在scope=singleton的时候有用,scope=prototype的时候,destroy-method是不会被调用的。这个后面会分析。
先来看看验证的代码:
HelloWorld.java里加入init() 和destory()

public class HelloWorld {
    private String message;

    public void setMessage(String message) {
        this.message = message;
    }

    public void getMessage() {
        System.out.println("Your Message : " + message);
    }

    public void init(){
        System.out.println("Bean is going through init.");
    }
    public void destroy() {
        System.out.println("Bean will destroy now.");
    }
}

MainApp.java, 使用AbstractApplicationContext 替代ApplicationContext, 前者是后者的子类,有一个doClose()方法,这个方法正是销毁Bean的方法,Spring不会主动销毁一个bean.

public class MainApp {
    public static void main(String[] args) {
        AbstractApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");
        HelloWorld objA = (HelloWorld) context.getBean("helloWorld");
        objA.getMessage();
        context.registerShutdownHook();
    }
}

执行结果:
Bean is going through init.
Your Message : null
Bean will destroy now.
接着分析下为什么scope=prototype, destroy-method就不被调用,
从源码来分析:new ClassPathXmlApplicationContext("Beans.xml");触发了refresh()方法进而调用createBean()来注册bean进来, createBean()里有这么一句代码:this.registerDisposableBeanIfNecessary(beanName, bean, mbd);
这个方法是:

otected void registerDisposableBeanIfNecessary(String beanName, Object bean, RootBeanDefinition mbd) {
        AccessControlContext acc = System.getSecurityManager() != null?this.getAccessControlContext():null;
//这里就已经排除了prototype的方法
        if(!mbd.isPrototype() && this.requiresDestruction(bean, mbd)) {
            if(mbd.isSingleton()) { //如果这个方法是singleton,那么就注册disposableBean, 这个注册就是写入一个disposableBeanMap。
                this.registerDisposableBean(beanName, new DisposableBeanAdapter(bean, beanName, mbd, this.getBeanPostProcessors(), acc));
            } else {
                Scope scope = (Scope)this.scopes.get(mbd.getScope());
                if(scope == null) {
                    throw new IllegalStateException("No Scope registered for scope \'" + mbd.getScope() + "\'");
                }

                scope.registerDestructionCallback(beanName, new DisposableBeanAdapter(bean, beanName, mbd, this.getBeanPostProcessors(), acc));
            }

这是前期工作,那么当调用doClose()的时候,当执行到

public void destroySingletons() {
        if(this.logger.isDebugEnabled()) {
            this.logger.debug("Destroying singletons in " + this);
        }

        Map disposableBeanNames = this.singletonObjects;
        synchronized(this.singletonObjects) {
            this.singletonsCurrentlyInDestruction = true;
        }

        Map i = this.disposableBeans;
        String[] var8;
        synchronized(this.disposableBeans) { // prototype没有注册disposableBeans,所以这里为空,下面的for就进不去,也就无法进入destroy方法的调用
            var8 = StringUtils.toStringArray(this.disposableBeans.keySet());
        }

        for(int var9 = var8.length - 1; var9 >= 0; --var9) {
            this.destroySingleton(var8[var9]);
        }

4.5 Bean属性继承, 这与java的类继承不一样
Bean有个属性 “parent=”,只要指定parent, 当前的bean就会继承来自parent的属性,而如果child和parent定义同样的属性,child的值会覆盖parent的值。
看下面例子,parent = helloWorld, 所以helloWorld的init-method, destory-method, scope都会被child:helloInida继承,但因为HelloIndia.java并没有定义init()和destroy(),所以运行会报错,因此这里要删掉init-method, 和destroy-method, 而helloIndia的property message1的值则会覆盖parent message1的值:

//HelloIndia.java
public class HelloIndia {
    private String message1;
    private String message2;
    private String message3;

    public void setMessage1(String message) {

        this.message1 = message;
    }
    public void setMessage2(String message) {
        this.message2 = message;
    }
    public void setMessage3(String message) {
        this.message3 = message;
    }

    public void getMessage1() {

        System.out.println("Your Message : " + message1);
    }
    public void getMessage2() {
        System.out.println("Your Message : " + message2);
    }
    public void getMessage3() {
        System.out.println("Your Message : " + message3);
    }
}
//HelloWorld.java
/**
 * Created by vickyy on 12/25/2018.
 */
public class HelloWorld {
    private String message1;
    private String message2;

    public void setMessage1(String message) {
        this.message1 = message;
    }
    public void setMessage2(String message) {
        this.message2 = message;
    }

    public void getMessage1() {
        System.out.println("Your Message : " + message1);
    }
    public void getMessage2() {
        System.out.println("Your Message : " + message2);
    }

    public void init(){
        System.out.println("Bean is going through init.");
    }
    public void destroy() {
        System.out.println("Bean will destroy now.");
    }
}
//MainApp.java
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * Created by vickyy on 12/25/2018.
 */
public class MainApp {
    public static void main(String[] args) {
        AbstractApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");
        HelloWorld objA = (HelloWorld) context.getBean("helloWorld");
        objA.getMessage1();
        objA.getMessage2();

        HelloIndia objB = (HelloIndia) context.getBean("helloIndia");
        objB.getMessage1();
        objB.getMessage2();
        objB.getMessage3();
        context.registerShutdownHook();
    }
}

//Beans.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-3.0.xsd">

    <bean id="helloWorld" class="HelloWorld" ~~删除线~~init-method = "init" destroy-method = "destroy"~~删除线~~  scope="singleton" >
        <property name = "message1" value = "Hello World!"/>
        <property name = "message2" value = "Hello Second World!"/>
    </bean>

    <bean id ="helloIndia" class = "HelloIndia" parent = "helloWorld">
        <property name = "message1" value = "Hello India!"/>
        <property name = "message3" value = "Namaste India!"/>
    </bean>

    <bean class="InitHelloWorld"/>
</beans>

因此,你可以设置一个beanTemplate,beanTemplate的属性设置如<bean id="beanTemplate" abstract="true">, 不能有class,但是要有abstract=true, 比如:

<?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-3.0.xsd">
    <bean id="beanTemplate" abstract="true">
        <property name="message1" value="Hello World!"/>
        <property name="message2" value="Hello Second World!"/>
        
    </bean>
    <bean id="helloWorld" class="HelloWorld" parent="beanTemplate">

    </bean>

    <bean id="helloIndia" class="HelloIndia" parent="helloWorld">

    </bean>

    <bean class="InitHelloWorld"/>

</beans>

4.6 关于Bean注入
IOC框架的一个重要的功能是为bean做初始化工作,因此,如果一个类的属性没有默认的初始化值,而又在程序里被调用,那么在Beam 配置(Beans.xml)中就要通过<property>来做初始化。如果属性是一个自定义的类,那么这个自定义的类也要作为Bean注入,而且要作为调用Bean的内在bean。而且只要在Beans.xml里用<property>来声明的属性在其声明的类里必须要有对应的setxxx()方法否则会报类似如下错误。
BeanCreationException: Error creating bean with name 'textEditor' defined in class path resource [Beans.xml]: Error setting property values; nested exception is org.springframework.beans.NotWritablePropertyException: Invalid property 'spellChecker' of bean class [TextEditor]: Bean property 'spellChecker' is not writable or has an invalid setter method. Did you mean 'spellChecker1'?
用下面这个例子来验证下:

//TextEditor.java
public class TextEditor {
    private SpellChecker spellChecker;
    private String str;

    // a setter method to inject the dependency.
    public void setSpellChecker(SpellChecker spellChecker) {
        System.out.println("Inside setSpellChecker." );
        this.spellChecker = spellChecker;
    }

    // a getter method to return spellChecker
    public SpellChecker getSpellChecker() {
        return spellChecker;
    }
    public void spellCheck() {
        spellChecker.checkSpelling();
    }
}

//MainApp.java
public class MainApp {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");
        TextEditor te = (TextEditor)context.getBean("textEditor");
        te.spellCheck();
    }
}

//Beans.xml部分
    <bean id="textEditor" class="TextEditor">
        <property name="spellChecker">
            <bean id="spellChecker" class="SpellChecker">

            </bean>
        </property>
        <property name="str" value = "hello"/>    这里设置了str这个属性,但是TextEditor.java里却没有为这个str编写setStr()方法,所以会报错。
    </bean>

PropertyDescriptor.java里会去拼凑这个方法名,用的就是set + 首字母大写的属性变量名!

if (writeMethodName == null) {
// SET_PREFIX = “set”
                writeMethodName = Introspector.SET_PREFIX + getBaseName();
            }

            Class<?>[] args = (type == null) ? null : new Class<?>[] { type };
            writeMethod = Introspector.findMethod(cls, writeMethodName, 1, args);
            if (writeMethod != null) {
                if (!writeMethod.getReturnType().equals(void.class)) {
                    writeMethod = null;
                }
            }
            try {
                setWriteMethod(writeMethod);
            } catch (IntrospectionException ex) {
                // fall through
            }
....

// Calculate once since capitalize() is expensive.
    String getBaseName() {
        if (baseName == null) {
            baseName = NameGenerator.capitalize(getName()); //获取首字母大写的变量名
        }
        return baseName;
    }

bean信息除了以XML配置出现,还可以用java 注解配置。

在一个类前用@Configuration,表示这个类可以被spring ioc容器作为bean的定义来源进行bean注入。如果一个方法前用@Bean表示这个方法返回的对象(这个方法一定要有返回对象)应该要被当成一个bean注入spring application context。
例子:这里就不需要Beans.xml 了!也就不需要getXX(), setXX();
注意: Because you are using Java-based annotations, so you also need to add CGLIB.jar from your Java installation directory and ASM.jar library which can be downloaded from asm.ow2.org.

@Configuration
public class HelloWorldConfig {
    @Bean
    public HelloWorld helloWorld(){
        return new HelloWorld();
    }
}

MainApp.java

//注意下,这里注入Bean的方法由原来的ClassPathXmlApplicationContext()变为AnnotationConfigApplicationContext(), 而且AnnotationConfigApplicationContext()还具有一次性加载多个configuration class的能力,看接下来的代码
        ApplicationContext ctx = new AnnotationConfigApplicationContext(HelloWorldConfig.class);
//因为没有定义bean id, 所以这里的getBean 用的是HelloWorld.class
        HelloWorld helloWorld = ctx.getBean(HelloWorld.class);
        helloWorld.setMessage("Hello world");
        helloWorld.getMessage();

AnnotationConfigApplicationContext()还具有一次性加载多个configuration class的能力:

public static void main(String[] args) {
   AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();

   ctx.register(AppConfig.class, OtherConfig.class);
   ctx.register(AdditionalConfig.class);
//手动触发refresh(),
   ctx.refresh();

   MyService myService = ctx.getBean(MyService.class);
   myService.doStuff();
}

对于嵌套依赖的bean又该如何写config呢?
在XML里,用的<bean>里的<property>里再写<bean>, 如果用java config, 只要把生成被依赖的bean的方法以参数的形式传入调用bean生成的方法就可以,但前提是调用bean必须有一个以这个bean作为参数的构造函数:
TextEditor.java

public class TextEditor {
    private SpellChecker spellChecker;
    public void spellCheck() {
        spellChecker.checkSpelling();
    }
//必须要有这个构造函数,它以SpellCheker作为参数, 在IOC注入的时候,做spellChecker这个属性的初始化工作
    public TextEditor(SpellChecker spellChecker){
        this.spellChecker = spellChecker;
    }
}

TextEditorConfig.java

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

推荐阅读更多精彩内容