Spring的IOC

作者: 我可能是个假开发 | 来源:发表于2020-02-27 11:11 被阅读0次

    一、入门案例

    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);
        }
    }
    
    image.png

    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);
        }
    }
    
    image.png image.png

    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>
    
    image.png

    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();
        }
    }
    

    打印结果:


    image.png

    3.注解注入

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

    image.png

    注意:当有多个类型都匹配上时,会再次按照名称匹配,如果相同了,也能匹配上。
    即注入时的类的名称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:类路径)

    image.png

    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注解参数指定具体使用哪一个:

    image.png

    相关文章

      网友评论

        本文标题:Spring的IOC

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