SSM初步学习-Bean初步学习

86 阅读8分钟

首先引入spring的依赖

<dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.2.10.RELEASE</version>
    </dependency>
</dependencies>

然后在resources目录下创建spring的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.xsd">
</beans>

最基本的bean配置方法如下所示

<bean id="bookDao" class="heimaSpring.dao.iml.BookDao"></bean>

该类如下所示

public class BookDao implements heimaSpring.dao.BookDao {


    private BookDao() {
        System.out.println("在spring中,BookDao 构造器采用反射方式,所以即使构造方法采用private修饰也能够调用,但是只能调用无参构造方法,如果加参数则会报错");

    }
    public void printSelf(){
        System.out.println("BookDao");
    }
    public static void main(String[] args) {
        var bookDao = new BookDao();
        bookDao.printSelf();
    }
}

配合如下代码即可看到该类的对象已经进入Bean之中,并可以将其从Bean之中取出使用

import heimaSpring.dao.iml.BookDao;
import heimaSpring.service.iml.BookService;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class App {
    public static void main(String[] args) {
//        加载配置文件,得到容器对象
        ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
        //获取资源,通过bean的id来获取bean
        var bookdao = (BookDao) ctx.getBean("bookDao");
        bookdao.printSelf();
       
    }
}

但是这依然无法解决代码之间耦合的问题,下列类BookService使用了一个BookDao来作为其成员变量,正常情况下我们需要new一个对象出来,但是这样一旦BookDao代码进行改变,BookService也要相应的进行改变。为了解决这种问题,我们不仅要将对象的创建权交给Bean,而且要将Bean中的BookDao交给BookService中的成员变量。想要做到这一点,我们需要在配置文件之中指明两者关系。

public class BookService implements InitializingBean, DisposableBean {
    private BookDao bookDao;
    //在配置文件之中指明Dao和Service的关系


    private BookService() {
        this.bookDao = bookDao;
    }

    public void setBookDao(BookDao bookDao) {
        this.bookDao = bookDao;
    }
    public void printDao(){
        bookDao.printSelf();
    }
    //可以通过bean配置初始化和销毁方法
    public void init(){System.out.println("bean init");}
    public void destoy(){System.out.println("bean destoy");}


    public void printSelf(){
        System.out.println("BookService");
    }

    public static void main(String[] args) {
        BookService bookService = new BookService();
        bookService.printDao();
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("bean afterPropertiesSet");
    }

    @Override
    public void destroy() throws Exception {
        System.out.println("bean destroy");

    }
}

具体的配置方法如下

<bean id="bookService" class="heimaSpring.service.iml.BookService"  init-method="init" destroy-method="destoy">
    <!--7.配置server与dao的关系,把dao放进service中-->
    <!--property标签表示配置当前bean的属性
    name属性表示配置哪一个具体的属性
    ref属性表示参照哪一个bean-->
    <property name="bookDao" ref="bookDao" ></property>
</bean>

同时,也要在BookServiceImpl类中,为BookDao提供setter方法。

  • 注意:配置中的两个bookDao的含义是不一样的 name="bookDao"中bookDao的作用是让Spring的IOC容器在获取到名称后,将首字母大写,前 面加set找对应的setBookDao()方法进行对象注入
  • ref="bookDao"中bookDao的作用是让Spring能在IOC容器中找到id为bookDao的Bean对象给 bookService进行注入
  • 完整的配置文件如下
<?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.xsd">
    <!--    1.导入spring依赖-->
    <!--    <dependency>-->
    <!--        <groupId>org.springframework</groupId>-->
    <!--        <artifactId>spring-context</artifactId>-->
    <!--        <version>5.2.10.RELEASE</version>-->
    <!--    </dependency>-->
    <!--   2. 配置bean-->
    <!--bean标签标示配置bean,id属性标示给bean起名字,class属性表示给bean定义类型-->
    <!--scope负责处理bean中的对象数量,默认为singleton,即多次调用只会生成同一个对象,想要生成不同的对象,需要将其改为prototype,当设置为prototype时,destroy-method不会被执行-->
    <!--可以继承InitializingBean, DisposableBean接口并使用其中方法,则无需在配置文件中配置初始化和销毁方法,但是配置文件中方法的优先级比较高-->
