超详细Spring学习笔记,深入底层

2 篇文章 0 订阅

文章目录

一、Spring概述

​ Spring是一个开源框架。Spring为简化企业级开发而生,使用Spring开发可以将Bean对象,Dao组件对象,Service组件对象等交给Spring容器来管理,这样使得很多复杂的代码在Spring中开发却变得非常的优雅和简洁,有效的降低代码的耦合度,极大的方便项目的后期维护、升级和扩展。Spring是一个IOC(DI)和AOP容器框架。

1.1 Spring的优良特性:

[1]非侵入式:基于Spring开发的应用中的对象可以不依赖于Spring的API。

[2]控制反转:IOC——Inversion of Control,指的是将对象交给Spring容器去创建。

[3]依赖注入:DI——Dependency Injection,是指依赖的对象不需要手动调用setXX方法去设置,而是通过配置赋值。

[4]面向切面编程:Aspect Oriented Programming——AOP面向切面编程。

[5]容器:Spring是一个容器,因为它包含并且管理应用对象的生命周期。

[6]组件化:Spring使用简单的组件配置组合成一个复杂的应用。在Spring中可以使用XML和Java注解组合这些对象。

[7]一站式:在IOC和AOP的基础上可以整合各种企业应用的开源框架和优秀的第三方类库(实际上Spring 自身也提供了表述层的SpringMVC和持久层的Spring JDBC)。

1.2 Spring模块介绍

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-geUyZZgb-1602896010174)(C:\Users\77161\Desktop\ssm-img\Spring模块介绍.jpg)]

Core Container(核心容器):

  • Beans:负责Bean工厂中Bean的装配,所谓Bean工厂即是创建对象的工厂,Bean的装配也就是对象的创建工作;
  • Core:这个模块即是负责IOC(控制反转)最基本的实现;
  • Context:Spring的IOC容器,因大量调用Spring Core中的函数,整合了Spring的大部分功能。Bean创建好对象后,由Context负责建立Bean与Bean之间的关系并维护。所以也可以把Context看成是Bean关系的集合;
  • SpEl:即Spring Expression Language(Spring表达式语言);

Data Access/Integration(数据访问/集成):

  • JDBC:对JDBC的简单封装;
  • ORM:支持数据集成框架的封装(如Mybatis,Hibernate);
  • OXM:即Object XML Mapper,它的作用是在Java对象和XML文档之间来回转换;
  • JMS:生产者和消费者的消息功能的实现;
  • Transations:事务管理,不多BB;

Web:

  • WebSocket:提供Socket通信,web端的的推送功能;
  • Servlet:Spring MVC框架的实现;
  • Web:包含web应用开发用到Spring框架时所需的核心类,包括自动载入WebApplicationContext特性的类,Struts集成类、文件上传的支持类、Filter类和大量辅助工具类;
  • Portlet:实现web模块功能的聚合(如网站首页(Port)下面可能会有不同的子窗口(Portlet));

AOP:

  • 面向切面;

Aspects:

  • 同样是面向切面的一个重要的组成部分,提供对AspectJ框架的整合;

Instrumentation(设备):

  • 相当于一个检测器,提供对JVM以及对Tomcat的检测;

Messaging(消息):

  • Spring提供的对消息处理的功能;

Test(测试):

  • 我们在做单元测试时,Spring会帮我们初始化一些测试过程当中需要用到的资源对象;

二、IOC控制反转和&DI依赖注入

2.1 IOC(Inversion of Control):反转控制

​ 在应用程序中的组件需要获取资源时,传统的方式是组件主动的从容器中获取所需要的资源,在这样的模式下开发人员往往需要知道在具体容器中特定资源的获取方式,增加了学习成本,同时降低了开发效率。

​ 反转控制的思想完全颠覆了应用程序组件获取资源的传统方式:反转了资源的获取方向——改由容器主动的将资源推送给需要的组件,开发人员不需要知道容器是如何创建资源对象的,只需要提供接收资源的方式即可,极大的降低了学习成本,提高了开发的效率。这种行为也称为查找的被动形式。

2.2 DI(Dependency Injection):依赖注入

​ IOC的另一种表述方式:即组件以一些预先定义好的方式(例如:setter 方法)接受来自于容器的资源注入。相对于IOC而言,这种表述更直接。

2.3 IOC容器在Spring中的实现

在通过IOC容器读取Bean的实例之前,需要先将IOC容器本身实例化。

Spring提供了IOC容器的两种实现方式:

  1. BeanFactory:IOC容器的基本实现,是Spring内部的基础设施,是面向Spring本身的,不是提供给开发人员使用的。
  2. ApplicationContext:BeanFactory的子接口,提供了更多高级特性。面向Spring的使用者,几乎所有场合都使用ApplicationContext而不是底层的BeanFactory。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OEg6AALm-1602896010179)(C:\Users\77161\Desktop\ssm-img\ApplicationContext.jpg)]

2.4 ApplicationContext的主要实现类

ClassPathXmlApplicationContext:对应类路径下的XML格式的配置文件;

FileSystemXmlApplicationContext:对应文件系统中的XML格式的配置文件;

在初始化时就创建单例的bean,也可以通过配置的方式指定创建的Bean是多实例的。

2.5 ConfigurableApplicationContext

它是ApplicationContext的子接口,包含一些扩展方法;

refresh()和close()让ApplicationContext具有启动、关闭和刷新上下文的能力;

2.6 WebApplicationContext

专门为WEB应用而准备的,它允许从相对于WEB根目录的路径中完成初始化工作;

三、IOC之bean的基本配置

3.1 获取bean对象

通过id获取对象:

@Test
public void test1() {
    // 在使用Spring框架的时候,一定要先获取Spring容器对象( Spring IOC 容器对象 )
    // 在Spring中,容器对象由接口ApplicationContext表示
    // ClassPathXmlApplicationContext类表示从ClassPath类路径下加载xml配置文件创建Spring容器对象
    ApplicationContext applicationContext =
            new ClassPathXmlApplicationContext("applicationContext.xml");
    // getBean()从容器中根据id获取Bean对象,获取出来的对象是Object类型,需要进行强转
    Person person = (Person) applicationContext.getBean("p1");
    System.out.println(person);
}

通过类型获取对象:

@Test
public void test2() {
    ApplicationContext applicationContext =
            new ClassPathXmlApplicationContext("applicationContext.xml");
    /**
     * 通过具体的类型获取
     * 1 如果通过class类型找到唯一一个,就返回
     * 2 如果没有找到就报错
     * 3 如果通过class类型找到多个,也报错
     * 4 获取出来的对象直接是该类型
     */
    Person person = applicationContext.getBean(Person.class);
    System.out.println(person);
}

通过id和类型获取对象:

@Test
public void test2() {
    ApplicationContext applicationContext =
            new ClassPathXmlApplicationContext("applicationContext.xml");
   
    Person person = applicationContext.getBean("p1",Person.class);
    System.out.println(person);
}

3.2 给bean的属性赋值

3.2.1 通过setXxx( )给bean赋值

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OFGjEvmS-1602896010181)(C:\Users\77161\Desktop\ssm-img\通过set方法给bean赋值.jpg)]

3.2.2 通过构造器给bean赋值

  • 使用index属性:
<bean class="com.JavaEE.pojo.Person" id="p4">
    <!--public Person(Integer id, String name, String phone, Integer age)
        index表示参数的索引顺序,从零开始,value表示参数值,通过下标可以改变赋值顺序-->
    <constructor-arg index="0" value="4" />
    <constructor-arg index="2" value="phone属性" />
    <constructor-arg index="1" value="name属性" />
    <constructor-arg index="3" value="18" />
</bean>
  • 使用type属性和index属性:
<bean class="com.JavaEE.pojo.Person" id="p5">
    <!--使用index和type属性可以重载的构造器
        构造器1 Person(Integer id, String name, String phone, Integer age)
        构造器2 Person(Integer id, String name, Integer age,String phone)

        index 是参数索引,value 是参数值,type是参数类型
		此处匹配的是构造器1
    -->
    <constructor-arg index="0" value="5" type="java.lang.Integer" />
    <constructor-arg index="1" value="类型赋值" type="java.lang.String" />
    <constructor-arg index="2" value="110" type="java.lang.String"/>
    <constructor-arg index="3" value="120" type="java.lang.Integer" />
</bean>

3.2.3 给bean的级联属性赋值:

<bean class="com.JavaEE.pojo.Person" id="p14">
    <property name="id" value="14" />
    <!-- 在spring中,如果要对属性使用级联属性赋值.需要先给car属性赋值 -->
    <property name="car">
        <bean class="com.JavaEE.pojo.Car" id="car">
            <property name="name" value="拖拉机" />
            <property name="carNo" value="京C333333" />
        </bean>
    </property>
    <!-- 级联属性,即属性的属性。 -->
    <property name="car.name" value="GTR" />
</bean>
  • 给级联属性赋值时一定要先注入对象,再注入对象的属性。

3.2.4 P命名空间

为了简化XML文件的配置,越来越多的XML文件采用属性而非子元素配置信息。

Spring从2.5版本开始引入了一个新的p命名空间,可以通过<bean>元素属性的方式配置Bean的属性。

使用p命名空间后,基于XML的配置方式将进一步简化。

<!-- p名称空间的使用格式 p:属性名="值" ,相当于调用setXxx()方法 -->
<bean class="com.JavaEE.pojo.Person" id="p6"
    p:id="6" p:name="刘优" p:age="40" p:phone="电话"
/>

3.2.5 赋值时可以使用的值

字面量:

  1. 可以使用字符串表示的值,可以通过value属性或value子节点的方式指定;
  2. 基本数据类型及其封装类、String等类型都可以采取字面值注入的方式;
  3. 若字面值中包含特殊字符,可以使用<![CDATA[]]>把字面值包裹起来;

