Spring学习笔记–Spring IOC基础
结合之前的手写ioc案例,Spring学习笔记–手写实现 IoC 和 AOP
Spring框架IOC实现:
bean的定义
- 纯XML(bean信息定义全部配置在XML中)
- xml+注解 部分bean使用xml定义,部分bean使用注解定义
- 纯注解方式 多有的bean都是用注解来定义
不同的bean的定义方式导致了IOC容器BeanFactory中不同的启动方式
BeanFactory :通过反射技术来实例化对象并维护对象之间的依赖关系。
- **xml **或者 xml+注解:
javaSE应用:ApplicationContext applicationContext = new ClassPathXmlApplication(“beans.xml”);或者 new FileSystemXmlApplicationContext(“c:/beans.xml”);
javaWeb应用:ContextLoaderListener(监听器去加载xml); - 注解
javaSE应用:ApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfig.class);
javaWeb应用:ContextLoaderListener(监听器去加载注解配置类)
BeanFactory:是Spring容器的顶层接口,它只是负责用来定义一些基础功能,定义一些基础规范,而ApplicationContext是它的子接口,所以ApplicationContext是具备BeanFactory提供的全部功能的。
ApplicationContext:是beanFactory的子接口,也是我们常用的,它有两个基于XML配置的实现类,ClassPathXmlApplicationContext和FileSystemXmlApplicationContext,以及基于注解配置的实现类AnnotationConfigApplicationContext从java配置类加载配置信息等。
通常,我们称BeanFactory为SpringIOC的基础容器,ApplicationContext是容器的高级接口,比BeanFactory要拥有更多的功能,比如国际化支持和资源访问(xml,java配置类)等等。
SpringContext为什么不直接使用BeanFactory?
这也是Spring设计的一个优雅之处,BeanFactory是属于顶层接口,它具有作为容器的一些基础的功能,而ApplicationContext作为它的子接口拥有比它更多的功能,比如资源加载等等,不是把所有的功能都放在一个接口之中,Spring层级划分的比较明细,需要实现那些功能就找对应的子接口即可。
启动IOC容器的方式
** java环境下启动IOC容器**
- ClassPathXmlApplicationContext:从类的根路径下加载配置文件(推荐使用)
- FileSystemXmlApplicationContext:从磁盘路径加载配置文件
- AnnotationConfigApplicationContext:纯注解模式下启动Spring容器
Web环境下启动IOC容器
纯Xml
- 从xml启动容器
xmlns是xmlnamespace的缩写表示命名空间,xsi:schemaLoaction下的xsd表示规则。
<?xml version="1.0" encoding="UTF-8"?>
<!--跟标签beans,里面配置一个又一个的bean子标签,每一个bean子标签都代表一个类的配置-->
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/tx
https://www.springframework.org/schema/tx/spring-tx.xsd
">
<!--id标识对象,class是类的全限定类名-->
<!--scope:定义bean的作用范围
singleton:单例,IOC容器中只有一个该类对象,默认为singleton
prototype:原型(多例),每次使用该类的对象(getBean),都返回给你一个新的对象,Spring只创建对象,不管理对象
-->
<bean id="accountDao" class="com.lagou.edu.dao.impl.JdbcTemplateDaoImpl" scope="singleton" init-method="init" destroy-method="destory">
<!--set注入使用property标签,如果注入的是另外一个bean那么使用ref属性,如果注入的是普通值那么使用的是value属性-->
<!--<property name="ConnectionUtils" ref="connectionUtils"/>
<property name="name" value="zhangsan"/>
<property name="sex" value="1"/>
<property name="money" value="100.3"/>-->
<!--<constructor-arg index="0" ref="connectionUtils"/>
<constructor-arg index="1" value="zhangsan"/>
<constructor-arg index="2" value="1"/>
<constructor-arg index="3" value="100.5"/>-->
<!--name:按照参数名称注入,index按照参数索引位置注入-->
<constructor-arg name="connectionUtils" ref="connectionUtils"/>
<constructor-arg name="name" value="zhangsan"/>
<constructor-arg name="sex" value="1"/>
<constructor-arg name="money" value="100.6"/>
<!--set注入注入复杂数据类型-->
<property name="myArray">
<array>
<value>array1</value>
<value>array2</value>
<value>array3</value>
</array>
</property>
<property name="myMap">
<map>
<entry key="key1" value="value1"/>
<entry key="key2" value="value2"/>
</map>
</property>
<property name="mySet">
<set>
<value>set1</value>
<value>set2</value>
</set>
</property>
<property name="myProperties">
<props>
<prop key="prop1">value1</prop>
<prop key="prop2">value2</prop>
</props>
</property>
</bean>
<bean id="transferService" class="com.lagou.edu.service.impl.TransferServiceImpl">
<!--set+ name 之后锁定到传值的set方法了,通过反射技术可以调用该方法传入对应的值-->
<property name="AccountDao" ref="accountDao"></property>
</bean>
<!--事务管理器-->
<bean id="transactionManager" class="com.lagou.edu.utils.TransactionManager">
<property name="ConnectionUtils" ref="connectionUtils"/>
</bean>
<!--代理对象工厂-->
<bean id="proxyFactory" class="com.lagou.edu.factory.ProxyFactory">
<property name="TransactionManager" ref="transactionManager"/>
</bean>
<!--Spring ioc 实例化Bean的三种方式-->
<!--方式一:使用无参构造器(推荐)-->
<bean id="connectionUtils" class="com.lagou.edu.utils.ConnectionUtils"></bean>
<!--另外两种方式是为了我们自己new的对象加入到SpringIOC容器管理-->
<!--方式二:静态方法-->
<!--<bean id="connectionUtils" class="com.lagou.edu.factory.CreateBeanFactory" factory-method="getInstanceStatic"/>-->
<!--方式三:实例化方法-->
<!--<bean id="createBeanFactory" class="com.lagou.edu.factory.CreateBeanFactory"></bean>
<bean id="connectionUtils" factory-bean="createBeanFactory" factory-method="getInstance"/>-->
<!--lazy-init默认为false立即加载-->
<bean id="accountPojo" class="com.lagou.edu.pojo.Account" lazy-init="true"/>
</beans>
SE应用方式测试及运行结果
@Test
public void TestIoc(){
//通过读取classpath下的xml文件来启动容器(xml模式SE应用下推荐)
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationcontext.xml");
//不推荐使用
//ApplicationContext applicationContext = new FileSystemXmlApplicationContext("c:/applicationContext.xml")
AccountDao accountDao = (AccountDao) applicationContext.getBean("accountDao");
System.out.println(accountDao);
}
Web应用编写web.xml
导入spring-web包,编写web.xml
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app>
<display-name>Archetype Created Web Application</display-name>
<!--配置Spring ioc容器的配置文件-->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml</param-value>
</context-param>
<!--使用监听器启动Spring的IOC容器-->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
</web-app>
Web应用中如何从容器中拿到对象
在Servlet初始话的过程中,首先去获取容器对象,Spring为我们提供了一个工具类,WebApplicationContextUtil.getWebApplicationContext(ServletContext sc);让我们在Servlet中获取到Spring的IOC容器。通过getBean()方法获取到代理对象工厂proxyFactory,生成代理对象具体代码如下:
@Override
public void init() throws ServletException {
WebApplicationContext webApplicationContext = WebApplicationContextUtils.getWebApplicationContext(this.getServletContext());
transferService = (TransferService) webApplicationContext.getBean("transferService");
/* WebApplicationContext webApplicationContext = WebApplicationContextUtils.getWebApplicationContext(this.getServletContext());
ProxyFactory proxyFactory = (ProxyFactory)webApplicationContext.getBean("proxyFactory");
transferService = (TransferService) proxyFactory.getJdkProxy(webApplicationContext.getBean("transferService")) ;*/
}
Bean定义中的细节
SpringIOC实例化Bean的三种方式:
使⽤⽆参构造函数(推荐)
在默认情况下,它会通过反射调⽤⽆参构造函数来创建对象。如果类中没有⽆参构造函数,将创建失败。
<!--配置service对象-->
<bean id="userService" class="com.lagou.service.impl.TransferServiceImpl">
</bean>
另外两种方式是为了我们自己new的对象加入到SpringIOC容器管理
使⽤静态⽅法创建
在实际开发中,我们使⽤的对象有些时候并不是直接通过构造函数就可以创建出来的,它可能在创建的过程 中会做很多额外的操作。此时会提供⼀个创建对象的⽅法,恰好这个⽅法是static修饰的⽅法,即是此种情 况。
例如,我们在做Jdbc操作时,会⽤到java.sql.Connection接⼝的实现类,如果是mysql数据库,那么⽤的就 是JDBC4Connection,但是我们不会去写 JDBC4Connection connection = new JDBC4Connection() ,因 为我们要注册驱动,还要提供URL和凭证信息,⽤ DriverManager.getConnection ⽅法来获取连接。那么在实际开发中,尤其早期的项⽬没有使⽤Spring框架来管理对象的创建,但是在设计时使⽤了 ⼯⼚模式解耦,那么当接⼊spring之后,⼯⼚类创建对象就具有和上述例⼦相同特征,即可采⽤此种⽅式配置。
<!--使⽤静态⽅法创建对象的配置⽅式-->
<bean id="userService" class="com.lagou.factory.BeanFactory"
factory-method="getTransferService"></bean>
使⽤实例化⽅法创建
此种⽅式和上⾯静态⽅法创建其实类似,区别是⽤于获取对象的⽅法不再是static修饰的了,⽽是类中的⼀ 个普通⽅法。此种⽅式⽐静态⽅法创建的使⽤⼏率要⾼⼀些。在早期开发的项⽬中,⼯⼚类中的⽅法有可能是静态的,也有可能是⾮静态⽅法,当是⾮静态⽅法时,即可 采⽤下⾯的配置⽅式:
<!--使⽤实例⽅法创建对象的配置⽅式-->
<bean id="beanFactory"
class="com.lagou.factory.instancemethod.BeanFactory"></bean>
<bean id="transferService" factory-bean="beanFactory" factorymethod="getTransferService"></bean>
Bean的作用范围及生命周期
作用范围在spring框架管理Bean对象的创建时,Bean对象默认都是单例的,但是它⽀持配置的⽅式改
变作⽤范围。作⽤范围官⽅提供的说明如下图:
在上图中提供的这些选项中,我们实际开发中⽤到最多的作⽤范围就是singleton(单例模式)和
prototype(原型模式,也叫多例模式)。scope的默认值是singleton,配置⽅式参考下⾯的代码:
<!--配置service对象-->
<bean id="transferService"
class="com.lagou.service.impl.TransferServiceImpl" scope="singleton">
</bean>
不同作用范围的生命周期
在多例模式中Spring只负责创建,不负责销毁。
Bean标签属性
DI依赖注入的xml配置
按照注入的方式分类:1.构造函数注入 2.set方法注入
构造函数注入:顾名思义,就是利用带参构造函数实现对类成员的数据赋值。使用constructor-arg标签,如果注入的是另外一个bean那么使用ref属性,如果注入的是普通值那么使用的是value属性。index表示下标。
set方法注入:它使用过类成员的set方法实现数据的注入。set注入使用property标签,如果注入的是另外一个bean那么使用ref属性,如果注入的是普通值那么使用的是value属性。
在List结构的集合数据注⼊时, array , list , set 这三个标签通⽤,另外注值的 value 标签内部可以直接写值,也可以使⽤ bean 标签配置⼀个对象,或者⽤ ref 标签引⽤⼀个已经配合的bean的唯⼀标识。
在Map结构的集合数据注⼊时, map 标签使⽤ entry ⼦标签实现数据注⼊, entry 标签可以使⽤key和value属性指定存⼊map中的数据。使⽤value-ref属性指定已经配置好的bean的引⽤。同时 entry 标签中也可以使⽤ ref 标签,但是不能使⽤ bean 标签。⽽ property 标签中不能使⽤ ref 或者 bean 标签引⽤对象
XML和注解结合的方式
注意:
1)实际企业开发中,纯xml模式使⽤已经很少了
2)引⼊注解功能,不需要引⼊额外的jar
3)xml+注解结合模式,xml⽂件依然存在,所以,spring IOC容器的启动仍然从加载xml开始
4)哪些bean的定义写在xml中,哪些bean的定义使⽤注解
第三方jar中的bean定义在xml,比如德鲁伊数据库连接池
自己开发的bean定义使用注解
- xml中标签与注解的对应(IoC)
@Component(“id”)传入正常xml配置方式中bean的id来实现bean定义,可以取代xml中的对应的bean标签 - DI 依赖注⼊的注解实现⽅式
**@Autowired(推荐使⽤)**自动装配,按照类型注入。
@Autowired为Spring提供的注解,需要导⼊包
org.springframework.beans.factory.annotation.Autowired。
@Autowired采取的策略为按照类型注⼊。
public class TransferServiceImpl {
@Autowired
private AccountDao accountDao;
}
如上代码所示,这样装配回去spring容器中找到类型为AccountDao的类,然后将其注⼊进来。这样会产⽣⼀个问题,当⼀个类型有多个bean值的时候,会造成⽆法选择具体注⼊哪⼀个的情况,这个时候我们需要配合着@Qualifier使⽤。
如果接口有多个实现类按照类型无法唯一锁定对象可以按照@Qualifier告诉Spring具体去装配哪个对象。
public class TransferServiceImpl {
@Autowired
@Qualifier(name="jdbcAccountDaoImpl")
private AccountDao accountDao;
}
这个时候我们就可以通过类型和名称定位到我们想注⼊的对象。
@Resource按照名称注入,也可以使用Type
@Resource 注解由 J2EE 提供,需要导⼊包 javax.annotation.Resource。
@Resource 默认按照 ByName ⾃动注⼊。
public class TransferService {
@Resource
private AccountDao accountDao;
@Resource(name="studentDao")
private StudentDao studentDao;
@Resource(type="TeacherDao")
private TeacherDao teacherDao;
@Resource(name="manDao",type="ManDao")
private ManDao manDao;
}
- 如果同时指定了 name 和 type,则从Spring上下⽂中找到唯⼀匹配的bean进⾏装配,找不到则抛出异常。
- 如果指定了 name,则从上下⽂中查找名称(id)匹配的bean进⾏装配,找不到则抛出异常。
- 如果指定了 type,则从上下⽂中找到类似匹配的唯⼀bean进⾏装配,找不到或是找到多个,都会抛出异常。
- 如果既没有指定name,⼜没有指定type,则⾃动按照byName⽅式进⾏装配;
注意@Resource 在 Jdk 11中已经移除,如果要使⽤,需要单独引⼊jar包
<dependency>
<groupId>javax.annotation</groupId>
<artifactId>javax.annotation-api</artifactId>
<version>1.3.2</version>
</dependency>
开启注解扫描,base-package指定扫描的包路径
<!--开启注解扫描,base-package指定扫描的包路径-->
<context:component-scan base-package="com.lagou.edu"/>
添加jdbc.properties文件,在applicationContext.xml中引入外部资源文件
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/bank
jdbc.username=root
jdbc.password=123456
整改之后的applicationContext.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:aop="http://www.springframework.org/schema/aop"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd
">
<!--开启注解扫描,base-package指定扫描的包路径-->
<context:component-scan base-package="com.lagou.edu"/>
<!--引入外部资源文件-->
<context:property-placeholder location="classpath:jdbc.properties"/>
<!--第三方jar中的bean定义在xml中-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
</beans>
纯注解模式
改造xm+注解模式,将xml中遗留的内容全部以注解的形式迁移出去,最终删除xml,从Java配置类启动对应注解
- @Configuration 注解,表名当前类是⼀个配置类
- @ComponentScan 注解,替代 context:component-scan
- @PropertySource,引⼊外部属性配置⽂件
- @Import 引⼊其他配置类,如果有多个配置类可以通过import来进行关联,配置启动类的全限定类名的时候使用一个类即可
- @Value 对变量赋值,可以直接赋值,也可以使⽤ ${} 读取资源配置⽂件中的信息
- @Bean 将⽅法返回对象加⼊ SpringIOC 容器
添加配置类SpringConfig
@Configuration
@ComponentScan({"com.lagou.edu"})
@PropertySource({"classpath:jdbc.properties"})
public classs SpringConfig{
@Value("${jdbc.driver}")
private String driverClassName;
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.usernmae}")
private String username;
@Value("${jdbc.password}")
private String password;
@Bean("dataSource")
public DataSource createDataSource(){
DruidDataSource druidDataSource = new DruidDataSource();
druidDataSource.setDriverClassName(driverClassName);
druidDataSource.setUrl(url);
druidDataSource.setUsername(username);
druidDataSource.setPassword(password);
return druidDataSource;
}
}
SE启动
@Test
public void testIoC() throws Exception {
// 通过读取classpath下的xml文件来启动容器(xml模式SE应用下推荐)
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfig.class);
AccountDao accountDao = (AccountDao) applicationContext.getBean("accountDao");
System.out.println(accountDao);
}
Web启动
通过监听器加载配置类。
web.xml中配置启动类的全限定类名,告诉监听器我们使用注解的方式启动IOC容器contextClass,AnnotationConfigWebApplicationContext:
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app>
<display-name>Archetype Created Web Application</display-name>
<!--告诉ContextloaderListener知道我们使用注解的方式启动ioc容器-->
<context-param>
<param-name>contextClass</param-name>
<param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContext</param-value>
</context-param>
<!--配置启动类的全限定类名-->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>com.lagou.edu.SpringConfig</param-value>
</context-param>
<!--使用监听器启动Spring的IOC容器-->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
</web-app>