Loading

21-Spring-1(bean)

1. 框架

  • 高度抽取可重用代码的一种设计,有高度的通用性。
  • 框架是多个「可重用模块」的集合(半成品软件),形成一个某个领域的整体解决方案。

2. Spring 模块

Spring是一个 IOC(DI) 和 AOP 容器框架 // 容器:管理所有的组件(类)

  • Test:Spring 的单元测试模块
    spring-test-4.0.0.RELEASE.jar
    
  • Core Container:核心容器(IOC)
    # 黑框代表功能的组成 jar,若要使用这部分的完整功能,jar 都需要导入
    spring-aspects-4.0.0.RELEASE.jar
    spring-core-4.0.0.RELEASE.jar
    spring-context-4.0.0.RELEASE.jar
    spring-expression-4.0.0.RELEASE.jar
    
  • AOP + Aspects:面向切面编程模块
    spring-aop-4.0.0.RELEASE.jar
    spring-aspects-4.0.0.RELEASE.jar
    
  • 数据访问/集成:访问数据库模块
    spring-jdbc-4.0.0.RELEASE.jar
    spring-orm-4.0.0.RELEASE.jar     # 对象关系映射
    spring-tx-4.0.0.RELEASE.jar      # 事务
    spring-oxm-4.0.0.RELEASE.jar     # x: xml
    spring-jms-4.0.0.RELEASE.jar
    
  • web:开发 web 应用的模块
    spring-websocket-4.0.0.RELEASE.jar
    spring-web-4.0.0.RELEASE.jar                # Servlet
    spring-webmvc-4.0.0.RELEASE.jar             # web
    spring-webmvc-portlet-4.0.0.RELEASE.jar
    

3. IOC

IOC:Inversion(反转) Of Control(控制,即资源的获取方式)

  • 主动式:要什么资源手动创建即可
  • 被动式:由容器(管理所有的组件,即封装功能的类)主动将资源推动给需要的组件,将原来的主动 new 资源变成被动地接受资源。

DI:Dependency Injection,依赖注入。IOC 是一种反转控制的思想,而 DI 是对 IOC 的一种具体实现。

4. HelloWorld

4.1 导包

Spring 运行依赖以上日志包,没有的话运行会报错。

4.2 写配置文件

创建一个源码文件夹 Source Folder → 创建一个 Spring Bean Configuration File,即 Spring 的核心配置文件。// 注意一定是放在源码文件夹,不然根本放不到 bin 目录!

4.3 编写测试类

@Test // 从容器中拿到这个组件
public void test() {
    // ApplicationContext 代表 IOC 容器 = 当前应用的配置文件在类路径下
    ApplicationContext ioc = new ClassPathXmlApplicationContext("ioc.xml");
    // 容器帮我们创建的对象
    Person person = (Person) ioc.getBean("person01");
    System.out.println(person);
}

4.4 Tips

  • src 源码包开始的路径,也称为“类路径的开始”;所有源码包的东西都会被合并放在类路径里面。
    • java:/bin/
    • web 类路径:/WEB-INF/classes
  • 需要导入额外的日志包:commons-logging-1.1.3.jar,否则启动将报错
  • 一定要先导包,再创建配置文件
  • Spring 的容器接管了该类对象的标志
  • ServletContext(IOC 容器的接口) 的继承结构
    • ClassPathXmlApplicationContext:IOC 容器的配置文件在类路径下
    • FileSystemXmlApplicationContext:IOC 容器的配置文件在磁盘路径下
  • 给容器中注册一个组件,我们也从容器中按照 id 拿到了这个组件的对象,所有组件的创建默认是在容器启动完成之前,而非是在获取组件时创建
  • 同一个组件在 IOC 容器中是单实例的
    Person person1 = (Person) ioc.getBean("person01");
    Person person2 = (Person) ioc.getBean("person01");
    System.out.println(person1 == person2); // true
    
  • 获取一个容器中没有的组件,抛异常:NoSuchBeanDefinitionException
  • 容器在对组件初始化的时候,是利用 setter 给属性赋值的 // JavaBean 属性名是由 setXxx 的 xxx 决定的

