likes
comments
collection
share

Spring的IOC

作者站长头像
站长
· 阅读数 11

一、入门案例

1.导入依赖:

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

2.编写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.xsd">

    <!-- 
    bean 标签:用于配置让 spring 创建对象,并且存入 ioc 容器之中
    id 属性:对象的唯一标识。
    class 属性:指定要创建对象的全限定类名
    -->
    <!-- 配置 service -->
    <bean id="userService" class="com.hcx.service.impl.UserSerivceImpl"></bean>
    <!-- 配置 dao -->
    <bean id="userDao" class="com.hcx.dao.impl.UserDaoImpl"></bean>
</beans>

3.使用

public class UserController {

    public static void main(String[] args) {
        //获取核心容器
        //ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");//更常用
        ApplicationContext applicationContext = new FileSystemXmlApplicationContext("D:\\config\\beans.xml");
        //根据id获取Bean对象
        //方式一
        UserService userService = (UserService) applicationContext.getBean("userService");
        //方式二
        UserDao userDao = applicationContext.getBean("userDao", UserDao.class);
        //com.hcx.service.impl.UserSerivceImpl@3e6fa38a
        System.out.println(userService);
        //com.hcx.dao.impl.UserDaoImpl@66a3ffec
        System.out.println(userDao);
//        UserService userService = (UserService) BeanFactory.getBean("userService");
//        userService.saveUser();
    }
}

二、重要类详解

1.BeanFactory

在构建核心容器时,创建对象采取的策略是延迟加载。即根据id获取对象时才创建对象。 适用场景:多例对象

public class UserController {
    public static void main(String[] args) {
        Resource resource = new ClassPathResource("beans.xml");
        BeanFactory beanFactory = new XmlBeanFactory(resource);
        UserService userService = (UserService) beanFactory.getBean("userService");
        System.out.println(userService);
    }
}

Spring的IOC

2.ApplicationContext

在构建核心容器时,创建对象采取的是立即加载的方式。即一读取完配置文件就马上创建对象。 适用场景:单例对象

public class UserController {
    public static void main(String[] args) {
        //获取核心容器
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
//        ApplicationContext applicationContext = new FileSystemXmlApplicationContext("D:\\config\\beans.xml");
        //根据id获取Bean对象
        //方式一
        UserService userService = (UserService) applicationContext.getBean("userService");
        //方式二
        UserDao userDao = applicationContext.getBean("userDao", UserDao.class);
        //com.hcx.service.impl.UserSerivceImpl@3e6fa38a
        System.out.println(userService);
        //com.hcx.dao.impl.UserDaoImpl@66a3ffec
        System.out.println(userDao);
    }
}

Spring的IOC

Spring的IOC

ApplicationContext的三个常用实现类: ①ClassPathXmlApplicationContext 可以加载类路径下的配置文件,要求配置文件必须在类路径下,否则无法加载。

②FileSystemXmlApplicationContext 可以加载磁盘任意路径下的配置文件(须有访问权限)

③AnnotationConfigApplicationContext 用于读取注解创建的容器

三、Bean详解

1.创建Bean对象的三种方式

①方式一:采用默认构造函数 <bean id="userService" class="com.hcx.service.impl.UserSerivceImpl"></bean> spring配置文件中使用bean标签,仅配置id和class属性(没有其他属性和标签),此时采用默认构造函数创建对象,如果类中没有默认构造函数,则对象无法创建。

    <!-- 
        bean 标签:用于配置让 spring 创建对象,并且存入 ioc 容器之中
        id 属性:对象的唯一标识。
        class 属性:指定要创建对象的全限定类名
    -->
    <!-- 配置 service -->
    <bean id="userService" class="com.hcx.service.impl.UserSerivceImpl"></bean>

②方式二:使用普通工厂中的方法创建对象 即使用某个类中的方法创建对象,并存入spring容器 当使用一些外部类时,如引入的jar中的类,都是字节码文件。

InstanceFactory:

/**
 * 模拟工厂类(该类可能存在于jar包中,无法修改代码提供默认构造函数)
 */
public class InstanceFactory {
    public UserService getUserService(){
        return new UserSerivceImpl();
    }
}

