Spring(1)_IOC/DI依赖注入/Bean的实例化方式(4种)以及Bean的作用域/生命周期 /bean属性注入

1.什么是Spring

spring是分层的,一站式的轻量级开源架构

JavaEE分层

  • 表现层:(页面数据显示、页面跳转调度),例如jsp/servlet
  • 业务层:(业务处理和功能逻辑、事务控制),例如service
  • 持久层:(数据存取和封装、和数据库打交道),例如dao
    Spring(1)_IOC/DI依赖注入/Bean的实例化方式(4种)以及Bean的作用域/生命周期 /bean属性注入_第1张图片

一站式
Spring提供了JavaEE各层的解决方案:
表现层:struts1、struts2、Spring MVC
业务层:Ioc、AOP、事务控制
持久层:JdbcTemplate、HibernateTemplate、ORM框架(对象关系映射)整合

轻量级
Spring的出现取代了EJB的臃肿、低效、繁琐复杂、脱离现实的情况. 而且使用spring编程是非侵入式的。


Spring的核心

  • IoC(Inverse of Control 控制反转):将对象创建权利交给Spring工厂进行管理。 比如说 Book book = new Book();现在: Book book = Spring工厂.getBook();
  • AOP(Aspect Oriented Programming 面向切面编程),基于动态代理的功能增强方式。

2.Spring IoC快速入门

2.1 Spring的开发包

1Spring项目的核心容器的最基本Jar包(4个):
Spring(1)_IOC/DI依赖注入/Bean的实例化方式(4种)以及Bean的作用域/生命周期 /bean属性注入_第2张图片
2.Spring框架所需的日志包(2个,依赖jar库中找):
默认采用apache commons-logging(JCL)日志框架+log4j的日志实现,还需要添加log4j的配置文件。
Spring(1)_IOC/DI依赖注入/Bean的实例化方式(4种)以及Bean的作用域/生命周期 /bean属性注入_第3张图片
Spring(1)_IOC/DI依赖注入/Bean的实例化方式(4种)以及Bean的作用域/生命周期 /bean属性注入_第4张图片
3.添加log4j.properties文件放置到src下。

### direct log messages to stdout ###
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target=System.err
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n

### direct messages to file mylog.log ###
log4j.appender.file=org.apache.log4j.FileAppender
log4j.appender.file.File=c\:mylog.log
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n

### set log levels - for more verbose logging change 'info' to 'debug' ###

log4j.rootLogger=info, stdout

2.2 传统方式业务代码编写的缺陷

public class UserServiceImpl implements IUserService{
	public void login() {
		System.out.println("UserServiceImpl-service层被调用了。。。");
		//实例化dao层
		//传统方式
		IUserDao userDao = new UserDaoImpl();
		userDao.findByUsernameAndPassword();
	}
}

public class UserDaoImpl implements IUserDao {
	@Override
	public void findByUsernameAndPassword() {
		System.out.println("UserDaoImpl-dao层被调用了");
	}
}

存在问题:代码过于耦合,上层代码过度依赖于下一层代码的实现:
UserDao userDao = new UserDaoImpl();
如果要更换实现类,或者实现类换一个名字,此时代码会报错,必须要修改原来的业务代码!
Spring(1)_IOC/DI依赖注入/Bean的实例化方式(4种)以及Bean的作用域/生命周期 /bean属性注入_第5张图片
怎么解决类与类之间如此密切的耦合问题呢?


2.3 IoC控制反转的实现

采用IoC(Inverse of Control,控制反转)的思想解决代码耦合问题。
简单的说就是引入工厂(第三者),将原来在程序中手动创建管理的依赖的UserDaoImpl对象,交给工厂来创建管理。

public class UserServiceImpl implements IUserService{
	public void login() {
		 System.out.println("UserServiceImpl-service层方法调用了");
		 //ioc方式:
		 //创建工厂,利用工厂提供依赖的对象
		 UserDAOFactory userDAOFactory = new UserDAOFactory();
		 UserDAOImpl userDAO = userDAOFactory.getUserDAO();
		 userDAO.findUserByUsernameAndPassword();
	}
}

public class UserDAOFactory {
	//提供获取对象的方法
	public UserDAOImpl getUserDAO(){
		//返回实例对象
		return new  UserDAOImpl ();
	}
}