null值:

<bean class="com.JavaEE.pojo.Person" id="p7">
    <property name="id" value="7" />
    <!-- 将name赋于null值 -->
    <property name="name" >
    <!-- null标签表示null值 -->
        <null></null>
    </property>
    <!-- 若不给存在的属性赋值,也会自动填上null值 -->
</bean>

外部已声明的bean:

<bean class="com.JavaEE.pojo.Car" id="c1">
    <property name="name" value="GTR" />
    <property name="carNo" value="京B66666" />
</bean>

<bean class="com.JavaEE.pojo.Person" id="p8">
    <property name="id" value="8" />
    <property name="name" value="强子" />
    <!-- ref表示引用 -->
    <property name="car" ref="c1" />
</bean>

内部bean:

​ bean实例仅仅给一个特定的属性使用时,可以将其声明为内部bean,但其不能使用在其他任何地方,不能被spring 容器直接获取。内部bean声明直接包含在<property><constructor-arg>元素里,不需要设置任何id或name属性。

<bean class="com.JavaEE.pojo.Person" id="p9">
    <property name="id" value="9" />
    <property name="name" value="庞家伟" />
    <property name="car" >
        <!-- 内部Bean -->
        <bean class="com.JavaEE.pojo.Car">
            <property name="name" value="大众"/>
            <property name="carNo" value="京A111111" />
        </bean>
        <!-- 内部bean不能使用在任何其他地方,不能被spring 容器直接获取 -->
    </property>
</bean>

3.3 集合属性

在Spring中可以通过一组内置的XML标签来配置集合属性,例如:<list><set><map>

3.3.1 数组 & List & Set

  • 配置java.util.List类型的属性,需要指定<list>标签,在标签里包含一些元素。这些标签可以通过<value>指定简单的常量值,通过<ref>指定对其他Bean的引用。通过<bean>指定内置bean定义。通过<null/>指定空元素。甚至可以内嵌其他集合。
   <bean id="shop" class="com.JavaEE.spring.bean.Shop" >
           <property name= "categoryList">
               <!-- 以字面量为值的List集合 -->
               <list>
                    <value> 历史</value >
                    <value> 军事</value >
               </list>
           </property>
           <property name= "bookList">
               <!-- 以bean的引用为值的List集合 -->
               <list>
                    <ref bean= "book01"/>
                    <ref bean= "book02"/>
               </list>
           </property>
     </bean >
  • 配置java.util.Set需要使用<set>标签,定义的方法与List一样。

  • 数组的定义和List一样,都使用<list>元素。

3.3.2 Map

  • Java.util.Map通过<map>标签定义,<map>标签里可以使用多个<entry>作为子标签。每个条目包含一个键和一个值。
    必须在<key>标签里定义键。因为键和值的类型没有限制,所以可以自由地为它们指定<value><ref><bean><null/>元素。

  • 可以将Map的键和值作为<entry>的属性定义:简单常量使用key和value来定义;bean引用通过key-ref和value-ref属性定义。

<bean class="com.JavaEE.pojo.Person" id="p11">
    <property name="id" value="11" />
    <property name="map1">
        <!-- map标签表示赋值的类型的map集合 -->
        <map>
            <!-- entry表示一个键值对 -->
            <entry key="key1" value="value1" />
            <entry key="key2" value="value2" />
            <entry key="key3" value="value3" />
        </map>
    </property>
    <property name="map2">
        <map>
             <!-- 表示key为bookKey01,value引用外部bean="book" -->
             <key>
				<value>bookKey01</value>
			</key>
			<ref bean="book"/>
        </map>
    </property>
</bean>

3.3.3 Properties

  • 使用<props>定义java.util.Properties,该标签使用多个<prop>作为子标签。每个<prop>标签必须定义key属性。
<bean class="com.JavaEE.spring.bean.DataSource" id="dataSource">
	<property name="properties">
		<props>
			<prop key="userName">root</prop>
			<prop key="password">root</prop>
			<prop key="url">jdbc:mysql://localhost:3306/mybatis</prop>
			<prop key="driverClass">com.mysql.jdbc.Driver</prop>
		</props>
	</property>
</bean>

3.3.4 util 名称空间

​ 如果只能将集合对象配置在某个bean内部,则这个集合的配置将不能重用。我们需要将集合bean的配置拿到外面,供其他bean引用。引入util名称空间,可以定义全局公共的集合信息,方便容器直接获取,或者是给属性赋值使用。

<util:list id="bookList">
	<ref bean="book01"/>
	<ref bean="book02"/>
	<ref bean="book03"/>
	<ref bean="book04"/>
	<ref bean="book05"/>
</util:list>

<util:list id="categoryList">
	<value>编程</value>
	<value>极客</value>
	<value>相声</value>
	<value>评书</value>
</util:list>

3.4 通过工厂创建bean

3.4.1 静态工厂方法创建bean

​ 调用静态工厂方法创建bean是将对象创建的过程封装到静态方法中。当客户端需要对象时,只需要简单地调用静态方法,而不用关心创建对象的细节。

​ 声明通过静态方法创建的bean需要在bean的class属性里指定静态工厂类的全类名,同时在factory-method属性里指定工厂方法的名称,最后使用<constrctor-arg>元素为该方法传递方法参数。

工厂类:

public class PersonFactory {
    public static Person createPerson(){
        return new Person(id,name,phone, age);
    }
}

xml:

<!-- 静态工厂方法使用class属性和factory-method属性组合使用
     class表示工厂的全类名,factory-method属性静态方法名 -->
<bean id="p15" class="com.JavaEE.factory.PersonFactory" factory-method="createPerson">
    <constructor-arg index="0" value="15" />
    <constructor-arg index="1" value="name属性" />
    <constructor-arg index="2" value="phone属性" />
    <constructor-arg index="3" value="22" />
</bean>

3.4.2 实例工厂方法创建bean

​ 实例工厂方法:将对象的创建过程封装到另外一个对象实例的方法里。当客户端需要请求对象时,只需要简单的调用该实例方法而不需要关心对象的创建细节。

​ 实现方式:先配置工厂类实例的bean,然后在factory-bean指定工厂类实例bean,在factory-method属性里指定该工厂方法的名称,最后使用<construtor-arg>元素为工厂方法传递方法参数。

工厂类:

public class PersonFactory {
    public Person createPerson2(){
        return new Person(id,name,phone, age);
    }
}

xml:

<!-- 配置工厂类实例的bean -->
<bean class="com.JavaEE.factory.PersonFactory" id="personFactory"/>

<!-- 工厂实例方法创建Bean对象,需要由 bean + factory-bean + factory-method组合实现 -->
<!-- factory-bean:工厂实例对象,factory-method:工厂方法名 -->
<bean id="p16" factory-bean="personFactory" factory-method="createPerson2" >
    <constructor-arg index="0" value="16" />
    <constructor-arg index="1" value="name属性" />
    <constructor-arg index="2" value="phone属性" />
    <constructor-arg index="3" value="23" />
</bean>

3.4.3 FactoryBean接口创建bean

​ Spring中有两种类型的bean,一种是普通bean,另一种是工厂bean,即FactoryBean。工厂bean跟普通bean不同,其返回的对象不是指定类的一个实例,其返回的是该工厂bean的getObject方法所返回的对象。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-p6n0yBfa-1602896010183)(C:\Users\77161\Desktop\ssm-img\FactoryBean接口.jpg)]

  • 工厂bean必须实现org.springframework.beans.factory.FactoryBean接口。

具体实现:创建一个类去实现FactoryBean接口—>实现里面的方法—>到Spring的配置文件中去配置

FactroryBean接口实现类:

public class PersonFactoryBean implements FactoryBean<Person> {
    /**
     * 创建Bean对象时创建的方法
     * @return
     * @throws Exception
     */
    @Override
    public Person getObject() throws Exception {
        return new Person(17,"FactoryBean接口方式","电话",18);
    }

    /**
     * 获取对象的Class类型的方法
     * @return
     */
    @Override
    public Class<?> getObjectType() {
        return Person.class;
    }

    /**
     * 是否是单例
     * @return
     */
    @Override
    public boolean isSingleton() {
        return false;
    }
}

xml:

<bean id="p17" class="com.JavaEE.factory.PersonFactoryBean" />

四、IOC之bean的高级配置

4.1 配置信息的继承

  • 通过继承实现bean配置信息的重用

背景:查看下面两个Employee的配置,其中dept属性是重复的。

<bean id="dept" class="com.JavaEE.parent.bean.Department">
	<property name="deptId" value="100"/>
	<property name="deptName" value="IT"/>
</bean>

<bean id="emp01" class="com.JavaEE.parent.bean.Employee">
	<property name="empId" value="1001"/>
	<property name="empName" value="Tom"/>
	<property name="age" value="20"/>
	<!-- 重复的属性值 -->	
	<property name="detp" ref="dept"/>
</bean>

<bean id="emp02" class="com.JavaEE.parent.bean.Employee">
	<property name="empId" value="1002"/>
	<property name="empName" value="Jerry"/>
	<property name="age" value="25"/>
	<!-- 重复的属性值 -->
	<property name="detp" ref="dept"/>
</bean>

配置信息的继承:以emp01作为父bean,继承后可以省略公共属性值的配置

<!-- parent属性设置继承哪个Bean的配置 -->
<bean id="emp02" parent="emp01">
	<property name="empId" value="1002"/>
	<property name="empName" value="Jerry"/>
	<property name="age" value="25"/>
</bean>
  • Spring允许继承bean的配置,被继承的bean称为父bean。继承这个父bean的bean称为子bean。
  • 子bean从父bean中继承配置,包括bean的属性配置。
  • 子bean也可以覆盖从父bean继承过来的配置。

