好久没写博文了,最近玩的太浪,打算把spring再复习一遍,以于温故知新。
先敬上一张spring的图
Spring架构图
Spring是一个非常活跃的开源框架,他是一个基于Core来构架多层javaEE系统的框架,它的主要目的就是为了简化企业的开发。
Spring是一种非入侵的方式来管理你的代码,Spring提倡”最少侵入”。Spring 的每一个模块都是独立的。
Spring-IOC
什么是IOC,IOC全称(Inversion of Control)即控制翻转。IOC的作用就是把对象的创建交给Spring来做。对比以前创建对象通过New来做,IOC有什么好处?
程序员不再关心对象的创建,直接拿来使用。
Spring创建对象
1)Spring所需jar包
spring.jar
commons-logging.jar
2)配置文件
applicationContext.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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">
</beans>
这里对Spring配置文件中的一些属性做一定的解释
beans:把一个类放入到Spring容器中,该类就是一个bean。
这里新建一个类:
public class Spring01 {
public void testSpring() {
System.out.println("hello word");
}
}
把这个bean(类)放入Spring容器中,怎么放?
id:唯一标识符
class:类的全路径
<bean id="test" class="com.liuzehui.spring.Spring01"></bean>
测试类:
测试对象,要想测试这个bean是否创建对象,则需要从spring容器中拿,那么问题来了,怎么启动spring容器?怎么拿这个对象?
public class TestSpring {
/***
* 1.启动spring容器
* 2.从spring容器中把对象取出来
* 3.对象调用方法
*/
@Test
public void testSpring() {
// 启动/加载Spring容器
ApplicationContext context = new ClassPathXmlApplicationContext(
"applicationContext.xml");
// 通过id从Spring容器中拿出对象
Spring01 spring = (Spring01) context.getBean("test");
// 对象调用方法
spring.testSpring();
}
}
输出结果:hello word
至此,Spring如何创建对象、如何启动Spring容器、怎样从Spring容器中获取bean、对象调用方法,就大功告成了。
Spring容器执行流程:
注意:Spring容器是调用默认的构造方法来进行对象的创建。
我们都知道一个类如果不显示的声明构造方法,系统则会自动生成一个无参构造。如果你不幸覆盖了这个无参的构造方法,即在无参构造方法中写入参数,又无显示声明一个无参构造方法。那么Spring则无法给你创建对象。
举个香甜的栗子:
还是这个Spring01类
public class Spring01 {
public Spring01(){
System.out.println("instance..");
}
public void testSpring() {
System.out.println("hello word");
}
}
我们重写了无参构造方法,并在构造方法执行的时候输出一句话。
再看测试类
public class TestSpring {
/***
* 1.启动spring容器
* 2.从spring容器中把对象取出来
* 3.对象调用方法
*/
@Test
public void testSpring() {
// 启动/加载Spring容器
ApplicationContext context = new ClassPathXmlApplicationContext(
"applicationContext.xml");
// 通过id从Spring容器中拿出对象
Spring01 spring = (Spring01) context.getBean("test");
// 对象调用方法
spring.testSpring();
System.out.println("after...");
}
}
没什么大变化,只在最后一句加了个输出语句。
Spring配置文件无变化,现在我们运行此测试方法,得到如下输出:
instance..
hello word
after...
可以很好的验证Spring容器是调用无参构造方法来创建对象,现在我们把这个无参的构造方法改写成有参的,如下。
public class Spring01 {
//有参数的构造方法
public Spring01(String name){
System.out.println("instance..");
}
public void testSpring() {
System.out.println("hello word");
}
}
传入一个参数,现在再运行测试类,则控制台会报错。
org.springframework.beans.factory.BeanCreationException: Error creating bean with name ‘test‘ defined in class path resource [applicationContext.xml]: Instantiation of bean failed; nested exception is org.springframework.beans.BeanInstantiationException: Could not instantiate bean class [com.liuzehui.spring.Spring01]: No default constructor found; nested exception is java.lang.NoSuchMethodException: com.liuzehui.spring.Spring01.<init>()
...
No default constructor found;
可知,没有默认的构造方法。则Spring容器无法创建对象
Spring容器创建对象的时机
什么是时机,就是Spring在什么时候创建对象的,在加载Spring容器时创建呢?还是在getBean时创建呢?
由刚才的经验就很可以测试第一个问题,在getBean之前输出一句话,看Spring01 类的构造方法是否输出instance,如果输出,则说明对象在Spring容器启动的时候就被创建了。
package com.liuzehui.test;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.liuzehui.spring.Spring01;
public class TestSpring {
/***
* 1.启动spring容器
* 2.从spring容器中把对象取出来
* 3.对象调用方法
*/
@Test
public void testSpring() {
// 启动/加载Spring容器
ApplicationContext context = new ClassPathXmlApplicationContext(
"applicationContext.xml");
System.out.println("not get object from Spring...");
// 通过id从Spring容器中拿出对象
Spring01 spring = (Spring01) context.getBean("test");
// 对象调用方法
spring.testSpring();
System.out.println("after...");
}
}
测试后输出:
instance..
not get object from Spring...
hello word
after...
根据输出的顺序,可以知道,先加载Spring容器,Spring容器通过反射加载class,然后调用此类的无参构造方法创建对象,因此会输出instance…
你肯定会想了,这样我Spring容器中有一万个对象,那么我不管用不用这些对象,只要在我加载Spring容器的时就创建他们? 那岂不是很占用内存空间?我能不能在使用某个对象的时候再创建他呢?答案是肯定的
lazy-init:他有三个值可供选择,default/fasle/true
因为我们知道默认的时候是default也就是在加载Spring容器的时候就创建对象,所以default表示在加载Spring容器的时候创建对象,而且false的效果和default等同,那么如果我设置lazy-init=true会有什么效果呢?
<bean id="test" class="com.liuzehui.spring.Spring01" lazy-init="true"></bean>
控制台输出以下结果:
not get object from Spring...
instance..
hello word
after...
由上可见,在加载Spring容器的时候并未创建对象,而是在要使用他的时候才创建的对象。
这样有个好处就是,我在用你的时候再创建对象,如果不用则不创建。
那么这两种方法怎么选择呢?答案是选择第一种,因为如果在加载容器的时候创建对象有问题能及早发现(配置bean写错等问题),第二种则不会及早发现错误。
Spring的bean中的scope
Spring的单例和多例,Spring默认用单例来管理对象的,为什么这么说呢,单例就是一个对象,从Spring容器中获取两次对象,比较一下就知道了
package com.liuzehui.test;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.liuzehui.spring.Spring01;
public class TestSpring {
/***
* 1.启动spring容器
* 2.从spring容器中把对象取出来
* 3.对象调用方法
*/
@Test
public void testSpring() {
// 启动/加载Spring容器
ApplicationContext context = new ClassPathXmlApplicationContext(
"applicationContext.xml");
// 通过id从Spring容器中拿出对象
Spring01 spring1 = (Spring01) context.getBean("test");
Spring01 spring2 = (Spring01) context.getBean("test");
System.out.println(spring1);
System.out.println(spring2);
}
}
控制台输出:
com.liuzehui.spring.Spring01@900bac2
com.liuzehui.spring.Spring01@900bac2
也可以比较这两个对象的hashCode值是否相等。
如果要想把单例变为多例则需要使用scope属性的prototype属性
scope:默认是singleton,表示单例。
scope=”prototype”:表示多例。可以把一个bean由单例改为多例。
scope=”request”,在WEB应用程序中,每一个实例的作用域都为request范围;
scope=”session”,在WEB应用程序中,每一个实例的作用域都为session范围;
再进行 测试:
<bean id="test" class="com.liuzehui.spring.Spring01" scope="prototype"></bean>
测试代码不变, 运行后控制台输出:
com.liuzehui.spring.Spring01@421fbfd6
com.liuzehui.spring.Spring01@326b9c84
可知,通过scope把单例改为多例。
注意:当scope=”prototype”时,在getBean时,创建对象。这样就在SSH整合的时候很完美了,因为把Strust2的action放入Spring容器中,总不能在启动Spring容器就创建对象吧,应该在请求action的时候创建对象,所以,在使用的时候才创建对象(请求此action)。
单例多例需要搞明白两个问题:
1. 什么是单例多例;
2. 如何产生单例多例;
3. 为什么要用单例多例
4. 什么时候用单例,什么时候用多例;
1. 什么是单例多例:
所谓单例就是所有的请求都用一个对象来处理,比如我们常用的service和dao层的对象通常都是单例的,而多例则指每个请求用一个新的对象来处理,比如action;
2. 如何产生单例多例:
在通用的SSH中,单例在spring中是默认的,如果要产生多例,则在配置文件的bean中添加scope=”prototype”;
3. 为什么用单例多例:
之所以用单例,是因为没必要每个请求都新建一个对象,这样子既浪费CPU又浪费内存;
之所以用多例,是为了防止并发问题;即一个请求改变了对象的状态,此时对象又处理另一个请求,而之前请求对对象状态的改变导致了对象对另一个请求做了错误的处理;
用单例和多例的标准只有一个:
当对象含有可改变的状态时(更精确的说就是在实际应用中该状态会改变),则多例,否则单例;
4. 何时用单例?何时用多例?
对于struts2来说,action必须用多例,因为action本身含有请求参数的值,即可改变的状态;
你用杯子喝可乐,喝完了不刷,继续去倒果汁喝,就是单例。始终在用一个对象。
你用杯子喝可乐,直接扔了杯子,换个杯子去倒果汁喝,就是多例,用一个则创建一个对象。
Spring-DI
DI就是依赖注入(Dependency Injection,依赖注入)
什么是依赖注入?依赖注入说白了就是为属性赋值,和java一样,为属性赋值可以同过setter方法或者构造方法赋值,在实际开发中在配置文件中为属性进行依赖注入的情况比较少,这里先按下不表,等以后用注解来讲解Spring的DI。
案例:用SpringIOC和DI模拟MVC
StudentDao:
package com.liuzehui.spring.mvc;
public interface StudentDao {
// 保存学生
public void save();
}
StudentDaoImpl:
package com.liuzehui.spring.mvc;
public class StudentDaoImpl implements StudentDao {
@Override
public void save() {
System.out.println("save student...");
}
}
StudentService:
package com.liuzehui.spring.mvc;
public interface StudentService {
// 保存学生
public void save();
}
StudentServiceImpl:
package com.liuzehui.spring.mvc;
public class StudentServiceImpl implements StudentService {
// 注入studentDao
private StudentDao studentDao;
public StudentDao getStudentDao() {
return studentDao;
}
public void setStudentDao(StudentDao studentDao) {
this.studentDao = studentDao;
}
@Override
public void save() {
studentDao.save();
}
}
StudentAction:
package com.liuzehui.spring.mvc;
public class StudentAction {
// 注入StudentService
private StudentService studentService;
public StudentService getStudentService() {
return studentService;
}
public void setStudentService(StudentService studentService) {
this.studentService = studentService;
}
// 执行保存操作
public void saveStudent() {
studentService.save();
}
}
Spring配置文件:
<!-- studentDao -->
<bean id="studentDao" class="com.liuzehui.spring.mvc.StudentDaoImpl"></bean>
<!-- studentService -->
<bean id="studentService" class="com.liuzehui.spring.mvc.StudentServiceImpl">
<property name="studentDao">
<ref bean="studentDao"/>
</property>
</bean>
<!-- studentAction -->
<bean id="studentAction" class="com.liuzehui.spring.mvc.StudentAction">
<property name="studentService">
<ref bean="studentService"/>
</property>
</bean>
测试类:
package com.liuzehui.test;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.liuzehui.spring.mvc.StudentAction;
public class TestSpring {
/***
* 1.启动spring容器 2.从spring容器中把对象取出来 3.对象调用方法
*/
@Test
public void testSpring() {
// 启动/加载Spring容器
ApplicationContext context = new ClassPathXmlApplicationContext(
"applicationContext.xml");
// 通过id从Spring容器中拿出对象
StudentAction studentAction = (StudentAction) context
.getBean("studentAction");
studentAction.saveStudent();
}
}
控制台输出:
save student...
用注解 模拟MVC
StudentDaoImpl:
@Repository
public class StudentDaoImpl implements StudentDao {
@Override
public void save() {
System.out.println("save student...");
}
}
StudentServiceImpl:
package com.liuzehui.spring.mvc;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class StudentServiceImpl implements StudentService {
// 注入studentDao
@Autowired
private StudentDao studentDao;
@Override
public void save() {
studentDao.save();
}
}
StudentAction:
package com.liuzehui.spring.mvc;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Controller;
@Controller
@Scope("prototype")
public class StudentAction {
// 注入StudentService
@Autowired
private StudentService studentService;
// 执行保存操作
public void saveStudent() {
studentService.save();
}
}
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:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-2.5.xsd">
<context:component-scan base-package="com"></context:component-scan>
</beans>
运行测试代码输出:
save student...
补充
这是我以前复制下来的,原文地址已经找不到了。作者写得太生动了!
首先想说说IoC(Inversion of Control,控制倒转)。这是spring的核心,贯穿始终。所谓IoC,对于spring框架来说,就是由spring来负责控制对象的生命周期和对象间的关系。这是什么意思呢,举个简单的例子,我们是如何找女朋友的?常见的情况是,我们到处去看哪里有长得漂亮身材又好的mm,然后打听她们的兴趣爱好、qq号、电话号、ip号、iq号………,想办法认识她们,投其所好送其所要,然后嘿嘿嘿……这个过程是复杂深奥的,我们必须自己设计和面对每个环节。传统的程序开发也是如此,在一个对象中,如果要使用另外的对象,就必须得到它(自己new一个,或者从JNDI中查询一个),使用完之后还要将对象销毁(比如Connection等),对象始终会和其他的接口或类藕合起来。
那么IoC是如何做的呢?有点像通过婚介找女朋友,在我和女朋友之间引入了一个第三者:婚姻介绍所。婚介管理了很多男男女女的资料,我可以向婚介提出一个列表,告诉它我想找个什么样的女朋友,比如长得像李嘉欣,身材像林熙雷,唱歌像周杰伦,速度像卡洛斯,技术像齐达内之类的,然后婚介就会按照我们的要求,提供一个mm,我们只需要去和她谈恋爱、结婚就行了。简单明了,如果婚介给我们的人选不符合要求,我们就会抛出异常。整个过程不再由我自己控制,而是有婚介这样一个类似容器的机构来控制。Spring所倡导的开发方式就是如此,所有的类都会在spring容器中登记,告诉spring你是个什么东西,你需要什么东西,然后spring会在系统运行到适当的时候,把你要的东西主动给你,同时也把你交给其他需要你的东西。所有的类的创建、销毁都由 spring来控制,也就是说控制对象生存周期的不再是引用它的对象,而是spring。对于某个具体的对象而言,以前是它控制其他对象,现在是所有对象都被spring控制,所以这叫控制反转。如果你还不明白的话,我决定放弃。
IoC的一个重点是在系统运行中,动态的向某个对象提供它所需要的其他对象。这一点是通过DI(Dependency Injection,依赖注入)来实现的。比如对象A需要操作数据库,以前我们总是要在A中自己编写代码来获得一个Connection对象,有了 spring我们就只需要告诉spring,A中需要一个Connection,至于这个Connection怎么构造,何时构造,A不需要知道。在系统运行时,spring会在适当的时候制造一个Connection,然后像打针一样,注射到A当中,这样就完成了对各个对象之间关系的控制。A需要依赖 Connection才能正常运行,而这个Connection是由spring注入到A中的,依赖注入的名字就这么来的。那么DI是如何实现的呢? Java 1.3之后一个重要特征是反射(reflection),它允许程序在运行的时候动态的生成对象、执行对象的方法、改变对象的属性,spring就是通过反射来实现注入的。
如果还不明白,放弃java吧!