发现问题:工厂方法仍然需要返回具体类型的实例对象,存在代码耦合
解决方案:使用反射技术传入具体类型的类字符串生产对象的实例:

public Object getBean(){
	Object bean = null;
	try {
		//传入类字符串,生产对象实例
		bean = Class.forName("cn.itcast.spring.a_quickstart.UserDAOImpl").newInstance();
	} catch (Exception e) {
		e.printStackTrace();
	}
	//返回具体类型的对象类型实例
	return bean;
}

//使用反射方法获取对象
 IUserDAO userDAO = (IUserDAO) userDAOFactory.getBean();
 userDAO.findUserByUsernameAndPassword();

发现问题:类字符串是固定的,怎么动态的传入不同的类字符串呢?
解决方案: 使用xml配置文件动态传入类字符串
IoC底层实现:工厂(设计模式)+反射(机制) + 配置文件(xml)。


2.3.1 Spring核心配置文件的编写

IoC控制反转的理解和实现
1.在src下建立applicationContext.xml (位置:applicationContext.xml文件放置到任何目录都可以,习惯上放在src目录或者 WEB-INF目录)
Spring(1)_IOC/DI依赖注入/Bean的实例化方式(4种)以及Bean的作用域/生命周期 /bean属性注入_第6张图片
2.参考规范文档配置xml的头信息:bean schema





3.配置本地提示:(联网的情况下会Myeclipse会自动下载关联相关约束文件)
Spring(1)_IOC/DI依赖注入/Bean的实例化方式(4种)以及Bean的作用域/生命周期 /bean属性注入_第7张图片
4.配置applicationContext.xml:









2.3.2 通过Spring的工厂获取Bean完成相关操作

在程序中创建spring工厂对象, 通过工厂对象加载spring的xml配置文件,生产配置文件中配置 的bean对应的对象

//加载Spring配置文件,创建Spring工厂对象
ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
//从spring工厂中,通过bean的id/name获取对象
IUserDao userDao = (IUserDao)ac.getBean("userDao");
userDao.findByUsernameAndPassword();

因为applicationContext.xml在src目录下所以可以直接用过文件夹获取

发现问题:该方式虽然解决了类与类之间的耦合关系,但却需要在获取对象的时候创建spring工厂,有没有更方便获取对象的依赖的方法呢?


2.4 DI依赖注入的实现

DI:Dependency Injection 依赖注入,在Spring框架负责创建Bean对象时,动态的将依赖对象注入到Bean组件(简单的说,可以将另外一个bean对象动态的注入到另外一个bean中。)
Spring(1)_IOC/DI依赖注入/Bean的实例化方式(4种)以及Bean的作用域/生命周期 /bean属性注入_第8张图片
Di的做法是:由Spring容器创建了Service、Dao对象,并且在配置中将Dao传入Servcie,那么Service对象就包含了Dao对象的引用。

1.将service对象也交给spring容器管理


	

	
	
	
	
	

2.在程序中定义属性提供setter方法:

public class UserServiceImpl implements IUserService {
	private IUserDao userDao;
	public void setUserDao(IUserDao userDao) {
		this.userDao = userDao;
	}

	public void login() {
		System.out.println("UserServiceImpl-service层方法调用了");
		//ioc:依赖注入
		userDao.findByUsernameAndPassword();
	}
}

3.测试运行,此时获取对象必须从spring工厂获取(在spring容器配置中才有依赖注入,自己创建的对象没有注入依赖关系)

public class SpringTest {
	//测试
	@Test
	public void test(){
		//创建spring工厂,获取spring管理的对象
		ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
		IUserService userService = (IUserService) ac.getBean("userService");
		userService.login();
	}
}

注意:userService 对象必须从Spring工厂中获取,才有依赖注入

总结:

  • IOC:控制反转,将对象创建管理的权利交给spring容器,获取对象通过spring工厂创建
    工厂(设计模式)+反射(机制) + 配置文件(xml)。
  • DI:在spring容器中创建管理多个对象,通过 property标签将对象注入到需要依赖的对象中

2.5 Spring的工厂(了解)