5. bean 的属性赋值

5.1 简单属性赋值

5.1.1 通过 property

<bean id="person02" class="cn.edu.nuist.bean.Person">
    <property name="lastName" value="李四"></property>
    <property name="age" value="28"></property>
    <property name="email" value="zhangsan@163.com"></property>
    <property name="gender" value="男"></property>
</bean>

5.1.2 通过 constructor-org

<!-- 多个重载构造器导致赋值错误情况, 可用 type 指定参数数据类型 -->
<bean id="person05" class="cn.edu.nuist.bean.Person">
    <constructor-arg value="小花"></constructor-arg>
    <constructor-arg value="18" type="java.lang.Integer"></constructor-arg>
    <constructor-arg value="女"></constructor-arg>
</bean>

<!--
可以省略 name 属性,但此时必须严格按照构造器声明的形参顺序赋值,也可用 index 属性指明形参索引
(String lastName, Integer age, String gender, String email)
-->
<bean id="person04" class="cn.edu.nuist.bean.Person">
    <constructor-arg value="小花"></constructor-arg>
    <constructor-arg value="18"></constructor-arg>
    <constructor-arg value="女"></constructor-arg>
    <constructor-arg value="xiaohua@hahaha.com"></constructor-arg>
</bean>

<bean id="person03" class="cn.edu.nuist.bean.Person">
    <constructor-arg name="lastName" value="小明"></constructor-arg>
    <constructor-arg name="gender" value="男"></constructor-arg>
    <constructor-arg name="age" value="10"></constructor-arg>
    <constructor-arg name="email" value="xiaoming@gmail.com"></constructor-arg>
</bean>

5.1.3 通过 p 名称空间

<!--
    通过 p 名称空间为 bean 赋值 // 需要手动导入 p 名称空间
    名称空间:在 XML 中名称空间是用来防止标签重复的
    <book>
        <b:name>西游记</b:name>
        <price>19.9</price>
        <author>
            <a:name>吴承恩</a:name>
            <gender>男</gender>
        </author>
    </book>

    带前缀的标签 <c:forEach>, <jsp:forward> ...
-->
<bean id="person06" class="cn.edu.nuist.bean.Person"
    p:age="18" p:email="xiaogang@163.com" p:lastName="小强" p:gender="男"
></bean>

5.2 复杂属性赋值

5.2.1 赋值 null

ioc.xml

<!-- 如下写法 或者 什么都不写就是 null -->
<bean id="person01" class="cn.edu.nuist.bean.Person">
    <property name="lastName">
        <null />
    </property>
</bean>

5.2.2 赋值 bean

  • 引用其他 bean
    <bean id="car01" class="cn.edu.nuist.bean.Car">
        <property name="brand" value="宝马"></property>
        <property name="color" value="黑色"></property>
        <property name="price" value="100000"></property>
    </bean>
    
    <!-- 测试复杂类型赋值 -->
    <bean id="person02" class="cn.edu.nuist.bean.Person">
        <!-- ref 代表该属性是引用一个外面的值 → car = ioc.getBean("car01") -->
        <property name="car" ref="car01"></property>
    </bean>
    
    -----------------------------------------------------
    @Test
    public void test() {
        Person person = ioc.getBean("person02", Person.class);
        Car car = ioc.getBean("car01", Car.class);
        System.out.println(person.getCar() == car); // true
    }
    
  • 引用内部 bean
    <!-- 测试引用内部 bean -->
    <bean id="person03" class="cn.edu.nuist.bean.Person">
        <property name="car">
            <!-- 对象,我们可以使用 bean 标签创建 → car = new Car()-->
            <bean class="cn.edu.nuist.bean.Car"></bean>
        </property>
    </bean>
    -----------------------------------------------------
    @Test
    public void test2() {
        Person person = ioc.getBean("person03", Person.class);
        Car car = ioc.getBean("car01", Car.class);
        System.out.println(person.getCar() == car); // false
    }
    