4.1.1 抽象bean

  • 父bean可以作为配置模板,也可以作为bean实例。
  • 若只想把父bean作为配置模板,可以设置<bean>的abstract 属性为true,这样Spring将不会实例化这个bean。
  • 如果一个bean的class属性没有指定,则必须是抽象bean。
  • 并不是<bean>元素里的所有属性都会被继承。比如:autowire,abstract等。
  • 也可以忽略父bean的class属性,让子bean指定自己的类,而共享相同的属性配置。但此时abstract必须设为true。
<!-- abstract="true" 表示当前配置信息,只能用于继承,不能被实例化 -->
<bean id="emp01" class="com.JavaEE.parent.bean.Employee" abstract="true">
	<property name="empId" value="1001"/>
	<property name="empName" value="Tom"/>
	<property name="age" value="20"/>
	<!-- 重复的属性值 -->	
	<property name="detp" ref="dept"/>
</bean>

4.2 bean之间的依赖

​ 有的时候创建一个bean的时候需要保证另外一个bean也被创建,这时我们称前面的bean对后面的bean有依赖。例如:要求创建Employee对象的时候必须创建Department。这里需要注意的是依赖关系不等于引用关系,Employee即使依赖Department也可以不引用它。

<!--
    1、在Spring容器中,Bean对象的创建顺序默认是他们在配置文件中,从上到下的顺序决定.
    2、可以在bean的配置上,使用属性depends-on表示此bean的创建对目标bean有依赖,即能让目标bean前置创建.
-->
<bean id="emp03" class="com.JavaEE.parent.bean.Employee" depends-on="dept">
	<property name="empId" value="1003"/>
	<property name="empName" value="Kate"/>
	<property name="age" value="21"/>
</bean>

<bean id="dept" class="com.JavaEE.parent.bean.Department">
	<property name="deptId" value="100"/>
	<property name="deptName" value="IT"/>
</bean>

4.3 bean的作用域

​ 在Spring中,可以在<bean>元素的scope属性里设置bean的作用域,以决定这个bean是单实例的还是多实例的。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SAIqnHZM-1602896010185)(C:\Users\77161\Desktop\ssm-img\bean的作用域.jpg)]

​ 默认情况下,Spring只为每个在IOC容器里声明的bean创建唯一一个实例,整个IOC容器范围内都能共享该实例:所有后续的getBean()调用和bean引用都将返回这个唯一的bean实例。该作用域被称为singleton,它是所有bean的默认作用域。

<!--   scope表示配置 Bean的作用域

       singleton            表示单例 (默认值)
                                1 会跟着Spring容器一起被创建
                                2 多次调用getBean()方法都返回同一个对象

       prototype            表示多例
                                1 不会跟着Spring容器一起被创建
                                2 每次调用getBean() 方法都会返回一个新创建的对象

       request              表示一次请求内多次调用getBean都返回同一个对象
       session              表示一个会话内多次调用getBean都返回同一个对象  
-->
<bean class="com.JavaEE.pojo.Person" id="p20" scope="singleton">
    <property name="id" value="20" />
</bean>
  • 当bean的作用域为singleton时,Spring会在IOC容器对象创建时就创建bean的对象实例。
  • 当bean的作用域为prototype时,Spring会在IOC容器获取bean的实例时才创建bean的对象实例。

4.4 bean的生命周期

Spring
IOC容器可以管理bean的生命周期,Spring允许在bean生命周期内特定的时间点执行指定的任务。

4.4.1 bean的生命周期管理过程

Spring IOC容器对bean的生命周期进行管理的过程:

  1. 通过构造器或工厂方法创建bean实例
  2. 为bean的属性设置值和对其他bean的引用
  3. 调用bean的初始化方法
  4. bean可以使用了
  5. 当容器关闭时,调用bean的销毁方法

具体实现:

  • 在javabean类提供初始化方法和销毁方法;
  • 在配置bean时,通过init-method和destroy-method
    属性为bean指定初始化和销毁方法;

JavaBean类:

public class Person {
    private Integer id;
    private String name;
    private String phone;
    private Integer age;
    private Car car;

    /*初始化方法 */
    public void init(){
        System.out.println(" init() 这里可以做一些初始化操作 ");
    }
    /* 销毁方法 */
    public void destroy(){
        System.out.println(" destroy() 这里可以做一些销毁操作 ");
    }
    /* 以下省略其他javabean方法 */
}

xml:

<!--
    init-method 是初始化方法 ( 在Bean对象创建之后马上调用 )
    destroy-method 是销毁方法 ( 在Spring容器关闭的时候调用 , 只对单例有效 )
-->
<bean id="p22" class="com.JavaEE.pojo.Person" scope="singleton"
      init-method="init" destroy-method="destroy"></bean>

4.4.2 bean的后置处理器

bean后置处理器允许在调用初始化方法前后对bean进行额外的处理。

bean后置处理器对IOC容器里的所有bean实例逐一处理,而非单一实例。其典型应用是:检查bean属性的正确性或根据特定的标准更改bean的属性。

bean后置处理器时需要实现接口:org.springframework.beans.factory.config.BeanPostProcessor。

在初始化方法被调用前后,Spring将把每个bean实例分别传递给上述接口的以下两个方法:

  • postProcessBeforeInitialization(Object, String)
  • postProcessAfterInitialization(Object, String)

具体实现:

  1. 编写一个类去实现BeanPostProcessor接口;
  2. 实现接口的两个方法;
  3. 到Spring的配置文件中去配置后置处理器;

后置处理器类:

public class MyBeanPostProcessor implements BeanPostProcessor {
    /**
     * 在初始化方法之前执行,做一些操作
     * @param bean  当前初始化的对象实例
     * @param beanName  当前初始化对象的id值
     * @return 返回值是当前初始化对象( 它会替代当前初始化对象 )
     */
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println( " 初始化之前 obj => " + bean + " , id =>" + beanName );
        return bean;
    }

    /**
     * 在初始化方法之后执行,做一些操作
     * @param bean  当前初始化的对象实例
     * @param beanName  当前初始化对象的id值
     */
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println( " 初始化之后 obj => " + bean + " , id =>" + beanName );

        if ("p22".equals(beanName)) {
            Person p = (Person) bean;
            p.setCar(new Car("QQ卡丁车", "京C444444"));
        }

        return bean;
    }
}

xml:

<!--
    init-method 是初始化方法 ( 在Bean对象创建之后马上调用 )
    destroy-method 是销毁方法 ( 在Spring容器关闭的时候调用 , 只对单例有效 )
-->
<bean class="com.JavaEE.pojo.Person" id="p22" scope="prototype"
      init-method="init" destroy-method="destroy"></bean>

<!-- 配置后置处理器 -->
<bean class="com.JavaEE.processor.MyBeanPostProcessor" />

添加bean后置处理器后bean的生命周期:

  1. 通过构造器或工厂方法创建bean实例
  2. 为bean的属性设置值和对其他bean的引用
  3. 将bean实例传递给bean后置处理器的postProcessBeforeInitialization()方法
  4. 调用bean的初始化方法
  5. 将bean实例传递给bean后置处理器的postProcessAfterInitialization()方法
  6. bean可以使用了
  7. 当容器关闭时调用bean的销毁方法

4.5 基于xml的自动装配

4.5.1 手动与自动装配概念

  • 手动装配:以value或ref的方式明确指定属性值都是手动装配。
  • 自动装配:根据指定的装配规则,不需要明确指定,Spring自动将匹配的属性值注入bean中。

4.5.2 自动装配的装配规则

  • 根据类型自动装配:将类型匹配的bean作为属性注入到另一个bean中。若IOC容器中有多个与目标bean类型一致的bean,Spring将无法判定哪个bean最合适该属性,所以不能执行自动装配。
  • 根据名称自动装配:必须将目标bean的名称和属性名设置的完全相同。
  • 通过构造器自动装配:当bean中存在多个构造器时,此种自动装配方式将会很复杂。不推荐使用。

注意:相对于使用注解实现的自动装配,在XML文档中进行的自动装配略显笨拙,在项目中更多的使用注解的方式实现。

