三、spring成长之路——springIOC容器详解(上)
[TOC]
一、springIOC
控制反转和依赖注入
:
简单的说就是将对象的创建,属性的的设置交给spring容器进行管理,而不再由用户自己创建,当用户需要使用该接口或者类的时候,直接注入就可以了,spring容器会自动帮助用户创建对象。
1.创建maven应用程序
【pom.xml】
1.引入spring依赖,junit依赖
2.引入maven插件——java编译插件
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.itcloud</groupId> <artifactId>resource</artifactId> <version>1.0-SNAPSHOT</version> <packaging>jar</packaging> <name>resource</name> <url>http://maven.apache.org</url> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties> <dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>4.3.15.RELEASE</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.7.0</version> <configuration> <source>1.8</source> <target>1.8</target> <encoding>UTF-8</encoding> </configuration> </plugin> </plugins> </build> </project>
该依赖会下载下面jar
<groupId>org.springframework</groupId> <artifactId>spring-context</artifactId>
- org.springframework:spring-aop:4.3.15.RELEASE
- org.springframework:spring-beans:4.3.15.RELEASE
- org.springframework:spring-core:4.3.15.RELEASE
- org.springframework:spring-expression:4.3.15.RELEASE
2.springIOC基础
基本概念:springIOC主要作用是用来管理javaBean的容器,将java对象以及对象和对象之间的关系交由Spring容器管理。
在没有spring容器之前对接口或者类进行实例化的时候都需要使用new
关键字,来进行对象的创建,那么自从有了spring,那么这些事情就交给了spring来做了。
2.1.了解spring的几种注入方式
【Teacher.java】
getter和setter方法在这里都会被省略。
一个老师对应多个学生,老师pojo类中包含setter注入,和List集合注入
public class Teacher implements Serializable { private Long id; private String name; private Integer age; private List<Student> students; }
【Student.java】
一个学生对应一个老师,学生包含多种注入方式,有setter,Properties类注入,map注入以及构造方法注入
注意点,
1.如果添加了有参构造方法(没有参构造),那么在进行注入的时候必须要进行构造方法的注入
2.如果既有有参构造和无参构造可以不进行构造方法的注入
public class Student implements Serializable { private Long id; private String name; private Teacher teacher; private Properties pro; private Map<String,Object> map; public Student(){} public Student(Long id, String name){ this.id = id; this.name = name; } }
【applicationContext.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="student" class="com.itcloud.pojo.Student"> <property name="id" value="1"/> <property name="name" value="小明"/> </bean> <bean id="student2" class="com.itcloud.pojo.Student"> <!-- 构造方法注入 --> <!--每一个标签都代表一个构造方法的属性,按照参数在构造方法中的顺序进行注入--> <constructor-arg value="2"/> <constructor-arg> <value>张三</value> </constructor-arg> <property name="teacher" ref="teacher" /> <!-- map注入 --> <property name="map"> <map> <entry key="1" value="语文" /> <entry key="2" value="数学" /> </map> </property> <!-- Properties注入 --> <property name="pro"> <props> <prop key="身高">1.8</prop> <prop key="体重">70kg</prop> </props> </property> </bean> <bean id="teacher" class="com.itcloud.pojo.Teacher"> <property name="id" value="100023" /> <property name="name" value="王老师" /> <property name="age" value="30" /> <!-- list集合注入 --> <property name="students"> <list> <ref bean="student2" /> <ref bean="student" /> </list> </property> </bean> </beans>
【TestIOC.java】进行数据的测试,debug观察数据
package com.itcloud.pojo; import org.junit.Test; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class TestIOC { //加载spring容器 private ApplicationContext context = new ClassPathXmlApplicationContext("classpath:spring/applicationContext.xml"); @Test public void testStudent() { //context.getBean()获取spring容器中管理的bean,参数为Id Student stu1 = (Student) context.getBean("student"); Student stu2 = context.getBean("student2", Student.class); Teacher teacher = context.getBean("teacher", Teacher.class); System.out.println("---------------------"); } }
2.2.p标签和c标签的注入方式
p代表的就是属性,c代表的就是构造方法。
添加标签头,引入p和c
xmlns:p="http://www.springframework.org/schema/p" xmlns:c="http://www.springframework.org/schema/c"
实例
c:id="3" c:name="smith"
就是构造方法中的两个参数 id和name,p:teacher-ref="teacher"
为类中中的属性teacher
<bean id="student3" class="com.itcloud.pojo.Student" c:id="3" c:name="smith" p:teacher-ref="teacher"/>
2.3.bean之间的继承关系
两个关键字段parent
和abstract
1.此时student3会继承student中的id,和name属性,这个是parent的作用,非必须标签
2.abstract表示student这个bean无法被实例化,即无法再代码中获取这个bean,也无法被外部所引用,非必须标签
例如:ref="student"是错的
<bean id="student" class="com.itcloud.pojo.Student" abstract="true"> <property name="id" value="1"/> <property name="name" value="小明"/> </bean> <bean id="student3" class="com.itcloud.pojo.Student" parent="student" p:teacher-ref="teacher"/>
2.4.bean的作用域
概述
作用域 | 描述 |
---|---|
单例(singleton) | (默认)每一个Spring IoC容器都拥有唯一的一个实例对象 |
原型(prototype) | 一个Bean定义,任意多个对象 |
scope="singleton"
默认值,只会产生一个实例化对象
scope="prototype"
原型,每次获取bean的时候都会获取一个新的实例化对象
<bean id="student4" class="com.itcloud.pojo.Student" parent="student" p:teacher-ref="teacher" scope="singleton"/>
2.6.bean的生命周期
两个关键点:
1.<bean/>
标签中的字段:init-method=""
表示bean初始化(构造方法)之后调用的方法 destroy-method=""
容器关闭之后调用的方法.
2. bean的后置处理器,需要实现方法,BeanPostProcessor
这个类,两个方法:
postProcessBeforeInitialization()
:在每个bean初始化后(构造方法)调用一次(在init-method
方法之前被调用)。
postProcessAfterInitialization()
:在init-method
之后被调用,destroy-method
之前被调用
实现案例0001
【LifeCycle.java】
package com.itcloud.pojo; public class LifeCycle { public LifeCycle(){ System.out.println("构造方法初始化.........."); } public void init(){ System.out.println("init()初始化方法......."); } public void destory(){ System.out.println("destory()销毁方法......."); } }
【applicationContext.xml】
init-method="init" destroy-method="destory"
<bean id="lifeCycle" class="com.itcloud.pojo.LifeCycle" init-method="init" destroy-method="destory"></bean>
【TestIOC.java】测试
public class TestIOC { //加载spring容器 private ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("classpath:spring/applicationContext.xml"); @Test public void testStudent() { LifeCycle lifeCycle = context.getBean("lifeCycle", LifeCycle.class); context.close(); //测试结果 /* 信息: Loading XML bean definitions from class path resource [spring/applicationContext.xml] 构造方法初始化.......... init()初始化方法....... 四月 08, 2018 10:09:34 上午 org.springframework.context.support.ClassPathXmlApplicationContext doClose 信息: Closing org.springframework.context.support.ClassPathXmlApplicationContext@27ddd392: startup date [Sun Apr 08 10:09:33 CST 2018]; root of context hierarchy destroy()销毁方法....... */ } }
第二个关键点实现案例0002
【CycleProcessor.java】
package com.itcloud.pojo; import org.springframework.beans.BeansException; import org.springframework.beans.factory.config.BeanPostProcessor; import org.springframework.stereotype.Component; public class CycleProcessor implements BeanPostProcessor { @Override public Object postProcessBeforeInitialization(Object bean, String name) throws BeansException { System.out.println("CycleProcessor start....." + name); return bean; } @Override public Object postProcessAfterInitialization(Object bean, String name) throws BeansException { System.out.println("CycleProcessor end....." + name); return bean; } }
【TestIOC.java】测试类不变,测试结果:
/* 构造方法初始化.......... CycleProcessor start.....lifeCycle init()初始化方法....... CycleProcessor end.....lifeCycle 四月 08, 2018 10:13:31 上午 org.springframework.context.support.ClassPathXmlApplicationContext doClose 信息: Closing org.springframework.context.support.ClassPathXmlApplicationContext@58c1670b: startup date [Sun Apr 08 10:13:00 CST 2018]; root of context hierarchy destroy()销毁方法....... */
2.7.工厂注入(了解即可)
注意点,如果工厂方法有参数,通过<constructor-arg value="xxx"></constructor-arg>
进行参数匹配
静态工厂注入
public class StudentFactory { public static Student getInstance(){ Student stu = new Student(); stu.setId(10L); stu.setName("小十"); return stu; } }
<bean id="student6" class="com.itcloud.pojo.StudentFactory" factory-method="getInstance"></bean>
测试
Student stu = context.getBean("student6", Student.class);
实例工厂注入
package com.itcloud.pojo; public class TeacherFactory { public Teacher getInstance(Long id, String name){ Teacher teacher = new Teacher(); teacher.setId(id); teacher.setName(name); return teacher; } }
<!-- 实例工厂必须单独配置一个bean --> <bean id="teacherFactory" class="com.itcloud.pojo.TeacherFactory"/> <bean id="teacher2" factory-bean="teacherFactory" factory-method="getInstance"> <constructor-arg> <value>222</value> </constructor-arg> <constructor-arg name="name" value="张老师" /> </bean>
测试
Teacher teacher = context.getBean("teacher2", Teacher.class);
FactoryBean配置
跳转标志
package com.itcloud.pojo; import org.springframework.beans.factory.FactoryBean; public class TeacherFactoryBean implements FactoryBean { private String name; @Override public Object getObject() throws Exception { Teacher teacher = new Teacher(); teacher.setName(name); return teacher; } @Override public Class<?> getObjectType() { return Teacher.class; } @Override public boolean isSingleton() { return true; } public void setName(String name) { this.name = name; } }
<!--FactoryBean方法配置bean--> <bean id="teacherBean" class="com.itcloud.pojo.TeacherFactoryBean"> <property name="name" value="李老师"/> </bean>
Teacher teacher = context.getBean("teacherBean", Teacher.class);
3.spring注解注入
三个注解将类注入到spring容器中,注意点:注解默认注入的Id为当前类的名称首字母小写
-
@Repository
主要用于dao,数据访问层 -
@Service
用于Service层,调用数据访问层的方法,进行逻辑操作 -
@Component
用户普通类的注册,用户自己定义的组件
我们知道Service层一定会调用dao层的相关方法,dao层已经被注册到Spring容器之中,这是后就需要使用Spring为我们提供的注解来引用对应的实例
-
@Autowired
按照类型进行匹配,如果一个接口存在两个子类,可以配合@Qualifier
注解来使用 -
@Resource
按照名称进行匹配,
3.1简单应用应用案例
【applicationContext-annotation.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" xmlns:p="http://www.springframework.org/schema/p" xmlns:c="http://www.springframework.org/schema/c" 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"> <!-- 此配置表示com.itcloud包及其子包支持注解配置 --> <context:component-scan base-package="com.itcloud"> </context:component-scan> </beans>
【UserDAO.java】
package com.itcloud.dao; public interface UserDAO { //用户更新 int update(); }
【UserDAOImpl.java】注意点:@Repository的value的值默认是(userDAOImpl)
@Repository public class UserDAOImpl implements UserDAO { @Override public int update() { System.out.println("进行数据库语句编写"); return 0; } }
【UserService.java】
package com.itcloud.service; public interface UserService { int doUpdate(); }
【UserServiceImpl.java】@Service的value的默认值是:userServiceImpl
ackage com.itcloud.service.impl; import com.itcloud.dao.UserDAO; import com.itcloud.service.UserService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @Service public class UserServiceImpl implements UserService { @Autowired private UserDAO userDAO; @Override public int doUpdate() { System.out.println("UserServiceImpl update()方法开始...."); userDAO.update(); return 0; } }
测试
context.getBean("userDAOImpl");//结果:userDAOImpl@127 即:获取到UserDAO的对象
理解bean之间的相互调用
我们在UserServiceImpl
这个类中可以看到如下这句话,这句话表示的意思就是引用外部bean,在没有Spring之前我们引入外部bean的过程是:private UserDAO userDAO = new UserDAOImpl()
,在有了Spring之后,spring会自动帮我们进行对象的创建,以及维护对象之间的关系。
@Autowired private UserDAO userDAO;
测试Autowired
@Test public void testAnnotation(){ UserService userService = context.getBean(UserService.class); userService.doUpdate(); } //结果 /* UserServiceImpl update()方法开始.... 进行数据库语句编写 */
前面提到,@Autowired
是根据类型进行注入的,此时因为UserDAO
只有一个子类,但是如果有两个子类要怎么书写呢:
//方案一,官方推荐 @Autowired @Qualifier("userDAOImpl") private UserDAO userDAO; //方案二 @Resource(name = "userDAOImpl") private UserDAO userDAO; //或者 @Resource private UserDAO userDAOImpl;
3.2理解开启注解支持配置
【applicationContext-annotation.xml】
<context:component-scan base-package="com.itcloud"> </context:component-scan>
这里也可以添加子元素,对注解数据进行过滤
最常用的过滤方式
<context:component-scan base-package="com.itcloud" use-default-filters="false"> <!--只包含Service注解的bean,其他注解不会被扫描--> <context:include-filter type="annotation" expression="org.springframework.stereotype.Service"/> </context:component-scan>
use-default-filters="false"
不使用默认过滤方式,如果为true的话,<context:include-filter type="annotation" expression="org.springframework.stereotype.Service"/>
表示的意思只是将@Service包含进来,其他注解也会包含的
<!--表示不扫描Repository注解--> <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Repository"/>
4.spring引用外部属性文件
4.1 applicationContext.xml文件配置bean并且注入属性文件中的内容
【value.properties】定义一个外部属性文件
value.driverName=com.mysql.jdbc.Driver value.url=jdbc:mysql://localhost:3306/test value.username=root value.password=123456
【DataSource.java】定义属性类
package com.itcloud.value; public class DataSource { private String driverName; private String username; private String password; private String url; //getter setter方法略 }
【applicationContext-annotation.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" xmlns:p="http://www.springframework.org/schema/p" xmlns:c="http://www.springframework.org/schema/c" 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"> <context:property-placeholder location="classpath:properties/value.properties"/> <bean id="dataSource" class="com.itcloud.value.DataSource"> <property name="username" value="${value.username}"/> <property name="password" value="${value.password}"/> <property name="driverName" value="${value.driverName}"/> <property name="url" value="${value.url}"/> </bean> </beans>
此时,当spring容器加载的时候,DataSource.java
被实例化, value.properties
属性文件中的内容会被注入到DataSource中。
4.2不通过配置文件的方式读取属性
【applicationContext-annotation.xml】开启注解配置
<context:component-scan base-package="com.itcloud"/> <context:property-placeholder location="classpath:properties/value.properties"/> <!--<bean id="dataSource" class="com.itcloud.value.DataSource">--> <!--<property name="username" value="${value.username}"/>--> <!--<property name="password" value="${value.password}"/>--> <!--<property name="driverName" value="${value.driverName}"/>--> <!--<property name="url" value="${value.url}"/>--> <!--</bean>-->
【DataSource.java】 此时可以没有setter方法
package com.itcloud.value; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; @Component public class DataSource { @Value("${value.driverName}") private String driverName; @Value("${value.username}") private String username; @Value("${value.password}") private String password; @Value("${value.url}") private String url; }