5.2.3 赋值 集合bean

  • 赋值 list
    <bean id="outerBook" class="cn.edu.nuist.bean.Book">
        <property name="bookName" value="狐狸在说什么"></property>
        <property name="author" value="韩国人"></property>
    </bean>
    
    <!-- 测试 list -->
    <bean id="person04" class="cn.edu.nuist.bean.Person">
        <property name="books">
            <!-- books = new ArrayList<Book>(); -->
            <list>
                <!-- list 标签体中添加每一个元素 -->
                <bean id="innerBook" class="cn.edu.nuist.bean.Book"
                        p:bookName="cmbyn" p:author="美国人"></bean>
                <!-- 引用外部一个元素 -->
                <ref bean="outerBook"></ref>
            </list>
        </property>
    </bean>
    
    -----------------------------------------------------
    @Test
    public void test3() {
        Person person = ioc.getBean("person04", Person.class);
        System.out.println(person.getBooks());
        // NoSuchBeanDefinitionException: No bean named 'innerBook' is defined
        // 所以,内部 bean 只能内部使用
        // Book innerBook = ioc.getBean("innerBook", Book.class);
        Book outerBook = ioc.getBean("outerBook", Book.class);
    }
    
  • 赋值 map
    <bean id="person05" class="cn.edu.nuist.bean.Person">
        <!-- map = new LinkedHashMap<>(); -->
        <property name="map">
            <map>
                <entry key="key01" value="value01"></entry>
                <entry key="key02" value="value02"></entry>
                <entry key="key03" value-ref="outerBook"></entry>
                <entry key="key04">
                    <bean class="cn.edu.nuist.bean.Car">
                        <property name="brand" value="宝马"></property>
                    </bean>
                </entry>
                <!--
                <entry key="key05">
                    <map></map>
                </entry>
                -->
            </map>
        </property>
    </bean>
    
  • 赋值 properties
    <bean id="person06" class="cn.edu.nuist.bean.Person">
        <property name="properties">
            <!-- properties = new Properties(); -->
            <props>
                <!--  因为 k=v 都是 String -->
                <prop key="username">root</prop>
                <prop key="password">123</prop>
            </props>
        </property>
    </bean>
    

5.2.4 util 名称空间

<bean id="person07" class="cn.edu.nuist.bean.Person">
    <!--  引用 util 名称空间创建的集合类型 bean -->
    <property name="map" ref="myMap"></property>
</bean>

<!-- new LinkedHashMap(); -->
<util:map id="myMap">
    <entry key="key01" value="value01"></entry>
    <entry key="key02" value="value02"></entry>
    <entry key="key03" value-ref="outerBook"></entry>
</util:map>

<util:list id="myList">
    <list></list>
    <bean class="cn.edu.nuist.bean.Person"></bean>
    <value>1101</value>
    <ref bean="myMap"/>
</util:list>

5.2.5 赋值 级联属性

<!-- 级联属性赋值:属性的属性 -->
<bean id="person08" class="cn.edu.nuist.bean.Person">
    <property name="car" ref="car01"></property>
    <!-- 改变 ref-car 的价格 -->
    <property name="car.price" value="9999"></property>
</bean>

5.3 继承实现配置信息的重用

<!--
abstract: 将 person01 设置为不可被获得实例,只能被用于继承的模板 bean
          否则将抛出异常 BeanIsAbstractException: Error creating
          bean with name 'person01': Bean definition is abstract
-->
<bean id="person01" class="cn.edu.nuist.bean.Person" abstract="true">
    <property name="lastName" value="张三"></property>
    <property name="age" value="30"></property>
    <property name="email" value="zhangsan@163.com"></property>
    <property name="gender" value="男"></property>