此时要获取的是InstanceFactory 类中的getUserService返回值

    <!--得到工厂实例-->
    <bean id="instanceFactory" class="com.hcx.factory.InstanceFactory"></bean>
    <!--
        factory-bean:指定工厂bean
        factory-method:获取对象的方法名
        userService对象由instanceFactory id所指向的工厂中的getUserService方法获取
    -->
    <bean id="userService" factory-bean="instanceFactory" factory-method="getUserService"></bean>

③方式三:使用工厂中的静态方法创建对象 使用某个类中的静态方法,并存入spring容器

StaticFactory:

public class StaticFactory {
    public static UserService getUserService(){
        return new UserSerivceImpl();
    }
}

xml配置:

<bean id="userService" class="com.hcx.factory.StaticFactory" factory-method="getUserService"></bean>

④注解方式: 作用和xml配置的<bean>标签功能一样 @Component:把当前类对象存入spring容器中 属性value用于指定bean的id,不写时默认为类名首字母小写 @Controller:一般用于表现层 @Service:一般用于业务层 @Repository:一般用于持久层

使用注解的方式,需要加入context命名空间,并且要配置告诉spring使用注解的方式,需要spring去扫描对应的包,配置如下:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:context="http://www.springframework.org/schema/context"
       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
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd">

    <!-- 告知 spring 创建容器时要扫描的包 -->
    <context:component-scan base-package="com.hcx"></context:component-scan>

</beans>

Spring的IOC

2.bean对象的作用范围

①xml配置: 默认情况spring创建的bean都是单例的。 bean的作用范围通过bean标签的scope属性可以指定: 取值:

  • singleton:单例(默认值)
  • prototype:多例
  • request:作用于web应用的请求范围
  • session:作用于web应用的会话范围
  • global-session:作用于集群环境的会话范围(全局会话范围),当不是集群环境时,等同于session
<bean id="userService" class="com.hcx.service.impl.UserSerivceImpl" scope="singleton"></bean>

②注解配置: **@Scope:**指定bean的作用范围 属性value:指定范围的取值,常用取值:singletonprototype

3.bean对象的生命周期

①单例对象 生命周期跟容器的相同,让其创建时对象创建,只要容器还在,对象一直存活,容器销毁,对象死亡。 通过init-methoddestory-method属性指定 UserSerivceImpl:

public class UserSerivceImpl implements UserService {
    public UserSerivceImpl() {
        System.out.println("对象创建了");
    }
    public void saveUser() {
        System.out.println("service实现类被调用了");
//        userDao.saveUser();
    }
    public void init() {
        System.out.println("对象初始化");
    }
    public void destory() {
        System.out.println("对象销毁");
    }
}

xml:

    <bean id="userService" class="com.hcx.service.impl.UserSerivceImpl" scope="singleton"
          init-method="init" destroy-method="destory"></bean>

调用:

public class UserController {
    public static void main(String[] args) {
        //获取核心容器
        ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
        //根据id获取Bean对象
        //方式一
        UserService userService = (UserService) applicationContext.getBean("userService");
        //方式二
//        UserDao userDao = applicationContext.getBean("userDao", UserDao.class);
        userService.saveUser();
        //手动关闭容器
        applicationContext.close();
    }
}

结果:

对象创建了
对象初始化
service实现类被调用了
对象销毁

②多例对象 出生:使用对象时spring框架创建 存活:对象只要在使用则一直存活 死亡:当对象长时间不用且没有其他对象引用时,由垃圾回收器回收

即使手动关闭容器,对象也不会销毁

    <bean id="userService" class="com.hcx.service.impl.UserSerivceImpl" scope="prototype"
          init-method="init" destroy-method="destory"></bean>

打印结果:

对象创建了
对象初始化
service实现类被调用了

③使用注解指定生命周期 作用与bean标签中使用init-methoddestroy-method作用相同 **PostConstruct:**用于指定初始化方法 **PreDestroy:**用于指定销毁方法

UserSerivceImpl :

@Service("userService")
public class UserSerivceImpl implements UserService {
    @Resource(name = "userDao1")
    private UserDao userDao;
    @PostConstruct
    public void init(){
        System.out.println("初始化方法");
    }
    @PreDestroy
    public void destroy(){
        System.out.println("销毁方法");
    }
    public void saveUser() {
        userDao.saveUser();
    }
}

调用:

