Spring学习01:控制反转(IOC)与依赖注入(DI)

程序的耦合和解耦

耦合:程序间的依赖关系,在开发中应该做到编译时不依赖,运行时才依赖

解耦:使用反射来创建对象,避免使用new关键字,并通过读取配置文件来获取要创建对象的全限定类名

Spring基于XML的IOC配置

1.创建Maven项目并在pom.xml中导入依赖坐标

<dependencies>
    	<!-- 引入核心容器的依赖-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.0.2.RELEASE</version>
        </dependency>
</dependencies>

2.在resources目录下创建bean.xml文件,把对象的创建交给spring来管理

 <?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="accountService" class="com.chenpeng.service.impl.AccountServiceImpl"></bean>
     <bean id="accountDao" class="com.chenpeng.dao.impl.AccountDaoImpl"></bean>
 </beans>

每个<bean>标签对应一个类,其中的属性:

  • id:该类的唯一标识,其他类可以引用
  • class:该类的全限定类名

3.在测试类中通过容器创建对象

//1.获取容器
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
//2.创建对象
AccountService as = ac.getBean("accountService", AccountService.class);

常用的容器有三种:

  1. ClassPathXmlApplicationContext:从类的根路径下加载配置文件创建容器
  2. FileSystemXmlApplicationContext:从磁盘路径上加载配置文件创建容器
  3. AnnotationConfigApplicationContext:读取注解创建容器

bean标签

  • 作用:配置spring管理的对象,默认调用类的无参构造函数创建对象
  • 属性:
    • id:类的唯一标识,其他类可以引用,也可作为getBean()方法的参数创建对象
    • class:该类的全限定类名
    • scope:指定对象的作用范围,可选值如下:
      • singleton:单例对象,默认值
      • prototype:多例对象
      • request:作用于web应用的请求范围
      • session:作用于web应用的会话范围
      • global-session:作用于集群环境的会话范围(全局会话范围),当不是集群环境时,它就是session
    • init-method:指定类中的初始化方法名称,在对象创建成功后执行
    • destroy-method:指定类中销毁方法名称(对多例对象无效)

bean的作用范围和生命周期

  1. 单例对象:scope=singleton
    • 作用范围:每个应用只有一个该对象的实例,它的作用范围就是整个应用
    • 生命周期:单例对象的生命周期和容器相同
      • 出生:当容器创建时出生
      • 活着:只要容器还在,对象就一直活着
      • 死亡:容器销毁时,对象消亡
  2. 多例对象:scope=prototype
    • 作用范围:每次访问对象时,都会重新创建对象实例
    • 生命周期:多例对象的创建与销毁不受容器控制
      • 出生:当我们使用对象时spring框架为我们创建
      • 活着:对象只要是在使用过程中就一直活着
      • 死亡:当对象长时间不用,且没有别的对象引用时,由java的垃圾回收器回收

创建bean的三种方式

  1. 使用默认构造函数创建,在spring的配置文件中使用bean标签,配以id和class属性之后,且没有其他属性和标签时采用的就是默认构造函数创建bean对象,此时如果类中没有默认构造函数,则对象无法创建

    <bean id="accountService" class="com.chenpeng.service.impl.AccountServiceImpl"></bean>
    
  2. 使用普通工厂中的方法创建对象(使用某个类中的方法创建对象,并存入spring容器)

    创建普通工厂如下

    public class InstanceFactory {
    	public IAccountService getAccountService(){
    		return new AccountServiceImpl();
    	}
    }
    
    <bean id="instanceFactory" class="com.chenpeng.factory.InstanceFactory"/>
    <bean id="accountService" factory-bean="instanceFactory" factory-method="getAccountService"/>
    

    其中<bean>标签的属性:

    • factory-bean属性:指定普通工厂的id
    • factory-method属性:指定普通工厂中生产对象的方法
  3. 使用工厂中的静态方法创建对象(使用某个类中的静态方法创建对象,并存入spring容器)

    创建静态工厂如下

    public class StaticFactory {
    	public static IAccountService getAccountService(){
    		return new AccountServiceImpl();
    	}
    }
    
    <bean id="accountService" class="com.chenpeng.factory.StaticFactory" factory-method="getAccountService"/>
    

    其中<bean>标签的属性:

    • factory-method属性::指定静态工厂中生产对象的静态方法