<!-- Person类:
	public class Person {

	private Integer id;
	private Car car;

	public Person(Car car) {
		this.car = car;
	} -->
<bean class="com.JavaEE.pojo.Car" id="car3">
    <property name="name" value="本田" />
    <property name="carNo" value="京B22222" />
</bean>
<bean class="com.JavaEE.pojo.Car" id="car">
    <property name="name" value="东风日产" />
    <property name="carNo" value="京B33333" />
</bean>
<!-- 自动注入指的是 按照某种指定的算法,自动的给子对象赋值

        	在autowire属性设置一种自动注入的算法

            default 和 no 都表示不自动赋值子对象

            byName 表示按子对象的属性名做为id到spring容器中查找并注入
                        1 如果找到就赋值
                        2 如果没有找到,就null空值

            byType  表示按子对象的类型到Spring容器中查找并注入
                        1 按类型如果找到一个就注入
                        2 如果没有找到,就null空值
                        3 如果找到多个就报错

            constructor 表示按构造器参数进行查找并注入
                        1 先按类型查找到一个,找到就赋值
                        2 如果按类型找到多个,接着按参数名做为id继续查找并注入
                        3 如果按类型找到多个,再按参数名做id没有找到,就不赋值
                        4 如果按类型找不到,也不赋值
-->
<bean class="com.JavaEE.pojo.Person" id="p21" autowire="constructor">
    <property name="id" value="21" />
    <!-- car属性自动装配 -->
</bean>

4.6 引用外部属性文件

​ 当bean的配置信息逐渐增多时,查找和修改一些bean的配置信息就变得愈加困难。这时可以将一部分信息提取到bean配置文件的外部,以properties格式的属性文件保存起来,同时在bean的配置文件中引用properties属性文件中的内容,从而实现一部分属性值在发生变化时仅修改properties属性文件即可。这种技术多用于连接数据库的基本信息的配置。

具体实现:(转到:五、Spring管理数据库连接池)

五、Spring管理数据库连接池

jdbc.properteis属性配置文件:

jdbc.username=root
jdbc.password=123456
jdbc.url=jdbc:mysql://localhost:3306/<数据库名>?useEncode=true&characterEncoding=utf-8
jdbc.driver=com.mysql.jdbc.Driver
jdbc.initialSize=5
jdbc.maxActive=10

Spring配置文件:

<!-- 引入外部properteis属性配置文件 -->
<!-- 使用context命名空间加载属性配置文件,location属性表示加载的文件的路径-->
<context:property-placeholder location="classpath:jdbc.properties" />

<!-- 配置数据库连接池 -->
<bean class="com.alibaba.druid.pool.DruidDataSource" id="dataSource">
    <property name="username" value="${user}" />
    <property name="password" value="${password}" />
    <property name="url" value="${url}" />
    <property name="driverClassName" value="${driverClassName}" />
    <property name="initialSize" value="${initialSize}" />
    <property name="maxActive" value="${maxActive}" />
</bean>

测试类:

@Test
public void test1() throws SQLException {
    //读取配置文件,创建Spring容器
    ApplicationContext applicationContext =
            new ClassPathXmlApplicationContext("applicationContext.xml");

    DataSource dataSource = (DataSource) applicationContext.getBean("dataSource");

    System.out.println(dataSource.getConnection());
}

六、Spring EL表达式

基本语法:SpEL使用**#{…}**作为定界符,所有在大框号中的字符都将被认为是SpEL表达式。

使用字面量:

整数:<property name="count" value="#{5}"/>
小数:<property name="frequency" value="#{89.7}"/>
科学计数法:<property name="capacity" value="#{1e4}"/>
String类型:<property name=“name” value="#{'Chuck'}"/>  <property name='name' value='#{"Chuck"}'/>
Boolean:<property name="enabled" value="#{false}"/>

引用其他bean:

<bean id="emp04" class="com.JavaEE.parent.bean.Employee">
	<property name="empId" value="1003"/>
	<property name="empName" value="Kate"/>
	<property name="age" value="21"/>
	<property name="detp" value="#{dept}"/>
</bean>

引用其他bean的属性值作为自己某个属性的值:

<bean id="emp05" class="com.JavaEE.parent.bean.Employee">
	<property name="empId" value="1003"/>
	<property name="empName" value="Kate"/>
	<property name="age" value="21"/>
	<property name="deptName" value="#{dept.deptName}"/>
</bean>

调用非静态方法:

<!-- 创建一个对象,在SpEL表达式中调用这个对象的方法 -->
<bean id="salaryGenerator" class="com.JavaEE.spel.bean.SalaryGenerator"/>

<bean id="employee" class="com.JavaEE.spel.bean.Employee">
	<!-- 通过对象方法的返回值为属性赋值 -->
	<property name="salayOfYear" value="#{salaryGenerator.getSalaryOfYear(5000)}"/>
</bean>

调用静态方法:

<bean id="employee" class="com.JavaEE.spel.bean.Employee">
	<!-- 在SpEL表达式中调用类的静态方法 -->
	<property name="circle" value="#{T(java.lang.Math).PI*20}"/>
</bean>

SpEL运算符:

①算术运算符:+、-、*、/、%、^

②字符串连接:+

③比较运算符:<、>、==、<=、>=、lt、gt、eq、le、ge

④逻辑运算符:and, or, not, |

⑤三目运算符:判断条件?判断结果为true时的取值:判断结果为false时的取值

⑥正则表达式:matches

七、注解功能

Spring扫描到被注解标识的组件,就会在Spring容器启动时自动创建相对应的bean。

7.1使用注解标识组件

普通组件:@Component 标识一个受Spring IOC容器管理的组件

持久化层组件:@Respository 标识一个受Spring IOC容器管理的持久化层组件

业务逻辑层组件:@Service 标识一个受Spring IOC容器管理的业务逻辑层组件

表述层控制器组件:@Controller 标识一个受Spring IOC容器管理的表述层控制器组件

组件作用域:@Scope 配置Bean的作用域 (单例,多例)

  • singleton:默认单例模式,即:Spring IOC容器中只会存在一个共享的bean实例,并且所有对bean的请求,只要id与该bean定义相匹配,则只会返回bean的同一实例,一个容器对应一个bean;

  • prototype 表示多例:每一次请求(将其注入到另一个bean中,或者以程序的方式调用容器的getBean()方法)都会产生一个新的bean实例,相当与一个new的操作;

组件命名规则:

[1] 默认使用组件的简单类名首字母小写后得到的字符串作为bean的id;

[2] 使用组件注解的value属性指定bean的id(起别名);

注意:事实上Spring并没有能力识别一个组件到底是不是它所标记的类型,即使将@Respository注解用在一个表述层控制器组件上面也不会产生任何错误,所以@Respository、@Service、@Controller这几个注解仅仅是为了让开发人员自己明确当前的组件扮演的角色。

7.2 扫描组件

组件被上述注解标识后还需要通过Spring进行扫描才能够侦测到。

①指定被扫描的package

<context:component-scan base-package="com.JavaEE.component"/>

②详细说明

[1]base-package属性指定一个需要扫描的基类包,Spring容器将会扫描这个基类包及其子包中的所有类。

[2]当需要扫描多个包时可以使用逗号分隔。

[3]如果仅希望扫描特定的类而非基包下的所有类,可使用resource-pattern属性过滤特定的类。

<context:component-scan base-package="com.JavaEE.component" resource-pattern="autowire/*.class"/>

[4]包含与排除

<context:component-scan>下可以拥有若干个include-filter和exclude-filter子节点;

<context:include-filter>子节点表示要包含的目标类;

注意:通常需要与use-default-filters属性配合使用才能够达到“仅包含某些组件”这样的效果。

即:通过将use-default-filters属性设置为false,禁用默认过滤器,然后扫描的就只是include-filter中的规则指定的组件了。

<context:exclude-filter>子节点表示要排除在外的目标类;

类别示例说明
annotationcom.JavaEE.XxxAnnotation过滤所有标注了XxxAnnotation的类。这个规则根据目标组件是否标注了指定类型的注解进行过滤。
assignablecom.JavaEE.BaseXxx过滤所有BaseXxx类的子类。这个规则根据目标组件是否是指定类型的子类的方式进行过滤。
aspectjcom.JavaEE.*Service+所有类名是以Service结束的,或这样的类的子类。这个规则根据AspectJ表达式进行过滤。
regexcom.JavaEE.anno.*所有com.JavaEE.anno包下的类。这个规则根据正则表达式匹配到的类名进行过滤。
customcom.JavaEE.XxxTypeFilter使用XxxTypeFilter类通过编码的方式自定义过滤规则。该类必须实现org.springframework.core.type.filter.TypeFilter接口

7.3 组件装配

​ Controller组件中往往需要用到Service组件的实例,Service组件中往往需要用到Repository组件的实例。Spring可以通过注解的方式帮我们实现属性的装配。

7.3.1 实现依据

​ 在指定要扫描的包时,<context:component-scan>元素会自动注册一个bean的后置处理器:AutowiredAnnotationBeanPostProcessor的实例。该后置处理器可以自动装配标记了**@Autowired**、@Resource或@Inject注解的属性。

7.3.2 @Autowired注解装配规则

@Autowired 注解默认按照byType方式在Spring容器中匹配资源类型兼容的bean:

1、如果找到唯一的类型,则自动装配;

2、如果资源类型兼容的bean不止一个,默认根据@Autowired注解标记的成员变量名作为id查找bean;

3、如果找不到资源类型兼容的bean,则抛出异常;

4、如果某一属性允许不被设置,即找不到资源类型兼容的bean时不抛出异常,而是该属性=null,则设置 @Autowired(required = false);

5、如果希望@Autowired 注解按照byName方式,则配合@Qualifier 注解使用,在里面指定注入bean的名称;

  • Spring甚至允许在方法的形参上标注@Qualifiter注解以指定注入bean的名称。

7.3.3 @Autowired注解使用场景

@Autowired注解一般用在组件属性的装配上。

@Autowired注解也可以应用在方法,此时Spring将会把所有形参进行自动装配。

@Autowired注解也可以应用在方法的形参上,此时Spring将会把指定形参进行自动装配。

@Autowired注解也可以应用在数组类型的属性上,此时Spring将会把所有匹配的bean进行自动装配。

@Autowired注解也可以应用在集合属性上,此时Spring读取该集合的类型信息,然后自动装配所有与之兼容的bean。

@Autowired注解用在java.util.Map上时,若该Map的键值为String,那么 Spring将自动装配与值类型兼容的bean作为值,并以bean的id值作为键。

7.3.4 @Resource与@Inject

  • @Resource默认按照byName方式进行bean匹配;

    1).若@Resource后面没有内容,则采用标注处的变量名或方法名作为name去匹配bean,找不到再按type去匹配;

    2).指定了name或者type则根据指定的类型去匹配bean;

    3).指定了name和type则根据指定的name和type去匹配bean,任何一个不匹配都将报错;

  • @Inject和@Autowired注解一样也是按类型注入匹配的bean,但没有reqired属性。

7.4 Spring专有测试

Spring为了让Junit测试变得更佳简单,写的测试代码更少,为Junit做了一些扩展操作。

  1. 实现一个Junit4 的运行器类.
  2. 在扩展的Junit4 的类中,有一个Spring容器,不再需要我们自己去实现这个容器.
  3. 使用Spring提供的扩展的Junit测试 , 还可以使用 Spring的依赖注入功能.
