--------------------------Spring 2.5 理论介绍--------------------------
Spring是一个开源的控制反转(Inversion ofControl,IOC)和面向切面(AOP)的容器框架,它的主要目的是简化企业开发
1. IOC控制反转
先看一段代码:
publicclass PersonServiceBean{
private PersonDaopersonDao=new PersonDaoBean();
public void save(Personperson){
personDao.save(person);
}
}
在这段代码中,PersonDaoBean类的对象是在PersonServiceBean类中创建的,所以PersonDaoBean类对象的创建依赖于PersonServiceBean
2. 依赖注入(DependencyInjection)
当我们把依赖对象交给外部容器负责创建,那么PersonServiceBean类可以改成:
publicclass PersonServiceBean{
private PersonDaopersonDao;
publicPersonServiceBean(PersonDao personDao){
this.personDao=personDao;
//通过构造器参数,让容器把创建好的依赖对象注入进PersonServiceBean,当然也可以使用setter方法进行注入
}
publicvoid save(Person person){
personDao.save(person);
}
}
所谓依赖注入就是指:在运行期,由外部容器动态地将依赖对象注入到组件中
3. 为什么要使用Spring呢
在项目中引入Spring立即可以带来下面的一些好处:
(1) 降低组件之间的耦合度,实现软件各层之间的解耦
Controller—-Service—-DAO
(2) 可以使用容器提供的众多服务,例如:事务管理服务,消息服务等等,当我们使用容器管理事务时,开发人员就不再需要手工控制事务,也不需要处理复杂的事务传播
(3) 容器提供单例模式支持,开发人员不再需要自己编写实现代码
(4) 容器提供了AOP技术,利用它很容易实现如权限拦截、运行期监控等功能
(5) 容器提供的众多辅佐类,使用这些类能够加快应用的开发,如:JdbcTemplate、HibernateTemplate
(6) Spring对于主流的应用框架提供了集成支持,如:集成Hibernate,JPA,Struts等,这样更便于应用的开发
4. 轻量级与重量级概念的划分
划分一个应用是否属于轻量级还是重量级,主要看它使用了多少服务,使用的服务越多,容器要为普通Java对象做的工作就越多,必然会影响到应用的发布时间或者是运行性能
对于Spirng容器,它提供了很多服务,但这些服务并不是预设为应用打开的,应用需要某种服务,就需要指明使用该服务。如果应用使用的服务很少,比如只使用了Spring的核心服务,那么我们可以认为此时应用属于轻量级的。如果应用使用了Spring提供的大部分功能,,这时应用就属于重量级。
目前EJB容器就因为它默认为应用提供了EJB规范中的所有功能,所以它属于重量级
---------------------搭建与测试Spring 2.5的开发环境---------------------
下载Spring的jar包,从Spring的官方网站下载
http://www.springsource.org/download可以找到Spring的不同版本
1. Spring经常使用到的jar檔:
dist/spring.jar
如果使用日志,需要下面这个jar包
lib/jakarta-commons/commons-logging.jar
如果使用AOP,需要下面3个jar檔
lib/aspect/aspectjweaver.jar和aspectjrt.jar
lib/cglib/cglib-nodep-2.1_3.jar
如果使用JSR-250中的注解,例如:@Resource/@PostConstruct/@PreDestroy,还需要下列jar檔
lib/j2ee/common-annotations.jar
如果使用Spring内部的Junit测试,还需要下面4个jar檔
dist/modules/spring-test.jar
lib/junit/junit-4.4.jar
lib/easymock/easymock.jar和easymockclassextension.jar
2. 搭建环境
(1) 导入jar包
(2) 创建Spring的配置文件,配置文件的名称可以任意,档可以存放在任何目录下,但考虑到通用性,一般放在类路径下
配置文件的范本:
<?xmlversion="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>
3. 创建一个需要使用Spring容器功能的接口及实现类
先创建一个Person类,并含有一个Hello方法
publicclass Person{
publicvoid Notify(){
System.out.println("HelloWorld");
}
}
对该类点击右键,选择Refactor—ExtractInterface 提取该类成一个接口
界面如下:
publicinterface PersonService {
publicabstractvoid Notify();
}
4. 修改配置文件Bean.xml实例化一个bean
在配置文件中的<beans>中添加
<bean id="personService" class="com.bean.Person"></bean>
5. 测试
创建一个java类进行测试
publicclass Test {
publicstaticvoid main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("Bean.xml");
PersonService person=(PersonService) ctx.getBean("personService");
person.Notify();
}
}
如果成功会打印出来:Hello Spring
--------------------利用MyEclipse加载配置文件的说明信息-----------------
由于spring的schema文件位于网络上,如果机器不能连接到网络,那么在编写配置信息时候就无法出现提示信息,解决方法有两种:
1.让机器上网,MyEclipse会自动从网络上下载schema文件并缓存在硬盘上。
2.手动添加schema檔,方法如下:
以” spring-beans-2.5.xsd”为例子
windwos->preferences->myeclipse->filesand editors->xml->xmlcatalog
点"add",在出现的窗口中的Key Type中选择URI,在location中选"Filesystem",然后在spring解压目录的dist/resources目录中选择spring-beans-2.5.xsd,回到设置窗口的时候不要急着关闭窗口,应把窗口中的Key Type改为Schemalocation,Key改为http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
Spring所包含的xsd不止这一个,还有spring-aop-2.5.xsd等,所以当遇到非语法的配置文件报错时,就很有可能是没有配置其中所用到的xsd檔
--------------------------实例化Spring容器---------------------------
1. 实例化Spring容器有两种方式:
方法一:
在类路径下寻找配置文件来实例化容器
ApplicationContextctx = new ClassPathXmlApplicationContext(new String[]{"beans.xml"});
方法二:
在文件系统路径下寻找配置文件来实例化容器
ApplicationContextctx = new FileSystemXmlApplicationContext(new String[]{“d://beans.xml“});
该方式不太推荐使用,因为每个操作系统的文件系统都是不同的,所以他的通用性不强,例如这个路径“d://beans.xml“是在window下的,而在linux就可能不是这个路径了
2. Spring的配置文件可以指定多个,可以通过String数组传入。
3. 当spring容器启动后,因为spring容器可以管理bean对象的创建,销毁等生命周期,所以我们只需从容器直接获取Bean对象就行,而不用编写一句代码来创建bean对象。从容器获取bean对象的代码如下:
OrderServiceservice = (OrderService)ctx.getBean("personService");
这里的OrderService表示一个界面
personService表示获取配置文件中<bean>的id属性所对应的类的实例
-------------------------Spring管理Bean的原理-------------------------
利用Java读取XML文档的技术来模拟实现一个容器,从而利用该容器获取配置文件中bean的实例
需要导入dom4j及相关一整套的jar包
1. 先创建一个JavaBean,用来保存配置文件中<bean>的id和class
publicclass BeanDefinition {
private String id;
private String className;
public BeanDefinition(String id,String className) {
this.id = id;
this.className = className;
}
public String getId() {
returnid;
}
publicvoid setId(String id) {
this.id = id;
}
public String getClassName() {
returnclassName;
}
publicvoid setClassName(String className) {
this.className = className;
}
}
2. 通过spring提供的jar中的某些特殊类的功能,例如:SAXReader、Document和XPath等,来模拟实现
publicclass ItcastClassPathXMLApplicationContext {
private List<BeanDefinition> beanDefines = newArrayList<BeanDefinition>();
private Map<String, Object> sigletons = new HashMap<String,Object>();
publicItcastClassPathXMLApplicationContext(String filename){
this.readXML(filename);
this.instanceBeans();
}
/**
*读取xml配置文件
*@paramfilename
*/
privatevoid readXML(String filename) {
SAXReader saxReader = new SAXReader();
Document document=null;
try{
URL xmlpath = this.getClass().getClassLoader().getResource(filename);
document =saxReader.read(xmlpath);
Map<String,String>nsMap = new HashMap<String,String>();
nsMap.put("ns","http://www.springframework.org/schema/beans");//加入命名空间
XPath xsub =document.createXPath("//ns:beans/ns:bean");//创建beans/bean查询路径
xsub.setNamespaceURIs(nsMap);//设置命名空间
List<Element> beans =xsub.selectNodes(document);//获取文档下所有bean节点
for(Element element: beans){
String id =element.attributeValue("id");//获取id属性值
String clazz =element.attributeValue("class"); //获取class属性值
BeanDefinitionbeanDefine = new BeanDefinition(id, clazz);
beanDefines.add(beanDefine);
}
}catch(Exception e){
e.printStackTrace();
}
}
/**
*完成bean的实例化
*/
privatevoid instanceBeans() {
for(BeanDefinition beanDefinition : beanDefines){
try {
if(beanDefinition.getClassName()!=null && !"".equals(beanDefinition.getClassName().trim()))
sigletons.put(beanDefinition.getId(),Class.forName(beanDefinition.getClassName()).newInstance());
} catch (Exception e) {
e.printStackTrace();
}
}
}
/**
*获取bean实例
*@parambeanName
*@return
*/
public Object getBean(StringbeanName){
returnthis.sigletons.get(beanName);
}
}
3. 最后进行测试
publicstaticvoid main(String[] args) {
ItcastClassPathXMLApplicationContext ctx = new ItcastClassPathXMLApplicationContext("Bean.xml");
PersonService person=(PersonService) ctx.getBean("personService");
person.Notify();
}
-------------------------三种实例化bean的方式--------------------------
1.使用类构造器实例化
<bean
id="personService" class="com.bean.service.impl.Person">
</bean>
这个是使用类构造器来实例化,也是我们最初见到的最简单的一种单实例,无论这个personService构造多少个,实质上也只有一个实例而已
这种方式实例化的bean,是一种
2. 使用静态工厂方法实例化
publicclass PersonStaticFactory {
publicstatic Person staticCreatePerson(){
returnnew Person();
}
}
工厂类里边提供一个静态方法,专门用于创建bean对象
修改配置文件,实例化该bean
<bean
id="personService2"class="com.bean.service.impl.PersonStaticFactory"factory-method="staticCreatePerson"/>
这种配置方法表示利用PersonStaticFactory的createPerson方法来实例化
测试:
PersonServiceperson2=(PersonService) ctx.getBean("personService2");
System.out.print("使用静态工厂方法实例化:");
person.Notify();
3. 使用实例工厂方法实例化:
publicclass PersonStaticFactory {
public Person createPerson(){
returnnew Person();
}
}
修改配置文件,实例化该bean
<bean
id="personFactory" class="com.bean.service.impl.PersonStaticFactory"/>
<bean
id="personService3"
factory-bean="personFactory"
factory-method="createPerson"/>
先创建工厂类的一个实例bean--"personFactory"
再使用这个工厂bean—"personFactory"来调用"createPerson"方法创建Person类bean的实例
测试:
PersonServiceperson3=(PersonService) ctx.getBean("personService3");
System.out.print("使用实例工厂方法实例化:");
person.Notify();
-----------------------------bean的作用域-----------------------------
在每个spring IoC容器中默认一个bean定义只有一个对象实例。我们可以设置scope属性的属性范围让一个bean初始化多个对象实例
<bean
id="personService"
class="com.bean.service.impl.Person"
scope="prototype"/>
这样每次从容器获取bean都是新的对象
在scope中还有其他参数:
Singleton:唯一值,在IOC的容器中,只能出现一个对象,类似类中的静态方法,当spring的容器加载的时候,自动对singleton类型的的bean进行实例化
Prototype:每次产生一个新对象
Request:为每一个http的请求创建一个全新的bean
Session:bean对象放置在session会话中,不同HTTPSession使用不同的Bean。该作用域仅适用于WebApplicationContext环境。
globalSession:同一个全局Session共享一个Bean,一般用于Protlet应用环境。该作用域仅适用于WebApplicationContext环境。
------------------------- bean的生命周期-------------------------
1. 预设情况下Spring会在容器启动时初始化bean的实例。
我们可以指定Bean节点的lazy-init=”true”属性来延迟初始化bean。
这时候,只有第一次获取bean才会初始化bean
例如:
<bean
id="personService"
class="com.bean.service.impl.Person"
lazy-init="true"/>
当lazy-init="false"时,表示在容器启动时对bean进行实例化
当lazy-init="true"时,表示进行延迟初始化,在ctx.getBean时对bean进行实例化
如果想对所有bean都应用延迟初始化,可以在根节点bean设置
default-lazy-init=”true”,例如:
<beans default-lazy-init="true"
</beans>
2. 在设置scope="prototype"属性时,是在getBean("personService")时才初始化bean的实例,而不设置时,就在容器启动时对bean进行实例化
3. 初始化方法
在bean添加一个init方法
publicvoid init(){
System.out.println("init方法被调用");
}
在<bean>中设置init-method="init"属性,再实例化bean后,会启动该初始化方法
为了更好地看到效果,添加Person类的构造方法,告诉我们bean何时被实例化
publicPerson(){
System.out.println("bean已被实例化");
}
当执行下列这段代码时
AbstractApplicationContextctx
=new ClassPathXmlApplicationContext("Bean.xml");
读取xml配置文件,初始化Spring容器
先构造bean,再调用init方法
4. 销毁方法
在bean添加一个destory方法
publicvoid destory(){
System.out.println("关闭Spring容器,destory方法被调用");
}
在<bean>中设置destroy-method="destory"属性,在关闭Spring容器前会启动该销毁方法
那么又如何来关闭Spring容器呢?
首先,Spring容器读取xml配置文件不能使用ApplicationContext,而要使用抽象类AbstractApplicationContext
AbstractApplicationContext ctx
= new ClassPathXmlApplicationContext("Bean.xml");
ctx.close();
AbstractApplicationContext也是被ApplicationContext所继承的
用close方法来正常关闭Spring容器
-----------------------Spring依赖注入之ref属性------------------------
所谓依赖注入就是指:在运行期,由外部容器动态地将依赖对象注入到组件中
这个时候控制权发生了转移,这就是所谓的控制反转
依赖注入一共有两种方法:
一个是使用property元素的ref属性,这样构造的bean可以被任意个其他bean使用
另一个是使用内部bean,后边将会介绍,但是他只能为一个bean所服务
例如:我要在Person类中由Spring容器在外部创建PersonDao类的实例,并注入进来
1. 创建一个PersonDao类,添加一个add方法
publicclass PersonDao implements PersonDaoService {
publicvoid add(){
System.out.println("这里是PersonDao实例调用了add方法");
}
}
2. 抽取接口 右键单击该类,选择Refactor—Extract Interface
publicinterface PersonService {
publicabstractvoid Notify();
}
3. 在Person类中添加一个PersonDao类的对象引用(属性),设置该对象的set方法,在notify方法中调用PersonDao对象的add方法
publicclass Person implements PersonService {
public PersonDao personDao;
publicvoid Notify(){
System.out.println("HelloSpring");
personDao.add();
}
publicvoid setPersonDao(PersonDao personDao) {
this.personDao = personDao;
}
}
4. 修改bean.xml配置文件
<bean id="personDao" class="com.bean.service.impl.PersonDao"></bean>
<bean id="personService" class="com.bean.service.impl.Person">
<property name="personDao"ref="personDao"></property>
</bean>
创建PersonDao类的bean实例,该类要被注入进Person类中
在personService实例中添加<property>属性,也就是通过property属性隐式执行一个setter方法
其中:
name元素表示为Person类中的personDao属性注入值,且该元素必须与setXXX方法的XXX部分的名称相同,且首字母小写,与struts中的formbean利用反射机制获取值的方式基本一致
ref元素表示要注入的bean的名称,也就是要注入的那个bean的id名称
5. 测试
依然调用person.Notify()方法,就会经过注入bean,调用PersonDao类的add方法
------------------------Spring依赖注入之内部bean-----------------------
内部bean只能被一个bean使用,不能被其他bean使用
与上个例子的步骤基本相同,只是xml配置文件的设置不一样
修改xml配置文件
<bean id="personService" class="com.bean.service.impl.Person">
<property name="personDao">
<bean class="com.bean.service.impl.PersonDao"></bean>
</property>
</bean>
------------------------Spring依赖注入之基本属性-----------------------
Spring容器不仅能注入依赖对象,还可以注入基本类型的属性
例如:我有一个id和name,要注入进Person类
1. 在Person类定义id和name属性,创建他们的set方法
publicclass Person implements PersonService {
publicintid;
public String name;
publicvoid Notify(){
System.out.println("Hello Spring");
System.out.println("id="+id+",name="+name);
}
publicvoid setId(int id) {
this.id = id;
}
publicvoid setName(String name) {
this.name = name;
}
}
2. 修改xml配置文件
<bean id="personService" class="com.bean.service.impl.Person">
<property name="id"value="1"></property>
<property name="name"value="Shiki"></property>
</bean>
3. 测试
依然调用person.Notify()方法,就会注入id和name属性,通过Notify方法打印出id和name的值
----------------------Spring依赖注入之集合类型属性----------------------
1. 定义4种集合类型的属性,生成相应的get/set方法,将其get方法添加进接口中,因为测试时要由接口对象来调用该方法
publicclass Person implements PersonService {
private Set<Person> set=newHashSet<Person>();
private List<Person> list = new ArrayList<Person>();
private Properties prop=new Properties();
private Map<String,String> map=newHashMap<String,String>();
public Set<Person> getSet() {
returnset;
}
publicvoid setSet(Set<Person> set) {
this.set = set;
}
public List<Person> getList() {
returnlist;
}
publicvoid setList(List<Person> list) {
this.list = list;
}
public Properties getProp() {
returnprop;
}
publicvoid setProp(Properties prop) {
this.prop = prop;
}
public Map<String, String>getMap() {
returnmap;
}
publicvoid setMap(Map<String, String> map) {
this.map = map;
}
}
2. 抽取界面
publicinterface PersonService {
publicabstractvoid Notify();
publicabstract Set<Person> getSet();
publicabstract List<Person> getList();
publicabstract Properties getProp();
publicabstract Map<String, String>getMap();
}
3. 修改xml配置文件
<bean id="personService" class="com.bean.service.impl.Person">
<property name="set">
<set>
<value>第一个</value>
<value>第二个</value>
<value>第三个</value>
</set>
//value卷标保存值,ref卷标保存对象实例,<ref bean="person"/>
</property>
<property name="list">
<list>
<value>第一个list元素</value>
<value>第二个list元素</value>
<value>第三个list元素</value>
</list>
</property>
<property name="prop">
<props>
<prop key="key1">value1</prop>
<prop key="key2">value2</prop>
<prop key="key3">value3</prop>
</props>
</property>
<property name="map">
<map>
<entry key="key-1" value="value-1"/>
<entry key="key-2" value="value-2"/>
<entry key="key-3" value="value-3"/>
</map>
</property>
</bean>
4. 测试
AbstractApplicationContext ctx
= new ClassPathXmlApplicationContext("Bean.xml");
PersonService person=(PersonService)ctx.getBean("personService");
System.out.println("========set===========");
for(String value : person.getSet()){
System.out.println(value);
}
System.out.println("========list===========");
for(String value : person.getList()){
System.out.println(value);
}
System.out.println("========properties===========");
for(Object key : person.getProp().keySet()){
System.out.println(key+"="+ person.getProp().getProperty((String)key));
}
System.out.println("========map===========");
for(String key : person.getMap().keySet()){
System.out.println(key+"="+ person.getMap().get(key));
}
-----------------------Spring依赖注入之bean构造器----------------------
使用bean构造器可以注入依赖对象或基本类型属性
在Person类中定义两个属性,并生成带有参数的构造器
publicclass Person implements PersonService {
public PersonDao personDao;
public String name;
public Person(PersonDaopersonDao,String name){
System.out.println("bean已被实例化");
System.out.println("使用构造器注入:");
this.personDao=personDao;
this.name=name;
}
}
修改xml配置文件
<bean id="personDao" class="com.bean.service.impl.PersonDao"/>
<bean id="personService" class="com.bean.service.impl.Person">
<constructor-arg index="0"type="com.bean.service.impl.PersonDao"ref="personDao"/>
<constructor-arg index="1" type="java.lang.String" value="Shiki"/>
</bean>
index属性表示构造器中参数的索引,type是参数的类型,ref或value指定要注入的对象或基本类型的值
---------------------Spring依赖注入之使用注解注入属性--------------------
如果我们不希望配置文件所配置的属性非常多非常复杂,我们可以使用注解的方式对依赖对象进行注入
1. 如果想使用注解的方式,则我们需要在xml配置文件中添加以下额外的信息:
<beans
xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.0.xsd"
http://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context-2.5.xsd">
<context:annotation-config/>
</beans>
这个配置隐式注册了多个处理器,用于对注释进行解析
这些处理器包括:
AutowiredAnnotationBeanPostProcessor:用来解析@Autowired注解
CommonAnnotationBeanPostProcessor:用来解析@Resource注解
PersistenceAnnotationBeanPostProcessor:用来处理持久化
RequiredAnnotationBeanProcessor
注解本身和xml的作用一样,也是用于配置,而不能自己干活,之所以在这里能够干活是因为背后有这些处理器进行处理
<context:annotation-config/>就针对注解所使用的处理器注入到了Spring容器中
注,不配置处理器的另一个方法是:
@Autowired 起作用必须事先在 Spring 容器中声明AutowiredAnnotationBeanPostProcessor Bean,该 BeanPostProcessor 将自动对标注 @Autowired 的 Bean 进行注入
<bean class="org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor"/>
2. 导入注解所需要的jar包
lib/j2ee/common-annotations.jar
3. 在java代码中使用@Autowired或@Resource注解方式进行装配。
(1) 这两个注解的区别是:@Autowired默认按类型装配,@Resource默认按名称装配,当找不到与名称匹配的bean才会按类型匹配
(2) @Autowired默认情况下要求依赖对象必须存在,如果允许null值,可以设置required属性为false;如果我们想使用名称匹配,可以结合@Qualifier注解一起使用
(3) @Resource注解和@Autowired注解某些方面一样,也可以标注在字段或属性的set方法上,但他默认按名称匹配,名称可以通过@Resource的name属性指定,如果没有指定name属性,当注解标注在字段上,即默认取字段的名称作为bean名称寻找依赖对象,当注解标注在属性的set方法上,即默认取属性名作为bean名称寻找依赖对象
(4) 如果没有指定name属性,并且按照默认的名称仍然找不到依赖对象时,@Resource注解会回退到按类型匹配。一旦指定了name属性,就只能按名称匹配了
在xml配置文件中配置Person和要注入的bean
<bean id="personDao" class="com.bean.service.impl.PersonDao"/>
<bean id="personService" class="com.bean.service.impl.Person">
在Person类中添加注解
publicclass Personimplements PersonService {
@Autowired
public PersonDao personDao;
publicvoid Notify(){
System.out.println("HelloSpring");
personDao.add();
}
}
@Autowired是按照类型匹配,结合@Qualifier注解一起使用就可以使用名称匹配
@Autowired(required=false),required=false属性表示不存在匹配的bean的时候,不报错
@Resource @Qualifier("personDao")
@Qualifier(“XXX”)括号中的内容表示要注入的bean的名称
当然,最好是直接使用按名称匹配的注解@Resource,如果按照名称找不到相应的bean,则会回退到按类型匹配,这样做最保险
-----------------------Spring依赖注入之自动装配-----------------------
注入依赖对象可以采用手工装配和自动装配
手工装配包括了:使用构造器注入,set方法注入,Field注入(注解方式)
以上讲解的装配方式都属于手动装配,在实际应用中建议使用手工装配,因为自动装配会产生未知情况,开发人员无法预见最终的装配结果
下面来讲解如何进行自动装配:
在xml配置文件中
<bean id="personDao" class="com.bean.service.impl.PersonDao"/>
<bean
id="personService"
class="com.bean.service.impl.Person"
autowire="xxx"
>
这里的autowire属性的取值有多种,可以设定按类型,名称,构造器和自省机制来自动匹配要注入的bean。
就这个程序而言,如果要进行名称匹配,在Person类中搜索属性的名称与该xml配置文件中的其他bean的名称相比较,如果存在,就将这个bean注入到Person类的属性之中
Autowire属性的取值如下:
byType:按类型匹配,可以根据属性的类型,在容器中寻找跟该类型匹配的bean。如果发现多个,那么将抛出异常,如果没有找到,即属性值为null
byName:按名称匹配,可以根据属性的名称,在容器中寻找跟该属性名匹配的bean。如果没有找到,即属性值为null
constructor与byType的方式类似,不同之处在于它应用于构造器函数。如果在容器中没有找到与构造器参数类型一致的bean,那么将会抛出异常
autodetect:通过bean类的自省机制(introspection)来决定是使用constructor还是byType方式进行自动匹配。如果发现默认的构造器,那么将使用byType方式
在Person类中不需要使用任何注解,但一定要注意!使用autowire与那两种注解方式不同,在person类中必须含有该依赖对象的set方法!否则就无法给依赖对象赋值了
publicclass Person implements PersonService {
public PersonDao personDao;
publicvoid Notify(){
System.out.println("HelloSpring");
System.out.println("id="+id+",name="+name);
personDao.add();
}
publicvoid setPersonDao(PersonDao personDao) {
this.personDao = personDao;
}
}
测试:
依然调用person.Notify()方法,根据autowire所指定的匹配方式来寻找bean,如果成功,打印出注入bean的add方法中的信息
-----------------------Spring自动扫描和管理Bean------------------------
前面的例子我们都是使用XML的bean定义来配置组件。在一个稍大的项目中,通常会有上百个组件,如果这些组件都采用XML的bean定义来配置,显然会增加配置文件的体积,即使使用注解的方式,也只能会减少<bean>中的属性,而<bean>元素的数量依旧会很大,查找及维护起来也不太方便。
由此,Spring2.5版本为我们引入了组件自动扫描机制,他可以在类路径底下寻找标注了@Component、@Service、@Controller、@Repository注解的类,并把这些类纳入进Spring容器中管理。它的作用和在xml檔中使用<bean>节点配置组件是一样的
要使用自动扫描机制,我们需要打开一下配置信息:(添加自动扫描的处理器)
<beans
xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context-2.5.xsd">
<context:component-scanbase-package="cn.itcast"/>
</beans>
其中base-package属性为需要扫描的包(含子包,也就是以cn.itcast开头的包都会被扫描到)
@Service用于标注业务层组件
@Controller用于标注控制层组件(如Struts中的Action)
@Repository用于标注数据访问组件,即DAO组件。
@Component泛指组件,即当组件不好归类的时候,我们可以使用这个注解进行标注
在类中添加这些注解,Spring容器就会根据指定的包路径自动扫描到这些类,并生成相应的bean实例,例如:
@Service
publicclass Person implements PersonService {}
如果没有指定名称,那么bean的名称就是类的简单名称,也就是把类名首字母小写的名称。在使用时,依然使用getBean(“xXX”)来获取的bean实例,这里边的xXX的名称就是那个类的简单名称
当然,也可以通过注解来给bean提供一个名称
@Repository("abc")
publicclass PersonDao implements PersonDaoService {}
getBean(“XXX”)获取时就使用abc这个提供的名称了
测试:
AbstractApplicationContext ctx
= new ClassPathXmlApplicationContext("Bean.xml");
PersonServiceperson=(PersonService) ctx.getBean("person");
PersonDaoService personDao=(PersonDaoService)ctx.getBean("abc");
System.out.println("person是否存在?"+person);
System.out.println("personDao是否存在?"+personDao);
结合@Scope(“XXX”)注解一起使用,可以改变该bean的作用域范围
@Service@Scope("prototype")
publicclass Person implements PersonService {}
prototype在之前讲解过,每次从容器获取bean都是新的对象,而默认获取的bean的物件是单例的
利用注解来标注初始化和销毁方法
@PostConstruct
publicvoid init(){
System.out.println("完成Spring容器初始化,init方法被调用");
}
@PreDestroy
publicvoid destory(){
System.out.println("Spring容器即将关闭,destory方法被调用");
}
-------------------------------静态代理-------------------------------
例如:要在输出“HelloWorld”前打印一个字符串“Welcome”
1. 定义一个接口类
package ttitfly.proxy;
public interface HelloWorld {
public void print();
// public void say();
}
2. 定义一个该接口的实现类
package ttitfly.proxy;
public class HelloWorldImpl implements HelloWorld{
public void print(){
System.out.println("HelloWorld");
}
// public void say(){
// System.out.println("Say Hello!");
// }
}
3. 定义一个静态代理类
package ttitfly.proxy;
public class StaticProxy implements HelloWorld{
public HelloWorld helloWorld ;
public StaticProxy(HelloWorld helloWorld){
this.helloWorld = helloWorld;
}
public void print(){
System.out.println("Welcome");
//相当于回调
helloWorld.print();
}
// public void say(){
// //相当于回调
// helloWorld.say();
// }
}
4. 测试类
package ttitfly.proxy;
public class TestStaticProxy {
public static void main(String[] args){
HelloWorld helloWorld = new HelloWorldImpl();
StaticProxy staticProxy = new StaticProxy(helloWorld);
staticProxy.print();
// staticProxy.say();
}
}
可以看出静态代理类有一个很不好的缺点:如果当接口加一个方法(把上面所有的代码的注释给去掉),所有的实现类和代理类里都需要做个实现。这就增加了代码的复杂度。动态代理就可以避免这个缺点。
-----------------------Proxy技术实现AOP(动态代理)----------------------
AOP应用于做权限系统,我们可能需要粗粒度的权限控制,或者细粒度的权限控制,细粒度的权限控制我们一般对方法进行拦截,拦截方法后判断用户是否具有权限,有权限就允许用户执行被拦截的方法
用Proxy技术来模拟一下实际的业务需求,在不使用AOP框架的时候,实现其功能
但Proxy有局限性,必须当目标类实现了接口,才可以使用jdk的Proxy来生成代理对象。
1. 创建需要代理的类
PersonServiceBean.java
publicclass PersonServiceBean{
private String user = null;
public String getUser() {
returnuser;
}
public PersonServiceBean(){}
public PersonServiceBean(Stringuser){
this.user = user;
}
public String getPersonName(Integerpersonid) {
System.out.println("我是getPersonName()方法");
return"xxx";
}
publicvoid save(String name) {
System.out.println("我是save()方法");
}
publicvoid update(String name, Integer personid) {
System.out.println("我是update()方法");
}
}
2. 抽取接口,抽取后,会自动将该类实现该接口
PersonService.java
publicinterface PersonService {
publicvoid save(String name);
publicvoid update(String name, Integer personid);
public String getPersonName(Integer personid);
}
3. 代理工厂(也就是一个代理的工具类,用来创建代理对象并拦截其方法)
JDKProxyFactory.java
publicclass JDKProxyFactoryimplements InvocationHandler{
private Object targetObject;
public ObjectcreateProxyIntance(Object targetObject){
this.targetObject = targetObject;
return Proxy.newProxyInstance(this.targetObject.getClass().getClassLoader(),
this.targetObject.getClass().getInterfaces(),this);
//三个参数为代理对象的类加载器,实现的接口,以及InvocationHandler类的实例
/* InvocationHandler是代理实例的调用处理程序实现的接口。
每个代码实例都具有一个关联的调用处理程序。
对代理实例调用方法时,将对方法调用进行编码并将其指派到它的调用处理程序的 invoke 方法。
通过创建代理对象后,由其所调用的方法都将被InvocationHandler界面的invoke方法所拦截,并根据需求来决定如何执行
*/
}
public Object invoke(Object proxy,Method method, Object[] args)
throws Throwable {//环绕通知
PersonServiceBean bean = (PersonServiceBean) this.targetObject;
Object result = null;
if(bean.getUser()!=null){
//..... advice()-->前置通知
try {
result = method.invoke(targetObject, args);
// afteradvice() -->后置通知
} catch (RuntimeException e) {
//exceptionadvice()--> 例外通知
}finally{
//finallyadvice(); -->最终通知
}
}
return result;
}
}
4. 测试类
publicclass AOPTest {
publicstaticvoid main(String[] args) {
JDKProxyFactory factory = new JDKProxyFactory();
PersonService service = (PersonService) factory.createProxyIntance(new PersonServiceBean("xxx"));
//创建一个代理对象
service.save("888");
//经过拦截,由invoke方法来调用
}
}
-----------------------------CGLIB技术实现----------------------------
如果这个类没有提供接口,那么我们就无法来创建Proxy代理对象,那么我们用一个第三方的创建代理对象的框架—cglib
spring中包含了它的jar檔 lib/cglib/cglib-nodep-2.1_3.jar
cglib可以生成目标类的子类,并重写父类非final修饰符的方法。
通过cglib,我们可以通过没有实现接口的类就可以创建代理对象
1. 继续使用上边的PersonServiceBean.java类作为目标类
2. 创建cglib代理工厂
publicclass CGlibProxyFactory implements MethodInterceptor{
private Object targetObject;
public Object createProxyIntance(Object targetObject){
this.targetObject = targetObject;
Enhancer enhancer = new Enhancer();//创建代理对象
enhancer.setSuperclass(this.targetObject.getClass());//非final
/*这个参数要填写一个父类,这个父类也就是目标类,就是要代理的那个类
当把目标类设置为代理对象父类的时候,代理技术会产生一个目标类的子类,在这个子类里边,它可以覆盖目标类中所有非final修饰的方法
实际上,cglib创建的代理对象是继承了目标类,对目标类的所有非final方法进行覆盖,在这些方法中可以添加一些自身的方法
*/
enhancer.setCallback(this);
//回调方法,与Proxy的原理相同,也必须要实现一个接口,当代理对象的业务方法被调用的时候,会回调这个方法,
returnenhancer.create();
}
public Object intercept(Object proxy,Method method, Object[] args,
MethodProxy methodProxy) throws Throwable {
PersonServiceBean bean = (PersonServiceBean) this.targetObject;
Object result = null;
if(bean.getUser()!=null){
result = methodProxy.invoke(targetObject, args);
}
return result;
}
}
3. 测试类
publicclass AOPTest {
publicstaticvoid main(String[] args) {
//cglib
CGlibProxyFactory factory = new CGlibProxyFactory();
PersonServiceBean service =(PersonServiceBean) factory.createProxyIntance(new PersonServiceBean("xxx"));
service.save("999");
}
}
-----------------------------AOP中的概念-----------------------------
Aspect(切面):指横切性关注点的抽象即为切面,它与类相似,只是两者的关注点不一样,类是对物体特征的抽象,而切面横切性关注点的抽象.
joinpoint(连接点):所谓连接点是指那些被拦截到的点。在spring中,这些点指的是方法,因为spring只支持方法类型的连接点,实际上joinpoint还可以是field或类构造器)
Pointcut(切入点):所谓切入点是指我们要对那些joinpoint进行拦截的定义.
Advice(通知):所谓通知是指拦截到joinpoint之后所要做的事情就是通知.通知分为前置通知,后置通知,异常通知,最终通知,环绕通知
Target(目标对象):代理的目标对象
Weave(织入):指将aspects应用到target对象并导致proxy对象创建的过程称为织入.
Introduction(引入):在不修改类代码的前提下, Introduction可以在运行期为类动态地添加一些方法或Field.
---------------------使用Spring进行面向切面(AOP)程序设计--------------------
AOP,主要是用于拦截方法
要进行AOP程序设计,首先我们要在spring的配置文件中引入aop命名空间:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/aophttp://www.springframework.org/schema/aop/spring-aop-2.5.xsd">
</beans>
Spring提供了两种切面声明方式,实际工作中我们可以选用其中一种:
基于XML配置方式声明切面。
基于注解方式声明切面。
基于注解方式
1. 首先启动对@AspectJ注解的支持(蓝色部分为预设的,红色部分是aop所用到的):
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/aophttp://www.springframework.org/schema/aop/spring-aop-2.5.xsd">
<aop:aspectj-autoproxy/>
<bean id="PersonServiceBean" class="com.aop.PersonServiceBean"/>
<bean id="myInterceptor" class="com.aop.MyInterceptor"/>
</beans>
<aop:aspectj-autoproxy/>表示打开配置项,为我们这个注解提供了解析的功能
注解本身不能自己干活,之所以在这里能够干活是因为背后有这些处理器进行处理
<aop:aspectj-autoproxyproxy-target-class="true"/>
参数proxy-target-class=true表示使用cglib来生成代理对象,默认false是使用JDKProxy的方法创建,但是要依据情况而定,如果提供的目标类没有实现接口就使用cglib,实现接口的使用JDKProxy
这里要注意
2. 创建界面及实现类
publicinterface PersonService {
publicvoid save(String name);
publicvoid update(String name, Integer id);
public String getPersonName(Integerid);
}
publicclass PersonServiceBean implements PersonService {
public String getPersonName(Integerid) {
System.out.println("我是getPersonName()方法");
return"xxx";
}
publicvoid save(String name) {
thrownew RuntimeException("例外,抛出异常");
//System.out.println("我是save()方法");
}
publicvoid update(String name, Integer id) {
System.out.println("我是update()方法");
}
}
3. 创建切面类
利用aop技术进行方法拦截,对类和方法用不同的注解进行标记,完成不同的功能
@Aspect//表示采用注解的方式声明切面
publicclass MyInterceptor {
/*Pointcut 切入点,用来定义我们需要拦截的方法,后边引用了一些aop表达是语言
1 2 3 4 5 6
@Pointcut("execution (* com.source..*.*(..))")
1号位置 execution表示执行,执行该方法的时候进行拦截
2号位置表示返回值类型,*号通配符,表示任意类型
3号位置表示包名,对哪个包下的类进行拦截
4号位置两个点表示对其包下的子包里边的内容也要进行拦截,如果不加点的话就表示只包含当前包里边的内容
5号位置第一个*号表示类,第二个*号表示方法,*.*表示对任意个类的所有方法进行拦截
6号位置表示方法的参数,..表示任意参数,有无都可以,1个,2个或者多个
*/
/*
通知的种类
@Before 前置通知
@AfterReturning 后置通知
@After 最终通知
@AfterThrowing 例外通知
@Around 环绕通知
*/
/*
通知执行的顺序(环绕通知包括进入方法和退出方法)
前置通知:Shiki
进入方法(环绕通知进入方法紧跟在方法前执行)
我是save()方法
后置通知:null(紧跟在方法后执行)
最终通知(finally中执行)
退出方法(环绕通知退出方法当方法执行完毕后执行)
*/
@Pointcut("execution(* com.aop.PersonServiceBean.*(..))")
privatevoid anyMethod() {}//利用@Pointcut注释声明一个切入点,名称为anyMethod
//aop表达是语言表示对com.aop.PersonServiceBean类下的所有方法进行拦截
@Before("anyMethod()&& args(name)")//括号里边填写的是切入点的名称和方法参数名
//该参数名必须与该通知定义的方法的参数名同名,否则会报异常
publicvoid doAccessCheck(String name) {
System.out.println("前置通知:"+ name);
}
@AfterReturning(pointcut="anyMethod()",returning="result")
publicvoid doAfterReturning(String result) {
System.out.println("后置通知:"+ result);
}
@After("anyMethod()")
publicvoid doAfter() {
System.out.println("最终通知");
}
@AfterThrowing(pointcut="anyMethod()",throwing="e")
publicvoid doAfterThrowing(Exception e) {
System.out.println("例外通知:"+ e);
}
@Around("anyMethod()")
public ObjectdoBasicProfiling(ProceedingJoinPoint pjp) throws Throwable {
//if(){//判断用户是否在权限这个环绕通知很适合于做权限验证如果用户存在,然后执行被拦截的方法
System.out.println("进入方法");
Object result = pjp.proceed();//表示被拦截的方法
System.out.println("退出方法");
//}
return result;
}
}
4. 修改配置文件,实例化该bean
<bean id="myInterceptor" class="com.aop.MyInterceptor"></bean>
<bean id="PersonServiceBean" class="com.aop.PersonServiceBean"></bean>
5. 测试类
publicclass Test {
publicstaticvoid main(String[] args) {
AbstractApplicationContext ctx = new ClassPathXmlApplicationContext("Bean.xml");
PersonService personService=(PersonService) ctx.getBean("PersonServiceBean");
personService.save("Shiki");
System.out.println();
personService.getPersonName(2);
}
}
基于XML配置方式
1. 与前一个基于注解的方式所使用的文件基本相同(接口和抽象类保持不变),只是在切面类中不再写入注解了,是一个只有各种通知方法的类
publicclass MyInterceptor_XML {
privatevoid anyMethod() {}
publicvoid doAccessCheck(String name) {
System.out.println("前置通知:"+ name);
}
publicvoid doAfterReturning(String result) {
System.out.println("后置通知:"+ result);
}
publicvoid doAfter() {
System.out.println("最终通知");
}
publicvoid doAfterThrowing(Exception e) {
System.out.println("例外通知:"+ e);
}
public ObjectdoBasicProfiling(ProceedingJoinPoint pjp) throws Throwable {
//if(){//判断用户是否在权限这个环绕通知很适合于做权限验证如果用户存在,然后执行被拦截的方法
System.out.println("进入方法");
Object result = pjp.proceed();//表示被拦截的方法
System.out.println("退出方法");
//}
return result;
}
}
2. 在Bean.xml文件中写入配置信息
<bean id="personServiceBean" class="com.aop.PersonServiceBean"></bean>
<bean id="aspectBean"class="com.aop.MyInterceptor_XML"></bean>
<aop:config>
<aop:aspect id="myaop" ref="aspectBean">
<aop:pointcut
id="mycut" expression="execution(* com.aop..*.*(..))"/>
<aop:before pointcut-ref="mycut" method="doAccessCheck"/>
<aop:after-returning
pointcut-ref="mycut" method="doAfterReturning"/>
<aop:after pointcut-ref="mycut" method="doAfter"/>
<aop:after-throwing
pointcut-ref="mycut" method="doAfterThrowing"/>
<aop:around
pointcut-ref="mycut" method="doBasicProfiling"/>
</aop:aspect>
</aop:config>
----------------------Spring与jdbc整合(配置数据源)---------------------
1. 配置数据源共有两种方法
在Bean.xml配置文件中配置数据源
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"destroy-method="close">
<property name="driverClassName"value="org.gjt.mm.mysql.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/hibernate?useUnicode=true&characterEncoding=UTF-8"/>
<property name="username"value="root"/>
<property name="password"value="root"/>
<property name="initialSize" value="1"/>
<!-- 连接池启动时的初始值 -->
<property name="maxActive"value="500"/>
<!-- 连接池的最大值 -->
<property name="maxIdle"value="2"/>
<!-- 最大空闲值.当经过一个高峰时间后,连接池可以慢慢将已经用不到的连接慢慢释放一部分,一直减少到maxIdle为止 -->
<property name="minIdle"value="1"/>
<!-- 最小空闲值.当空闲的连接数少于阀值时,连接池就会预申请去一些连接,以免洪峰来时来不及申请 -->
</bean>
使用属性占位符方式配置数据源
使用<context:property-placeholderlocation=“jdbc.properties”/>属性占位符
将属性文件的信息引入配置文件中
<context:property-placeholder location="jdbc.properties"/>
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"destroy-method="close">
<property name="driverClassName"value="${driverClassName}"/>
<property name="url" value="${url}"/>
<property name="username"value="${username}"/>
<property name="password"value="${password}"/>
<!-- 连接池启动时的初始值 -->
<property name="initialSize"value="${initialSize}"/>
<!-- 连接池的最大值 -->
<property name="maxActive"value="${maxActive}"/>
<!-- 最大空闲值.当经过一个高峰时间后,连接池可以慢慢将已经用不到的连接慢慢释放一部分,一直减少到maxIdle为止 -->
<property name="maxIdle"value="${maxIdle}"/>
<!-- 最小空闲值.当空闲的连接数少于阀值时,连接池就会预申请去一些连接,以免洪峰来时来不及申请 -->
<property name="minIdle"value="${minIdle}"/>
</bean>
2. 创建数据表
CREATETABLE `person` (
`id` int(11) NOT NULL auto_increment,
`name` varchar(20) NOT NULL,
PRIMARY KEY (`id`)
)ENGINE=InnoDB DEFAULT CHARSET=gbk;
3. 创建javabean
publicclass Person {
private Integer id;
private String name;
public Person(){}
public Person(String name) {
this.name = name;
}
public Integer getId() {
returnid;
}
publicvoid setId(Integer id) {
this.id = id;
}
public String getName() {
returnname;
}
publicvoid setName(String name) {
this.name = name;
}
}
4. 创建界面及实现类
publicinterface PersonService {
/**
*保存person
*@paramperson
*/
publicvoid save(Person person);
/**
*更新person
*@paramperson
*/
publicvoid update(Person person);
/**
*获取person
*@parampersonid
*@return
*/
public Person getPerson(Integerpersonid);
/**
*获取所有person
*@return
*/
public List<Person>getPersons();
/**
*删除指定id的person
*@parampersonid
*/
publicvoid delete(Integer personid);
}
@Transactional
publicclass PersonServiceBean implements PersonService {
private JdbcTemplate jdbcTemplate;
publicvoid setDataSource(DataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
}
publicvoid delete(Integer personid) {
jdbcTemplate.update("delete from person where id=?", new Object[]{personid},newint[]{java.sql.Types.INTEGER});
//所有方法的参数基本类似,第一个为sql语句,第二个是给?传递的参数,第三个是?参数的类型码
}
public Person getPerson(Integerpersonid) {
return (Person)jdbcTemplate.queryForObject("select * from person where id=?", new Object[]{personid}, newint[]{java.sql.Types.INTEGER}, new PersonRowMapper());
}
@SuppressWarnings("unchecked")
public List<Person> getPersons() {
return(List<Person>)jdbcTemplate.query("select* from person", newPersonRowMapper());
//由于sql语句中没有参数,所以也就不存在参数类型码的问题了
}
publicvoid save(Person person) {
jdbcTemplate.update("insert into person(name) values(?)", new Object[]{person.getName()},
newint[]{java.sql.Types.VARCHAR});
}
publicvoid update(Person person) {
jdbcTemplate.update("update person set name=? where id=?", new Object[]{person.getName(), person.getId()},
newint[]{java.sql.Types.VARCHAR, java.sql.Types.INTEGER});
}
}
5. 创建查询操作中所使用到的PersonRowMapper类
publicclass PersonRowMapper implements RowMapper {
public Object mapRow(ResultSet rs, int index) throws SQLException {
Person person = new Person(rs.getString("name"));
person.setId(rs.getInt("id"));
return person;
}
}
6. 创建Junit测试类
publicclass PersonServiceTest {
privatestatic PersonService personService;
@BeforeClass
publicstaticvoid setUpBeforeClass() throws Exception {
try {
ApplicationContext cxt = new ClassPathXmlApplicationContext("jdbcTemplate_properties_Bean.xml");
personService = (PersonService) cxt.getBean("personService");
} catch (RuntimeException e) {
e.printStackTrace();
}
}
@Testpublicvoid save(){
for(int i=0; i<5; i++)
personService.save(new Person("传智播客"+ i));
}
@Testpublicvoid getPerson(){
Person person = personService.getPerson(1);
System.out.println(person.getName());
}
@Testpublicvoid update(){
Person person = personService.getPerson(1);
person.setName("张xx");
personService.update(person);
}
@Testpublicvoid delete(){
personService.delete(1);
}
@Testpublicvoid getBeans(){
for(Person person : personService.getPersons()){
System.out.println(person.getName());
}
}
}
7. 对Spring的事务管理进行测试,查看当抛出不同异常时是否进行回滚操作(以删除方法为例)
1)抛出一个运行期例外,
publicvoid delete(Integer personid) {
jdbcTemplate.update("delete from person where id=?", new Object[]{personid},
newint[]{java.sql.Types.INTEGER});
thrownew RuntimeException("运行期例外");
}
程序回滚
2)抛出一个Check这种例外(就是普通的Exception)
publicvoid delete(Integer personid) throws Exception{
jdbcTemplate.update("delete from person where id=?", new Object[]{personid},
newint[]{java.sql.Types.INTEGER});
thrownew Exception("异常");
}
程序不回滚,虽然最后抛出异常,但是之前方法会执行
3)Spring开启的事务默认情况下如果碰到运行期例外,运行期例外一般也叫做unchecked例外,这种例外事务会回滚,还有一种叫做checked例外,这种例外事务不会回滚
如果想让checked例外的方法进行回滚操作,可以使用@Transactional()注解来修改它的行为
@Transactional(rollbackFor=Exception.class)
publicvoid delete(Integer personid) throws Exception{
jdbcTemplate.update("delete from person where id=?", new Object[]{personid},
newint[]{java.sql.Types.INTEGER});
thrownew Exception("运行期例外");
}
rollbackFor这个属性指定了即使出现了checked这种例外,它也会对这种事务进行回滚
相反,如果抛出的是运行期例外,可以使用noRollbackFor来指定它不进行回滚
@Transactional(noRollbackFor=RuntimeException.class)
publicvoid delete(Integer personid) throws Exception{
jdbcTemplate.update("delete from person where id=?", new Object[]{personid},
newint[]{java.sql.Types.INTEGER});
thrownew RuntimeException("运行期例外");
}
8. 有的方法不需要事务的支持,例如像查询方法等,可以声明该方法不支持事务
@Transactional(propagation=Propagation.NOT_SUPPORTED)
public Person getPerson(Integerpersonid) {
return (Person)jdbcTemplate.queryForObject("select * from person where id=?", new Object[]{personid},
newint[]{java.sql.Types.INTEGER}, newPersonRowMapper());
}
当我们打入了这个事务传播属性时,Spring容器在方法执行前就不会开启事务了
----------------------Spring与jdbc整合(配置事务)----------------------
1. 配置事务时,需要在xml配置文件中引入用于声明事务的tx命名空间
事务的配置方式有两种:注解方式和基于XML配置方式。
在Bean.xml配置文件中引入用于声明事务的tx命名空间
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/txhttp://www.springframework.org/schema/tx/spring-tx-2.5.xsd
http://www.springframework.org/schema/aophttp://www.springframework.org/schema/aop/spring-aop-2.5.xsd
http://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context-2.5.xsd">
采用注解方式配置事务
创建Spring容器的事务管理器
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
添加解析注解的处理器
<tx:annotation-driventransaction-manager="txManager"/>
注解本身不能开启事务,只能起到配置的作用,之所以注解能够起作用,是因为添加了可以解析注解的处理器,transaction-manager用来指定一个事务管理器
在类中添加事务的注解
@Service@Transactional
publicclass PersonServiceBean implements PersonService {
}
可以与自动装配一起使用,定义该类使用事务处理,并自动扫描创建该类的bean
采用XML方式配置事务
依然使用com.jdbcTemplate包下的类文件
修改Bean.xml配置文件
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource"ref="dataSource"/>
</bean>
<aop:config>
<aop:pointcut id="transactionPointcut"expression="execution(* cn.itcast.service..*.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="transactionPointcut"/>
</aop:config>
<tx:advice id="txAdvice" transaction-manager="txManager">
<tx:attributes>
<tx:method name="get*"read-only="true" propagation="NOT_SUPPORTED"/>
<tx:method name="*"/>
</tx:attributes>
</tx:advice>
<bean id="personService" class="cn.itcast.service.impl.PersonServiceBean">
<property name="dataSource"ref="dataSource"/>
</bean>
---------------------------事务管理与传播属性---------------------------
事务的级别
1. REQUIRED:业务方法需要在一个事务中运行。如果方法运行时,已经处在一个事务中,那么加入到该事务,否则为自己创建一个新的事务。
2. NOT_SUPPORTED:声明方法不需要事务。如果方法没有关联到一个事务,容器不会为它开启事务。如果方法在一个事务中被调用,该事务会被挂起,在方法调用结束后,原先的事务便会恢复执行。
3. REQUIRESNEW:属性表明不管是否存在事务,业务方法总会为自己发起一个新的事务。如果方法已经运行在一个事务中,则原有事务会被挂起,新的事务会被创建,直到方法执行结束,新事务才算结束,原先的事务才会恢复执行。
4. MANDATORY:该属性指定业务方法只能在一个已经存在的事务中执行,业务方法不能发起自己的事务。如果业务方法在没有事务的环境下调用,容器就会抛出例外。
5. SUPPORTS:这一事务属性表明,如果业务方法在某个事务范围内被调用,则方法成为该事务的一部分。如果业务方法在事务范围外被调用,则方法在没有事务的环境下执行。
6. Never:指定业务方法绝对不能在事务范围内执行。如果业务方法在某个事务中执行,容器会抛出例外,只有业务方法没有关联到任何事务,才能正常执行。
7. NESTED:如果一个活动的事务存在,则运行在一个嵌套的事务中. 如果没有活动事务, 则按REQUIRED属性执行.它使用了一个单独的事务,这个事务拥有多个可以回滚的保存点。内部事务的回滚不会对外部事务造成影响。它只对DataSourceTransactionManager事务管理器起效,
增删改查的方法默认的事务级别不同:
Update方法默认的事务传播属性为NESTED
------------------------------事务隔离级别-----------------------------
数据库系统提供了四种事务隔离级别供用户选择。不同的隔离级别采用不同的锁类型来实现,在四种隔离级别中,Serializable的隔离级别最高,Read Uncommited的隔离级别最低。大多数据库默认的隔离级别为Read Commited,如SqlServer,当然也有少部分数据库默认的隔离级别为Repeatable Read ,如Mysql
Read Uncommited:读未提交数据(会出现脏读,不可重复读和幻读)。
Read Commited:读已提交数据(会出现不可重复读和幻读)
Repeatable Read:可重复读(会出现幻读)
Serializable:串行化
脏读:一个事务读取到另一事务未提交的更新新据。
不可重复读:在同一事务中,多次读取同一数据返回的结果有所不同。换句话说就是,后续读取可以读到另一事务已提交的更新数据。相反,“可重复读”在同一事务中多次读取数据时,能够保证所读数据一样,也就是,后续读取不能读到另一事务已提交的更新数据。
幻读:一个事务读取到另一事务已提交的insert数据。
---------Spring2.x+Hibernate3.x+Struts1.x整合开发(Jar包的选择)---------
hibernate核心安装包下的:
hibernate3.jar
lib/required/*.jar
lib/optional/ehcache-1.2.3.jar
hibernate 注解安装包下的
lib/test/slf4j-log4j12.jar
Spring安装包下的
dist/spring.jar
dist/modules/spring-webmvc-struts.jar
lib/jakarta-commons/commons-logging.jar
commons-dbcp.jar、commons-pool.jar
lib/aspectj/aspectjweaver.jar、aspectjrt.jar
lib/cglib/cglib-nodep-2.1_3.jar
lib/j2ee/common-annotations.jar
lib/log4j/log4j-1.2.15.jar
Struts
下载struts-1.3.8-lib.zip,需要使用到解压目录下的所有jar,建议把jstl-1.0.2.jar和standard-1.0.2.jar更换为1.1版本。Spring中已经存在一个antlr-2.7.6.jar,所以把struts中的antlr-2.7.2.jar删除,避免jar冲突。
数据库驱动jar
根据所使用的数据库来选择
------------------------Spring2.x+Hibernate3.x-----------------------
Hibernate的配置文件可以不写,完全依靠Spring的Bean.xml文件进行配置,由Spring来构造sessionFactory和TransactionManager的实例
以Student程序为例
1. 载入jar包
2. 创建数据表
CREATETABLE `student` (
`id` int(11) NOT NULL auto_increment,
`name` varchar(20) NOT NULL,
PRIMARY KEY (`id`)
)ENGINE=InnoDB DEFAULT CHARSET=gbk;
3. 创建javabean和*.hbm.xml
publicclass Student implements java.io.Serializable {
private Integer id;
private String name;
public Integer getId() {
returnthis.id;
}
publicvoid setId(Integer id) {
this.id = id;
}
public String getName() {
returnthis.name;
}
publicvoid setName(String name) {
this.name = name;
}
}
<hibernate-mapping>
<class name="com.bean.Student"table="student" catalog="hibernate">
<id name="id"type="java.lang.Integer">
<column name="id"/>
<generator class="native"/>
</id>
<property name="name"type="java.lang.String">
<column name="name"length="20" not-null="true"/>
</property>
</class>
</hibernate-mapping>
4. 配置Bean.xml文件
(1) 配置数据源,目地是为了提高性能,减少链接对象的创建数量
<bean id="dataSource"
class="org.apache.commons.dbcp.BasicDataSource">
<property name="driverClassName"
value="com.mysql.jdbc.Driver">
</property>
<property name="url"
value="jdbc:mysql://localhost:3306/itcast?useUnicode=true&characterEncoding=UTF-8">
</property>
<property name="username"value="root"></property>
<property name="password"value="root"></property>
</bean>
在url中,应该是写为useUnicode=true&characterEncoding=UTF-8,但是&号是xml文件中的特殊字符,要对它进行转义为&characterEncoding=UTF-8指定了客户端使用什么编码与数据库进行通讯
(2) 创建sessionFactory
其中所使用的LocalSessionFactoryBean类除了会创建一个单例的sessionFactory对象之外,专门集成Hibernate做一些额外的工作,例如:接管Hibernate的事务管理
<bean id="sessionFactory"class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
<property name="dataSource" ref="dataSource"></property>
<property name="hibernateProperties">
<value>
hibernate.dialect=org.hibernate.dialect.MySQL5Dialect
hibernate.hbm2ddl.auto=update
hibernate.show_sql=true
hibernate.format_sql=true
</value>
</property>
<property name="mappingResources">
<list>
<value>com/bean/Student.hbm.xml</value>
</list>
</property>
</bean>
hibernateProperties配置Hibernate的属性,mappingResources指定Hibernate的映射档
(3) 创建DAO类的实例
<bean id="studentDAO"class="com.dao.StudentDAO">
<property name="sessionFactory"ref="sessionFactory">
</property>
</bean>
(4) 事务管理
<bean id="txManager"class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory"/>
</bean>
<tx:annotation-driven transaction-manager="txManager"/>
Spring为我们开启事务,底层是需要使用session来创建,这里使用的HibernateTransactionManager是关于Hibernate的事务管理器,因为它要得到session,通过session来开启一个事务
需要通过sessionFactory来能得到session,所以就会有一个sessionFactory属性,用前边创建好的sessionFactory实例给该属性注入进去
5. 创建DAO类,抽取接口(并添加事务管理的注解)
类
publicclass StudentDAO extends HibernateDaoSupport implements StudentDAOService{
publicvoid saveStudent(Student s){
HibernateTemplate template=this.getHibernateTemplate();
template.save(s);
}
}
界面
publicinterface StudentDAOService {
}
6. 测试类
publicstaticvoid main(String[] args) {
Student s=new Student();
s.setName("Shiki");
ApplicationContext ctx=new ClassPathXmlApplicationContext("Bean.xml");
StudentDAO dao=(StudentDAO)ctx.getBean("studentDAO");
dao.saveStudent(s);
}
--------------------------Spring 2.x+Struts1.x-----------------------
Struts与Spring集成方案共有两种方式:
1. Struts集成Spring
2. Spring集成Struts
------------------------Struts 1.x集成Spring2.x----------------------
1. 载入jar包
2. 在web容器中实例化spring容器(利用Listener监听器来实例化)
在web.xml文件中加入
<!-- 指定spring的配置文件,默认从web根目录寻找配置文件,我们可以通过spring提供的classpath:前缀指定从类路径下寻找 -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:beans.xml</param-value>
</context-param>
<!-- 对Spring容器进行实例化 -->
<listener>
<listener-class>
org.springframework.web.context.ContextLoaderListener
</listener-class>
</listener>
Listener在启动的时候会向application中放入Spring容器实例,也就是可以从application获取到Spring的容器实例,从而调用getBean方法
3. 在web容器中配置struts,
<servlet>
<servlet-name>struts</servlet-name>
<servlet-class>org.apache.struts.action.ActionServlet</servlet-class>
<init-param>
<param-name>config</param-name>
<param-value>/WEB-INF/struts-config.xml</param-value>
</init-param>
<load-on-startup>0</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>struts</servlet-name>
<url-pattern>*.do</url-pattern>
</servlet-mapping>
4. 创建Action,并为该Action在struts-config.xml文件中添加配置信息
publicclass PersonAction extends Action {
@Override
public ActionForward execute(ActionMapping mapping, ActionFormarg1, HttpServletRequest request, HttpServletResponse response) throws Exception {
WebApplicationContext ctx=
WebApplicationContextUtils.getWebApplicationContext(this.getServlet().getServletContext());
PersonService personService=
(PersonService)ctx.getBean("personService");
request.setAttribute("person",personService.getPersons());
return mapping.findForward("list");
}
}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE struts-config PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration1.1//EN"
"http://jakarta.apache.org/struts/dtds/struts-config_1_1.dtd">
<struts-config>
<action-mappings>
<action path="/person"
type="com.Spring2.x_Struts1.x.PersonAction.java"
validate="false">
<forward name="list" path="/WEB-INF/page/personlist.jsp"></forward>
</action>
</action-mappings>
</struts-config>
5. 在Spring配置文件中构造bean实例
<bean id="personService" class="com.jdbcTemplate.PersonServiceBean">
<property name="dataSource" ref="dataSource"/>
</bean>
引用com.jdbcTemplate包中的Person、PersonService及PersonServiceBean
6. 编写personlist.jsp页面,在页面中迭代出person中的数
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<%@taglib uri="http://struts.apache.org/tags-bean"prefix="bean" %>
<%@taglib uri="http://struts.apache.org/tags-logic"prefix="logic" %>
<%@ taglib uri="http://struts.apache.org/tags-html"prefix="html" %>
<%@ taglib uri="http://struts.apache.org/tags-tiles"prefix="tiles" %>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
</head>
<body>
<logic:iterate id="person"name="person" scope="request">
id=<bean:writename="id"/>,name=<bean:write name="name"/>
</logic:iterate>
</body>
</html>
-----------------------Spring 2.x集成Struts1.x -----------------------
1. 试想,在Struts集成Spring的Action中,
WebApplicationContext ctx=
WebApplicationContextUtils.getWebApplicationContext(this.getServlet().getServletContext());
PersonService personService=
(PersonService)ctx.getBean("personService");
这段复杂的代码,我们是否可以使用Spring的依赖注入功能来实现呢?
所以,我们打算将Action交给Spring管理,然后Action就不再由Struts框架给我们生成,而是由Spring容器帮我们创建
把Action交给Spring管理后,我们可以使用依赖注入在action中注入业务层的bean。
但要注意一点,要确保Action的path属性值与bean的名称相同。
Struts配置:
<action path="/person/list" ...>
</action>
Spring 配置:
<bean name="/person/list" class="cn.itcast.web.action.PersonAction"/>
默认配置bean的名称为id属性,但是/属于特殊字符,所以只能使用name属性来配置
2. 在struts配置文件中添加进spring的请求控制器,该请求控制器会先根据action的path属性值到spring容器中寻找跟该属性值同名的bean。如果寻找到即使用该bean处理用户请求
<controller>
<set-propertyproperty="processorClass"
value="org.springframework.web.struts.DelegatingRequestProcesso"/>
</controller>
请求管理器org.springframework.web.struts.DelegatingRequestProcessor的处理过程。
当处理器根据/person/list.do这个名称到Spring容器中找不到这个bean的时候,就会交给Struts进行处理了,Struts就会根据action中的type属性将Action对象new出来,放在缓存里边去
----------------------Spring 3.0 Security安全框架----------------------
1. 加入Security的jar包
2. 修改web.xml配置文件
<?xml version="1.0"encoding="UTF-8"?>
<web-app version="2.5"xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
<!-- 安全框架的配置 -->
<filter>
<filter-name>springSecurityFilterChain</filter-name><!-- 名子不能更换 -->
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
<filter-name>springSecurityFilterChain</filter-name>
<url-pattern>/*</url-pattern><!-- 过滤所有的请求操作 -->
</filter-mapping>
<!-- Spring的配置 ,加载Spring的配置文件-->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/applicationContext.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
</web-app>
3. 在Spring的配置文件中加入Security的命名空间
<?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:security="http://www.springframework.org/schema/security"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/security
http://www.springframework.org/schema/security/spring-security-3.0.xsd">
</beans>
4. 配置Spring配置文件
<!-- 安全级别的设定 -->
<security:http auto-config="true">
<security:intercept-url pattern="/**" access="ROLE_UESR"/>
<!-- 要求登录的用户必须拥有某个角色 -->
</security:http>
security:http表示所有与web命名空间相关的上级,
intercept-url表示要拦截的url形式,比如
<intercept-urlpattern=”/secure/**” access=”isAuthenticated()” />
表示根目录下的secure目录需要经过验证后才能访问的。
Pattern用来匹配请求当中的内容,access表示模式哪一种权限的使用者
form-login是Spring Security自动为你生成的一个简陋的登录页面,即使你没有创建任何登录页面,当然你也可以修改,但不建议你修改,因为你可以不使用默认的,可以采用如下方式:<security:form-login login-page="/login.jsp"/>自定义一个登录页面。
容易出错的地方
先看一个小例子:
<http use-expressions="true">
<intercept-url pattern="/secure/extreme/**"
access="hasRole('ROLE_SUPERVISOR')"/>
<intercept-url pattern="/secure/**"access="isAuthenticated()" />
<!—
Disable web URIauthorization, as we're using <global-method-security> and have @Securedthe services layer instead
<intercept-urlpattern="/listAccounts.html" access="isRememberMe()" />
<intercept-urlpattern="/post.html" access="hasRole('ROLE_TELLER')" />
-->
<http use-expressions="true">
表示这里的配置可以使用一种表达式,这种表达式就是类似于isAuthenticated()这样的写法,在后面会看到与这种写法不一样但同样可以达到相同效果的写法。
在Spring翻译文档(也是翻译自官方文档)或英文官方文档中,给出的与上面例子功能相似的说明大概是这样的:
<httpauto-config='true'>
<intercept-url pattern="/**" access="ROLE_USER" />
</http>
注意到与上面例子不同的地方了吗?
还是注意第一行,这回不是<httpuse-expressions=”true”>而是<http auto-config=’true’>了,而下面的配置
<intercept-urlpattern=”/**” access=”ROLE_USER” />也与上面的写法不同,事实上如果是<httpuse-expressions=”true”>的话,这句access=”ROLE_USER”就是错的,正确的写法就应该为:hasRole(‘ROLE_USER’)。
这不得不说这是Spring Security的文档与例子不搭配的一个低级错误,因为当一个使用者在打开例子又看到文档说明时,他往往不知道这两者有何区别,就如同我刚使用的时候一样,我在使用<httpuse-expressions=”true”>的同时,将access=”ROLE_USER”这种写法也写了进来,结果可想而知,报了错!报错信息诸如:
org.apache.jasper.JasperException:java.lang.IllegalArgumentException: Failed to evaluate
expression 'ROLE_SUPERVISOR'
就是说use-expressions这种表示法是不认识access=”ROLE_USER”这种写法的,另外,当使用了<httpuse-expressions=”true”>时,一定要在配置中至少有一个符合use-expressions的表示法,否则就会报类似如下错误:
org.springframework.expression.spel.SpelEvaluationException:EL1008E:(pos 0): Field or property
'ROLE_SUPERVISOR' cannot befound on object of type 'org.springframework.security.web.access.
expression.WebSecurityExpressionRoot'
<!-- 认证管理 -->
<security:authentication-manager alias="auther_manager">
<security:authentication-provider>
<!-- 进行密码的加密操作 -->
<security:password-encoder hash="md5"/>
<security:user-service>
<security:user name="admin" password="21232f297a57a5a743894a0e4a801fc3"authorities="ROLE_UESR"/><!-- 通过用户名,设定角色信息 -->
</security:user-service>
</security:authentication-provider>
</security:authentication-manager>
<!-- 添加提示信息的显示操作 -->
<bean id="messageSource" class="org.springframework.context.support.ReloadableResourceBundleMessageSource">
<property
name="basename" value="classpath:message_zh_CN">
</property>
</bean>
<!-- 登录的错误的信息,会作为异常存放在sesssion中,key : SPRING_SECURITY_LAST_EXCEPTION -->、
5. 在这个例子中
<security:user name="admin" password="21232f297a57a5a743894a0e4a801fc3"authorities="ROLE_UESR"/> security:user卷标中的password属性,对密码的信息进行了加密--MD5加密
在登录页面中输入密码后,安全框架自动对密码进行MD5加密,然后在与配置文件中的密码进行比较
那么如何生成一个字符串的MD5码呢
import org.springframework.security.authentication.encoding.Md5PasswordEncoder;
import sun.security.provider.MD5;
publicclass Test {
publicstaticvoid main(String[] args) {
Md5PasswordEncoder encoder=new Md5PasswordEncoder();
String s = encoder.encodePassword("xxx", null);
System.out.println(s);
}
}
6. 创建index.jsp檔,用来测试一下Spring的安全框架是否配置成功
<%@ page language="java"import="java.util.*" pageEncoding="gbk"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTDHTML 4.01 Transitional//EN">
<html>
<head>
</head>
<body>
这是首页 ,欢迎你!!!!
<br>
经历Spring Security框架后,Session的属性
<br>
${sessionScope.SPRING_SECURITY_LAST_USERNAME}
${sessionScope.SPRING_SECURITY_CONTEXT}
</body>
</html>
当有请求到来的时候,Spring Security框架开始查找要访问的资源是否存在访问的权利
要访问index.jsp页面时,自动产生一个登录的页面,如果登录成功,也就是表示具有访问的权利,就可以连结到index.jsp页面了
7. 由自己来编写login.jsp登录页面,不使用安全框架所生成的登录页面
在浏览器中进入登录页面后,右键选择”查看源文件”,可以看到由安全框架所生成的登录页面的一些信息,例如form中action的配置,input中name的写法等
<html>
<head>
</head>
<body>
<h3>用户的登录</h3>
<font color=red>
</font><br>
<form action="/SS/j_spring_security_check" method="post">
用户名:<input type="text" name="j_username"><br>
密码:<input type="password" name="j_password"><br>
<input type="submit" value="登录">
</form>
</body>
</html>
action="/SS/j_spring_security_check"
name="j_username"
这些信息都有由安全框架规定的格式,所以我们就可以依靠此配置信息来创建基于安全框架的自己的登录页面
在Spring的配置文件中配置有关login.jsp登录页面的内容
<!-- 安全级别的设定 -->
<security:http auto-config="true">
<security:form-login login-page="/login.jsp"/>
<!-- 配置如果没有权限,定位登录页面 -->
<security:intercept-url pattern="/login.jsp" filters="none"/>
<!-- 将登录的页面,设置成为非过滤状态 -->
<security:intercept-url pattern="/**" access="ROLE_UESR"/>
<!-- 要求登录的用户必须拥有某个角色 -->
</security:http>
创建login.jsp
<%@ page language="java" import="java.util.*" pageEncoding="gbk"%>
<html>
<head>
</head>
<body>
<h3>用户的登录</h3>
<font color=red>
${sessionScope.SPRING_SECURITY_LAST_EXCEPTION.message}</font><br>
<form action="${pageContext.request.contextPath }/j_spring_security_check"method="post">
用户名:<input type="text" name="j_username"><br>
密码:<input type="password" name="j_password"><br>
<input type="submit" value="登录">
</form>
</body>
</html>
再次进行测试,但是会发现一个新的问题
如果输入的内容不正确,不会产生任何提示,那么如何显示错误信息呢?
这样就需要使用安全框架所提供的”国际化输出”功能来实现
安全框架中把所有的错误都绑定为了一个异常,而且提供了一个国际化文件,该文件的位置在
spring-security-core-3.0.2.RELEASE/org/springframework/security/路径下
会有很多不同语言版本的资源文件
其中messages_zh_CN.properties就是中文的资源文件
找到该档后,继续对Spring的配置文件添加国际化输出的内容
<!-- 添加提示信息的显示操作 -->
<bean id="messageSource" class="org.springframework.context.support.ReloadableResourceBundleMessageSource">
<property name="basename"value="classpath:message_zh_CN">
</property>
<property name="basename"value="classpath:org.springframework.security/message_zh_CN">
</property>
</bean>
<!-- 登录的错误的信息,会作为异常存放在sesssion中 ,key :SPRING_SECURITY_LAST_EXCEPTION -->
在登录页面中来获取该国际化输出信息,用一个表达式来封装,如果登录不成功就提示出来
<font color=red>
${sessionScope.SPRING_SECURITY_LAST_EXCEPTION.message}
</font><br>
但是又会有一个问题出现,如果用户名或密码错误,提示的信息是”坏的凭证”,那么用户有如何能看得懂呢?
可以自己重写编写一个配置文件,但是key必须与原先的资源文件中使用的key对应起来,且必须明白其key的含义,最后将key与自己编写的中文的value对应起来,放入新的档中
再Spring的配置文件中,修改配置文件的路径,例如:
message_zh_CN.properties文件
AbstractUserDetailsAuthenticationProvider.badCredentials=/u60A8/u65E0/u6743/u8BBF/u95EE/u8BE5/u8D44/u6E90,/u8BF7/u767B/u5F55
将该文件放置在src目录下,配置其路径为:
<property name="basename" value="classpath:message_zh_CN"></property>
8. 将配置文件中配置的用户名和密码等信息保存在数据库中,以便实现对数据库的操作
创建数据表,添加资料
创建数据库表时要注意,users表和authorities表是安全框架中自带的用户表和权限表,表中的字段名一定不能自定义,要与安全框架中指定的名称相同,否则会出现不匹配的问题
/*用户表*/
create table users(
username varchar(50) primary key,
password varchar(50) not null,
enable` tinyint(1) not null,
);
/*权限表*/
create table authorities(
username varchar(50) not null,
authority varchar(50) not null,
constraint fk_author_users foreign key (username) referencesusers(username)
);
/*创建索引*/
create unique indexix_auth_username on authorities(username,authority)
/*插入相关的资料*/
insert intousers(username,password,enable) values('admin','21232f297a57a5a743894a0e4a801fc3',1);
insert intousers(username,password,enable) values('user','ee11cbb19052e40b07aac0ca060c23ee',1);
insert into authorities values('admin','ROLE_ADMIN');
insert into authorities values('user','ROLE_USER');
在Spring的配置文件中,创建数据源(注意建表时字段的名子)
<!-- 创建数据源 -->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource>
<!-- 更换成为Spring的数据源管理 -->
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/test"/>
<property name="username" value="root"></property>
<property name="password" value="1234"></property>
</bean>
修改Spring的配置文件,去掉”认证管理”部分,添加”新的认证管理”
存储数据是,注意是否使用MD5的加密操作,配合Spring的认证权限的设置
<!--新的认证管理(新的数据库的验证) -->
<security:authentication-manager alias="manager">
<security:authentication-provider>
<security:password-encoder hash="md5" />
<security:jdbc-user-servicedata-source-ref="dataSource"/>
</security:authentication-provider>
</security:authentication-manager>
修改index.jsp页面的内容
<%@ page language="java" import="java.util.*" pageEncoding="gbk"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
</head>
<body>
这是首页 ,欢迎你!!!!
<br>
<a href="admin.jsp">进入admin.jsp页面</a>
</body>
</html>
添加一个admin.jsp页面
<%@ page language="java" import="java.util.*" pageEncoding="gbk"%>
<html>
<head>
</head>
<body>
this is Admin page
</body>
</html>
修改Spring的配置文件,重新设定安全级别,当用户通过登录进入index.jsp后,只有ROLE_ADMIN权限的用户可以进入admin.jsp,其他权限均不可以
<!-- 安全级别的设定 -->
<security:httpauto-config="true">
<!-- 配置如果没有权限,定位登录页面 -->
<security:form-login login-page="/login.jsp" />
<!-- 将登录的页面,设置成为非过滤状态 -->
<security:intercept-url pattern="/login.jsp" filters="none"/>
<!-- 该页面不进行权限的验证 -->
<security:intercept-url pattern="/admin.jsp" access="ROLE_ADMIN"/>
<!--必须是admin用户才能访问 -->
<security:intercept-url pattern="/**" access="ROLE_USER"/>
<!-- 过滤所有的请求 -->
</security:http>
所以按照上述的过程配置后,运行会出错
这是由于没有配置首页,也就是index.jsp对访问者的权限操作,重新修改配置文件中的安全级别的设定
<!-- 安全级别的设定 -->
<security:httpauto-config="true">
<!-- 配置如果没有权限,定位登录页面 -->
<security:form-login login-page="/login.jsp" />
<!-- 将登录的页面,设置成为非过滤状态 -->
<security:intercept-url pattern="/login.jsp" filters="none"/>
<!-- 该页面不进行权限的验证 -->
<security:intercept-url pattern="/admin.jsp" access="ROLE_ADMIN"/>
<!--必须是admin用户才能访问 -->
<security:intercept-url pattern="/index.jsp" access="ROLE_ADMIN,ROLE_USER"/>
<!--首页访问的权限 -->
<security:intercept-url pattern="/**" access="ROLE_USER"/>
<!-- 过滤所有的请求 -->
</security:http>
运行程序后,如果该用户没有权限访问admin.jsp页面,就会报一个异常:
HTTP Status 403 – Access is denied
那么我们可以自己定义一个accessAdmin.jsp错误页面来提示使用者没有权限登录
<%@ page language="java" import="java.util.*" pageEncoding="gbk"%>
<html>
<head>
</head>
<body>
您的访问权限不足,请更换身份登录
</body>
</html>
在Spring的配置文件中,要配置该错误页面
修改安全级别的设定
<security:http
auto-config="true" access-denied-page="/accessAdmin.jsp">
……
</security:http>
access-denied-page="/accessAdmin.jsp"表示如果用户访问该接口不具备相应的权限,按么就会跳转到access-denied-page属性所指定的页面中
9. 在页面中获取登录用户的信息
有两种方式:使用Shared组件和使用卷标的方式
使用Shared组件
<%@ page language="java" import="java.util.*" pageEncoding="gbk"%>
<%@page import="org.springframework.security.core.context.SecurityContext"%>
<%@page import="org.springframework.security.core.context.SecurityContextHolder"%>
<%@page import="org.springframework.security.core.Authentication"%>
<%@page import="org.springframework.security.core.userdetails.UserDetails"%>
<html>
<head>
</head>
<body>
This is admin page. <br>
<%
SecurityContextsecContext=SecurityContextHolder.getContext();//获取SpringSecurity的上下文
Authentication auth=secContext.getAuthentication();
//获取认证的对象
Object p=auth.getPrincipal();
//获取主体物件
String username="";
if(p instanceofUserDetails){//UserDetails这个类是属于权限认证内容的,是Spring安全框架给提供的
username=((UserDetails)p).getUsername();
}else{
username=p.toString();
}
out.print(username);
%>
</body>
</html>
使用标签的方式
<%@ page language="java" import="java.util.*" pageEncoding="gbk"%>
<%@taglib uri="http://www.springframework.org/security/tags"prefix="sec" %>
<html>
<head>
</head>
<body>
This is admin page. <br>
欢迎:<sec:authenticationproperty="name"></sec:authentication>
</body>
</html>
10. 根据使用者权限的不同,让不同权限的使用者所看到的页面信息也不同
例如在这个例子中,ROLE_ADMIN权限的用户登录后,可以点击”进入admin.jsp页面”这个连接进入到admin.jsp页面中,而ROLE_USER权限的用户就不具备此功能,所以就不能让此权限的用户登录后看到”进入admin.jsp页面”这个超链接
所以要使用AOP面向切面的技术,实现对方法的拦截控制
先使用Spring安全框架所提供的卷标来实现此功能,主要用于不同的权限建造不同组件的时候,大多数使用这个,可以直接在页面中配置,我们来修改admin.jsp文件
<%@ page language="java" import="java.util.*" pageEncoding="gbk"%>
<%@taglib uri="http://www.springframework.org/security/tags"prefix="sec" %>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
</head>
<body>
这是首页 ,欢迎你!!!!
<br>
<sec:authorize ifAllGranted="ROLE_ADMIN">
<a href="admin.jsp">进入admin.jsp页面</a>
</sec:authorize>
<!-- 表示只有该权限的用户登录后,才能在页面中显示卷标对中所包含的内容 -->
</body>
</html>
11. Spring安全框架的web架构是完全基于Servlet的过滤器,其中安全的过滤器链可以管理request和response,还可以制作一批次的安全组件
<!-- 配置Spring 过滤器链 -->
<security:filter-chain-map path-type="ant">
<security:filter-chain pattern="/webServices/**"filters="
securityContextPersistenceFilterWithASFalse,
basicAuthenticationFilter,
"/>
</security:filter-chain-map>
Pattern属性表示要对什么作为请求,filters来配置一些标准型过滤器
12. AOP技术的使用,方法权限的控制
抽象类的使用
在实际的类中,pig继承类animal
Timer任务调度器
每月前10天每隔15分钟触发一次
Spring的事务配置
Spring配置文件中关于事务配置总是由三个组成部分,分别是DataSource、TransactionManager和代理机制这三部分,无论哪种配置方式,一般变化的只是代理机制这部分。
DataSource、TransactionManager这两部分只是会根据数据访问方式有所变化,比如使用Hibernate进行数据访问 时,DataSource实际为SessionFactory,TransactionManager的实现为 HibernateTransactionManager。
具体如下图:
根据代理机制的不同,总结了五种Spring事务的配置方式,配置文件如下:
第一种方式:每个Bean都有一个代理
<?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:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
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
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd">
<bean id="sessionFactory"
class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
<property name="configLocation" value="classpath:hibernate.cfg.xml" />
<property name="configurationClass" value="org.hibernate.cfg.AnnotationConfiguration" />
</bean>
<!-- 定义事务管理器(声明式的事务) -->
<bean id="transactionManager"
class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory" />
</bean>
<!-- 配置DAO -->
<bean id="userDaoTarget" class="com.bluesky.spring.dao.UserDaoImpl">
<property name="sessionFactory" ref="sessionFactory" />
</bean>
<bean id="userDao"
class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
<!-- 配置事务管理器 -->
<property name="transactionManager" ref="transactionManager" />
<property name="target" ref="userDaoTarget" />
<property name="proxyInterfaces" value="com.bluesky.spring.dao.GeneratorDao" />
<!-- 配置事务属性 -->
<property name="transactionAttributes">
<props>
<prop key="*">PROPAGATION_REQUIRED</prop>
</props>
</property>
</bean>
</beans>
第二种方式:所有Bean共享一个代理基类
<?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:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
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
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd">
<bean id="sessionFactory"
class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
<property name="configLocation" value="classpath:hibernate.cfg.xml" />
<property name="configurationClass" value="org.hibernate.cfg.AnnotationConfiguration" />
</bean>
<!-- 定义事务管理器(声明式的事务) -->
<bean id="transactionManager"
class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory" />
</bean>
<bean id="transactionBaseProxy"
class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean"
lazy-init="true" abstract="true">
<!-- 配置事务管理器 -->
<property name="transactionManager" ref="transactionManager" />
<!-- 配置事务属性 -->
<property name="transactionAttributes">
<props>
<prop key="*">PROPAGATION_REQUIRED</prop>
</props>
</property>
</bean>
<!-- 配置DAO -->
<bean id="userDaoTarget" class="com.bluesky.spring.dao.UserDaoImpl">
<property name="sessionFactory" ref="sessionFactory" />
</bean>
<bean id="userDao" parent="transactionBaseProxy" >
<property name="target" ref="userDaoTarget" />
</bean>
</beans>
第三种方式:使用拦截器
<?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:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
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
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd">
<bean id="sessionFactory"
class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
<property name="configLocation" value="classpath:hibernate.cfg.xml" />
<property name="configurationClass" value="org.hibernate.cfg.AnnotationConfiguration" />
</bean>
<!-- 定义事务管理器(声明式的事务) -->
<bean id="transactionManager"
class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory" />
</bean>
<bean id="transactionInterceptor"
class="org.springframework.transaction.interceptor.TransactionInterceptor">
<property name="transactionManager" ref="transactionManager" />
<!-- 配置事务属性 -->
<property name="transactionAttributes">
<props>
<prop key="*">PROPAGATION_REQUIRED</prop>
</props>
</property>
</bean>
<bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
<property name="beanNames">
<list>
<value>*Dao</value>
</list>
</property>
<property name="interceptorNames">
<list>
<value>transactionInterceptor</value>
</list>
</property>
</bean>
<!-- 配置DAO -->
<bean id="userDao" class="com.bluesky.spring.dao.UserDaoImpl">
<property name="sessionFactory" ref="sessionFactory" />
</bean>
</beans>
第四种方式:使用tx卷标配置的拦截器
<?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:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
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
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd">
<context:annotation-config />
<context:component-scan base-package="com.bluesky" />
<bean id="sessionFactory"
class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
<property name="configLocation" value="classpath:hibernate.cfg.xml" />
<property name="configurationClass" value="org.hibernate.cfg.AnnotationConfiguration" />
</bean>
<!-- 定义事务管理器(声明式的事务) -->
<bean id="transactionManager"
class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory" />
</bean>
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="*" propagation="REQUIRED" />
</tx:attributes>
</tx:advice>
<aop:config>
<aop:pointcut id="interceptorPointCuts"
expression="execution(* com.bluesky.spring.dao.*.*(..))" />
<aop:advisor advice-ref="txAdvice"
pointcut-ref="interceptorPointCuts" />
</aop:config>
</beans>
第五种方式:全注解
<?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:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
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
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd">
<context:annotation-config />
<context:component-scan base-package="com.bluesky" />
<tx:annotation-driven transaction-manager="transactionManager"/>
<bean id="sessionFactory"
class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
<property name="configLocation" value="classpath:hibernate.cfg.xml" />
<property name="configurationClass" value="org.hibernate.cfg.AnnotationConfiguration" />
</bean>
<!-- 定义事务管理器(声明式的事务) -->
<bean id="transactionManager"
class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory" />
</bean>
</beans>
此时在DAO上需加上@Transactional注解,如下:
package com.bluesky.spring.dao;
import java.util.List;
import org.hibernate.SessionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.orm.hibernate3.support.HibernateDaoSupport;
import org.springframework.stereotype.Component;
import com.bluesky.spring.domain.User;
@Transactional
@Component("userDao")
public class UserDaoImpl extends HibernateDaoSupport implements UserDao {
public List<User> listUsers() {
return this.getSession().createQuery("from User").list();
}
}
注意的几点: 1 @Transactional 只能被应用到public方法上, 对于其它非public的方法,如果标记了@Transactional也不会报错,但方法没有事务功能. 2 默认情况下,一个有事务方法, 遇到RuntiomeException 时会回滚 . 遇到 受检查的异常 是不会回滚 的. 要想所有异常都回滚,要加上 @Transactional( rollbackFor={Exception.class,其它异常}) . @Transactional 的所有可选属性如下:
事务的隔离级别 有如下可选: 可以去看spring源码 : org.springframework.transaction.annotation.Isolation 我的"隔离级别"相关文章 有不明白的,可以去看看.
事务的传播属性 ,有如下可选 可以去看spring源码 : org.springframework.transaction.annotation.Propagation
|
隔离级别:
数据库提供了四种事务隔离级别, 不同的隔离级别采用不同的锁类开来实现.
在四种隔离级别中, Serializable的级别最高, Read Uncommited级别最低.
大多数数据库的默认隔离级别为: Read Commited,如Sql Server , Oracle.
少数数据库默认的隔离级别为Repeatable Read, 如MySQL InnoDB存储引擎
即使是最低的级别,也不会出现 第一类 丢失 更新问题 .
丢失 更新 :
当两个或多个事务选择同一行,然后基于最初选定的值更新该行时,会发生丢失更新问题。每个事务都不知道其它事务的存在。最后的更新将重写由其它事务所做的更新,这将导致数据丢失。
例:
事务A和事务B同时修改某行的值,
1.事务A将数值改为1并提交
2.事务B将数值改为2并提交。
这时数据的值为2,事务A所做的更新将会丢失。
解决办法:对行加锁,只允许并发一个更新事务。
脏读: 一个事务读到另一个事务未提交的更新数据
例:
1.Mary的原工资为1000, 财务人员将Mary的工资改为了8000(但未提交事务)
2.Mary读取自己的工资 ,发现自己的工资变为了8000,欢天喜地!
3.而财务发现操作有误,回滚了事务,Mary的工资又变为了1000, 像这样,Mary记取的工资数8000是一个脏资料。
不可重复读: 在同一个事务中,多次读取同一数据,返回的结果有所不同. 换句话说就是,后续读取可以读到另一个事务已提交的更新数据. 相反"可重复读"在同一事务多次读取数据时,能够保证所读数据一样,也就是后续读取不能读到另一事务已提交的更新数据.
例:
1.在事务1中,Mary 读取了自己的工资为1000,操作并没有完成
2.在事务2中,这时财务人员修改了Mary的工资为2000,并提交了事务.
3.在事务1中,Mary 再次读取自己的工资时,工资变为了2000
解决办法:如果只有在修改事务完全提交之后才可以读取数据,则可以避免该问题。
幻读: 一个事务读取到另一个事务已提交的insert数据.
例:
第一个事务对一个表中的数据进行了修改,这种修改涉及到表中的全部资料行。同时 ( 此时第一事务还未提交 ) ,第二个事务向表中插入一行新资料。这时第一个事务再去读取表时 , 发现表中还有没有修改的资料行,就好象发生了幻觉一样。