ApplicationContext直译为应用上下文,是用来加载Spring框架配置文件,来构建Spring的工厂对象,它也称之为Spring容器的上下文对象,也称之为Spring的容器。
ApplicationContext 只是BeanFactory(Bean工厂,Bean就是一个java对象) 一个子接口:
Spring(1)_IOC/DI依赖注入/Bean的实例化方式(4种)以及Bean的作用域/生命周期 /bean属性注入_第9张图片
为什么不直接使用顶层接口对象来操作呢?

  • BeanFactory 采取延迟加载,第一次getBean时才会初始化Bean
  • Beanfactory的用法:
    BeanFactory ac = new XmlBeanFactory(new FileSystemResource(“D:\applicationContext.xml”));
  • ApplicationContext是对BeanFactory扩展,提供了更多功能
    国际化处理
    事件传递
    Bean自动装配
    各种不同应用层的Context实现

ApplicationContext 更加强大, 所以现在开发基本没人使用BeanFactory。

【扩展】Bean获取的两种方式:

  • 通过spring容器的bean的id/name来获取
  • 根据bean的类型或者bean接口的类型获取,一般使用接口类型
public void test(){
	ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
	
	//1.通过spring容器的bean的id/name来获取
	IUserService userService = (IUserService) ac.getBean("userService");
	
	//2.根据bean的类型或者bean接口的类型获取,一般使用接口类型
	IUserService userService2 = (IUserService) ac.getBean(IUserService.class);
	
	userService.login();
	userService2.login();
}

常用根据名称获取(id/name),即第一种方式,使用spring容器中的标识获取对象
如果根据类型获取,配置了多个类型的话,则抛出异常,如果是通过id/name获取,则不会抛异常


		
	
	

	


3.IoC容器装配Bean_基于XML配置方式

3.1 实例化Bean的四种方式

1.无参数构造器(最常用)

//1.默认构造器(spring在创建bean的时候自动调用无参构造器来实例化,相当于new Bean1())
public class Bean1 {

}

//2.在spring容器applicationContext.xml中配置


//3.创建测试类获取bean对象
public  void test(){
	//创建spring工厂
	ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
	//1.默认构造器获取bean对象
	Bean1 bean1 = (Bean1) ac.getBean("bean1");
	System.out.println(bean1);
}

如果Bean1类,没有无参构造,就会报错:
No default constructor found; nested exception is java.lang.NoSuchMethodException:

2.静态工厂方法

//1.静态工厂方法构造,用来初始化Bean1的时候,可以初始化其他东西
public class Bean1 {
	
}

//2.静态工厂
public class Bean1Factory {
	//静态方法,用来返回对象的实例
	public static Bean1 getBean1(){
		//在做实例化的时候,可以做其他的事情,即可以在这里写初始化其他对象的代码
		//Connection conn....
		return new Bean1();
	}
}



Spring(1)_IOC/DI依赖注入/Bean的实例化方式(4种)以及Bean的作用域/生命周期 /bean属性注入_第10张图片

在执行new ClassPathXmlApplicationContext(“applicationContext.xml”)时,就将所有的对象都生成了,无参构造就调用无参构造函数生成,如果是静态工厂方式就直接通过反射执行工厂类的静态方法,这个过程中是不需要创建工厂类的,所以Spring是单例的,之后获取的bean1都是同一个

3.实例工厂方法

//1.实例工厂方式创建
public class Bean1 {
	
}

//2.实例工厂,必须new工厂,再获取bean
public class Bean1Factory {
	//普通方法,非静态方法
	public Bean1 getBean1(){
		//初始化实例对象返回
		return new Bean1();
	}
}




 

Spring(1)_IOC/DI依赖注入/Bean的实例化方式(4种)以及Bean的作用域/生命周期 /bean属性注入_第11张图片

4.FactoryBean方式
之前讲过BeanFactory(工厂类的顶层接口),而这里是FactoryBean,是两个完全不同的概念

public class Bean1 {
	
}

//实现FactoryBean的方式  
//泛型:你要返回什么类型的对象,泛型就是什么
public class Bean1Factory implements FactoryBean {
	public Bean1Factory() {
		System.out.println("sss");
	}
	
	//用来获取bean的实例对象
	public Bean1 getObject() throws Exception {
		//写一写初始化数据库连接等代码
		System.out.println("ggg");
		return new Bean1();
	}
	public Class getObjectType() {
		return null;
	}
	public boolean isSingleton() {
		return false;
	}
}