import calculator.Calculator;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

// @ContextConfiguration注解的作用是指定Spring容器需要的配置文件路径
@ContextConfiguration("classpath:ioc01.xml")
// @RunWith表示使用Spring扩展的Junit测试类来测试代码
@RunWith(SpringJUnit4ClassRunner.class)

public class AopTest {
    
    @Autowired
    private Calculator calculator;

    @Test
    public void test1(){
        calculator.add(1,0);
    }
}

八、依赖泛型注入

在Spring 4.x中可以为子类注入子类对应的泛型类型的成员变量的引用。

创建两个带泛型的类,并配置两者的依赖关系,对于继承这两个类的子类,如果泛型相同,则会继承这种依赖关系:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KL54dKjX-1602896010186)(C:\Users\77161\Desktop\ssm-img\依赖泛型注入.jpg)]

BaseRepository:

public class BaseRepository<T> {

    public void save(){
        System.out.println("BaseRepository save...");
    }
}

BaseService:

import org.springframework.beans.factory.annotation.Autowired;

public class BaseService<T> {

    @Autowired
    private BaseRepository<T> repository;

    public void save(){
        repository.save();
    

UserRepository:

import org.springframework.stereotype.Repository;

@Repository
public class UserRepository extends BaseRepository<User> {

    public void save(){
        System.out.println("UserRepository save...");
    }
}

UserService:

import org.springframework.stereotype.Service;

@Service
public class UserService extends BaseService<User> {

}

test:

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Main {

    public static void main(String[] args) {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("beans-generic-di.xml");
        UserService userService = (UserService) ctx.getBean("userService");
        userService.save();
    }
}

结果:UserRepository save...

  • 在以上的代码中,BaseService中引用了BaseReponsitory,并且在BaseService的add方法中调用了BaseReponsitory的add方法。
  • 在他们的子类中,继承了这种关系,因此我们在测试方法中调用userService.add(); 也是可以成功地调用UserReponsitory中的add方法而不是BaseReponsitory的add方法。
  • 根据泛型T自动注入相应的Reponsitory。

九、AOP切面编程

9.1 AOP概述

​ AOP(Aspect-Oriented Programming,面向切面编程):程序在运行期间,动态地将某段代码插入到原来方法代码的某些位置中。

  • AOP编程操作的主要对象是切面(aspect),而切面模块化横切关注点

​ 在应用AOP编程时,仍然需要定义公共功能,但可以明确的定义这个功能应用在哪里,以什么方式应用,并且不必修改受影响的类。这样一来横切关注点就被模块化到特殊的类里——这样的类我们通常称之为“切面”。

  • AOP的好处:

    1、每个事物逻辑位于一个位置,代码不分散,便于维护和升级;

    2、业务模块更简洁,只包含核心业务代码;

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YEaIWWYM-1602896010187)(C:\Users\77161\Desktop\ssm-img\AOP概述图解.jpg)]

9.2 使用代理实现非核心业务

9.2.1 动态代理

​ 代理设计模式的原理:使用一个代理将对象包装起来,然后用该代理对象取代原始对象。任何对原始对象的调用都要通过代理。代理对象决定是否以及何时将方法调用转到原始对象上。

  • 动态代理可以在不知道被代理类的前提下编写代理逻辑,运行时才决定被代理对象,适用性好且代理逻辑易于扩展。

9.2.2 使用JDK动态代理

优点:这种方式可以把非核心业务抽取出来,方便的在后期进行维护和升级。

缺点:使用jdk动态代理,需要有接口。如果没有接口。就无法使用jdk动态代理。

非核心业务类:

import java.util.Arrays;

//日志,验证等功能实现类
public class LogUtil {
    public static void logBefore(String methodName,Object...args){
        System.out.println("执行"+methodName+",参数:"+ Arrays.asList(args));
    }
    public static void logAfter(String methodName){
        System.out.println("执行"+methodName+"完毕");
    }


    public static void logAfterReturning(String methodName, Object res) {
        System.out.println("执行"+methodName+",结果:" + res);
    }

    public static void logAfterThrowing(String methodName, Exception e) {
        System.out.println("执行"+methodName+"时,发生异常:"+e.getMessage());
    }
}

代理类:

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

//代理类
public class JDKProxy {
    /**
     *
     * @param target 目标对象
     * @return
     */
    public static Object creatProxy(Object target){
        //获取类加载器
        ClassLoader classLoader = target.getClass().getClassLoader();
        //获取被目标对象接口信息
        Class<?>[] interfaces = target.getClass().getInterfaces();

        /**
         * 核心方法:生产代理对象
         * @param classLoader 类加载器,通常用被代理类的类加载器,我们希望被代理和代理类使用同一个类加载器
         * @param interfaces 目标对象的接口信息
         * @param InvocationHandler  处理器
         * Proxy:提供用于创建动态代理类和实例的静态方法,它还是由这些方法创建的所有动态代理类的超类。
         */
        return Proxy.newProxyInstance(classLoader, interfaces, new InvocationHandler() {

            /**
             * 拦截器
             * @param proxy 代理对象
             * @param method 目标方法
             * @param args  目标方法中的参数
             * @return   目标方法执行之后的结果
             * @throws Throwable
             */
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                Object res =  null;
                LogUtil.logBefore(method.getName(),args);//前置增强

                try{
                    res = method.invoke(target, args);//目标方法
                    LogUtil.logAfterReturning(method.getName(), res);//返回增强
                } catch (Exception e) {
                    LogUtil.logAfterThrowing(method.getName(), e);// 异常增强
                    throw  new RuntimeException(e);
                }finally {
                    LogUtil.logAfter(method.getName());//后置增强
                }

                return res;
            }
        });
    }
}

9.2.3 使用cglib动态代理

Cglib动态代理它不管目标对象有没有实现接口.它都可以实现代理技术.

Cglib动态代理是通过修改目标对象的字节码程序产生一个子类.生成一个代理对象实例.

Cglib产生的代理对象是目标对象的子类.

import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;
//用cglib创建动态代理对象
public class CglibProxy {
    public static Object creatCglibProxy(Object target){
        // 增强器 它负责产生一个Cglib代理对象实例
        Enhancer enhancer = new Enhancer();

        //指定具体的目标对象(加载目标对象形成对应的字节码文件)
        enhancer.setSuperclass(target.getClass());

        // 设置方法拦截器==跟InvocationHandler接口功能一样,
        //是代理对象调用方法时就会执行的接口(专门对目标方法进行增强)
        enhancer.setCallback(new MethodInterceptor() {
            /**
             * intercept 会拦截所有的目标方法 等价于invoke()
             * 只要代理对象方法调用,就会执行intercept()方法
             * @param proxy 代理对象
             * @param method 目标方法
             * @param args  目标方法中的参数
             * @param methodProxy 方法反射对象的代理对象
             * @return 返回值是代理对象调用方法的返回值
             * @throws Throwable
             */
            @Override
            public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
                // 前置增强
                LogUtil.logBefore(method.getName(),args);
                //调用目标方法
                Object res = method.invoke(target, args);
                // 后置增强
                LogUtil.logAfter(method.getName());
                return res;
            }
        });
        // 返回Cglib创建的代理对象实例
        return enhancer.create();
    }

}

9.3 AOP编程术语

通知(Advice)

切面必须要完成的各个具体工作。

切面(Aspect)

封装横切关注点信息的类,每个关注点体现为一个通知方法。

横切关注点

从每个方法中抽取出来的同一类非核心业务。

目标(Target)

目标对象就是被关注的对象。或者被代理的对象。

代理(Proxy)

为了拦截目标对象方法,而被创建出来的那个对象,就叫做代理对象。

连接点(Joinpoint)

横切关注点在程序代码中的具体体现,对应程序执行的某个特定位置。例如:类某个方法调用前、调用后、方法捕获到异常后等。

切入点(pointcut)

定位连接点的方式。每个类的方法中都包含多个连接点,所以连接点是类中客观存在的事物。如果把连接点看作数据库中的记录,那么切入点就是查询条件——AOP可以通过切入点定位到特定的连接点。

在Spring中切入点通过org.springframework.aop.Pointcut 接口进行描述,它使用类和方法作为连接点的查询条件。

AOP术语图解:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FcDAKt8j-1602896010188)(C:\Users\77161\Desktop\ssm-img\AOP术语图解.jpg)]

9.4 使用Spring实现AOP简单切面编程(AspectJ)

9.4.1 在Spring中启用AspectJ注解支持:

①导入jar包

需要代入的jar包:
com.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar
junit_4.12.jar
org.hamcrest.core_1.3.0.jar
spring-aop-5.2.5.RELEASE.jar
spring-aspects-5.2.5.RELEASE.jar
spring-beans-5.2.5.RELEASE.jar
spring-context-5.2.5.RELEASE.jar
spring-core-5.2.5.RELEASE.jar
spring-expression-5.2.5.RELEASE.jar
spring-jcl-5.2.5.RELEASE.jar

②在配置文件引入aop名称空间

<?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.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
    <!--包扫描,扫描包里所有注解-->
    <context:component-scan base-package="aop,calculator"></context:component-scan>
    <!--通过配置的形式自动创建代理对象-->
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>

9.4.2 用AspectJ注解声明切面

①要在Spring中声明AspectJ切面,只需要在IOC容器中将切面声明为bean实例。

②当在Spring IOC容器中初始化AspectJ切面之后,Spring IOC容器就会为那些与 AspectJ切面相匹配的bean创建代理。

③在AspectJ注解中,切面只是一个带有@Aspect注解的Java类,它往往要包含很多通知。

④通知是标注有某种注解的简单的Java方法。

⑤AspectJ支持5种类型的通知注解:

[1]@Before:前置通知,在方法执行之前执行