Spring的依赖注入

依赖注入

通过控制反转,对象在创建的时候,由spring为我们将对象所依赖的对象的引用传递给它

注入数据的分类

  1. 基本类型和String
  2. 其他bean类型(在配置文件中或者注解配置过的bean)
  3. 复杂类型/集合类型

依赖注入的方法

构造函数注入

  • 使用的标签<constructor-arg>

  • 标签出现的位置:<bean>标签的内部

  • 标签中的属性:

    • type:用于指定要注入的数据的数据类型,该数据类型也是构造函数中某个或某些参数的类型
    • index:用于指定要注入的数据给构造函数中指定索引位置的参数赋值,索引的位置是从0开始
    • name:用于指定给构造函数中指定名称的参数赋值
    • value:用于提供基本类型和String类型的数据
    • 用于指定其他的bean类型数据(指的是在spring的IOC核心容器中出现过的bean对象)
  • 优势:在获取bean对象时,注入数据是必须的操作,否则对象无法创建成功

  • 弊端:改变了bean对象的实例化方式,使我们在创建对象时,如果用不到这些数据,也必须提供

    public class AccountServiceImpl implements AccountService {
    
        //如果是经常变化的数据,并不适用于注入的方式
        private String name;
        private Integer age;
        private Date birthday;
    
        public AccountServiceImpl(String name, Integer age, Date birthday) {
            this.name = name;
            this.age = age;
            this.birthday = birthday;
        }
    }
    
    <bean id="now" class="java.util.Date" scope="prototype"></bean>
    
    <bean id="accountService" class="cn.maoritian.service.impl.AccountServiceImpl">
    	<constructor-arg name="name" value="myname"></constructor-arg>
    	<constructor-arg name="age" value="18"></constructor-arg>
    	<constructor-arg name="birthday" ref="now"></constructor-arg>
    </bean>
    

set方法注入(更常用)

  • 使用的标签:<property>

  • 标签出现的位置:<bean>标签的内部

  • 标签中的属性:

    • name:用于指定注入时所调用set方法名称
    • value:用于提供基本类型和String类型的数据
    • ref:用于指定其他的bean类型数据(指的是在spring的IOC核心容器中出现过的bean对象)
  • 优势:创建对象时没有明确的限制,可以直接使用默认构造函数

  • 弊端:如果有某个成员必须有值,则获取对象时有可能set方法没有执行

    public class AccountServiceImpl implements IAccountService {
    
    	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;
    	}
    }
    
    <bean id="now" class="java.util.Date" scope="prototype"></bean>
    
    <bean id="accountService" class="cn.maoritian.service.impl.AccountServiceImpl">
    	<property name="name" value="myname"></property>
    	<property name="age" value="21"></property>
    	<property name="birthday" ref="now"></property>
    </bean>
    

复杂类型/集合类型的注入