public class UserController {
    @Autowired
    private UserService userService;
    public static void main(String[] args) {
        //获取核心容器
        ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
        //根据id获取Bean对象
        UserService userService = (UserService) applicationContext.getBean("userService");
        userService.saveUser();
        //手动关闭才会调用销毁方法
        applicationContext.close();
    }
}

打印结果:

初始化方法
保存用户1
销毁方法

四、Spring的依赖注入

依赖注入:Dependency Injection。它是 spring 框架核心 ioc 的具体实现 程序在编写时,通过控制反转,把对象的创建交给了 spring,但是代码中不可能出现没有依赖的情况。 ioc 解耦只是降低他们的依赖关系,但不会消除。 例如:我们的业务层仍会调用持久层的方法。 这种业务层和持久层的依赖关系,在使用 spring 之后,就让 spring 来维护了。 简单的说,就是框架把持久层对象传入业务层,而不用我们自己去获取。

依赖关系的管理:交由spring来维护 当前类需要用到其他类对象时,由spring为我们提供,我们只需要在配置文件中说明。

能注入的数据类型:

  • 基本类型和String
  • 其他bean类型(在配置文件或者注解配置过的bean)
  • 复杂类型(集合类型)

注入的方式:

  • 使用构造器
  • 使用set方法
  • 使用注解

注意:经常变化的一些数据不适用于注入的方式

1.构造函数注入

在bean标签中使用标签constructor-arg,属性:

  • type:指定要注入的数据的数据类型,该数据类型是构造函数中某个或某些参数的数据类型
  • index:指定给构造函数中指定索引位置的参数赋值。(索引从0开始)
  • name:指定给构造函数中指定名称的参数赋值
  • value:赋的值是基本数据类型和 String 类型
  • ref:赋的值是其他 bean 类型(必须是在配置文件中配置过的 bean)

注意:typeindexname用于指定给构造函数中的哪个参数赋值

UserSerivceImpl:

public class UserSerivceImpl implements UserService {
    private String name;
    private Integer age;
    private Date birthday;
    public UserSerivceImpl(String name, Integer age, Date birthday) {
        this.name = name;
        this.age = age;
        this.birthday = birthday;
    }
    public void saveUser() {
        System.out.println("service实现类被调用了:"+name+","+age+","+birthday);
    }
}

beans.xml

    <bean id="userService" class="com.hcx.service.impl.UserSerivceImpl">
        <constructor-arg name="name" value="极多人小红"></constructor-arg>
        <constructor-arg name="age" value="24"></constructor-arg>
        <constructor-arg name="birthday" ref="date"></constructor-arg>
    </bean>

    <!--配置日期对象-->
    <bean id="date" class="java.util.Date"></bean>

使用:

public class UserController {

    public static void main(String[] args) {
        //获取核心容器
        ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
        //根据id获取Bean对象
        UserService userService = (UserService) applicationContext.getBean("userService");
        userService.saveUser();
    }
}

结果:

service实现类被调用了:极多人小红,24,Wed Dec 11 20:09:09 CST 2019

此种方式的优缺点: 优点:在获取bean对象时,注入数据是必须的操作,否则无法创建对象(如果同时提供无参构造器则没问题) 缺点:改变了bean对象的实例化方式,使得在创建对象时,即使不使用这些数据也必须提供。

2.set 方法注入

通过配置文件给 bean 中的属性传值:使用 set 方法的方式, property标签:

  • name:找的是类中 set 方法后面的部分(并不是属性的名称)
  • ref:给属性赋值是其他 bean 类型的
  • value:给属性赋值是基本数据类型和 string 类型的

UserSerivceImpl:

 public class UserSerivceImpl implements UserService {
    private String name;
    private Integer age;
    private Date birthday;
    public void setName(String name) {
        this.name = name;
    }
    public void setAge(Integer age) {
        this.age = age;
    }
    public void setBirthday(Date birthday) {
        this.birthday = birthday;
    }
    public void saveUser() {
        System.out.println("service实现类被调用了:"+name+","+age+","+birthday);
    }
}

beans.xml:

    <!-- 配置 service -->
    <bean id="userService" class="com.hcx.service.impl.UserSerivceImpl">
        <property name="name" value="极多人小红"></property>
        <property name="age" value="21"></property>
        <property name="birthday" ref="date"></property>
    </bean>

    <!--配置日期对象-->
    <bean id="date" class="java.util.Date"></bean>