总结:

  • 无参数构造器:Spring工厂通过反射调用class对象的newInstance方法来创建实例对象,最常用
  • 静态工厂方式:Spring工厂通过反射执行工厂类的静态方法,获取实例对象(这个过程中不用创建静态工厂类)
  • 实例工厂方式:Spring工厂通过反射创建实例工厂对象,之后引用这个工厂类获取实例的方法来创建实例对象
  • FactoryBean方式:工厂类需要实现Bean1Factory接口,Spring在构建工厂类时执行getObject方法获取实例

4.Bean的作用域/生命周期/Bean属性依赖注入

4.1 Bean的作用域

由spring创建的bean对象在什么情况下有效。
Spring(1)_IOC/DI依赖注入/Bean的实例化方式(4种)以及Bean的作用域/生命周期 /bean属性注入_第12张图片
项目中常用:singleton 单例 prototype 多例
Singleton: 在一个spring容器中,对象只有一个实例。(默认值)
Prototype: 在一个spring容器中,存在多个实例,每次getBean 返回一个新的实例。

Spring默认是Singleton(单例),在读取applicationContext.xml,就会通过工厂创建所有的bean对象,之后之后直接通过id/name从工厂中获取

测试:

//单例bean
public class SingletonBean {
	public SingletonBean() {
		System.out.println("SingletonBean:初始化了单例");
	}
}

//多实例bean
public class PrototypeBean {
	public PrototypeBean() {
		System.out.println("--PrototypeBean初始化了多例的");
	}
}

//定义spring容器


创建测试类

public void test(){
	ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
	System.out.println("---读取配置文件结束---");
	
	SingletonBean singletonBean1=(SingletonBean)ac.getBean("singletonBean");
	SingletonBean singletonBean2=(SingletonBean)ac.getBean("singletonBean");
	System.out.println("s1="+singletonBean1);
	System.out.println("s2="+singletonBean2);
	
	PrototypeBean prototypeBean1=(PrototypeBean)ac.getBean("prototypeBean");
	PrototypeBean prototypeBean2=(PrototypeBean)ac.getBean("prototypeBean");
	System.out.println("p1="+prototypeBean1);
	System.out.println("p2="+prototypeBean2);
}

结果如下
Spring(1)_IOC/DI依赖注入/Bean的实例化方式(4种)以及Bean的作用域/生命周期 /bean属性注入_第13张图片
1.当bean的scope是默认时,表示单例,此时当我们在读取配置文件,生成Spring工厂时就会创建这个bean,之后通过getBean获取到的都是同一个对象
2.prototype时,在我们调用工厂类对象.getBean方法时,才去构建对象

4.2 Bean的生命周期

通过spring工厂,可以控制bean的生命周期。

1.在xml配置Bean的初始化和销毁方法
通过 init-method属性 指定实例化后 的调用方法
通过 destroy-method属性 指定销毁对象前的方法

public class SingletonBean {
	public SingletonBean() {
		System.out.println("SingletonBean的无参构造被调用了");
	}
	//初始化后自动调用
	public void init(){
		System.out.println("SingletonBean-init初始化时调用");
	}
	//销毁时调用
	public void destroy(){
		System.out.println("SingletonBean-destroy销毁时调用");
	}
}



Spring(1)_IOC/DI依赖注入/Bean的实例化方式(4种)以及Bean的作用域/生命周期 /bean属性注入_第14张图片
我们发现并没有执行销毁方法,这是因为当方法运行结束,jvm直接关了,Spring容器还来不及销毁对象
解决方式:手动销毁spring容器,自动销毁单例的对象
Spring(1)_IOC/DI依赖注入/Bean的实例化方式(4种)以及Bean的作用域/生命周期 /bean属性注入_第15张图片
提示:销毁方法的执行必须满足两个条件:
1)单例(singleton)的bean才会可以手动销毁。
2)必须手动关闭容器(调用close的方法)时,才会执行手动销毁的方法。


2.后处理Bean(BeanPostProcessor接口)了解
后处理Bean也称之为Bean的后处理器,作用是:在Bean初始化的前后,对Bean对象进行增强。它既可以增强一个指定的Bean,也可以增强所有的Bean,底层很多功能(如AOP等)的实现都是基于它的,Spring可以在容器中直接识别调用。

示例:
1.要对“所有”的bean的初始化的时候进行增强(打印一句话)