集合字段及其对应的标签按照集合的结构分为两类,相同结构的集合标签之间可以互相替换

  1. 只用键的结构:

    • 数组字段: <array>标签表示数组,<value>标签表示数组内的成员
    • List字段: <list>标签表示集合,<value>标签表示集合内的成员
    • Set字段: <set>标签表示集合,<value>标签表示集合内的成员
  2. 键值对的结构:

    • Map字段: <map>标签表示集合,<entry>标签表示集合内的键值对,其key属性表示键,value属性表示值
    • Properties字段: <props>标签表示集合,<prop>标签表示键值对,其key属性表示键,标签内的内容表示值
    public class AccountServiceImpl implements AccountService {
    	
    	private String[] myArray;
    	private List<String> myList;
    	private Set<String> mySet;
    	private Map<String,String> myMap;
    	private Properties myProps;
    
    	public void setMyStrs(String[] myArray) {
    		this.myArray = myArray;
    	}
    	
    	public void setMyList(List<String> myList) {
    		this.myList = myList;
    	}
    	
    	public void setMySet(Set<String> mySet) {
    		this.mySet = mySet;
    	}
    	
    	public void setMyMap(Map<String, String> myMap) {
    		this.myMap = myMap;
    	}
    	
    	public void setMyProps(Properties myProps) {
    		this.myProps = myProps;
    	}
    	
    	@Override
    	public void saveAccount() {
    		System.out.println(Arrays.toString(myArray));
    		System.out.println(myList);
    		System.out.println(mySet);
    		System.out.println(myMap);
    		System.out.println(myProps);
    	}
    }
    
    <bean id="accountService" class="cn.maoritian.service.impl.AccountServiceImpl">
    	<property name="myStrs">
    		<array>
    			<value>value1</value>
    			<value>value2</value>
    			<value>value3</value>
    		</array>
    	</property>
    
    	<property name="myList">
    		<list>
    			<value>value1</value>
    			<value>value2</value>
    			<value>value3</value>
    		</list>
    	</property>
    
    	<property name="mySet">
    		<set>
    			<value>value1</value>
    			<value>value2</value>
    			<value>value3</value>
    		</set>
    	</property>
    
    	<property name="myMap">
    		<map>
    			<entry key="key1" value="value1"></entry>
    			<entry key="key2">
    				<value>value2</value>
    			</entry>
    			
    		</map>
    	</property>
    
    	<property name="myProps">
    		<props>
    			<prop key="key1">value1</prop>
    			<prop key="key2">value2</prop>
    		</props>
    	</property>
    </bean>
    

Spring基于注解的IOC配置

常用注解

用于创建对象的注解

  • @Component
    • 作用:用于把当前类对象存入spring容器中
    • 属性:value:用于指定bean的id,不写时它的默认值是当前类名且首字母小写
  • @Controller:一般用在表现层
  • @Service:一般用在业务层
  • @Repository:一般用在持久层
  • @Controller,@Servic和@Repository的作用和属性和@Component一样,这些注解的作用相当于bean.xml中的<bean>标签

用于注入数据的注解

  • @Autowired
    • 作用:自动按照类型注入,只要容器中有唯一一个bean对象类型和要注入的变量类型匹配,就可以注入成功
    • 注入过程:
      • 如果IOC容器中没有任何bean的类型和要注入的变量类型匹配,则报错
      • 如果IOC容器中有多个类型匹配时会报错,可以指定要注入的bean对象的id
    • 出现位置:可以是变量上,可以是方法上
    • 在使用注解注入时,set方法不是必须的
  • @Qualifier
    • 作用:在按照类注入的基础上再按照名称注入,在给类成员注入时不能单独使用,但是在给方法参数注入时可以
    • 属性:
      • value:用于指定注入的bean的id
  • @Resource
    • 作用:直接按照bean的id注入,可以独立使用
    • 属性:
      • name:用于指定注入bean的id
  • @Value
    • 作用:用于注入基本类型和String类型的数据
    • 属性:
      • value:用于指定数据的值,使用spring中的el表达式--SpEL
  • 这些注解的作用相当于bean.xml中<bean>标签中的<property>标签
  • 注意:集合的类型注入只能通过xml实现

用于改变作用范围的注解

  • @Scope
    • 作用:用于指定bean的作用范围
    • 属性:
      • value:指定范围的取值,常用取值:Singleton和Prototype
  • 该注解的作用相当于bean.xml中<bean>标签中的<scope>标签