</bean>

<!-- parent: 指定当前 bean 的配置信息继承于哪个,类型可省 -->
<bean id="person02" parent="person01">
    <property name="lastName" value="李四"></property>
</bean>

6. bean 补充

6.1 bean 之间的依赖

只是改变 bean 的创建顺序。

<!--
默认按照配置顺序创建 bean,如何改变 bean 的创建顺序?
bean 之间的依赖(只是改变创建顺序) → depends-on 属性
 -->
<bean id="car01" class="cn.edu.nuist.bean.Car" depends-on="person01, book01"></bean>
<bean id="person01" class="cn.edu.nuist.bean.Person"></bean>
<bean id="book01" class="cn.edu.nuist.bean.Book"></bean>
----------------
Person 被创建了
Book 被创建了
Car 被创建了

6.2 bean 多实例

<!--
测试 bean 的作用域,分别创建单实例和多实例的 bean → 使用 scope 属性
    prototype:多实例,容器启动时不会创建,在 getBean 时才创建对象,每次获取都是新的对象
    singleton:单实例(默认),在容器启动完成之前就已经创建好对象,保存在容器中了
    request:在 web 环境下,同一次请求创建一个 bean 实例(没用)
    session:在 web 环境下,同一次会话创建一个 bean 实例(没用)
-->
<bean id="book" class="cn.edu.nuist.bean.Book" scope="prototype"></bean>

6.3 工厂 bean

bean 的创建默认就是框架利用反射 new 出来的 bean 实例。

工厂模式:工厂帮我们创建对象,一个专门创建对象的类,这个类就是工厂。

6.3.1 静态工厂

静态工厂本身不用创建对象,通过静态方法调用。

ioc.xml

<!--
1. 静态工厂
    class 指定为静态工厂全类名
    factory-method 指定造对象的方法名
    constructor-arg 可以为工厂方法传参
-->
<bean id="airplane01" factory-method="getAirplane"
        class="cn.edu.nuist.factory.AirplaneStaticFactory">
    <!-- 可以为方法指定参数 -->
    <constructor-arg value="李四"></constructor-arg>
</bean>

AirplaneStaticFactory

public class AirplaneStaticFactory {
    public static Airplane getAirplane(String captain) {
        System.out.println("静态工厂正在造飞机...");
        Airplane ap = new Airplane();
        ap.setCaptain(captain);
        ap.setLengthOfWing("50");
        ap.setNumber("1101");
        return ap;
    }
}

6.3.2 实例工厂

工厂本身需要创建对象,然后调用工厂对象的成员方法。

ioc.xml

<!-- 2. 实例工厂 -->
<bean id="airplaneInstanceFactory"
        class="cn.edu.nuist.factory.AirplaneInstanceFactory"></bean>

<!--
factory-method: 指定这个实例工厂中哪个方法是工厂方法
factory-bean: 指定创建当前对象是使用的哪个工厂
-->
<bean id="airplane02" class="cn.edu.nuist.bean.Airplane"
        factory-bean="airplaneInstanceFactory"
        factory-method="getAirplane">
    <constructor-arg value="王五"></constructor-arg>
</bean>

AirplaneInstanceFactory:

public class AirplaneInstanceFactory {
    public Airplane getAirplane(String captain) {
        System.out.println("实例工厂正在造飞机...");
        Airplane ap = new Airplane();
        ap.setCaptain(captain);
        ap.setLengthOfWing("50");
        ap.setNumber("1101");
        return ap;
    }
}

6.3.3 FactoryBean

实现了 FactoryBean 接口的类是 Spring 可以认识的工厂类。
该工厂类返回的 bean 的无论设置的是单实例还是多实例,都是在获取的时候才创建对象。

public class MyFactoryBean implements FactoryBean<Book> {