<!--    当因为开发习惯导致的id出现重名时,可以使用name属性为其起别名,name可以起多个别名,其中以,分隔-->
    <bean id="bookService" name="service1,service2" class="heimaSpring.service.iml.BookService"  init-method="init" destroy-method="destoy" scope="prototype">
        <!--7.配置server与dao的关系,把dao放进service中-->
        <!--property标签表示配置当前bean的属性
        name属性表示配置哪一个具体的属性
        ref属性表示参照哪一个bean,建议使用id值,但是也可以使用name-->
        <property name="bookDao" ref="bookDao" ></property>
    </bean>

    <bean id="bookDao" class="heimaSpring.dao.iml.BookDao"></bean>

</beans>

这里要着重说明一下scope属性,默认为单例,一般情况下也都是单例,这意味着每次使用的都是同一个对象,如果一个类需要创建多个不同的对象,建议不要将其放入Bean之中。

  1. bean在容器中是单例的,会不会产生线程安全问题?
  • 如果对象是有状态对象,即该对象有成员变量可以用来存储数据的, 因为所有请求线程共用一个bean对象,所以会存在线程安全问题。 如果对象是无状态对象,即该对象没有成员变量没有进行数据存储的, 因方法中的局部变量在方法调用完成后会被销毁,所以不会存在线程安全问题。
  1. 哪些bean对象适合交给容器进行管理?
  • 表现层对象
  • 业务层对象
  • 数据层对象
  • 工具对象
  1. 哪些bean对象不适合交给容器进行管理?
  • 封装实例的域对象,因为会引发线程安全问题,所以不适合。

- 接下来描述Bean的实例化方法

  • 实例化bean的三种方式,构造方法,静态工厂和实例工厂
  • 其中构造方法采取的反射方式调用无参构造方法
  • 静态工厂方法已经快被淘汰了
  • 实例工厂操作如下,首先创建UserDaoFactory,该类要有方法能提供bean中所需要的实例化对象
public class UserDaoFactory {
    public UserDaoImp getUserDao(){
        return new UserDaoImp();
    }
}

然后在配置文件之中加入这两行

image.png

<!--    采取实例化工厂创建bean-->
<!--    实例化工厂运行的顺序是:-->
<!--    创建实例化工厂对象,对应的是第一行配置-->
<!--    调用对象中的方法来创建bean,对应的是第二行配置-->
<!--    factory-bean:工厂的实例对象-->
<!--    factory-method:工厂对象中的具体创建对象的方法名-->
<!--    factory-method:具体工厂类中创建对象的方法名-->
    <bean id="userFactory" class="heimaSpring.factory.UserDaoFactory"/>
    <bean id="userDao" factory-method="getUserDao" factory-bean="userFactory"/>

为了简化这种配置方法,spring提供了另外一种方法

//实例工厂实例化的方式就已经介绍完了,配置的过程还是比较复杂,所以Spring为了简化这种配置方式就提供了一种叫FactoryBean的方式来简化开发。
public class UserDaoFactoryBean implements FactoryBean<UserDao> {
    //代替原始实例工厂中创建对象的方法
    //查看源码会发现,FactoryBean接口其实会有三个方法,分别是:
//    T getObject() throws Exception;
//    Class<?> getObjectType();
//    default boolean isSingleton() {
//        return true;
//    }
//    方法一:getObject(),被重写后,在方法中进行对象的创建并返回
//    方法二:getObjectType(),被重写后,主要返回的是被创建类的Class对象
//    方法三:没有被重写,因为它已经给了默认值,从方法名中可以看出其作用是设置对象是否为单例,默认true,从意思上来看,我们猜想默认应该是单例,如何来验证呢?思路很简单,就是从容器中获取该对象的多个值,打印到控制台,查看是否为同一个对象。
    public UserDao getObject() throws Exception {
        return new UserDaoImp();
    }
    //返回所创建类的Class对象
    public Class<?> getObjectType() {
        return UserDao.class;
    }
}

对应的配置文件为

<!--    spring中简化实例化工厂方法,测试方法等无需更改-->
<!--    该方法这在Spring去整合其他框架的时候会被用到,所以这种方式需要大家理解掌握。-->
   <bean id="userDaoSpring"name="userDaoSpring"class="heimaSpring.factory.UserDaoFactoryBean"/>

bean生命周期

image.png 首先是在配置文件之中定义初始化和销毁方法,这种方式优先级比较高且经常使用

<!--可以继承InitializingBean, DisposableBean接口并使用其中方法,则无需在配置文件中配置初始化和销毁方法,但是配置文件中方法的优先级比较高-->
<bean id="bookService" name="service1,service2" class="heimaSpring.service.iml.BookService"  init-method="init" destroy-method="destoy" scope="prototype">
    <!--7.配置server与dao的关系,把dao放进service中-->
    <!--property标签表示配置当前bean的属性
    name属性表示配置哪一个具体的属性
    ref属性表示参照哪一个bean,建议使用id值,但是也可以使用name-->
    <property name="bookDao" ref="bookDao" ></property>