和生命周期相关的注解

  • @ PostConstruct
    • 作用:用于指定初始化方法
  • @PreDestroy
    • 作用:用于指定销毁方法

Spring的半注解配置和纯注解配置

半注解配置

半注解配置时,仍然使用ClassPathXmlApplicationContext类加载配置文件,但是需要在配置文件中告知spring创建容器时要扫描的包,然后将spring的注解加在类的定义上或者类对象上

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       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的名称空间和约束-->
    <context:component-scan base-package="cn.maoritian"></context:component-scan>
</beans>

纯注解配置

纯注解配置时,使用配置类代替bean.xml,使用AnnotationConfigApplicationContext类从spring配置类中读取IOC配置

配置类中使用的注解

  • @Configuration
    • 作用:指定当前类是一个配置类
    • 当被配置的类作为AnnotationConfigApplicationContext的参数时,可以不写
  • @ComponentScan
    • 作用:用于通过注解指定spring在创建容器时要扫描的包
    • 属性:
      • value:和basePackage的作用一样,都是用于指定创建容器时要扫描的包
  • @Bean
    • 作用:用于把当前方法中的返回值作为bean对象存入spring的IOC容器中
    • 属性:
      • name:用于指定bean的id,默认值是当前方法的名称
    • 细节:使用注解配置方法时,如果方法有参数,spring框架会去容器中查找有没有可用的bean对象,查找的方式和Autowired注解的作用是一样的
  • @Import
    • 作用:用于导入其他的配置类
  • @PropertySource
    • 作用:用于指定properties的位置
    • 属性:
      • value:指定文件的名称和路径,关键字:classpath,表示类路径下

使用纯注解配置

  1. dao层实现类

    @Repository("accountDao")
    public class AccountDaoImpl implements AccountDao {
    	@Autowired//自动按照类型注入
    	private QueryRunner runner;
    	
    	public List<Account> findAll() {
    		try {
    			return runner.query("select * from account",new BeanListHandler<Account>(Account.class));
    		} catch (SQLException e) {
    			throw new RuntimeException(e);
    		}
    	}
    }
    
  2. service层实现类

    @Service("accountService")
    public class AccountServiceImpl implements AccountService {
    	@Autowired
    	private AccountDao accountDao;
    	
    	public List<Account> findAll() {
    		return accountDao.findAll();
    	}
    }
    
  3. 创建config包,在其中创建配置类

    SpringConfiguration主配置类

    @Configuration
    @ComponentScan("com.chenpeng")//创建容器时要扫描的包
    @Import(JdbcConfig.class)//引入配置类,可以引入多个
    @PropertySource("classpath:jdbc.properties")//从类的根路径下读取配置文件
    public class SpringConfiguration {
    
    }
    

    JDBCConfig配置类

    /**
     * 数据库连接的配置类
     */
    
    public class JdbcConfig {
        @Value("${jdbc.driver}")
        private String driver;
    
        @Value("${jdbc.url}")
        private String url;
    
        @Value("${jdbc.user}")
        private String user;
    
        @Value("${jdbc.password}")
        private String password;
        /**
         * 创建一个QueryRunner对象
         * @param dataSource
         * @return
         */
        @Bean(name="runner")
        @Scope("prototype")
        public QueryRunner createQueryRunner(DataSource dataSource){
            return new QueryRunner(dataSource);
        }
    
        /**
         * 创建数据源对象
         * @return
         */
        @Bean("dataSource")
        public DataSource createDataSource(){
            ComboPooledDataSource ds = new ComboPooledDataSource();
            try {
                ds.setDriverClass(driver);
                ds.setJdbcUrl(url);
                ds.setUser(user);
                ds.setPassword(password);
                return ds;
            } catch (PropertyVetoException e) {
                throw new RuntimeException(e);
            }
        }
    }
    
posted @ 2020-04-14 10:33  codeDD  阅读(104)  评论(0编辑  收藏  举报