[2]@After:后置通知,在方法执行之后执行

[3]@AfterRunning:返回通知,在方法返回结果之后执行

[4]@AfterThrowing:异常通知,在方法抛出异常之后执行

[5]@Around:环绕通知,围绕着方法执行

9.5 Spring的切入点表达式

切入点表达式语法格式:

  • execution(访问权限 返回值类型 方法全限定名(参数类型列表))
execution(public int com.JavaEE.aop.Calculator.add(int, int)

通配符:

  • * 表示任意的意思;
  • … 表示多层路径,或任意多个任意类型参数;

切入点表达式连接:&& 、||

9.6 Spring的通知详解

通知:在具体的连接点上要执行的操作。

一个切面可以包括一个或者多个通知。

通知所使用的注解的值往往是切入点表达式。

前置通知:在方法执行之前执行的通知;

  • 使用@Before注解。

后置通知:后置通知是在连接点完成之后执行的,即连接点返回结果或者抛出异常的时候;

  • 使用@After注解

返回通知:在连接点进行正常返回时才执行的通知;

  • 使用@AfterReturning注解

在返回通知中访问连接点的返回值

  • 在返回通知中,只要将returning属性添加到@AfterReturning注解中,就可以访问连接点的返回值。该属性的值即为用来传入返回值的参数名称

  • 必须在通知方法的签名中添加一个同名参数。在运行时Spring AOP会通过这个参数传递返回值

  • 原始的切点表达式需要出现在pointcut属性中

异常通知:只在连接点抛出异常时才执行异常通知;

将throwing属性添加到@AfterThrowing注解中,也可以访问连接点抛出的异常。

  • Throwable是所有错误和异常类的顶级父类,所以在异常通知方法可以捕获到任何错误和异常。

如果只对某种特殊的异常类型感兴趣,可以将参数声明为其他异常的参数类型。然后通知就只在抛出这个类型及其子类的异常时才被执行。

环绕通知:环绕通知是所有通知类型中功能最为强大的,能够全面地控制连接点,甚至可以控制是否执行连接点。

对于环绕通知来说,连接点的参数类型必须是ProceedingJoinPoint。它是 JoinPoint的子接口,允许控制何时执行,是否执行连接点。

在环绕通知中需要明确调用ProceedingJoinPoint的proceed()方法来执行被代理的方法。如果忘记这样做就会导致通知被执行了,但目标方法没有被执行。

注意:环绕通知的方法需要返回目标方法执行之后的结果,即调用 joinPoint.proceed();的返回值,否则会出现空指针异常。

@Component //用组件标注Component,让容器自动创建对象
@Aspect //把类标注成切面
public class LogAop {

    //切入点表达式:通过表达式的方式定位一个或多个具体的连接点。
    @Pointcut("execution(public Integer calculator.CalculatorImpl.*(..))")
    public void pointcut(){
        //注解需要使用环境,所以绑定一个空方法
    }

    @Before("pointcut()")
    public void logBefore(){
        System.out.println("前置通知————方法准备开始");
    }

    @After("pointcut()")
    public void logAfter(){
        System.out.println("后置通知————方法执行完毕");
    }

    @AfterReturning(value = "pointcut()",returning = "res")
    public Object afterReturning(Object res){
        System.out.println("结果为:"+res);
        return res;
    }

    @AfterThrowing(value = "pointcut()",throwing = "e")
    public void afterThrowing(Throwable e){
        System.out.println("错误异常信息:"+e.getMessage());
    }

    @Around("pointcut()")
    public Object around(ProceedingJoinPoint joinPoint){

        try {
            System.out.println("around前置通知————"+joinPoint.getSignature().getName()+"准备开始");
            Object res = joinPoint.proceed();
            System.out.println("around返回通知:"+res);
            return res;
        } catch (Throwable e) {
            System.out.println("around错误异常信息:"+e.getMessage());
        }finally {
            System.out.println("around后置通知————"+joinPoint.getSignature().getName()+"执行完毕");
        }
        return null;
    }

}

9.7 Spring的连接点详解

​ 切入点表达式通常都会是从宏观上定位一组方法,和具体某个通知的注解结合起来就能够确定对应的连接点。那么就一个具体的连接点而言,我们可能会关心这个连接点的一些具体信息,例如:当前连接点所在方法的方法名、当前传入的参数值等等。这些信息都封装在JoinPoint接口的实例对象中。

@After("pointcut()")
    public static void logAfter(JoinPoint joinPoint){
        System.out.println("后置通知执行"+joinPoint.getSignature().getName());
    }

只需要在通知方法的参数中,加入一个JoinPoint参数。就可以获取到拦截方法的信息。

  • 注意:是org.aspectj.lang.JoinPoint这个类。

9.8 重用切入点定义

编写AspectJ切面时,可以直接在通知注解中书写切入点表达式。但同一个切点表达式可能会在多个通知中重复出现。

在AspectJ切面中,可以通过@Pointcut注解将一个切入点声明成简单的方法。

@Pointcut("execution(public Integer calculator.CalculatorImpl.*(..))")
    public void pointcut(){
        //注解需要使用环境,所以绑定一个空方法
    }
  • 其他通知可以通过方法名称引入该切入点

​ 切入点方法的访问控制符同时也控制着这个切入点的可见性。如果切入点要在多个切面中共用,最好将它们集中在一个公共的类中。在这种情况下,它们必须被声明为public。在引入这个切入点时,必须将类名也包括在内。如果类没有与这个切面放在同一个包中,还必须包含包名。

9.9 指定切面的优先级

在同一个连接点上应用不止一个切面时,除非明确指定,否则它们的优先级是不确定的。

切面的优先级可以通过实现Ordered接口或利用@Order注解指定。

  • 实现Ordered接口,getOrder()方法的返回值越小,优先级越高。

  • 使用@Order注解决定通知执行的顺序(值越小,越先执行)。

@Aspect 
@Order(1)
public class LogAop {...}

9.10 Spring切面中的代理对象

在Spring中,可以对有接口的对象和无接口的对象分别进行代理。在使用上有些细微的差别。

  1. 如果被代理的对象实现了接口。在获取对象的时候,必须要以接口来接收返回的对象。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8qfJE2tA-1602896010190)(C:\Users\77161\Desktop\ssm-img\Spring切面中的代理对象1.jpg)]

  1. 如果被代理对象,如果没有实现接口。获取对象的时候使用对象类型本身 。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-T0pTRGXv-1602896010191)(C:\Users\77161\Desktop\ssm-img\Spring切面中的代理对象2.jpg)]

9.11 基于xml配置aop

<?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:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">

    <!--目标对象-->
    <bean id="calculator" class="com.JavaEE.xml.calculator.CalculatorImpl"></bean>
    <!--切面-->
    <bean id="logaspect" class="com.JavaEE.xml.aop.LogAop"></bean>

    <!--切面和目标对象的关系-->
    <aop:config>
        <!--指定切面类
             <aop:aspect  等价于 @aspect
        -->
          <aop:aspect ref="logaspect" order="0">
              <!--设置切入点表达式-->
              <aop:pointcut id="cut" expression="execution(public int com.JavaEE.xml.calculator.Calculator.*(..))"/>
              <!--前置通知-->
              <aop:before method="logBefore" pointcut-ref="cut"></aop:before>
              <aop:after method="logAfter" pointcut-ref="cut"></aop:after>
              <aop:after-returning method="afterReturning" pointcut-ref="cut" returning="a"></aop:after-returning>
              <aop:after-throwing method="afterThrowing" pointcut-ref="cut" throwing="e"></aop:after-throwing>
              <aop:around method="around" pointcut-ref="cut"></aop:around>

          </aop:aspect>

    <!--    <aop:aspect></aop:aspect>-->


    </aop:config>

</beans>

十、JDBCTemplate

​ 为了使JDBC更加易于使用,Spring在JDBC API上定义了一个抽象层,以此建立一个JDBC存取框架。作为Spring JDBC框架的核心,JDBC模板的设计目的是为不同类型的JDBC操作提供模板方法,通过这种方式,可以在尽可能保留灵活性的情况下,将数据库存取的工作量降到最低。可以将Spring的JdbcTemplate看作是一个小型的轻量级持久化层框架,和我们之前使用过的DBUtils风格非常接近。

10.1 环境准备

1)导入jar包

①IOC容器所需要的JAR包
	commons-logging-1.1.1.jar
	spring-beans-4.0.0.RELEASE.jar
	spring-context-4.0.0.RELEASE.jar
	spring-core-4.0.0.RELEASE.jar
	spring-expression-4.0.0.RELEASE.jar
②JdbcTemplate所需要的JAR包
	spring-jdbc-4.0.0.RELEASE.jar
	spring-orm-4.0.0.RELEASE.jar
	spring-tx-4.0.0.RELEASE.jar
③数据库驱动和数据源
	druid-1.1.9.jar
	mysql-connector-java-5.1.7-bin.jar
④Aop
	com.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar
	spring-aop-5.2.5.RELEASE.jar
	spring-aspects-5.2.5.RELEASE.jar

2)创建连接数据库基本信息属性文件(porperties)

jdbc.username=root
jdbc.password=123456
jdbc.url=jdbc:mysql://localhost:3306/jdbctemplate?useEncode=true&characterEncoding=utf-8
jdbc.driver=com.mysql.jdbc.Driver