也可以像下面这么做

//可以继承InitializingBean, DisposableBean接口并使用其中方法,则无需在配置文件中配置初始化和销毁方法
public class BookService implements InitializingBean, DisposableBean 

@Override
public void afterPropertiesSet() throws Exception {
    System.out.println("bean afterPropertiesSet");
}

@Override
public void destroy() throws Exception {
    System.out.println("bean destroy");

关闭容器的方法

//      加载配置文件,得到容器对象,ClassPathXmlApplicationContext继承自ApplicationContext,为了验证容器的销毁,所以使用ClassPathXmlApplicationContext
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
        //程序结束后直接退出虚拟机,所以无法看到destory方法,想要调用该方法,需要先关闭容器,再退出虚拟机
        //关闭容器方法有两种,一种是直接采取close方法暴力退出
//        ctx.close();
        //注册一个关闭钩子,且该方法可以在任意地方执行
        ctx.registerShutdownHook();

DI相关

向一个类中传递数据的方式有几种?

  • 普通方法(set方法)
  • 构造方法
  • 依赖注入描述了在容器中建立bean与bean之间的依赖关系的过程,如果bean运行需要的是数字或字符串呢?
  • 引用类型
  • 简单类型(基本数据类型与String)
  • Spring就是基于上面这些知识点,为我们提供了两种注入方式,分别是:
  • setter注入:
  1. 简单类型
  2. 引用类型
  • 构造器注入:
  1. 简单类型
  2. 引用类型

setter注入

使用setter注入引用类型的方法之前已经有了演示,采取以下方式即可,但是前提是要在类中提供对应的setter方法

property name="bookDao" ref="bookDao"

引用类型使用的是ref,但是简单数据类型还是使用ref么? ref是指向Spring的IOC容器中的另一个bean对象的,对于简单数据类型,没有对应的bean对象, 该如何配置? 对于简单数据类型使用的是

<property name="age" value="50"/>
  • 接下来将对以下类进行配置
package heimaSpring.service.iml;

import heimaSpring.dao.BookDao;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;

import java.util.List;
import java.util.Map;

//可以继承InitializingBean, DisposableBean接口并使用其中方法,则无需在配置文件中配置初始化和销毁方法
public class BookService implements InitializingBean, DisposableBean {
    private String userName;
    private Integer age;
    private Map<String,String> map;
    private List<Integer> list;
    private BookDao bookDao;
    //在配置文件之中指明Dao和Service的关系


    public BookService(String userName, Map<String, String> map) {
        this.userName = userName;
        this.map = map;
    }

    @Override
    public String toString() {
        return "BookService{" +
                "userName='" + userName + ''' +
                ", age=" + age +
                ", map=" + map +
                ", list=" + list +
                ", bookDao=" + bookDao +
                '}';
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public List<Integer> getList() {
        return list;
    }

    public void setList(List<Integer> list) {
        this.list = list;
    }

    public BookDao getBookDao() {
        return bookDao;
    }

    private BookService() {
        this.bookDao = bookDao;
    }

    public void setBookDao(BookDao bookDao) {
        this.bookDao = bookDao;
    }
    public void printDao(){
        bookDao.printSelf();
    }
    //可以通过bean配置初始化和销毁方法
    public void init(){System.out.println("bean init");}
    public void destoy(){System.out.println("bean destoy");}


    public void printSelf(){
        System.out.println("BookService");
    }

    public static void main(String[] args) {
        BookService bookService = new BookService();
        bookService.printDao();
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("bean afterPropertiesSet");
    }

    @Override
    public void destroy() throws Exception {
        System.out.println("bean destroy");

    }
}

对上面的类进行注入的配置文件如下

<?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.xsd">
    <bean id="bookDao" class="heimaSpring.dao.iml.BookDao"></bean>
    <bean id="bookService" class="heimaSpring.service.iml.BookService">
        <property name="bookDao" ref="bookDao"></property>
        <property name="age" value="50"/>
        <property name="list">
            <list>
                <value>10</value>
                <value>10</value>
                <value>10</value>
            </list>
        </property>
        <constructor-arg name="map">
            <map>
                <entry key="1" value="one"></entry>
                <entry key="2" value="two"></entry>
            </map>
        </constructor-arg>
        <constructor-arg name="userName" value="yuanfengyu"></constructor-arg>
    </bean>
</beans>

构造器注入和setter注入区别不大,只是标签从property变为了constructor-arg,且标签内部的name顺序并无要求。

  • 但是这样就会造成name属性和类中的变量名强耦合,当类中的变量名变化时name属性也必须相应的变化,虽然变量名一般情况下并不会改变,所以上述方法仍旧是主流方法
  1. 删除name属性,添加type属性,按照类型注入
<constructor-arg name="java.lang.String" value="yuanfengyu"></constructor-arg>
  1. 按照变量顺序注入,但是又会带来新的耦合
<constructor-arg index="1" value="100"/>

介绍完两种参数的注入方式,具体我们该如何选择呢?

  1. 强制依赖使用构造器进行,使用setter注入有概率不进行注入导致null对象出现 强制依赖指对象在创建的过程中必须要注入指定的参数
  2. 可选依赖使用setter注入进行,灵活性强 可选依赖指对象在创建过程中注入的参数可有可无
  3. Spring框架倡导使用构造器,第三方框架内部大多数采用构造器注入的形式进行数据初始化,相对严谨
  4. 如果有必要可以两者同时使用,使用构造器注入完成强制依赖的注入,使用setter注入完成可选 依赖的注入
  5. 实际开发过程中还要根据实际情况分析,如果受控对象没有提供setter方法就必须使用构造器注入
  6. 自己开发的模块推荐使用setter注入

自动配置

自动装配方式:

  • 按类型(常用)
  • 实际上就是取消property改用autowire
<!--    需要注入属性的类中对应属性的setter方法不能省略-->
<!--    被注入的对象必须要被Spring的IOC容器管理-->
<!--    按照类型在Spring的IOC容器中如果找到多个对象,会报NoUniqueBeanDefinitionException-->
<bean id="bookService" class="heimaSpring.service.iml.BookService" autowire="byType">
  • 按名称(byName)
<!--    对外部类来说,setBookDao方法名,去掉set后首字母小写是其属性名-->
<!--    为什么是去掉set首字母小写?-->
<!--    这个规则是set方法生成的默认规则,set方法的生成是把属性名首字母大写前面加set形成-->
<!--    的方法名-->
<!--    所以按照名称注入,其实是和对应的set方法有关,但是如果按照标准起名称,属性名和set对-->
<!--    应的名是一致的-->
<!--    如果按照名称去找对应的bean对象,找不到则注入Null-->
<!--    当某一个类型在IOC容器中有多个对象,按照名称注入只找其指定名称对应的bean对象,不会报错-->
<!--    两种方式介绍完后,以后用的更多的是按照类型注入。-->
<!--    最后对于依赖注入,需要注意一些其他的配置特征:-->
<!--    1. 自动装配用于引用类型依赖注入,不能对简单类型进行操作-->
<!--    2. 使用按类型装配时(byType)必须保障容器中相同类型的bean唯一,推荐使用-->
<!--    3. 使用按名称装配时(byName)必须保障容器中具有指定名称的bean,因变量名与配置耦合,不推-->
<!--    荐使用-->
<!--    4. 自动装配优先级低于setter注入与构造器注入,同时出现时自动装配配置失效-->

集合注入

<!--        注入数组类型-->
<!--        <property name="array">-->
<!--            <array>-->
<!--                <value>100</value>-->
<!--                <value>200</value>-->
<!--                <value>300</value>-->
<!--            </array>-->
<!--        </property>-->
<!--        注入Set类型数据-->
<!--        <property name="set">-->
<!--            <set>-->
<!--                <value>itcast</value>-->
<!--                <value>itheima</value>-->
<!--                <value>boxuegu</value>-->
<!--                <value>boxuegu</value>-->
<!--            </set>-->
<!--        </property>-->
<!--        注入Properties类型数据-->
<!--        <property name="properties">-->
<!--            <props>-->
<!--                <prop key="country">china</prop>-->
<!--                <prop key="province">henan</prop>-->
<!--                <prop key="city">kaifeng</prop>-->
<!--            </props>-->
<!--        </property>-->
<!--        property标签表示setter方式注入,构造方式注入constructor-arg标签内部也可以写-->
<!--        <array>、<list>、<set>、<map>、<props>标签-->
<!--        List的底层也是通过数组实现的,所以<list>和<array>标签是可以混用-->
<!--        集合中要添加引用类型,只需要把<value>标签改成<ref>标签,这种方式用的比较少-->