优点:创建对象时没有明确的限制,可以直接使用默认构造函数 缺点:如果某个成员必须有值,则获取对象时有可能set方法没有执行。

复杂类型注入

用于给List结构注入的标签包括:listarrayset 用于给Map结构集合注入的标签包括:mapproperties 即结构相同的数据类型,标签都通用。

UserSerivceImpl:

public class UserSerivceImpl implements UserService {
    private String[] strArrs;
    private List<String> strList;
    private Set<String> strSet;
    private Map<String,String> strMap;
    private Properties properties;
    public void setStrArrs(String[] strArrs) {
        this.strArrs = strArrs;
    }
    public void setStrList(List<String> strList) {
        this.strList = strList;
    }
    public void setStrSet(Set<String> strSet) {
        this.strSet = strSet;
    }
    public void setStrMap(Map<String, String> strMap) {
        this.strMap = strMap;
    }
    public void setProperties(Properties properties) {
        this.properties = properties;
    }
    public void saveUser() {
        System.out.println("service实现类被调用了");
        System.out.println(Arrays.toString(strArrs));
        System.out.println(strList);
        System.out.println(strSet);
        System.out.println(strMap);
        System.out.println(properties);
    }
}

beans.xml:

<bean id="userService" class="com.hcx.service.impl.UserSerivceImpl">
        <property name="strArrs">
            <array>
                <value>arr1</value>
                <value>arr2</value>
                <value>arr3</value>
            </array>
        </property>

        <property name="strList">
            <list>
                <value>list1</value>
                <value>list2</value>
                <value>list3</value>
            </list>
        </property>

        <property name="strSet">
            <set>
                <value>set1</value>
                <value>set2</value>
                <value>set3</value>
            </set>
        </property>

        <property name="strMap">
            <map>
                <entry key="keya" value="map1"></entry>
                <entry key="keyb">
                    <value>map2</value>
                </entry>
            </map>
        </property>

        <property name="properties">
            <props>
                <prop key="propertieskey1">properties1</prop>
                <prop key="propertieskey2">properties2</prop>
            </props>
        </property>
    </bean>

调用:

public class UserController {

    public static void main(String[] args) {
        //获取核心容器
        ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
        //根据id获取Bean对象
        UserService userService = (UserService) applicationContext.getBean("userService");
        userService.saveUser();
    }
}

打印结果: Spring的IOC

3.注解注入

@Autowired:按照类型注入 只要容器中有唯一的一个bean对象类型和要注入的变量类型匹配,就能注入成功。 如果容器中没有bean的类型和要注入的变量匹配,报错。 如果容器中有多个类型匹配时,报错。 Spring的IOC

注意:当有多个类型都匹配上时,会再次按照名称匹配,如果相同了,也能匹配上。 即注入时的类的名称private UserDao userDao2;userDao2与声明时的注解@Repository("userDao2")匹配上了。

@Qualifier:按照类型注入的基础之上再按照名称注入。 (给方法参数注入时可以单独使用,给类成员注入时则不能单独使用,必须配合@Autowired注解) value属性:用于指定注入bean的id

@Service("userService")
public class UserSerivceImpl implements UserService {
    @Autowired
    @Qualifier("userDao1")
    private UserDao userDao;
    public void saveUser() {
        userDao.saveUser();
    }
}

@Resource:按照bean的id注入,可以单独使用 属性name:用于指定bean的id

@Service("userService")
public class UserSerivceImpl implements UserService {
//    @Autowired
//    @Qualifier("userDao1")
    @Resource(name = "userDao1")
    private UserDao userDao;
    public void saveUser() {
        userDao.saveUser();
    }
}

总结:以上三个注解只能注入bean类型的数据。

@Value:用于注入基本类型和String类型数据 属性value:指定数据的值,可以使用SpEL(spring的el表达式) SpEL写法:${表达式}

位置:变量、方法

五、使用纯注解的方式配置(没有beans.xml配置文件)

beans.xml:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:context="http://www.springframework.org/schema/context"
       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
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd">

    <!-- 告知 spring 创建容器时要扫描的包 -->
    <context:component-scan base-package="com.hcx"></context:component-scan>

    <bean id="runner" class="org.apache.commons.dbutils.QueryRunner"  scope="prototype">
        <constructor-arg name="ds" ref="dataSource"></constructor-arg>
    </bean>

    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <property name="driverClass" value="com.mysql.jdbc.Driver"></property>
        <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/springdemo"></property>
        <property name="user" value="root"></property>
        <property name="password" value="root"></property>
    </bean>
