美文网首页
spring(一)Bean的定义和使用

spring(一)Bean的定义和使用

作者: V_Jan | 来源:发表于2018-12-27 10:03 被阅读0次

    学习资源来自: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();
        }
    }
    

    相关文章

      网友评论

          本文标题:spring(一)Bean的定义和使用

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