//后处理bean:用来对bean进行功能增强,可以实现,对所有或者某个bean的初始化进行增强
public class MyBeanPostProcessor implements BeanPostProcessor{

	//初始化之后调用
	//参数1:Spring管理的对象    参数2:对象的名称
	@Override
	public Object postProcessAfterInitialization(Object bean, String beanName)
			throws BeansException {
		 System.out.println(beanName + "初始化前被增强了");
		 return bean;
	}
	
	//初始化时调用的
	@Override
	public Object postProcessBeforeInitialization(Object bean, String beanName)
			throws BeansException {
		System.out.println(beanName + "初始化后被增强了");
		 return bean;
	}
}


 

此时我们去加载Spring配置文件(会初始化单例对象)
Spring(1)_IOC/DI依赖注入/Bean的实例化方式(4种)以及Bean的作用域/生命周期 /bean属性注入_第16张图片
而多例对象在通过getBean时,初始化,此时被加强

4.3 Bean属性依赖注入

1.属性依赖注入的三种方式
什么是bean属性注入?就是对一个bean的属性赋值,有三种方式:

  • 构造器参数注入:
  • setter方法属性注入
  • 接口注入

Spring 框架规范中通过配置文件配置的方式,只支持构造器参数注入和setter方法属性注入,不支持接口注入 !所以我们这里只讲前面两种

2.构造器参数注入 constructor-arg
不再使用无参构造器实例化对象,而是调用有参构造器,通过注入属性来实例化对象

//目标,构造器参数注入,new car直接将参数的值直接赋值
public class Car {
	private Integer id;
	private String name;
	private Double price;
	
	//有参构造
	public Car(Integer id, String name, Double price) {
		this.id = id;
		this.name = name;
		this.price = price;
	}
	
	@Override
	public String toString() {
		return "Car [id=" + id + ", name=" + name + ", price=" + price + "]";
	}
}

配置applicationContext.xml



	
	
	
	

测试
Spring(1)_IOC/DI依赖注入/Bean的实例化方式(4种)以及Bean的作用域/生命周期 /bean属性注入_第17张图片
1.定位属性的标签,可以混用


2.自标签的属性赋值问题,可以使用子标签的value,效果和value属性一样



3.setter方法属性注入 property
使用的默认的构造器(new Bean()),但必须提供属性的setter方法,使用setter方法也是企业经常使用的属性注入方式。
两步:在类中加入setter方法,在配置文件中使用property

//setter方法属性注入
public class Person {
	private Integer id;
	private String name;
	private Car car;
	//必须提供setter属性方法
	public void setId(Integer id) {
		this.id = id;
	}
	public void setName(String name) {
		this.name = name;
	}
	public void setCar(Car car) {
		this.car = car;
	}
	
	@Override
	public String toString() {
		return "Person [id=" + id + ", name=" + name + ", car=" + car + "]";
	}
}


	
	
	
	


4.p名称空间的使用-了解
什么是名称空间?
作用:Schema区分同名元素。(有点类似于java的包)
在这里插入图片描述
为简化XML文件的配置,Spring2.5版本开始引入了一个新的p名称空间。简单的说,它的作用是为了简化setter方法属性依赖注入配置的,它不是真正的名称空间。
它的使用方法:

p:<属性名>="xxx" 引入常量值
p:<属性名>_ref="xxx" 引用其它Bean对象

步骤:
1.引入p名称空间


2.将 子元素 简化为 元素的属性注入




配置时不需要 子元素,简化了配置 .

5.spEL表达式的使用 –会用即可
spEL(Spring Expression Language)是一种表达式语言,它是spring3.x版本的新特性。
它的作用是:支持在运行时操作和查询对象,其语法类似统一的EL语言,但是SpEL提供了额外的功能,功能更强大。
什么是EL、OGNL、spEL?

EL:操作servlet相关的一些对象和相关的值
OGNL:主要操作struts2值栈
spEL:操作bean相关的

语法: #{…} , 引用另一个Bean 、属性、 方法 , 运算
SpEL表达式的使用功能比较多,Bean操作相关的通常有:

  • #{beanid}引用Bean(具体对象)
  • #{beanid.属性}引用Bean的属性
  • #{beanid.方法(参数)}调用bean的方法


你可能感兴趣的:(Spring)