    @Override
    public Book getObject() throws Exception {
        System.out.println("MyFactoryBean 创建 Book 对象");
        Book book = new Book();
        book.setBookName(UUID.randomUUID().toString());
        return book;
    }

    // 返回创建的对象的类型
    @Override
    public Class<?> getObjectType() {
        return Book.class;
    }

    @Override
    public boolean isSingleton() {
        return true;
    }
}

Test

@Test
public void test() {
    Object bean = ioc.getBean("myFactoryBean");
    Object bean2 = ioc.getBean("myFactoryBean");
    System.out.println(bean == bean2); // true
}

6.4 带有生命周期方法的 bean

<!--
自定义初始化方法和销毁方法,方法要求没有参数,但可以抛出异常
init-method, destroy-method
-->
<bean id="book01" class="cn.edu.nuist.bean.Book"
        init-method="init" destroy-method="destroy"
        <!-- scope="prototype" -->
></bean>
  • Test1
  • Test2
  • Test3

6.5 bean 的后置处理器

/*
 * 1. 编写后置处理器实现类
 * 2. 将后置处理器注册在配置文件中
 * 注:就算没有定义 init-method,后置处理器也会默认其有,然后继续工作
 */
public class MyBeanPostProcessor implements BeanPostProcessor {

    /**
     * 初始化方法之前调用
     */
    @Override
    public Object postProcessBeforeInitialization(Object bean
            , String beanName) throws BeansException {
        System.out.println(beanName + "BeforeInitialization..." + bean);
        // 返回的是什么,容器中保存的就是什么
        return bean;
    }

    /**
     * 初始化方法之后调用
     */
    @Override
    public Object postProcessAfterInitialization(Object bean
            , String beanName) throws BeansException {
        System.out.println(beanName + "AfterInitialization..." + bean);
        return bean;
    }
}

ioc.xml

<bean id="book01" class="cn.edu.nuist.bean.Book"
        init-method="init" destroy-method="destroy">
    <property name="bookName" value="狐狸在说什么"></property>
</bean>
<bean id="car01" class="cn.edu.nuist.bean.Car"></bean>
<bean id="beanPostProcessor" class="cn.edu.nuist.bean.MyBeanPostProcessor"></bean>

7. 引入外部属性文件

  • dbconfig.properties
    jdbc.user=root
    jdbc.password=root
    jdbc.jdbcUrl=jdbc:mysql:///test
    jdbc.driverClass=com.mysql.jdbc.Driver
    
  • ioc.xml
    <!-- 引用外部属性文件;"classpath:" 固定写法,引入类路径下资源 -->
    <context:property-placeholder location="classpath:dbconfig.properties"/>
    
    <!-- 数据库连接池作为单实例是最好的,一个项目就一个连接池,连接池里面管理很多连接 -->
    <!-- 可以让 Spring 帮我们创建连接池对象(管理连接池) -->
    <!-- value 属性值中不要放多余的空格,如:"${jdbc.user} " -->
    
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <property name="user" value="${jdbc.user}"></property>
        <property name="password" value="${jdbc.password}"></property>
        <property name="jdbcUrl" value="${jdbc.jdbcUrl}"></property>
        <property name="driverClass" value="${jdbc.driverClass}"></property>
    </bean>
    
    <!-- username 是 Spring 的 key 中的关键字 → 系统用户名 -->
    <bean id="car01" class="cn.edu.nuist.bean.Car">
        <property name="brand" value="${username}"></property>
    </bean>
    
  • Test
    @Test
    public void test() {
        System.out.println(ioc.getBean("car01", Car.class).getBrand()); // 10646
        DataSource dataSource = ioc.getBean("dataSource", DataSource.class);
        System.out.println(dataSource);
    }
    

注意!${...} 是取出配置文件中的值,而 #{...} 是 Spring 的表达式语言,别搞错了。

posted @ 2020-09-15 11:47  tree6x7  阅读(93)  评论(0编辑  收藏  举报