3)在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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">

    <context:component-scan base-package="com.JavaEE"></context:component-scan>
    <!--引入属性文件-->
    <context:property-placeholder location="classpath:jdbc.properties"></context:property-placeholder>
    <!--数据源-->
    <bean id="datasource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="username" value="${jdbc.username}"></property>
        <property name="password" value="${jdbc.password}"></property>
        <property name="url" value="${jdbc.url}"></property>
        <property name="driverClassName" value="${jdbc.driver}"></property>
    </bean>
    <!--创建jdbcTemplatebean对象 -->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
          <property name="dataSource" ref="datasource"></property>
    </bean>
    <!--创建NamedParameterJdbcTemplatebean对象 可命名参数-->
	<bean id="namedParameterJdbcTemplate" class="org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate">
       <constructor-arg name="dataSource" ref="datasource"></constructor-arg>
	</bean>

</beans>

10.2 持久化操作

10.2.1 增删改

  • JdbcTemplate.update(String sql,
    Object… args)
@Autowired //对jdbcTempplate自动装配
private JdbcTemplate jdbcTemplate;
    
//将id=5的记录的salary字段更新为1300.00
@Test
public void t1() {
	String sql="update employee set salary=? where id=?";
	int count = jdbcTemplate.update(sql, 1300, 5);
	System.out.println(count);
}

10.2.2 批量增删改

  • JdbcTemplate.batchUpdate(String sql, List<Object[]> args)

​ Object[]封装了SQL语句每一次执行时所需要的参数;List集合封装了SQL语句多次执行时的所有参数;

@Autowired
private JdbcTemplate jdbcTemplate;
//批量插入
@Test
    public void t2() {
        String sql="insert into employee values(null,?,?)";

        List<Object[]> params=new ArrayList<>();
          params.add(new Object[]{"tom",12000});
          params.add(new Object[]{"jack",120000});
          params.add(new Object[]{"rose",12});
        int[] count = jdbcTemplate.batchUpdate(sql, params);
        for (int i : count) {
            System.out.println(i);
        }
    }

10.2.3 查询单行

  • JdbcTemplate.queryForObject(String sql,
    RowMapper, Object… args)
@Autowired
private JdbcTemplate jdbcTemplate;	
//查询id=5的数据库记录,封装为一个Java对象返回
   
@Test
public void t3() {
     String sql="select * from employee where id=?";
        Employee employee = jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<>(Employee.class), 5);
        System.out.println(employee);
 }

10.2.4 查询多行

  • JdbcTemplate.query(String sql, RowMapper, Object… args)

    RowMapper对象依然可以使用BeanPropertyRowMapper

@Autowired
private JdbcTemplate jdbcTemplate;
//查询salary>4000的数据库记录,封装为List集合返回

@Test
public void t4(){
        List<Employee> emps = jdbcTemplate.query("select * from employee where salary>?", new BeanPropertyRowMapper<>(Employee.class), 4000);
        System.out.println(emps);
}

10.2.5 查询单一值

  • JdbcTemplate.queryForObject(String sql,
    Class, Object… args)
@Autowired
private JdbcTemplate jdbcTemplate;
//查询最大salary   单行单列  queryForObject

@Test
public void t5(){
        BigDecimal bigDecimal = jdbcTemplate.queryForObject("select max(salary) from employee", BigDecimal.class);
        System.out.println(bigDecimal);
}

10.3 使用命名参数的JdbcTemplate

​ 在Hibernate的HQL查询中我们体验过具名参数的使用,相对于基于位置的参数,具名参数具有更好的可维护性,在SQL语句中参数较多时可以考虑使用具名参数。在Spring中可以通过NamedParameterJdbcTemplate类的对象使用带有具名参数的SQL语句。

10.3.1 环境配置

<!-- 过IOC容器创建NamedParameterJdbcTemplate对象 -->
<bean 
	id="namedTemplate" 
	class="org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate">
	<!-- 没有无参构造器,必须传入数据源或JdbcTemplate对象 -->
	<constructor-arg ref="dataSource" ref="datasource"/>
</bean>

10.3.2 命名参数的格式

INSERT INTO depts (dept_name) VALUES (:deptName)

10.3.3 具体例子

@Autowired

//使用带有命名参数的SQL语句插入一条员工记录,并以Map形式传入参数值

@Test
public void t6(){
     String sql="insert into employee values(null,:name,:sal)";
     Map<String,Object> params=new HashMap<>();
     params.put("name","jerry");
     params.put("sal",1200000);
     int count = namedParameterJdbcTemplate.update(sql, params);
     System.out.println(count);
}

10.4 使用JdbcTemplate实现Dao

JdbcTemplate类是线程安全的,所以可以在IOC容器中声明它的单个实例,并将这个实例注入到所有的Dao实例中。

@Repository
public class EmployeeDao {
	
	//通过IOC容器自动注入
	@Autowired
	private JdbcTemplate jdbcTemplate;
	
	public Employee get(Integer id){
		//…
	}
}

十一、声明式事务

将事务管理代码从业务方法中分离出来,以声明的方式来实现事务管理。

11.1 spring的事务管理

11.1.1 声明式事务管理

​ 事务管理代码的固定模式作为一种横切关注点,可以通过AOP方法模块化,进而借助Spring AOP框架实现声明式事务管理。Spring在不同的事务管理API之上定义了一个抽象层,通过配置的方式使其生效,从而让应用程序开发人员不必了解事务管理API的底层实现细节,就可以使用Spring的事务管理机制。

  • Spring既支持编程式事务管理,也支持声明式的事务管理。

Spring事务管理底层原理

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FEfWdjZ9-1602896010192)(C:\Users\77161\Desktop\ssm-img\Spring事务管理底层原理.jpg)]

11.1.2 spring提供的事务管理器

​ Spring的核心事务管理抽象是PlatformTransactionManager。它为事务管理封装了一组独立于技术的方法。无论使用Spring的哪种事务管理策略(编程式或声明式),事务管理器都是必须的。

  • 事务管理器可以以普通的bean的形式声明在Spring IOC容器中。

11.1.3 spring事务管理器的主要实现

①DataSourceTransactionManager:在应用程序中只需要处理一个数据源,而且通过JDBC存取。

②JtaTransactionManager:在JavaEE应用服务器上用JTA(Java Transaction API)进行事务管理。

③HibernateTransactionManager:用Hibernate框架存取数据库。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hGO6sNsp-1602896010193)(C:\Users\77161\Desktop\ssm-img\事务管理器的主要实现.jpg)]

11.2 快速实现声明式事务管理

配置文件:

<?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:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">

    <context:component-scan base-package="com.JavaEE"></context:component-scan>
    <context:property-placeholder location="classpath:jdbc.properties"></context:property-placeholder>

    <bean id="ds" class="com.alibaba.druid.pool.DruidDataSource">
          <property name="username" value="${jdbc.username}"></property>
         <property name="password" value="${jdbc.password}"></property>
         <property name="url" value="${jdbc.url}"></property>
        <property name="driverClassName" value="${jdbc.driver}"></property>
    </bean>

    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
         <property name="dataSource" ref="ds"></property>
    </bean>
     <!--事务管理器 transactionManager 默认名字-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="ds"></property>
    </bean>
    <!--支持事务注解的形式-->
   <tx:annotation-driven  transaction-manager="transactionManager"></tx:annotation-driven>
</beans>

在需要进行事务控制的方法上加注解:

@Transactional
public void updateUserBook(){
    bookDao.updateBook();
    userDao.updateUser();
}

11.3 @Transactional的属性说明

readOnly

该属性用于设置当前事务是否为只读事务,设置为true表示只读,false则表示可读写,默认值为false。

@Transactional(readOnly=true) //只读可提高运行效率

rollbackFor

该属性用于设置需要进行回滚的异常类数组,当方法中抛出指定异常数组中的异常时,则进行事务回滚。

指定单一异常类:@Transactional(rollbackFor=RuntimeException.class)

指定多个异常类:@Transactional(rollbackFor={RuntimeException.class, Exception.class})

rollbackForClassName

该属性用于设置需要进行回滚的异常类名称数组,当方法中抛出指定异常名称数组中的异常时,则进行事务回滚。

指定单一异常类名称:@Transactional(rollbackForClassName=“RuntimeException”)

指定多个异常类名称:@Transactional(rollbackForClassName={“RuntimeException”,“Exception”})

noRollbackFor

该属性用于设置不需要进行回滚的异常类数组,当方法中抛出指定异常数组中的异常时,不进行事务回滚。

指定单一异常类:@Transactional(noRollbackFor=RuntimeException.class)

指定多个异常类:@Transactional(noRollbackFor={RuntimeException.class, Exception.class})

noRollbackForClassName

该属性用于设置不需要进行回滚的异常类名称数组,当方法中抛出指定异常名称数组中的异常时,不进行事务回滚。

指定单一异常类名称:@Transactional(noRollbackForClassName=“RuntimeException”)

指定多个异常类名称:@Transactional(noRollbackForClassName={“RuntimeException”,“Exception”})

timeout

该属性用于设置事务的超时秒数,以秒为单位,默认值为-1表示永不超时。

设置超时秒数为两秒:@Transactional(timeout = 2)

propagation:事务传播行为。

isolation:事务隔离级别。

11.4 事务传播行为

当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。

方法可能继续在现有事务中运行,也可能开启一个新事务,并在自己的事务中运行。

事务的传播行为可以由传播属性指定。

常量名称解释
PROPAGATION_REQUIRED先判断当前方法有没有事务,如果有就是用当前的事务,如果没有事务,开启一个新的事务。
PROPAGATION_REQUIRES_NEW新建事务,如果当前存在事务,把当前事务挂起。新建的事务将和被挂起的事务没有任何关系,是两个独立的事务,外层事务失败回滚之后,不能回滚内层事务执行的结果,内层事务失败抛出异常,外层事务捕获,也可以不处理回滚操作
PROPAGATION_SUPPORTS支持当前事务,如果当前没有事务,就以非事务方式执行。
PROPAGATION_MANDATORY支持当前事务,如果当前没有事务,就抛出异常。
PROPAGATION_NOT_SUPPORTED以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
PROPAGATION_NEVER以非事务方式执行,如果当前存在事务,则抛出异常。
PROPAGATION_NESTED如果事务在运行,则当前方法就在这个事务的嵌套事务内运行。否则,就气动一个新的事务,并在它自己的事务内运行。它使用了一个单独的事务,这个事务拥有多个可以回滚的保存点。内部事务的回滚不会对外部事务造成影响。它只对DataSourceTransactionManager事务管理器起效。

11.5 事务的隔离等级

​ 数据库系统必须具有隔离并发运行各个事务的能力,使它们不会相互影响,避免各种并发问题。一个事务与其他事务隔离的程度称为隔离级别。SQL标准中规定了多种事务隔离级别,不同隔离级别对应不同的干扰程度,隔离级别越高,数据一致性就越好,但并发性越弱。

常量名称解释
ISOLATION_DEFAULT 默认这是个 PlatfromTransactionManager 默认的隔离级别,使用数据库默认的事务隔离级别。另外四个与 JDBC 的隔离级别相对应。
ISOLATION_READ_UNCOMMITTED 读未提交这是事务最低的隔离级别,它充许另外一个事务可以看到这个事务未提交的数据。这种隔离级别会产生脏读,不可重复读和幻像读。
ISOLATION_READ_COMMITTED 读已提交保证一个事务修改的数据提交后才能被另外一个事务读取。另外一个事务不能读取该事务未提交的数据。可以防止脏读,但可能出现不可重复读。
ISOLATION_REPEATABLE_READ 可重复读确保一个事务可以多次从一个字段中读取到相同的值,即这个事务执行期间禁止其它事务对这个字段进行更新。这种事务隔离级别可以防止脏读,不可重复读。但是可能出现幻像读。
ISOLATION_SERIALIZABLE 串行化确保一个事务可以多次从一个表中读取到相同的行,在这个事务执行期间,禁止其它事务对这个表进行添加、更新、删除操作。可以避免任何并发问题,但性能十分低下。

补充:

  1. SQL规范所规定的标准,不同的数据库具体的实现可能会有些差异
  2. mysql中默认事务隔离级别是可重复读时并不会锁住读取到的行
  3. 事务隔离级别为读提交时,写数据只会锁住相应的行
  4. 事务隔离级别为可重复读时,如果有索引(包括主键索引)的时候,以索引列为条件更新数据,会存在间隙锁间隙锁、行锁、下一键锁的问题,从而锁住一些行;如果没有索引,更新数据时会锁住整张表。
  5. 事务隔离级别为串行化时,读写数据都会锁住整张表
  6. 隔离级别越高,越能保证数据的完整性和一致性,但是对并发性能的影响也越大,鱼和熊掌不可兼得啊。对于多数应用程序,可以优先考虑把数据库系统的隔离级别设为Read Committed,它能够避免脏读取,而且具有较好的并发性能。尽管它会导致不可重复读、幻读这些并发问题,在可能出现这类问题的个别场合,可以由应用程序采用悲观锁或乐观锁来控制。

11.6 事务的并发问题

  1. 脏读:事务A读取了事务B更新的数据,然后B回滚操作,那么A读取到的数据是脏数据。
  2. 不可重复读:事务 A 多次读取同一数据,事务 B 在事务A多次读取的过程中,对数据作了更新并提交,导致事务A多次读取同一数据时,结果 不一致。
  3. 幻读:系统管理员A将数据库中所有学生的成绩从具体分数改为ABCDE等级,但是系统管理员B就在这个时候插入了一条具体分数的记录,当系统管理员A改结束后发现还有一条记录没有改过来,就好像发生了幻觉一样,这就叫幻读。
  4. 丢失更新:两个事务同时更新一行数据,最后一个事务的更新会覆盖掉第一个事务的更新,从而导致第一个事务更新的数据丢失,这是由于没有加锁造成的。
  • 注意:不可重复读的和幻读很容易混淆,不可重复读侧重于修改,幻读侧重于新增或删除。解决不可重复读的问题只需锁住满足条件的行,解决幻读需要锁表。

11.7 使用XML配置声明式事务

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">

    <context:component-scan base-package="com.JavaEE.xml"></context:component-scan>
    <context:property-placeholder location="classpath:jdbc.properties"></context:property-placeholder>

    <bean id="ds" class="com.alibaba.druid.pool.DruidDataSource">
          <property name="username" value="${jdbc.username}"></property>
         <property name="password" value="${jdbc.password}"></property>
         <property name="url" value="${jdbc.url}"></property>
        <property name="driverClassName" value="${jdbc.driver}"></property>
    </bean>

    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
         <property name="dataSource" ref="ds"></property>
    </bean>
     <!--事务管理器 transactionManager 默认名字-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="ds"></property>
    </bean>

<!--以下是xml声明式事务配置-->    
<!--advice 通知(方法)	transaction-manager:配置事务管理器-->
    <tx:advice id="transAdvice" transaction-manager="transactionManager">
        <!--attributes 属性:匹配规则-->
        <tx:attributes>
            <!--method 具有事务特性的方法,里面可以设置事务的属性-->
            <tx:method name="updateBookUser" read-only="false" isolation="DEFAULT" propagation="REQUIRED" timeout="-1"/>
            <!--添加的规则 以save开头-->
            <tx:method name="save*"></tx:method>
            <!--删除以del开头-->
            <tx:method name="del*"></tx:method>
            <!--修改以update开头-->
            <tx:method name="update*"></tx:method>
            <!--查询-->
            <tx:method name="query*" read-only="true"></tx:method>
            <!--如果以上的匹配不上,则执行。 通常只有查询,所以设置为只读-->
            <tx:method name="*" read-only="true"></tx:method>
        </tx:attributes>
    </tx:advice>

    <!--把事务以通知的形式作用到目标方法上:找出目标方法所在-->
     <aop:config>
        <aop:pointcut id="cut" expression="execution(* com.JavaEE.xml.service.*.*(..))"/>
         <!--advisor:引用通知(匹配规则)-->
        <aop:advisor advice-ref="transAdvice" pointcut-ref="cut"></aop:advisor>
     </aop:config>

</beans>

十二、在Web应用中使用Spring

整合Spring和Web容器分三个步骤:

  1. 导入spring-web-4.0.0.RELEASE.jar
  2. 在web.xml中配置org.springframework.web.context.ContextLoaderListener监听器监听ServletContext的初始化
  3. 在web.xml中配置contextConfigLocation上下文参数。配置Spring配置文件的位置,以用于初始化Spring容器

基本思路:

​ 通过注册监听器ContextLoaderListener,Web应用可以在启动时加载Spring的ApplicationContext对象。这个监听器会将加载好的ApplicationContext对象保存到Web应用的ServletContext中。随后Servlet或可以访问ServletContext的任意对象就能通过一个辅助方法来访问Spring的应用程序上下文了。

监听器类:

   org.springframework.web.context.ContextLoaderListener

​ 监听器通过查找Web应用初始化参数contextConfigLocation来获取bean配置文件的位置。如果有多个bean配置文件,可以通过逗号或空格进行分隔。contextConfigLocation的默认值为/WEB-INF/applicationContext.xml。若实际的文件和默认值一致则可以省略这个Web应用的初始化参数

获取ApplicationContext对象:

WebApplicationContextUtils.getWebApplicationContext(getServletContext())

具体实现:

xml配置文件:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">
    <!--上下文件参数contextConfigLocation  -->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:bean.xml</param-value>
    </context-param>

    <listener>
        <!--servletContext jsp:application
            servletContext: 当tomcat启动时创建servletContext
            把spring容器对象创建完毕之后存放到servletContext作用域
            tomcat启动时读取bean.xml的信息完成spring容器的初始化
        -->
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

</web-app>

Servlet层:

@WebServlet(value = "/springServlet")
public class SpringServlet extends HttpServlet {


    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		doGet(request,response);
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
       
        WebApplicationContext webApplicationContext = WebApplicationContextUtils.getWebApplicationContext(getServletContext()); 

    }
}

.jar
2. 在web.xml中配置org.springframework.web.context.ContextLoaderListener监听器监听ServletContext的初始化
3. 在web.xml中配置contextConfigLocation上下文参数。配置Spring配置文件的位置,以用于初始化Spring容器

基本思路:

​ 通过注册监听器ContextLoaderListener,Web应用可以在启动时加载Spring的ApplicationContext对象。这个监听器会将加载好的ApplicationContext对象保存到Web应用的ServletContext中。随后Servlet或可以访问ServletContext的任意对象就能通过一个辅助方法来访问Spring的应用程序上下文了。

监听器类:

   org.springframework.web.context.ContextLoaderListener

​ 监听器通过查找Web应用初始化参数contextConfigLocation来获取bean配置文件的位置。如果有多个bean配置文件,可以通过逗号或空格进行分隔。contextConfigLocation的默认值为/WEB-INF/applicationContext.xml。若实际的文件和默认值一致则可以省略这个Web应用的初始化参数

获取ApplicationContext对象:

WebApplicationContextUtils.getWebApplicationContext(getServletContext())

具体实现:

xml配置文件:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">
    <!--上下文件参数contextConfigLocation  -->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:bean.xml</param-value>
    </context-param>

    <listener>
        <!--servletContext jsp:application
            servletContext: 当tomcat启动时创建servletContext
            把spring容器对象创建完毕之后存放到servletContext作用域
            tomcat启动时读取bean.xml的信息完成spring容器的初始化
        -->
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

</web-app>

Servlet层:

@WebServlet(value = "/springServlet")
public class SpringServlet extends HttpServlet {


    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		doGet(request,response);
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
       
        WebApplicationContext webApplicationContext = WebApplicationContextUtils.getWebApplicationContext(getServletContext()); 

    }
}
  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值