</beans>

@Configuration:指定当前类是一个配置类 当配置类作为AnnotationConfigApplicationContext对象创建的参数传递时,可以省略该注解。 @ComponentScan(basePackage="com.hcx"):指定spring在创建容器时需要扫描的包 属性value和basePackages:作用相同,都是用于指定创建容器时需要扫描的包。 使用此注解就相当于在xml中配置了:<context:component-scan base-package="com.hcx"></context:component-scan> **@Bean:**用于把当前方法的返回值作为bean对象存入spring的ioc容器中 属性name:指定bean的id,默认值为当前方法名 @import:导入其他配置类 属性value:用于指定其他配置类的字节码(使用了该注解的类就是主配置类) @PropertySource:用于指定配置文件的路径 属性value:指定文件名称和路径(classpath:类路径)

Spring的IOC

SpringConfiguration:

@Configuration
@ComponentScan("com.hcx")
public class SpringConfiguration {

    @Bean(name = "runner")
    @Scope("prototype")
    public QueryRunner createQueryRunner(DataSource dataSource) {
        return new QueryRunner(dataSource);
    }

    @Bean(name = "dataSource")
    public DataSource createDataSource() {
        try {
            ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource();
            comboPooledDataSource.setDriverClass("com.mysql.jdbc.Driver");
            comboPooledDataSource.setJdbcUrl("jdbc:mysql://localhost:3306/springdemo");
            comboPooledDataSource.setUser("root");
            comboPooledDataSource.setPassword("root");
            return comboPooledDataSource;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

测试:

public class UserServiceTest {
    @Test
    public void testSelectAll(){
        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfiguration.class);
        UserService userService = applicationContext.getBean("userService", UserService.class);
        List<User> userList = userService.findAll();
        for(User user : userList){
            System.out.println(user);
        }
    }
}

注意:使用注解配置,方法如果有参数,spring会寻找有没有对应的bean对象,查找方式与Autowired一样。

@import注解

当有多个配置类时,可以通过@import注解导入其他的配置类 主配置类:

@ComponentScan("com.hcx")
@Import(JdbcConfig.class)
public class SpringConfiguration {
}

Jdbc配置类:

public class JdbcConfig {

    @Bean(name = "runner")
    @Scope("prototype")
    public QueryRunner createQueryRunner(DataSource dataSource) {
        return new QueryRunner(dataSource);
    }

    @Bean(name = "dataSource")
    public DataSource createDataSource() {
        try {
            ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource();
            comboPooledDataSource.setDriverClass("com.mysql.jdbc.Driver");
            comboPooledDataSource.setJdbcUrl("jdbc:mysql://localhost:3306/springdemo");
            comboPooledDataSource.setUser("root");
            comboPooledDataSource.setPassword("root");
            return comboPooledDataSource;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

@PropertySource注解

使用读取配置文件的方式读取数据库配置信息: 将数据库的配置抽取到单独的properties文件中: jdbcConfig.properties:

jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/springdemo
jdbc.useranme=root
jdbc.password=root

JdbcConfig:

public class JdbcConfig {

    @Value("${jdbc.driver}")
    private String driver;

    @Value("${jdbc.url}")
    private String url;

    @Value("${jdbc.username}")
    private String username;

    @Value("${jdbc.password}")
    private String password;

    @Bean(name = "runner")
    @Scope("prototype")
    public QueryRunner createQueryRunner(DataSource dataSource) {
        return new QueryRunner(dataSource);
    }

    @Bean(name = "dataSource")
    public DataSource createDataSource() {
        try {
            ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource();
            comboPooledDataSource.setDriverClass(driver);
            comboPooledDataSource.setJdbcUrl(url);
            comboPooledDataSource.setUser(username);
            comboPooledDataSource.setPassword(password);
            return comboPooledDataSource;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

在配置类中指定读取该配置: SpringConfiguration:

@ComponentScan("com.hcx")
@Import(JdbcConfig.class)
@PropertySource("classpath:jdbcConfig.properties")
public class SpringConfiguration {
}

在方法参数上使用@Qualifier注解

当有多个数据源时,可以在创建对象时通过@Qualifier注解参数指定具体使用哪一个:

Spring的IOC