Spring IoC简述
本章介绍 Spring IoC:对象工厂及依赖注入;
IoC入门
1.什么是IoC
IoC 是 Inversion of Control 的缩写,翻译为控制反转。作用是解决对象之间的耦合度过高的问题,从而降低程序的复杂度
不使用IOC的时候系统中对象之间联系过于紧密和复杂牵一发动全身,类之间关系过于复杂
使用IOC之后 可以理解为在这一堆对象中添加一个控制工具把对象之间的耦合度降低
通过中间的IOC容器协调各个对象之间的关系。我们可以看到如果把IOC容器拿掉对象之间的耦合度几乎为0
这样的话开发不同部分的人也无需因为其他人的开发情况影响到自己的进度。
在A对象使用B对象的过程中,如果不用IOC 则A要主动创建B对象才能使用。 如果使用IOC则只需要A在书写时声明要用到B对象。 IOC会择机创建B对象让A使用。 这种创建对象的方式就可以理解为创建对象的控制权从原来的A主动创建,到A被动接受。形成了“控制反转”。
Spring IoC 的核心如下:
1.工厂负责对象生命周期的管理(spring 管理创建于销毁)。
2.对象的依赖由工厂完成注入(spring维护对象间的关系)。
Spring提出了对象工厂的概念,由Spring工厂来管理对象的生命周期。所谓对象生命周期指的是从对象的创建一直到对象的销毁都由Spring来管理。我们无需再自己new对象,而是从Spring工厂中获取需要的对象。甚至对象的依赖也由工厂来注入,无需手动注入依赖。
Spring工厂是ApplicationContext接口,通常我们使用的是AnnotationConfigApplicationContext类。其中Spring工厂内部是通过Map类型来维护的。
IoC入门案例1:使用IoC的方式创建对象
思路:
创建一个Maven项目,约定项目的编码格式和java编译版本,导入Spring的核心jar包,创建相关类。
1.UserDao目标类上加@Component
创建UserDao类, 书写方法 findAll ,在类上添加注解@component(”名字”),用来告知spring可以通过指定名字来创建该UserDao对象
//声明当前这个UserDao这个类,交给Spring框架去管理,可以通过ud这
// 个标记在Spring中获取到UserDao的对象
@Component("ud")
public class UserDao {
public void findAll() {
System.out.println("查询所有");
}
}
2.SpringConfiguration配置类添加@Configuration 和 @ComponentScan
创建配置类 SpringConfiguration,类上添加@Configuration注解和@ComponentScan注解.(作用是用来告知Spring该类是配置类,并要扫描的包有哪些)
//声明当前类是配置类
@Configuration
//声明这个配置类,可以扫描到那些包
//Spring要去管理UserDao的话,要把项目中UserDao的位置告诉Spring框架,
// 通过扫描包名形式告诉Spring
@ComponentScan(basePackages = {"com.czxy.dao")
public class SpringConfiguration {
}
3.测试类Test01通过ApplicationContext的getBean(“名字”)获取对象
//测试Spring IOC 管理对象
public static void main(String[] args) {
//创建Spring相关的类,获取UserDao对象
//ApplicationContext通过Spring的上下文 (环境创建、获取、管理bean)
//new AnnotationConfigApplicationContext 注解配置上下文对象
ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfiguration.class);
//通过getBean形式用ud标记获取UserDao对象
UserDao userDao = (UserDao) context.getBean("ud");
userDao.findAll();
}
IoC入门案例2:使用Spring实现依赖注入
创建Service类,提供findAll方法,调用UserDao的findAll,在测试类中调用Service进行测试。
步骤如下:
1.定义UserService类并添加@Component注解;
2.在UserService类中添加private UserDao userDao依赖;
3.在userDao成员上添加@Resource注解指定依赖。
代码:
SpringConfiguration.java
//声明当前类是配置类
@Configuration
//声明这个配置类,可以扫描到那些包,把service加入要扫描的包中
//Spring要去管理UserDao的话,要把项目中UserDao的位置告诉Spring框架,
// 通过扫描包名形式告诉Spring
@ComponentScan(basePackages = {"com.czxy.dao", "com.czxy.service"})
public class SpringConfiguration {
}
UserService.java
//设置UserService类在Spring中的标记是us
@Component("us")
public class UserService {
//设置UserDao,从Spring中自动获取,获取到的对象是名字叫ud的类的对象
@Resource(name = "ud")
private UserDao userDao;//=new UserDao();
public void finAll(){
System.out.println("userService函数执行 findAll");
userDao.findAll();
}
}
测试类
@Test
public void test02(){
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfiguration.class);
//通过us获取UserService对象
UserService userService = (UserService) applicationContext.getBean("us");
userService.findAllUsers();
}
IoC入门案例3:面向接口编程
提供dao的接口和实现类、提供service的接口和实现类,程序之间使用接口,将所有实现类交予spring管理,完成程序间的解耦。
步骤如下
1.编写UserDao接口和实现类,并在实现类中添加@Component注解
2.编写UserService接口和实现类,并在实现类中添加@Component注解
3.在userDao成员变量中添加@Resource注解,注入dao的实现类。
4.编写配置类
5.编程测试类
dao接口和实现类
public interface UserDao {
public void findAll();
}
@Component("userDaoImpl")
public class UserDaoImplA implements UserDao {
public void findAll(){
System.out.println("A方式 查询所有");
}
}
service接口和实现类
public interface UserService {
public void findAllUsers();
}
@Component("userServiceImpl")
public class UserServiceImplA implements UserService {
@Resource(name = "userDaoImpl")
private UserDao userDao ;//= new UserDao();
public void findAllUsers(){
System.out.println("开始查找");
userDao.findAll();
System.out.println("查找结束");
}
}
配置类 不变
@ComponentScan(basePackages = {"com.czxy.dao","com.czxy.service"})
@Configuration
public class SpringConfiguration {
}
测试类
@Test
public void test03(){
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfiguration.class);
UserService userService = (UserService) applicationContext.getBean("userServiceImpl");
userService.findAllUsers();
}
IoC入门案例4:整合JUnit4
修改测试
//声明该类使用用SpringJunit测试
@RunWith(SpringJUnit4ClassRunner.class)
//告知去SpringConfiguration类去找相关配置信息
@ContextConfiguration(classes = {SpringConfiguration.class})
public class UserDaoTest {
//声明要注入的类
@Resource(name = "userDaoImplA")
private UserDao userDao;
//测试
@Test
public void testFindAll(){
userDao.findAll();
}
}
IoC详解
1.Bean 的创建
前面已经学习了创建Bean的注解@Component,Spring还提供了一些衍生注解。
注解 描述
@Component 将修饰的资源交予spring管理。 value属性:为资源命名(唯一标识)
@Controller 衍生注解,与@Component作用和属性相同。特用于修饰表示层的资源。
@Service 衍生注解,与@Component作用和属性相同。特用于修饰业务逻辑层的资源。
@Repository 衍生注解,与@Component作用和属性相同。特用于修饰数据访问层的资源。
以上4个注解修饰的类,我们通常称为注册bean。目的是将某类的实例对象,添加到spring容器中。
2.依赖注入(DI)
依赖注入指的是给组件注入值。
类似于给电脑的USB接口插入USB设备一样。
下面的代码就描述给userDao 注入userDaoImplA
我们可以使用如下的注解对不同形式的数据进行注入
注解 描述 修饰位置
@Resource(name=”…”) 按照指定名称注入对象 字段、setter方法 @
Resource 按照类型注入对象 字段、setter方法
@Value 注入简单值 字段、setter方法、参数
@PropertySource 加载properties配置文件 类
2.1.按照名称注入
按照名称来注入userDao对象
dao层
@Repository("userDao1")
public class UserDaoImpl implements UserDao {
public void findAll(){
System.out.println("入门案例");
}
}
service
@Service("userService1")
public class UserServiceImpl implements UserService{
@Resource(name="userDao1")
private UserDao userDao;
public void findAll(){
System.out.println("user service ...");
userDao.findAll();
}
}
2.2.按照类型注入
分别创建dao ,service ,和测试类,按照类型注入对应的对象
dao层
@Repository
public class UserDaoImpl implements UserDao {
public void findAll(){
System.out.println("入门案例");
}
}
service
@Service
public class UserServiceImpl implements UserService{
@Resource
private UserDao userDao;
public void findAll(){
System.out.println("user service ...");
userDao.findAll();
}
}
测试
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes={SpringConfigruation.class})
public class UserDaoTest {
@Resource
private UserService userService;
@Test
public void testFindAll(){
userService.findAll();
}
}
2.3.普通数据注入
字符串类型的成员变量和方法参数注入数据.
固定值注入
public class SpringConfigruation {
@Value("com.mysql.jdbc.Driver")
private String driver;
@Value("jdbc:mysql://127.0.0.1:3306/crm_ssm_v1_0")
public void setUrl(String url){
System.out.println("字段:" + driver);
System.out.println("方法:" + url);
}
}
测试
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes={SpringConfigruation.class})
public class UserDaoTest {
@Test
public void testFindAll() {
}
}
2.4.properties数据注入
需求: 把properties文件中的数据读取出来,在测试类中展示
总体思路:
使用@PropertySource加载properties配置文件,“classpath:”固定前缀,表示从类路径下加载配置文件。
@Value(${jdbc.driver}) 获得配置文件中指定key的内容
默认:将整个表达式进行注入,及 driver变量的值是 ${jdbc.driver}
固定代码:必须配置PropertySourcesPlaceholderConfigurer实例。
项目结构如下:
jdbc.properties配置文件
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://127.0.0.1:3306/test1
jdbc.username=root
jdbc.password=1234
配置类 添加内容
@Configuration
@PropertySource("classpath:db.properties")
public class SpringConfig02 {
// 在4.2.4版本读取properties必须写,必须要写的固定格式
@Bean
public static PropertySourcesPlaceholderConfigurer create(){
return new PropertySourcesPlaceholderConfigurer();
}
}
测试类:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfig02.class)
public class TestB {
@Value("${jdbc.driver}")
private String driverClassName;
@Value("${jdbc.url}")
private String url;
@Test
public void test01(){
System.out.println("driverClassName="+driverClassName);
System.out.println("url="+url);
}
}
测试结果:正常获取数据
3.@Bean(工厂Bean)
把别人创建的类,交给IOC管理可以使用方法配合注解@Bean的方式实现.
通过@Component等注解,将我们自己编写的对象配置到spring容器中。
通过@Resource等注解,将我们自己编写的对象之间的关系配置到spring容器中。
实际开发中,我们经常会遇到某些类不是我们写的,此时我们希望通过IOC对这种类进行管理,我们就没法办在这个类上加@Component等注解了. 这个时候可以创建一个方法在方法上使用@Bean来实现对这些类对象的管理
3.1.基本使用:类型注入
@Bean用于修饰方法,将方法创建的对象添加到spring容器
需求: 假设UserDao不是我们写的类,无法使用@Component注解,
创建一个方法用于获取UserDao对象
准备如下几个类
UserDao:
package com.czxy.demo03;
//假设这个类我们无法修改,所以不能在这个类上面添加@Component注解
public class UserDao {
public void findAll(){
System.out.println("查询所有 ");
}
}
SpringConfig03:
@Configuration
public class SpringConfig03 {
//提供一个方法 可以返回UserDao对象,在该方法的上面加上@Bean,
//就可以把该方法返回的对象交给IOC容器了.
@Bean
public UserDao getUserDao(){
return new UserDao();
}
}
测试类:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfig03.class)
public class TestC {
@Resource
private UserDao userDao;
@Test
public void test01(){
userDao.findAll();
}
}
测试结果:
3.2.基本使用:指定名称注入
上面例子中,在方法上只是书写了一个@Bean.并没有给期产生的对象命名如果想命名可以通过如下方式.
@Bean(name=”名字”) 可以为当前对象设置一个名称,如果没有使用name设置名称,默认名为“方法名”。
需求:
UserDao是个接口,有俩实现类UserDaoImplA和UserDaoImplB, 这三个类假设我们都不能修改.
现在想获取这俩实现类的对象,交给IOC管理. 设计完成该例子.
项目结构如下:
UserDao接口:
public interface UserDao {
public void findAll();
}
UserDaoImplA实现类:
public class UserDaoImplA implements UserDao {
@Override
public void findAll() {
System.out.println("A 方式实现查询所有用户 ");
}
}
UserDaoImplB实现类:
public class UserDaoImplB implements UserDao {
@Override
public void findAll() {
System.out.println("B 方式实现查询所有用户 ");
}
}
配置类:
@Configuration
public class SpringConfig04 {
//返回UserDaoImplA实现类的对象.
//该对象存放到IOC容器中的标记userDaoImplA
@Bean(name ="userDaoImplA" )
public UserDao getUserDaoA(){
return new UserDaoImplA();
}
//返回UserDaoImplB实现类的对象,
//该对象存放到IOC容器中的标记userDaoImplB
@Bean(name ="userDaoImplB" )
public UserDao getUserDaoB(){
return new UserDaoImplB();
}
}
测试类:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfig04.class)
public class TestD {
//此处使用userDaoImplA或者userDaoImplB
//就可以获取相应的实现类对象了
@Resource(name = "userDaoImplB")
private UserDao userDao;
@Test
public void test01(){
userDao.findAll();
}
}
测试结果:
userDaoImplB对应:
userDaoImplA对应:
3.3.依赖注入:引用类型
当某一个方法的参数是一个被IOC管理的对象时,可以通过@Bean的方式,自动注入该对象.
如UserDao对象被IOC管理了. 那么 若有方法 形如testXXX(UserDao userDao) 则可以在方法上添加@Bean,来自动注入UserDao对象.
示例1:
把UserDao交给IOC
在配置类中书写一个方法show(UserDao userDao),完成自动注入,在测试类中进行测试.
UserDao:
//UserDao交给IOC
@Repository
public class UserDao {
public void findAll(){
System.out.println("查询所有");
}
}
配置类:SpringConfig06
@Configuration
@ComponentScan(basePackages = "com.czxy.demo06")
public class SpringConfig06 {
//通过@Bean 把对象自动注入给userDao
//show方法的返回值类型不能是void. 所以此时使用返回String确保语法正确.
@Bean
public String show(UserDao userDao){
System.out.println("完成自动注入:"+userDao);
//测试调用userDao方法
userDao.findAll();
return null;
}
}
测试类:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfig06.class)
public class TestA1 {
@Test
public void test02(){
//无需书写任何代码, 单纯执行
//该方法就会看到自动注入UserDao
}
}
测试结果: 可以看到 完成了自动注入.
示例2:
把UserDao对象存放到spring容器中, 然后再UserService方法中要使用UserDao,此时可以直接把UserDao当做参数传递到方法中
配置类:
@Configuration
@ComponentScan(basePackages = {"com.czxy.demo02"})
public class SpringConfiguration {
//此时把UserDao对象添加到Spring容器中
@Bean
public UserDao getUserDao(){
return new UserDaoImpl();
}
//该方法中需要使用UserDao对象,此时该对象以方法参数的形式直接传递进来即可. UserDao会自动从Spring容器中获取对象.
@Bean
public UserService getUserService(UserDao userDao){
System.out.println(userDao);
return new UserServiceImpl();
}
}
测试类;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfiguration.class)
public class TestA {
@Resource
public UserService userService;
@Test
public void test01(){
System.out.println(userService);
}
}
测试结果:
3.4.依赖注入:简单类型
需求: 把properties文件中的字符串 以简单类型的方式添加到参数中 打印对应的值。
相应代码:
@PropertySource("classpath:db.properties")
public class SpringConfiguration2 {
@Bean
public static PropertySourcesPlaceholderConfigurer create(){
return new PropertySourcesPlaceholderConfigurer();
}
//通过@Value来注入简单类型.
//该方法有@Bean所以最终会吧返回的UserServiceImpl对象放在IOC容器中
@Bean
public UserService createUserService(@Value("${jdbc.username}") String name, @Value("${jdbc.password}") String pwd){
System.out.println("name = "+name+" pwd ="+pwd);
return new UserServiceImpl();
}
}
测试类
代码:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfiguration2.class)
public class TestB {
@Resource
private UserService userService;
@Test
public void test01(){
System.out.println(userService);
}
}
测试结果:
4.Bean的作用域
通过@Scope可以Bean的作用域,也就是通知spring是否每次都创建新对象。
注解 描述 取值
@Scope 用于设置Bean的作用域; singleton:默认值,单例的. prototype:多例的.
单例模式: 整个IOC容器中只有该实体类的一个对象
多例模式: 整个IOC容器中该实体类有多个对象
示例:
搞一个User类,在配置类中设置单例模式和多例模式,创建两个对象观察效果.
配置类:
测试类:
单例模式结果: 地址编号相同,说明是用的同一个对象
保持测试类不变,只更改为多例模式
测试结果:打印的两个地址编号不同,说明IOC容器中有多个对象
其他取值(了解)
1.request :WEB项目中,Spring创建一个Bean的对象,将对象存入到request域中.
2.session :WEB项目中,Spring创建一个Bean的对象,将对象存入到session域中.
3.globalSession :WEB项目中,应用在Portlet环境.如果没有Portlet环境那么globalSession相当于session
5.生命周期
生命周期指单实例对象由创建到销毁的整个过程。
此处,主要研究初始化方法和销毁方法。
5.1.实例Bean
实例Bean同时2个注解,来控制类中那个方法初始化方法,那个方法是销毁方法。
注解 | 描述 |
---|---|
@PostConstruct | 初始化方法,项目启动时执行,只会被调用一次。 |
@PreDestroy | 销毁方法,项目关闭时执行,只会被调用一次。 |
实例Dog;
@Component
public class Dog {
@PostConstruct
public void init(){
System.out.println("狗 初始化");
}
public void eat(){
System.out.println("狗 吃吃吃..");
}
@PreDestroy
public void destory(){
System.out.println("狗 销毁 ");
}
}
配置类
@ComponentScan(basePackages={"com.czxy.domain"})
public class SpringConfigruation {
}
测试类
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfiguration.class)
public class TestC {
@Resource
private Dog dog;
@Test
public void test01(){
dog.eat();
}
}
测试结果 :
5.2.工厂Bean
工厂Bean通过@Bean的2个属性完 成初始化和销毁方法的配置。
initMethod:配置初始化方法
destroyMethod:配置销毁方法
实体类:
public class Cat {
public void init(){
System.out.println("猫 初始化");
}
public void eat(){
System.out.println("猫 吃吃吃..");
}
public void destory(){
System.out.println("猫 销毁 ");
}
}
配置类;
@Configuration
@ComponentScan(basePackages = {"com.czxy.demo02"})
public class SpringConfiguration {
@Bean(initMethod = "init" ,destroyMethod = "destory")
public Cat getCat(){
return new Cat();
}
}
测试类:
@Resource
private Cat cat;
@Test
public void test02(){
cat.eat();
}
测试结果: