Spring4.x 基础

196558-20180816091527177-1206906856.png

把以下 jar 包加入到工程的 classpath 下:

搭建Spring开发环境

196558-20180816091316685-458275463.png
Spring 的配置文件: 一个典型的 Spring 项目需要创建一个或多个 Bean 配置文件, 这些配置文件用于在 Spring IOC 容器里配置 Bean. Bean 的配置文件可以放在 classpath 下, 也可以放在其它目录下.

(1).Spring_HelloWorld

Helloworld.class

public class Helloworld {
        private String name;

    public void setName(String name) {
        System.out.println("setName:"+name);
        this.name = name;
    }

    public void  hello(){
        System.out.println("hello:"+name);
    }
}

输出

    public static void main(String[] args) {
//        Helloworld helloworld = new Helloworld();
//        helloworld.setName("tangsan");
//        helloworld.hello();

        //1.创建Spring的IOC容器对象
        ApplicationContext context =
                new ClassPathXmlApplicationContext("applicationContext.xml");
        //2.从IOC获取 Bean 实例
        Helloworld hello = (Helloworld)context.getBean("helloWorld");
         hello.hello();
    }

(2).IOC与DI

  • IOC(Inversion of Control):其思想是反转资源获取的方向. 传统的资源查找方式要求组件向容器发起请求查找资源. 作为回应, 容器适时的返回资源. 而应用了 IOC 之后, 则是容器主动地将资源推送给它所管理的组件, 组件所要做的仅是选择一种合适的方式来接受资源. 这种行为也被称为查找的被动形式
  • DI(Dependency Injection) — IOC 的另一种表述方式:即组件以一些预先定义好的方式(例如: setter 方法)接受来自如容器的资源注入. 相对于 IOC 而言,这种表述更直接

(3).Spring 中的 Bean 配置

配置形式:

  • 基于 XML 文件的方式;
  • 基于注解的方式

配置方式:

  • 通过全类名(反射)
  • 通过工厂方法(静态工厂方法 & 实例工厂方法)
  • FactoryBean

IOC 容器

  • BeanFactory
  • ApplicationContext

依赖注入的方式:

  • 属性注入;
  • 构造器注入;

(3.1).在 Spring 的 IOC 容器里配置 Bean

<!--
      配置 bean:
      class: bean 全类名,通过反射的方式在 IOC 容器中创建 Bean,所以要求 Bean 中必须有无参数的构造器
      id : 唯一,bean名称
    -->
    <bean id="NameSetValue" class="com.cnblogs.tangge.spring.HelloWorld">
      <property name="name" value="tangsansan"></property>
    </bean>

(3.2).Spring容器

  • 在 Spring IOC 容器读取 Bean 配置创建 Bean 实例之前, 必须对它进行实例化. 只有在容器实例化后, 才可以从 IOC 容器里获取 Bean 实例并使用.
  • Spring 提供了两种类型的 IOC 容器实现.
    • BeanFactory: IOC 容器的基本实现.
    • ApplicationContext: 提供了更多的高级特性. 是 BeanFactory 的子接口.

区别:

  • BeanFactory 是 Spring 框架的基础设施,面向 Spring 本身;
  • ApplicationContext 面向使用 Spring 框架的开发者,几乎所有的应用场合都直接使用 ApplicationContext 而非底层的 BeanFactory。
  • 无论使用何种方式, 配置文件时相同的。

(3.2.1).ApplicationContext

  • ApplicationContext 的主要实现类:
    • ClassPathXmlApplicationContext:从 类路径下加载配置文件
    • FileSystemXmlApplicationContext: 从文件系统中加载配置文件
  • ConfigurableApplicationContext 扩展于 ApplicationContext,新增加两个主要方法:refresh() 和 close(), 让 ApplicationContext 具有启动、刷新和关闭上下文的能力
  • ApplicationContext 在初始化上下文时就实例化所有单例的 Bean。
  • WebApplicationContext 是专门为 WEB 应用而准备的,它允许从相对于 WEB 根目录的路径中完成初始化工作
    196558-20180814100014082-1276509326.png

(3.2.2).从IOC获取Bean

调用 ApplicationContext 的 getBean() 方法
196558-20180814102203685-657144947.png

(3.3).依赖注入的方式

  • 属性注入
  • 构造器注入
  • 工厂方法注入(很少使用,不推荐)

(3.3.1).属性注入

  • 属性注入即通过 setter 方法注入Bean 的属性值或依赖的对象
  • 属性注入使用 <property> 元素, 使用 name 属性指定 Bean 的属性名称,value 属性或 <value> 子节点指定属性值
  • 属性注入是实际应用中最常用的注入方式
 <bean id="NameSetValue" class="com.cnblogs.tangge.spring.HelloWorld">
      <property name="name" value="tangsansan"></property>
    </bean>

(3.3.2).构造器注入

  • 通过构造方法注入Bean 的属性值或依赖的对象,它保证了 Bean 实例在实例化后就可以使用。
  • 构造器注入在 <constructor-arg> 元素里声明属性, <constructor-arg> 中没有 name 属性
  <bean id="carAndprice" class="Models.Car">
    <constructor-arg value="Audi"></constructor-arg>
    <constructor-arg value="Shanghia"></constructor-arg>
    <constructor-arg value="300000"></constructor-arg>
  </bean>

  <!--
    构造方法注入配置 bean 的属性
    可以指定参数的位置(index)和参数的类型(type)
  -->
  <bean id="carAndSpeed" class="Models.Car">
    <constructor-arg value="BMW"  type="java.lang.String"></constructor-arg>
    <constructor-arg value="Shanghia" index="1" type="java.lang.String"></constructor-arg>
    <constructor-arg value="240" type="int"></constructor-arg>
  </bean>

(3.3.3).属性值细节

  • 字面值:可用字符串表示的值,可以通过 元素标签或 value 属性进行注入。
  • 基本数据类型及其封装类、String 等类型都可以采取字面值注入的方式
  • 若字面值中包含特殊字符,可以使用 <![CDATA[]]> 把字面值包裹起来。
  <bean id="carAndSpeed" class="Models.Car">
    <constructor-arg value="BMW"  type="java.lang.String"></constructor-arg>
    <constructor-arg index="1" type="java.lang.String">
      <value><![CDATA[<Shanghia>^]]></value>
    </constructor-arg>
    <constructor-arg value="240" type="int"></constructor-arg>
  </bean>

结果:

Car{brand='BMW', corp='<Shanghia>^', price=0.0, maxSpeed=240}

(3.4).Bean之间引用关系

  • 组成应用程序的 Bean 经常需要相互协作以完成应用程序的功能. 要使 Bean 能够相互访问, 就必须在 Bean 配置文件中指定对 Bean 的引用
  • 在 Bean 的配置文件中, 可以通过 <ref> 元素或 ref 属性为 Bean 的属性或构造器参数指定对 Bean 的引用.
  • 也可以在属性或构造器里包含 Bean 的声明, 这样的 Bean 称为内部 Bean

(3.4.1).外部 Bean

创建 Person 类

package Models;

public class Person {
    private String name;
    private int age;
    private Car car;

    public Person() {
    }

    /**
     * 如果不创建3个构造函数,会报错。Could not resolve matching constructor
     * @param name
     * @param age
     * @param car
     */
    public Person(String name, int age, Car car) {
        this.name = name;
        this.age = age;
        this.car = car;
    }


    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", car=" + car +
                '}';
    }
}

XML
注意 ref

  <bean id="Person" class="Models.Person">
    <constructor-arg value="10" type="int"></constructor-arg>
    <constructor-arg value="是男是女" type="java.lang.String"></constructor-arg>
    <constructor-arg name="car" ref="carAndSpeed"></constructor-arg>
  </bean>

执行

ApplicationContext context =
                new ClassPathXmlApplicationContext("applicationContext.xml");
Person person = (Person)context.getBean("Person");
        System.out.println(person);
//  Person{name='是男是女', age=10, car=Car{brand='BMW', corp='<Shanghia>^', price=0.0, maxSpeed=240}}

(3.4.2).内部 Bean

  • 当 Bean 实例仅仅给一个特定的属性使用时, 可以将其声明为内部 Bean. 内部 Bean 声明直接包含在 <property><constructor-arg> 元素里, 不需要设置任何 id 或 name 属性
  • 内部 Bean 不能使用在任何其他地方
  <!--property 必须通过 setter 注入,所以必须在Person类添加 set方法-->
  <bean id="PersonIn" class="Models.Person">
    <property name="name" value="内部Bean"></property>
    <property name="age" value="20"></property>
    <property name="car">
      <bean id="carAndSpeed" class="Models.Car">
        <constructor-arg value="benz" type="java.lang.String"></constructor-arg>
        <constructor-arg index="1" type="java.lang.String">
          <value><![CDATA[<Shanghia>^]]></value>
        </constructor-arg>
        <constructor-arg value="300" type="int"></constructor-arg>
      </bean>
    </property>
  </bean>

(3.4.3).注入参数详解:null 空值和级联属性

  • 可以使用专用的 <null/>元素标签为 Bean 的字符串或其它对象类型的属性注入 null 值 ,或者<null></null>
  • 和 Struts、Hiberante 等框架一样,Spring 支持级联属性的配置。

null空值

<constructor-arg name="car"><null/></constructor-arg>

级联属性

  <bean id="PersonOut" class="Models.Person">
    <constructor-arg value="10" type="int"></constructor-arg>
    <constructor-arg value="是男是女" type="java.lang.String"></constructor-arg>
    <constructor-arg name="car" ref="carAndSpeed"></constructor-arg>
    <!--Spring 支持级联属性的配置。property需要setter 注意:属性需要先初始化才可以为级联属性赋值,
    否则有异常,和 Structs2 不同-->
    <property name="car.maxSpeed" value="218"></property>
  </bean>

(3.4.4).集合属性

1).List、数组、Set
  • 在 Spring中可以通过一组内置的 xml 标签(例如: <list>, <set><map>) 来配置集合属性.
  • 配置 java.util.List 类型的属性, 需要指定 <list> 标签, 在标签里包含一些元素. 这些标签可以通过 <value> 指定简单的常量值, 通过 <ref> 指定对其他 Bean 的引用. 通过<bean> 指定内置 Bean 定义. 通过 <null/> 指定空元素. 甚至可以内嵌其他集合.
  • 数组的定义和 List 一样, 都使用 <list>
  • 配置 java.util.Set 需要使用 <set> 标签, 定义元素的方法与 List 一样.
  <bean id="personlist" class="Models.PersonList">
    <constructor-arg value="周星星"></constructor-arg>
    <constructor-arg value="50"></constructor-arg>
    <constructor-arg name="car">
      <list>
        <ref bean="carAndSpeed" />
        <ref bean="carAndprice" />
      </list>
    </constructor-arg>
  </bean>
2).Map
  • Java.util.Map 通过 <map> 标签定义, <map> 标签里可以使用多个 <entry> 作为子标签. 每个条目包含一个键和一个值.
  • 必须在 <key> 标签里定义键
  • 因为键和值的类型没有限制, 所以可以自由地为它们指定 <value>, <ref>, <bean><null> 元素.
  • 可以将 Map 的键和值作为 <entry> 的属性定义: 简单常量使用 key 和 value 来定义; Bean 引用通过 key-ref 和 value-ref 属性定义
  • 使用 <props> 定义 java.util.Properties, 该标签使用多个 <prop> 作为子标签. 每个 <prop> 标签必须定义 key 属性.
  <bean id="personmap" class="Models.PersonMap">
    <constructor-arg value="周星星"></constructor-arg>
    <constructor-arg value="50"></constructor-arg>
    <constructor-arg name="car">
    <!--使用map节点及map的entry子节点配置 Map类型的成员变量-->
      <map>
        <entry key="一号车" value-ref="carAndSpeed"></entry>
        <entry key="二号车" value-ref="carAndprice"></entry>
      </map>
    </constructor-arg>
  </bean>
PersonMap person = (PersonMap)context.getBean("personmap");
        System.out.println(person.toString());
        //Person{name='周星星', age=50, car={一号车=Car{brand='BMW', corp='<Shanghia>^--', price=0.0, maxSpeed=218},
        // 二号车=Car{brand='Audi', corp='Shanghia', price=0.0, maxSpeed=300000}}}
3).Properties

创建一个 DataSource 类

public class DataSource {
    private Properties properties;

    public Properties getProperties() {
        return properties;
    }

    public void setProperties(Properties properties) {
        this.properties = properties;
    }
}

配置Bean

  <bean id="datasource" class="Models.DataSource">
    <property name="properties">
      <!--使用 props 和 prop 子节点为 properties 赋值-->
      <props>
        <prop key="user">root</prop>
        <prop key="pass">123</prop>
        <prop key="jdbcurl">jdbc:mysql:///test</prop>
        <prop key="driverClass">com.mysql.jdbc.Driver</prop>
      </props>
    </property>
  </bean>

调用

        DataSource source = context.getBean(DataSource.class);
        System.out.println(source.getProperties());
        //{jdbcurl=jdbc:mysql:///test, driverClass=com.mysql.jdbc.Driver, user=root, pass=123}
4).使用 util scheme 定义集合

可以使用 util schema 里的集合标签定义独立的集合 Bean. 需要注意的是, 必须在 根元素里添加 util schema 定义

  <!--导入 util 命名空间  http://www.springframework.org/schema/util-->
  <util:list id="utilcars">
    <ref bean="carAndSpeed" />
    <ref bean="carAndprice" />
  </util:list>

  <util:map id="utilmap">
    <entry key="一号车" value-ref="carAndSpeed"></entry>
    <entry key="二号车" value-ref="carAndprice"></entry>
  </util:map>

  <bean id="personmap" class="Models.PersonMap">
    <constructor-arg value="周星星"></constructor-arg>
    <constructor-arg value="50"></constructor-arg>
    <constructor-arg name="car" ref="utilmap">
      <!--使用map节点及map的entry子节点配置 Map类型的成员变量-->
      <!--<map>-->
      <!--<entry key="一号车" value-ref="carAndSpeed"></entry>-->
      <!--<entry key="二号车" value-ref="carAndprice"></entry>-->
      <!--</map>-->
    </constructor-arg>
  </bean>

(3.4.5).P 命名空间

为了简化 XML 文件的配置,越来越多的 XML 文件采用属性而非子元素配置信息。
Spring 从 2.5 版本开始引入了一个新的 p 命名空间,可以通过 <bean> 元素属性的方式配置 Bean 的属性。
使用 p 命名空间后,基于 XML 的配置方式将进一步简化

<bean id="PersonWithP" class="Models.Person" p:name="孔明" p:age="33"></bean>

(3.5).XML 配置里的 Bean 自动装配

  • Spring IOC 容器可以自动装配 Bean. 需要做的仅仅是<bean>autowire 属性里指定自动装配的模式
  • byType(根据类型自动装配): 若 IOC 容器中有多个与目标 Bean 类型一致的 Bean. 在这种情况下, Spring 将无法判定哪个 Bean 最合适该属性, 所以不能执行自动装配.
  • byName(根据名称自动装配): 必须将目标 Bean 的名称和属性名设置的完全相同.
  • constructor(通过构造器自动装配): 当 Bean 中存在多个构造器时, 此种自动装配方式将会很复杂. 不推荐使用

缺点:

  • 在 Bean 配置文件里设置 autowire 属性进行自动装配将会装配 Bean 的所有属性. 然而, 若只希望装配个别属性时, autowire 属性就不够灵活了.
  • autowire 属性要么根据类型自动装配, 要么根据名称自动装配, 不能两者兼而有之.

一般情况下,在实际的项目中很少使用自动装配功能,因为和自动装配功能所带来的好处比起来,明确清晰的配置文档更有说服力一些

  <bean id="address" class="com.cnblogs.tangge.autowire.Address" p:city="重庆" p:street="小龙坎"></bean>
  <bean id="car" class="com.cnblogs.tangge.autowire.Car" p:brand="Audi" p:price="300000"></bean>
  <!--
    使用autowire属性指定自动装配方式:
    byName:根据 bean 的名字和当前 bean 的setter 风格的属性名进行自动装配。
    byType:根据 bean 的属性类型自动装配。
  -->
  <bean id="person" class="com.cnblogs.tangge.autowire.Person" p:name="Tom"
    autowire="byName"></bean>
</beans>

3.6.bean 之间的关系:继承;依赖

1).继承

  • Spring 允许继承 bean 的配置, 被继承的 bean 称为父 bean. 继承这个父 Bean 的 Bean 称为子 Bean
  • 子 Bean 从父 Bean 中继承配置, 包括 Bean 的属性配置
  • 子 Bean 也可以覆盖从父 Bean 继承过来的配置
  • 父 Bean 可以作为配置模板, 也可以作为 Bean 实例. 若只想把父 Bean 作为模板, 可以设置 的abstract 属性为 true, 这样 Spring 将不会实例化这个 Bean
  • 并不是 <bean> 元素里的所有属性都会被继承. 比如: autowire, abstract 等.
  • 也可以忽略父 Bean 的 class 属性, 让子 Bean 指定自己的类, 而共享相同的属性配置. 但此时 abstract 必须设为 true

parent 继承

  <bean id="address" class="com.cnblogs.tangge.autowire.Address" p:city="重庆" p:street="小龙坎"></bean>
  <!--bean 配置的继承:使用 parent 属性指定继承哪个 bean 的配置,这里继承了 class与 p:street-->
  <bean id="address2"  p:city="重庆1" parent="address"></bean>

结果

        Address address  =(Address) context.getBean("address");
        System.out.println(address);
        address  =(Address) context.getBean("address2");
        System.out.println(address);
        /*
        Address{city='重庆', street='小龙坎'}
        Address{city='重庆1', street='小龙坎'}
         */

abstract 抽象,作为模板

  <!--
  1.若只想把父 Bean 作为模板, 可以设置 <bean> 的abstract 属性为 true, 这样 Spring 将不会实例化这个 Bean,只能继承
  2.如果没有 class 属性,则必须是一个抽象 bean
   -->
  <bean id="address" class="com.cnblogs.tangge.autowire.Address" p:city="重庆" p:street="小龙坎" abstract="true"></bean>
  <!--bean 配置的继承:使用 parent 属性指定继承哪个 bean 的配置,这里继承了 class与 p:street-->
  <bean id="address2"  p:city="重庆1" parent="address"></bean>

2).依赖
作用:depends-on用来指定Bean初始化及销毁时的顺序。

  • Spring 允许用户通过 depends-on 属性设定 Bean 前置依赖的Bean,前置依赖的 Bean 会在本 Bean 实例化之前创建好
  • 如果前置依赖于多个 Bean,则可以通过逗号,空格或的方式配置 Bean 的名称
  <bean id="car" class="com.cnblogs.tangge.autowire.Car" p:brand="Audi" p:price="300000"></bean>
  <!--要求再配置person时,必须有一个关联car. id="personDependsOn" 这个bean依赖于 id="car" 这个bean-->
  <bean id="personDependsOn" class="com.cnblogs.tangge.autowire.Person" p:name="www" p:address-ref="address2"
    depends-on="car"></bean>

3.7.Bean 的作用域

  • 在 Spring 中, 可以在 <bean> 元素的 scope 属性里设置 Bean 的作用域.
  • 默认情况下, Spring 只为每个在 IOC 容器里声明的 Bean 创建唯一一个实例, 整个 IOC 容器范围内都能共享该实例:所有后续的 getBean() 调用和 Bean 引用都将返回这个唯一的 Bean 实例.该作用域被称为 singleton, 它是所有 Bean 的默认作用域.
类别说明
singleton在 SpringIOC 容器中只存在一个实例,Bean以单实例的形式存在
prototype每次调用 getBean() 返回一个新实例
request每次HTTP请求,返回一个新Bean,该作用域适用 WebApplicationContext环境
session同一个HTTP Session共享一个Bean,不同HTTP Session使用不同的Bean。该作用域适用 WebApplicationContext环境
<bean id="car" class="com.cnblogs.tangge.autowire.Car" p:brand="Audi" p:price="300000"></bean>

代码

        ApplicationContext ctx = new ClassPathXmlApplicationContext("beans-scope.xml");
        Car car1= (Car) ctx.getBean("car");
        Car car2= (Car) ctx.getBean("car");
        System.out.println(car1.equals(car2)); //true

添加 scope="prototype"

  <bean id="car" class="com.cnblogs.tangge.autowire.Car" p:brand="Audi" p:price="300000" scope="prototype"></bean>

返回结果为: System.out.println(car1.equals(car2)); //false

3.8.使用外部属性文件

  • 在配置文件里配置 Bean 时, 有时需要在 Bean 的配置里混入系统部署的细节信息(例如: 文件路径, 数据源配置信息等). 而这些部署细节实际上需要和 Bean 配置相分离
  • Spring 提供了一个 PropertyPlaceholderConfigurer 的 BeanFactory 后置处理器, 这个处理器允许用户将 Bean 配置的部分内容外移到属性文件中. 可以在 Bean 配置文件里使用形式为 ${var} 的变量, PropertyPlaceholderConfigurer 从属性文件里加载属性, 并使用这些属性来替换变量.
  • Spring 还允许在属性文件中使用 ${propName},以实现属性之间的相互引用。

示例:
导入mysql和jdbc的jar包

  <bean id="datasource" class="org.apache.commons.dbcp2.BasicDataSource" >
    <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"></property>
    <property name="url" value="jdbc:mysql://localhost:3306/day22_jdbc?serverTimezone = GMT"></property>
    <property name="username" value="root"></property>
    <property name="password" value="123"></property>
  </bean>

java

 DataSource dataSource = (DataSource) ctx.getBean("datasource");
        System.out.println(dataSource.getConnection());
        //1763344271, URL=jdbc:mysql://localhost:3306/day22_jdbc?serverTimezone = GMT, UserName=root@localhost, MySQL Connector/J

注册PropertyPlaceholderConfigurer

196558-20180815163111677-1042581481.png
db.properties

drivername=com.mysql.cj.jdbc.Driver
url = jdbc:mysql://localhost:3306/day22_jdbc?serverTimezone = GMT
user = root
pass = 123

bean设置

  <context:property-placeholder location="classpath:db.properties"/>
  <!--使用外部配置文件-->
  <bean id="datasource2" class="org.apache.commons.dbcp2.BasicDataSource" >
    <property name="driverClassName" value="${drivername}"></property>
    <property name="url" value="${url}"></property>
    <property name="username" value="${user}"></property>
    <property name="password" value="${pass}"></property>
  </bean>

3.9.SpEL

  • Spring 表达式语言(简称SpEL):是一个支持运行时查询和操作对象图的强大的表达式语言。
  • 语法类似于 EL:SpEL 使用 #{…} 作为定界符,所有在大框号中的字符都将被认为是 SpEL
  • SpEL 为 bean 的属性进行动态赋值提供了便利
  • 通过 SpEL 可以实现:
    • 通过 bean 的 id 对 bean 进行引用
    • 调用方法以及引用对象中的属性
    • 计算表达式的值
    • 正则表达式的匹配
      196558-20180815173552577-1846954395.png
      196558-20180815173604910-1931940328.png
      196558-20180815173613934-1304996796.png
      196558-20180815173639812-685310032.png
      196558-20180815173647250-2818071.png

address.class

package com.cnblogs.tangge.spel;
public class Address {
        private String  city;
        private String street;

    public String getCity() {
        return city;
    }

    public void setCity(String city) {
        this.city = city;
    }

    public String getStreet() {
        return street;
    }

    public void setStreet(String street) {
        this.street = street;
    }

    @Override
    public String toString() {
        return "Address{" +
                "city='" + city + '\'' +
                ", street='" + street + '\'' +
                '}';
    }
}

car.class

package com.cnblogs.tangge.spel;

public class Car {

    public Car() {
        System.out.println("default constuct...");
    }

    //品牌
    private String brand;

    private double price;
    //轮胎周长
    private double tyrePerimeter;

    public void setBrand(String brand) {
        this.brand = brand;
    }
    //#{car.price > 300000 ?'金领':'白领'}的时候,price 这里必须有getter方法。
   public double getPrice() {
        return price;
    }
    
    public void setPrice(double price) {
        this.price = price;
    }

    @Override
    public String toString() {
        return "Car{" +
                "brand='" + brand + '\'' +
                ", price=" + price +
                ", tyrePerimeter=" + tyrePerimeter +
                '}';
    }

    public void setTyrePerimeter(double tyrePerimeter) {
        this.tyrePerimeter = tyrePerimeter;
    }
}

person.class

package com.cnblogs.tangge.spel;

/**
 * @Description:
 * @Package: com.cnblogs.tangge.autowire
 * @ClassName: Person
 * @Author: tangge
 * @CreateDate: 2018年08月14 16:39
 * @Version: 1.0
 **/
public class Person {

    private String name;
    private Car car;
    //引用 address bean 的 city 属性
    private String city;
    //根据car的 price 决定 info:car 的 price >=300000
    private String info;

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", car=" + car +
                ", city='" + city + '\'' +
                ", info='" + info + '\'' +
                '}';
    }

    public void setName(String name) {
        this.name = name;
    }


    public void setCar(Car car) {
        this.car = car;
    }

    public void setCity(String city) {
        this.city = city;
    }

    public void setInfo(String info) {
        this.info = info;
    }
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="address" class="com.cnblogs.tangge.spel.Address">
  <!--spel 字面量-->
  <property name="city" value="#{'Beijing'}"></property>
  <property name="street" value="Wudaokou"></property>
</bean>

  <bean id="car" class="com.cnblogs.tangge.spel.Car">
  <!--spel 字面量-->
  <property name="brand" value="#{'audi'}"></property>
  <property name="price" value="#{300000}"></property>
  <!--spel 调用类的静态属性 T()-->
  <property name="tyrePerimeter" value="#{T(java.lang.Math).PI * 80}"></property>
</bean>

  <bean id="person" class="com.cnblogs.tangge.spel.Person">
    <property name="name" value="#{'tangsansan'}"></property>
    <!--spel 引用其他对象的属性-->
    <property name="city" value="#{address.city}"></property>
    <!--spel 引用其他对象-->
    <property name="car" value="#{car}"></property>
    <!--spel if esle变体-->
    <property name="info" value="#{car.price > 300000 ?'金领':'白领'}"></property>
  </bean>
</beans>

<!--
Address{city='Beijing', street='Wudaokou'}
Car{brand='audi', price=300000.0, tyrePerimeter=251.32741228718345}
Person{name='tangsansan', car=Car{brand='audi', price=300000.0, tyrePerimeter=251.32741228718345}, city='Beijing', info='白领'}
-->

3.9 Bean 生命周期

3.9.1 init-method 与 destroy-method

  • Spring IOC 容器可以管理 Bean 的生命周期, Spring 允许在 Bean 生命周期的特定点执行定制的任务.
  • Spring IOC 容器对 Bean 的生命周期进行管理的过程:
    1.通过构造器或工厂方法创建 Bean 实例
    2.为 Bean 的属性设置值和对其他 Bean 的引用
    3.调用 Bean 的初始化方法
    4.Bean 可以使用了
    5.当容器关闭时, 调用 Bean 的销毁方法
  • 在 Bean 的声明里设置 init-method 和 destroy-method 属性, 为 Bean 指定初始化和销毁方法.
package com.cnblogs.tangge.cycle;

public class Car {
private String brand;

    public Car() {
        System.out.println("Car constructor...");
    }

    public void setBrand(String brand) {
        System.out.println("setBrand..");
        this.brand = brand;
    }

    public void  init(){
        System.out.println("init....");
    }
    public void  destroy(){
        System.out.println("destroy....");
    }

    @Override
    public String toString() {
        return "Car{" +
                "brand='" + brand + '\'' +
                '}';
    }
}

Bean配置

  <bean id="car" class="com.cnblogs.tangge.cycle.Car" init-method="init" destroy-method="destroy">
    <property name="brand" value="audi"></property>
  </bean>

测试

  public static void main(String[] args) {
        //ApplicationContext 扩展类,支持close()
        ConfigurableApplicationContext ctx = new ClassPathXmlApplicationContext("bean-cycle.xml");
        Car car = (Car) ctx.getBean("car");
        System.out.println(car);
        ctx.close();
        /*
        Car constructor...  //构造器
        setBrand..          //setter
        init....            //init()
        Car{brand='audi'}   //toString()
        destroy....         //destroy()
         */
    }

3.9.2 后置处理器

BeanPostProcessor

  • Bean 后置处理器允许在调用初始化方法前后对 Bean 进行额外的处理.
  • Bean 后置处理器对 IOC 容器里的所有 Bean 实例逐一处理, 而非单一实例. 其典型应用是: 检查 Bean 属性的正确性或根据特定的标准更改 Bean 的属性.
  • 对Bean 后置处理器而言, 需要实现 BeanPostProcessor 接口. 在初始化方法被调用前后, Spring 将把每个 Bean 实例分别传递给上述接口的以下两个方法:
    • Object postProcessBeforeInitialization(Object bean, String beanName)
    • Object postProcessAfterInitialization(Object bean, String beanName)
  • Spring IOC 容器对 Bean 的生命周期进行管理的过程:
    • 通过构造器或工厂方法创建 Bean 实例
    • 为 Bean 的属性设置值和对其他 Bean 的引用
    • 将 Bean 实例传递给 Bean 后置处理器的 postProcessBeforeInitialization 方法
    • 调用 Bean 的初始化方法
    • 将 Bean 实例传递给 Bean 后置处理器的 postProcessAfterInitialization方法
    • Bean 可以使用了
    • 当容器关闭时, 调用 Bean 的销毁方法

新建一个MyBeanPostprocessor.class

package com.cnblogs.tangge.cycle;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;

public class MyBeanPostprocessor implements BeanPostProcessor {

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.printf("postProcessBeforeInitialization:%s,%s %n",bean,beanName);
        if ("car".equals(beanName)){
        Car car = new Car();
            car.setBrand("BMW");
            return car;
        }
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.printf("postProcessAfterInitialization:%s,%s %n",bean,beanName);
        return bean;
    }
}

配置bean

  <!--
    实现BeanPostProcessor接口,提供具体接口
    Object postProcessBeforeInitialization(Object bean, String beanName) init-method 之前被调用
    Object postProcessAfterInitialization(Object bean, String beanName) init-method 之后被调用

    bean:bean 实例本身
    beanName: IOC 容器配置的 bean 的名字
    返回值:是实际上返回给用户的那个 bean,注意:以上两个方法修改 bean,甚至返回一个新的 bean
  -->
  <!--配置 bean 后置处理器:不需要id,IOC容器自动识别是一个 BeanPostProcessor-->
  <bean class="com.cnblogs.tangge.cycle.MyBeanPostprocessor"></bean>

运行

    public static void main(String[] args) {
        //ApplicationContext 扩展类,支持close()
        ConfigurableApplicationContext ctx = new ClassPathXmlApplicationContext("bean-cycle.xml");
        Car car = (Car) ctx.getBean("car");
        System.out.println(car);
        ctx.close();
        /**
         Car constructor...                                     //1.构造器
         setBrand..                                             //2.setter
         postProcessBeforeInitialization:Car{brand='audi'},car  //3.postProcessBeforeInitialization
         Car constructor...                                      //postProcessBeforeInitialization 先new个car 在stter
         setBrand..
         init....                                                //4.init
         postProcessAfterInitialization:Car{brand='BMW'},car    //5.postProcessAfterInitialization
         Car{brand='BMW'}                                        //toString()
         destroy....                                             //6.destroy()
         */
    }

4.其他配置方式(除了反射)

4.1 工厂方法

4.1.1.静态方法 factory-method

调用静态工厂方法创建 Bean是将对象创建的过程封装到静态方法中. 当客户端需要对象时, 只需要简单地调用静态方法, 而不同关心创建对象的细节.
要声明通过静态方法创建的 Bean, 需要在 Bean 的 class 属性里指定拥有该工厂的方法的类, 同时在 factory-method 属性里指定工厂方法的名称. 最后, 使用 <constrctor-arg> 元素为该方法传递方法参数.

package com.cnblogs.tangge.factory;
import java.util.HashMap;
import java.util.Map;

public class staticCarFactory {
    private static Map<String,Car> cars = new HashMap<>();
    static {
        cars.put("audi",new Car("audi",300000));
        cars.put("ford",new Car("ford",230000));
    }

 //静态方法
    public static Car getCar(String name){
        return cars.get(name) ;
    }
}

Bean
constructor-arg:如果工厂方法需要传入参数,则使用constructor-arg来配置参数

    <!--静态方法配置bean,不是配置工厂方法实例,而是bean实例-->
  <!--
  class:指向静态方法全类名
  factory-method:指向静态工厂方法的名字
  constructor-arg:如果工厂方法需要传入参数,则使用constructor-arg来配置参数
  -->
  <bean id="car" class="com.cnblogs.tangge.factory.staticCarFactory" factory-method="getCar">
    <constructor-arg value="ford"></constructor-arg>
  </bean>

这里测试的结果为:Car{brand='ford', price=230000.0}

4.1.2.实例方法 factory-bean

要声明通过实例工厂方法创建的 Bean

  • 在 bean 的 factory-bean 属性里指定拥有该工厂方法的 Bean
  • factory-method 属性里指定该工厂方法的名称
  • 使用 construtor-arg 元素为工厂方法传递方法参数
    InstanceCarFactory.class
package com.cnblogs.tangge.factory;

import java.util.HashMap;
import java.util.Map;

/**
 * @Description:实例工厂的方法
 *
 **/
public class InstanceCarFactory {
    private static Map<String,Car> cars = null;

    public InstanceCarFactory() {
        cars = new HashMap<>();
        cars.put("audi",new Car("audi",300000));
        cars.put("ford",new Car("ford",230000));
    }

    public Car getInstanceCar(String name){
        return cars.get(name) ;
    }
}

Bean

  <!--实例工厂方法-->
  <bean id="carFactory" class="com.cnblogs.tangge.factory.InstanceCarFactory"/>
  <bean id="car2" factory-bean="carFactory" factory-method="getInstanceCar">
    <constructor-arg value="audi"></constructor-arg>
  </bean>

调用结果:Car{brand='audi', price=300000.0}

4.2 FactoryBean

  • Spring 中有两种类型的 Bean, 一种是普通Bean, 另一种是工厂Bean, 即FactoryBean.
  • 工厂 Bean 跟普通Bean不同, 其返回的对象不是指定类的一个实例, 其返回的是该工厂 Bean 的 getObject 方法所返回的对象
    196558-20180816114934769-1286044687.png
    FactoryBean接口定义
package org.springframework.beans.factory;

public interface FactoryBean<T> {
    T getObject() throws Exception;

    Class<?> getObjectType();

    boolean isSingleton();
}

FactoryBean 通常是用来创建比较复杂的bean,一般的bean 直接用xml配置即可,但如果一个bean的创建过程中涉及到很多其他的bean 和复杂的逻辑,用xml配置比较困难,这时可以考虑用FactoryBean。
创建我的FactoryBean

package com.cnblogs.tangge.factoryBean;

import org.springframework.beans.factory.FactoryBean;

public class MyFactoryBean implements FactoryBean<Car> {
    
    private String brand;

    public String getBrand() {
        return brand;
    }

    public void setBrand(String brand) {
        this.brand = brand;
    }
    /**
     *
     * @return 返回bean的对象
     * @throws Exception
     */
    @Override
    public Car getObject() throws Exception {
        return new Car(brand,500000);
    }

    /**
     *
     * @return bean的类型
     */
    @Override
    public Class<?> getObjectType() {
        return Car.class;
    }

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


}

Bean

  <!--
    通过FactoryBean 配置 Bean实例
    class: 指向 FactoryBean 的全类名
    property: 配置 FactoryBean 属性
    但实际返回的实例却是 FactoryBean 的 getObject() 方法返回的实例
  -->
  <bean id="car" class="com.cnblogs.tangge.factoryBean.MyFactoryBean">
    <property name="brand" value="BMW"></property>
  </bean>

测试结果:Car{brand='BMW', price=500000.0}

5.其他配置形式(除了基于XML)

5.1.注解

  • 在classPath中扫描组件,侦测和实例化具有特定注解的组件
  • 特定组件包括:
    • @Component: 基本注解, 标识了一个受 Spring 管理的组件
    • @Respository: 标识持久层组件
    • @Service: 标识服务层(业务层)组件
    • @Controller: 标识表现层组件
      Spring 有默认的命名策略: 使用非限定类名, 第一个字母小写. 也可以在注解中通过 value 属性值标识组件的名称
  • 当在组件类上使用了特定的注解之后, 还需要在 Spring 的配置文件中声明 <context:component-scan>
    • base-package 属性指定一个需要扫描的基类包,Spring 容器将会扫描这个基类包里及其子包中的所有类.
    • 当需要扫描多个包时, 可以使用逗号分隔.
    • 如果仅希望扫描特定的类而非基包下的所有类,可使用 resource-pattern 属性过滤特定的类,示例:
      196558-20180816145113351-228555805.png
    • <context:include-filter> 子节点表示要包含的目标类
    • <context:exclude-filter> 子节点表示要排除在外的目标类
    • context:component-scan 下可以拥有若干个 context:include-filter 和 context:exclude-filter 子节点

context:include-filter 和 context:exclude-filter 子节点支持多种类型的过滤表达式:

Filter Type示例描述
annotationorg.example.SomeAnnotation符合SomeAnnoation的target class
assignableorg.example.SomeClass指定class或interface的全名
aspectjorg.example..*Service+AspectJ語法
regexorg.example.Default.*Regelar Expression
customorg.example.MyTypeFilterSpring3新增自訂Type,實作org.springframework.core.type.TypeFilter

196558-20180816152208125-1895083330.png
TestObject.class

package com.cnblogs.tangge.annoncation;

import org.springframework.stereotype.Component;

@Component
public class TestObject {

}

UserController.class

package com.cnblogs.tangge.annoncation.Controller;

import org.springframework.stereotype.Controller;


@Controller
public class UserController {
        public void execute(){
            System.out.println("UserController execute...");
        }
}

UserService.class

package com.cnblogs.tangge.annoncation.Service;

import org.springframework.stereotype.Service;

@Service
public class UserService {
    public void  add(){
        System.out.println("UserService add...");
    }
}

接口 UserRepository.class

package com.cnblogs.tangge.annoncation.Repository;

public interface UserRepository {

    void save();
}

UserRepositoryImp.class

package com.cnblogs.tangge.annoncation.Repository;

import org.springframework.stereotype.Repository;

@Repository("userRepository")
public class UserRepositoryImp implements UserRepository {

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

bean-annocation.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"
  xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
  <!--指定Spring扫描的包-->
  <context:component-scan base-package="com.cnblogs.tangge.annoncation"></context:component-scan>
</beans>

测试

    public static void main(String[] args) {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("bean-annocation.xml");

        TestObject test = (TestObject) ctx.getBean("testObject");
        System.out.println(test);
        UserController controller = (UserController) ctx.getBean("userController");
        System.out.println(controller);
        UserService service = (UserService) ctx.getBean("userService");
        System.out.println(service);
        UserRepository repository = (UserRepository) ctx.getBean("userRepository");
        System.out.println(repository);
    }
        /*
    com.cnblogs.tangge.annoncation.TestObject@1990a65e
    com.cnblogs.tangge.annoncation.Controller.UserController@64485a47
    com.cnblogs.tangge.annoncation.Service.UserService@25bbf683
    com.cnblogs.tangge.annoncation.Repository.UserRepositoryImp@6ec8211c
     */

5.1.1 resource-pattern

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

  <context:component-scan base-package="com.cnblogs.tangge.annoncation"
    resource-pattern="repository/*.class"></context:component-scan>

报错:No bean named 'testObject' available
只能扫描 UserRepository

//        TestObject test = (TestObject) ctx.getBean("testObject");
//        System.out.println(test);
//        UserController controller = (UserController) ctx.getBean("userController");
//        System.out.println(controller);
//        UserService service = (UserService) ctx.getBean("userService");
//        System.out.println(service);
        UserRepository repository = (UserRepository) ctx.getBean("userRepository");
        System.out.println(repository);

5.1.2 <context:exclude-filter>

  <context:component-scan base-package="com.cnblogs.tangge.annoncation">
    <!--不包含Repository类-->
    <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Repository" />
  </context:component-scan>

执行

public static void main(String[] args) {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("bean-annocation.xml");

        TestObject test = (TestObject) ctx.getBean("testObject");
        System.out.println(test);
        UserController controller = (UserController) ctx.getBean("userController");
        System.out.println(controller);
        UserService service = (UserService) ctx.getBean("userService");
        System.out.println(service);
        UserRepository repository = (UserRepository) ctx.getBean("userRepository");
        System.out.println(repository);
    }
    /*
    com.cnblogs.tangge.annoncation.TestObject@1990a65e
    com.cnblogs.tangge.annoncation.Controller.UserController@64485a47
    com.cnblogs.tangge.annoncation.Service.UserService@25bbf683
    Exception in thread "main" org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named 'userRepository' available
    'userRepository' 不可用
     */

5.1.3 <context:include-filter>

<!--
    context:include-filter:指定包含哪些表达式的组件,必须与use-default-filters配合使用
    use-default-filters:设置为false
  -->
  <context:component-scan base-package="com.cnblogs.tangge.annoncation" use-default-filters="false">
    <!--只包含Repository类-->
    <context:include-filter  type="annotation" expression="org.springframework.stereotype.Repository" />
  </context:component-scan>

这里执行,直接失败,因为只能实例 userRepository,其他都失败。

5.1.4 type="assignable"

指定class或interface的全名

  <context:component-scan base-package="com.cnblogs.tangge.annoncation" use-default-filters="false">
    <!--只包含Repository类,
    type=assignable :  指定class或interface的全名
    expression : class全名
    -->
    <context:include-filter  type="assignable" expression="com.cnblogs.tangge.annoncation.Repository.UserRepository" />
  </context:component-scan>

和上面效果一样,只能实例化 userRepository。

5.1.5 组件装配

<context:component-scan> 元素还会自动注册 AutowiredAnnotationBeanPostProcessor 实例, 该实例可以自动装配具有 @Autowired@Resource@Inject注解的属性.

@Autowired

  • 构造器, 普通字段(即使是非 public), 一切具有参数的方法都可以应用@Authwired 注解
    Bean设置
  <context:component-scan base-package="com.cnblogs.tangge.annoncation">
  </context:component-scan>

UserController.class
调用 userService.add();

package com.cnblogs.tangge.annoncation.Controller;

import com.cnblogs.tangge.annoncation.Service.UserService;
import org.springframework.stereotype.Controller;


@Controller
public class UserController {

    private UserService userService;
        public void execute(){
            System.out.println("UserController execute...");
            userService.add();
        }
}

UserService.class
调用 userRepository.save();

package com.cnblogs.tangge.annoncation.Service;

import com.cnblogs.tangge.annoncation.Repository.UserRepository;
import org.springframework.stereotype.Service;

@Service
public class UserService {
    private UserRepository userRepository;
    public void  add(){
        System.out.println("UserService add...");
        userRepository.save();
    }
}

调用

public static void main(String[] args) {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("bean-annocation.xml");
        UserController controller = (UserController) ctx.getBean("userController");
        controller.execute();
    }

执行结果:

UserController execute...
    at com.cnblogs.tangge.annoncation.Controller.UserController.execute(UserController.java:13)
    at com.cnblogs.tangge.annoncation.annocationMain.main(annocationMain.java:29)

我们UserController在添加@Autowired

@Controller
public class UserController {
    @Autowired
    private UserService userService;
        public void execute(){
            System.out.println("UserController execute...");
            userService.add();
        }
}

也在UserService添加@Autowired

@Service
public class UserService {

    @Autowired
    private UserRepository userRepository;
    public void  add(){
        System.out.println("UserService add...");
        userRepository.save();
    }
}

执行结果:
UserController execute...
UserService add...
UserRepository save..

  • 默认情况下, 所有使用 @Authwired 注解的属性都需要被设置. 当 Spring 找不到匹配的 Bean 装配属性时, 会抛出异常, 若某一属性允许不被设置, 可以设置 @Authwired 注解的 required 属性为 false
    现在修改UserRepositoryImp
package com.cnblogs.tangge.annoncation.Repository;

import com.cnblogs.tangge.annoncation.TestObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;

@Repository("userRepository")
public class UserRepositoryImp implements UserRepository {
    @Autowired
    TestObject testObject;
    @Override
    public void save() {
        System.out.println("UserRepository save..");
        System.out.println(testObject);
    }
}

然后把TestObject的@Component 去掉

package com.cnblogs.tangge.annoncation;

public class TestObject {

}

报错:

DependencyException: Error creating bean with name 'userRepository': Unsatisfied dependency expressed through field 'testObject'; 

修改 @Autowired(required = false) 不检查 TestObject

@Repository("userRepository")
public class UserRepositoryImp implements UserRepository {
    @Autowired(required = false)
    TestObject testObject;
    @Override
    public void save() {
        System.out.println("UserRepository save..");
        System.out.println(testObject);
    }
}

执行结果:
UserController execute...
UserService add...
UserRepository save..
null

  • 默认情况下, 当 IOC 容器里存在多个类型兼容的 Bean 时, 通过类型的自动装配将无法工作. 此时可以在 @Qualifier 注解里提供 Bean 的名称. Spring 允许对方法的入参标注 @Qualifiter 已指定注入 Bean 的名称

UserRepositoryImp修改,去掉命名@Repository,使用默认

/@Repository("userRepository")
@Repository
public class UserRepositoryImp implements UserRepository {
    @Autowired(required = false)
    TestObject testObject;
    @Override
    public void save() {
        System.out.println("UserRepository save..");
        System.out.println(testObject);
    }
}

创建第2个Repository,UserjdbcRrpository.class

package com.cnblogs.tangge.annoncation.Repository;

import org.springframework.stereotype.Repository;

@Repository
public class UserjdbcRrpository implements UserRepository {

    @Override
    public void save() {
        System.out.println("UserjdbcRrpository save...");
    }
}

执行结果:
报错:expected single matching bean but found 2: userjdbcRrpository,userRepositoryImp

下面,修改UserService,添加 @Qualifier,指定注入的Bean名称

@Service
public class UserService {

    @Autowired
    @Qualifier("userjdbcRrpository")
    private UserRepository userRepository;
    public void  add(){
        System.out.println("UserService add...");
        userRepository.save();
    }
}

执行结果:
UserController execute...
UserService add...
UserjdbcRrpository save...

实际上,@Qualifier("userjdbcRrpository")还可以写到形参前面,

@Service
public class UserService {

    private UserRepository userRepository;
    
    @Autowired
    public void setUserRepository(@Qualifier("userjdbcRrpository") UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    public void  add(){
        System.out.println("UserService add...");
        userRepository.save();
    }
}
  • @Authwired 注解也可以应用在数组类型的属性上, 此时 Spring 将会把所有匹配的 Bean 进行自动装配.
  • @Authwired 注解也可以应用在集合属性上, 此时 Spring 读取该集合的类型信息, 然后自动装配所有与之兼容的 Bean.
  • @Authwired 注解用在 java.util.Map 上时, 若该 Map 的键值为 String, 那么 Spring 将自动装配与之 Map 值类型兼容的 Bean, 此时 Bean 的名称作为键值

6.Spring4.x:泛型依赖注入

Spring 4.x 中可以为子类注入子类对应的泛型类型的成员变量的引用
196558-20180817120542673-1556470662.png

7.Spring AOP

  • AOP(Aspect-Oriented Programming, 面向切面编程): 是一种新的方法论, 是对传统 OOP(Object-Oriented - Programming, 面向对象编程) 的补充.
  • AOP 的主要编程对象是切面(aspect), 而切面模块化横切关注点.
  • 在应用 AOP 编程时, 仍然需要定义公共功能, 但可以明确的定义这个功能在哪里, 以什么方式应用, 并且不必修改受影响的类. 这样一来横切关注点就被模块化到特殊的对象(切面)里.
  • AOP 的好处:
    • 每个事物逻辑位于一个位置, 代码不分散, 便于维护和升级
    • 业务模块更简洁, 只包含核心业务代码.
      196558-20180817150923073-231420169.png
  • 切面(Aspect): 横切关注点(跨越应用程序多个模块的功能)被模块化的特殊对象
  • 通知(Advice): 切面必须要完成的工作
  • 目标(Target): 被通知的对象
  • 代理(Proxy): 向目标对象应用通知之后创建的对象
  • 连接点(Joinpoint):程序执行的某个特定位置:如类某个方法调用前、调用后、方法抛出异常后等。连接点由两个信息确定:方法表示的程序执行点;相对点表示的方位。例如 ArithmethicCalculator#add() 方法执行前的连接点,执行点为 ArithmethicCalculator#add(); 方位为该方法执行前的位置
  • 切点(pointcut):每个类都拥有多个连接点:例如 ArithmethicCalculator 的所有方法实际上都是连接点,即连接点是程序类中客观存在的事务。AOP 通过切点定位到特定的连接点。类比:连接点相当于数据库中的记录,切点相当于查询条件。切点和连接点不是一对一的关系,一个切点匹配多个连接点,切点通过 org.springframework.aop.Pointcut 接口进行描述,它使用类和方法作为连接点的查询条件。

7.1 AspectJ 注解

AspectJ:Java 社区里最完整最流行的 AOP 框架.
在 Spring2.0 以上版本中, 可以使用基于 AspectJ 注解或基于 XML 配置的 AOP

1、aspectjweaver.jar 下载地址:aspectjweaver.jar
2、下载
com.springsource.org.aopalliance-1.0.0.jar
com.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar
spring-aop-4.3.18.RELEASE.jar
spring-aspects-4.3.18.RELEASE.jar

  • 要在 Spring 应用中使用 AspectJ 注解, 必须在 classpath 下包含 AspectJ 类库: aopalliance.jar、aspectj.weaver.jar 和 spring-aspects.jar
  • 将 aop Schema 添加到 <beans> 根元素中.
  • 要在 Spring IOC 容器中启用 AspectJ 注解支持, 只要在 Bean 配置文件中定义一个空的 XML 元素 <aop:aspectj-autoproxy>
  • 当 Spring IOC 容器侦测到 Bean 配置文件中的 <aop:aspectj-autoproxy> 元素时, 会自动为与 AspectJ 切面匹配的 Bean 创建代理.
  • 用 AspectJ 注解声明切面

  • 要在 Spring 中声明 AspectJ 切面, 只需要在 IOC 容器中将切面声明为 Bean 实例. 当在 Spring IOC 容器中初始化 AspectJ 切面之后, Spring IOC 容器就会为那些与 AspectJ 切面相匹配的 Bean 创建代理.
  • 在 AspectJ 注解中, 切面只是一个带有 @Aspect 注解的 Java 类.
  • 通知是标注有某种注解的简单的 Java 方法.
  • AspectJ 支持 5 种类型的通知注解:
    • @Before: 前置通知, 在方法执行之前执行
    • @After: 后置通知, 在方法执行之后执行
    • @AfterRunning: 返回通知, 在方法返回结果之后执行
    • @AfterThrowing: 异常通知, 在方法抛出异常之后
    • @Around: 环绕通知, 围绕着方法执行

7.1.1.前置通知

  1. <aop:aspectj-autoproxy/>
  2. 横向关注点抽象到切面类中
    • 加入@Component
    • @Aspect 注解
  3. 各种通知

bean

  <context:component-scan base-package="com.cnblogs.tangge"></context:component-scan>
  <!--使用 AspjectJ 注解起作用:自动为匹配的类生成代理对象-->
  <aop:aspectj-autoproxy/>

接口ArithmeticCalculator

package com.cnblogs.tangge.spring.aop.impl;

public interface ArithmeticCalculator {
        int add(int i, int j);
        int sub(int i, int j);

        int mul(int i, int j);
        int div(int i, int j);
}

实现类ArithmeticCalculatorImpl

package com.cnblogs.tangge.spring.aop.impl;

import org.springframework.stereotype.Component;

@Component
public class ArithmeticCalculatorImpl implements ArithmeticCalculator {

    @Override
    public int add(int i, int j) {
        int result = i+j;
        return result;
    }

    @Override
    public int sub(int i, int j) {
        int result = i-j;
        return result;
    }

    @Override
    public int mul(int i, int j) {
        int result = i*j;
        return result;
    }

    @Override
    public int div(int i, int j) {
        int result = i/j;
        return result;
    }
}

创建一个AOP

package com.cnblogs.tangge.spring.aop.impl;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

//把这个类声明为一个切面:需要把该类放入IOC容器中。再声明为一个切面
@Aspect
@Component
public class LogAspect {

    //声明一个前置通知:在目标方法开始之前
    @Before("execution(public int com.cnblogs.tangge.spring.aop.impl.ArithmeticCalculator.add(int,int))")
    public void beforeMethod() {
        System.out.println("the method add begins");
    }

    public void afterMethod() {
        System.out.println("sub result:");
    }
}

测试

public class demo {

    public static void main(String[] args) {
        //1.创建Spring 的IOC容器
        ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");

        //2.从IOC 容器获取 bean
        ArithmeticCalculator arithmeticCalculator = ctx.getBean(ArithmeticCalculator.class);

        //3.使用bean
        int result = arithmeticCalculator.add(1,3);
        System.out.println("result:"+result);
        /*
         the mothod add begins
         result:4
         */
    }
}

7.1.2 后置通知,返回通知,异常通知

package com.cnblogs.tangge.spring.aop.impl;

import java.util.Arrays;
import java.util.List;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

//把这个类声明为一个切面:需要把该类放入IOC容器中。再声明为一个切面
@Aspect
@Component
public class LogAspect {

    //声明一个前置通知:在目标方法开始之前
    @Before("execution(public int com.cnblogs.tangge.spring.aop.impl.ArithmeticCalculator.*(int,int))")
    public void beforeMethod(JoinPoint joinPoint) {
        String methodName = joinPoint.getSignature().getName();
        //Arrays.asList(T... a) 返回由指定数组支持的固定大小的列表。
        List<Object> args  = Arrays.asList(joinPoint.getArgs());
        System.out.println("前置通知@Before:the method "+methodName+"  begins"+args);
    }

    /**
     * 方法执行后,执行的代码
     * @param joinPoint
     */
    @After("execution(public int com.cnblogs.tangge.spring.aop.impl.ArithmeticCalculator.*(..))")
    public void afterMethod(JoinPoint joinPoint) {
        String methodName = joinPoint.getSignature().getName();
        System.out.println("后置通知@After:the method "+methodName+"  end..");
    }

    /**
     * 返回通知, 在方法返回结果之后执行
     * @param joinPoint
     * @param result1 可以访问方法返回值
     */
    @AfterReturning(value = "execution(public int com.cnblogs.tangge.spring.aop.impl.ArithmeticCalculator.*(..))",returning = "result1")
    public void afterReturningMethod(JoinPoint joinPoint,Object result1) {
        String methodName = joinPoint.getSignature().getName();
        System.out.println("返回通知@AfterReturning:the method "+methodName+"  AfterReturning..The result with:"+result1);
    }

    /**
     * 异常通知,可以访问异常对象,且可以指定出现特定异常时在执行通知代码
     * @param joinPoint
     * @param exception
     */
    @AfterThrowing(value = "execution(public int com.cnblogs.tangge.spring.aop.impl.ArithmeticCalculator.*(..))",
            throwing = "exception")
    public void afterThrowingMethod(JoinPoint joinPoint,Exception exception) {
        String methodName = joinPoint.getSignature().getName();
        System.out.println("异常通知@AfterReturning:the method "+methodName+"  throwing exception:"+exception);
    }
}

执行结果:

前置通知@Before:the method add begins[1, 3]
后置通知@After:the method add end..
返回通知@AfterReturning:the method add AfterReturning..The result with:4
result:4
前置通知@Before:the method div begins[10, 2]
后置通知@After:the method div end..
返回通知@AfterReturning:the method div AfterReturning..The result with:5
result:5

7.1.3 环绕通知

  /**
     * 环绕通知
     * @param joinPoint
     */
    @Around(value = "execution(public int com.cnblogs.tangge.spring.aop.impl.ArithmeticCalculator.*(..))")
    public Object AroundMethod(ProceedingJoinPoint joinPoint) {
        String methodName = joinPoint.getSignature().getName();
        System.out.println("-----进入环绕通知-----");
        //执行目标方法
        Object result = null;
        try {
            System.out.println("前置通知@Before:the method "+methodName+"  begins"+ Arrays.asList(joinPoint.getArgs()));
            //执行目标方法
            result = joinPoint.proceed();
            System.out.println("返回通知@AfterReturning:the method "+methodName+"  AfterReturning..The result with:"+result);
        } catch (Throwable throwable) {
            System.out.println("异常通知@AfterReturning:the method "+methodName+"  throwing exception:"+throwable.toString());
            throwable.printStackTrace();
        }
        System.out.println("后置通知@After:the method "+methodName+"  end..");

        return result;
    }

执行结果:

-----进入环绕通知-----
前置通知@Before:the method add begins[1, 3]
返回通知@AfterReturning:the method add AfterReturning..The result with:4
后置通知@After:the method add end..
result:4
-----进入环绕通知-----
前置通知@Before:the method div begins[10, 2]
返回通知@AfterReturning:the method div AfterReturning..The result with:5
后置通知@After:the method div end..
result:5

7.1.3 优先级 @Order

切面的优先级可以通过实现 Ordered 接口或利用 @Order 注解指定.
实现 Ordered 接口, getOrder() 方法的返回值越小, 优先级越高.
若使用 @Order 注解, 序号出现在注解中

196558-20180820111629664-1896591591.png

7.1.4 重用切入点 @Pointcut

        /**
         * @Pointcut 声明切入点表达式
         * 其他通知直接使用方法名引用当前的切入点
      */
    @Pointcut("execution(public int com.cnblogs.tangge.spring.aop.impl.ArithmeticCalculator.*(..))")
        public void declarexecution() {}

其他方法通过 @Pointcut 注解将一个切入点声明成简单的方法. 切入点的方法体通常是空的, 因为将切入点定义与应用程序逻辑混在一起是不合理的.

    /**
     * 环绕通知
     * @param joinPoint
     */
    @Around(value = "declarexecution()")
    public Object AroundMethod(ProceedingJoinPoint joinPoint) {
    ...
    }

7.2 在 XML 配置的 AOP

  • 除了使用 AspectJ 注解声明切面, Spring 也支持在 Bean 配置文件中声明切面. 这种声明是通过 aop schema 中的 XML 元素完成的.
  • 正常情况下, 基于注解的声明要优先于基于 XML 的声明. 通过 AspectJ 注解, 切面可以与 AspectJ 兼容, 而基于 XML 的配置则是 Spring 专有的. 由于 AspectJ 得到越来越多的 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 http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">

  <!--1.配置bean-->
  <bean id="arithmeticCalculator" class="com.cnblogs.tangge.spring.aop.impl.ArithmeticCalculatorImpl"></bean>

  <!--2.配置切面的bean-->
  <bean id="logAspect" class="com.cnblogs.tangge.spring.aop.impl.LogAspect"></bean>

  <!--3.配置AOP-->
  <aop:config>
    <!--配置切点表达式-->
    <aop:pointcut id="pointcut" expression="execution(public int com.cnblogs.tangge.spring.aop.impl.ArithmeticCalculator.*(..))"></aop:pointcut>
    <!--配置切面及通知-->
    <aop:aspect ref="logAspect" order="2">
      <aop:around method="AroundMethod" pointcut-ref="pointcut"></aop:around>
    </aop:aspect>
  </aop:config>
</beans>

196558-20180820121621924-878025464.png

8 JdbcTemplate

  • 每次使用都创建一个 JdbcTemplate 的新实例, 这种做法效率很低下.
  • JdbcTemplate 类被设计成为线程安全的, 所以可以再 IOC 容器中声明它的单个实例, 并将这个实例注入到所有的 DAO 实例中.
  • JdbcTemplate 也利用了 Java 1.5 的特定(自动装箱, 泛型, 可变长度等)来简化开发
  • Spring JDBC 框架还提供了一个 JdbcDaoSupport 类来简化 DAO 实现. 该类声明了 jdbcTemplate 属性, 它可以从 IOC 容器中注入, 或者自动从数据源中创建.

db.properties

drivername=com.mysql.cj.jdbc.Driver
url = jdbc:mysql://localhost:4040/day22_jdbc?serverTimezone = GMT
user = root
pass = 123

Bean配置 applicationContext.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  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
  http://www.springframework.org/schema/context/spring-context.xsd">

  <!--引用外部文件db.properties-->
  <context:property-placeholder location="classpath:db.properties"/>

  <!--配置jdbc-->
  <bean id="datasource" class="org.apache.commons.dbcp2.BasicDataSource" >
    <property name="driverClassName" value="${drivername}"></property>
    <property name="url" value="${url}"></property>
    <property name="username" value="${user}"></property>
    <property name="password" value="${pass}"></property>
  </bean>

  <!--配置Spring的JdbcTemplate-->
  <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
    <property name="dataSource" ref="datasource"></property>
  </bean>

  <!--指定Spring扫描的包
  <context:component-scan> 元素还会自动注册 AutowiredAnnotationBeanPostProcessor 实例,
  该实例可以自动装配具有 @Autowired 和 @Resource 、@Inject注解的属性.
  -->
  <context:component-scan base-package="com.cnblogs.tangge.jdbc">
  </context:component-scan>
</beans>

下面进行测试

package com.cnblogs.tangge.jdbc;

import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import javax.sql.DataSource;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;

public class JDBCTest {

    ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
    JdbcTemplate jdbcTemplate = (JdbcTemplate) ctx.getBean("jdbcTemplate");

    public static void main(String[] args) {
        JDBCTest test = new JDBCTest();
        try {
            test.UpdateTemplate();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 更新:insert,update,delete
     */
    public void UpdateTemplate() {
        String sql = "UPDATE `day22_jdbc`.`sort` SET  `sname`=? WHERE (`sid`=?);\n ";
        jdbcTemplate.update(sql, "什么球", 5);
    }

    /**
     * 批量更新:insert,update,delete
     */
    public void InsertTemplate() {
        String sql = "insert into sort(`sname`, `sprice`, `sdesc`) VALUES (?,?,?) ";
        List<Object[]> batchList = new ArrayList<>();
        batchList.add(new Object[]{"棒球", 36.22, "体育用品"});
        batchList.add(new Object[]{"冰箱", 1466.99, "加点用品"});
        jdbcTemplate.batchUpdate(sql, batchList);
    }

    /**
     * 读取一条数据,得到对应的对象
     * 注意不是调用 queryForObject(String sql, Class<T> requiredType, Object... args)
     * 需要: queryForObject(String sql, RowMapper<T> rowMapper, Object... args)
     * 1.RowMapper<T> 指定如何去映射行结果集的行,常用实现类 BeanPropertyRowMapper
     * 2.不支持级联属性 JdbcTemplate 是一个小工具,不是 ORM 框架
     */
    public void testQueryForObject() {
        String sql = "select * from employee where id = ?";
    //Employee employee  =jdbcTemplate.queryForObject(sql,Employee.class,1);
        RowMapper<Employee> rowMapper = new BeanPropertyRowMapper<>(Employee.class);
        Employee employee = jdbcTemplate.queryForObject(sql, rowMapper, 1);
        System.out.println(employee);
        //结果:Employee{id=1, lastName='sansan', firstName='tang', email='fenm@qq.com'}
    }

    /**
     * 读取一条数据,得到对应的对象
     * 配置Dao,调用Dao封装方法
     */
    public void testQueryForObjectByDao() {
        //EmployeeDao dao = new EmployeeDao();
        //这里需要bean配置,直接new是傻了,EmployeeDao类上 @Repository自动实例
        EmployeeDao dao = ctx.getBean(EmployeeDao.class);
        Employee employee = dao.getEmployee(1);
        System.out.println(employee);
        //结果:Employee{id=1, lastName='sansan', firstName='tang', email='fenm@qq.com'}
    }

    /**
     * 查询:实体类集合
     * 不是调用的 queryForList
     */
    public void TestQueryForList() {
        String sql = "select * from employee";
        RowMapper<Employee> rowMapper = new BeanPropertyRowMapper<>(Employee.class);
        List<Employee> employee = jdbcTemplate.query(sql, rowMapper);
        System.out.println(employee);
        //[Employee{id=1, lastName='sansan', firstName='tang', email='fenm@qq.com'},
        // Employee{id=2, lastName='li', firstName='xiao', email='denm@qq.com'}]
    }

    /**
     * 查询:单个的值
     */
    public void testQueryForOne() {
        String sql = "select count(*) from employee";
        Long count = jdbcTemplate.queryForObject(sql, Long.class);
        System.out.println(count);  //2
    }

    public void TestConnection() throws SQLException {
        DataSource dataSource = (DataSource) ctx.getBean("datasource");
        System.out.println(dataSource.getConnection());
    }
}

Dao封装方法

@Repository
public class EmployeeDao {

    @Autowired
    private  JdbcTemplate jdbcTemplate;

    public  Employee getEmployee(int id) {
        String sql = "select * from employee where id = ?";
        RowMapper<Employee> rowMapper  = new BeanPropertyRowMapper<>(Employee.class);
        Employee employee  =jdbcTemplate.queryForObject(sql,rowMapper,id);
      return employee;
    }
}

调用

 /**
     * 读取一条数据,得到对应的对象
     * 配置Dao,调用Dao
     */
    public void testQueryForObjectByDao() {
        //EmployeeDao dao = new EmployeeDao();
        //这里需要bean配置,直接new是傻了,EmployeeDao类上 @Repository自动实例
        EmployeeDao dao = ctx.getBean(EmployeeDao.class);
        Employee employee = dao.getEmployee(1);
        System.out.println(employee);
        //结果:Employee{id=1, lastName='sansan', firstName='tang', email='fenm@qq.com'}
    }

8.1 在JDBC使用具名参数 NamedParameterJdbcTemplate

  • 在 SQL 语句中使用具名参数时, 可以在一个 Map 中提供参数值, 参数名为键
  • 也可以使用 SqlParameterSource 参数
  • 批量更新时可以提供 Map 或 SqlParameterSource 的数组

bean

 <!--配置namedParameterJdbcTemplate,该对象可使用具名参数
  其没有无参构造器,所以必须制定构造器-->
  <bean id="namedParameterJdbcTemplate" class="org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate">
    <constructor-arg ref="datasource"></constructor-arg>
  </bean>

实现


NamedParameterJdbcTemplate namedParameterJdbcTemplate = ctx.getBean(NamedParameterJdbcTemplate.class);

 /**
     * update(String sql, Map<String, ?> paramMap)
     * Map<String, ?> paramMap 可以为参数起名字。
     * 1. 优点:若多个参数,不用对应位置,直接对应参数名
     * 2. 缺点:麻烦。
     */
    public void testNamedParameterJdbcTemplate() {
        String sql = "insert into employee(`last_name`, `first_name`, `email`) VALUES (:ln,:fn,:email) ";
        Map<String,Object> paramMap = new HashMap<>();
        paramMap.put("ln","ww");
        paramMap.put("fn","cc");
        paramMap.put("email","wccw@qq.com");
        namedParameterJdbcTemplate.update(sql,paramMap);
    }

    /**
     * 具名参数可以使用 update(String sql, SqlParameterSource paramSource)
     * 1.SQL 语句中参数名和类属性一致
     * 2.使用 SqlParameterSource 的 BeanPropertySqlParameterSource 实现类作为参数,
     * (:字段)具有setter与getter方法。
     */
    public void testNamedParameterJdbcTemplateWithParamSource() {
        String sql = "insert into employee(`last_name`, `first_name`, `email`) VALUES (:lastName,:firstName,:email) ";
        Employee employee = new Employee();
        employee.setLastName("XYZ");
        employee.setFirstName("XYZ");
        employee.setEmail("XYZ@qq.com");
        SqlParameterSource source  = new BeanPropertySqlParameterSource(employee);
        namedParameterJdbcTemplate.update(sql,source);
    }

9 事务

事务的四个关键属性(ACID)

  • 原子性(atomicity): 事务是一个原子操作, 由一系列动作组成. 事务的原子性确保动作要么全部完成要么完全不起作用.
  • 一致性(consistency): 一旦所有事务动作完成, 事务就被提交. 数据和资源就处于一种满足业务规则的一致性状态中.
  • 隔离性(isolation): 可能有许多事务会同时处理相同的数据, 因此每个事物都应该与其他事务隔离开来, 防止数据损坏.
  • 持久性(durability): 一旦事务完成, 无论发生什么系统错误, 它的结果都不应该受到影响. 通常情况下, 事务的结果被写到持久化存储器中

196558-20180821110613027-725345551.png

196558-20180821110553968-1952603682.png

Repository层

public interface BookShopDao {

    //根据书号找单价
    public int findBookPriceByIsbn(String isbn);

    //更新书的库存(书号对应的库存-1)
    public boolean updateBookStock(String isbn);

    //更新用户账户余额:username的 blance -price
    public boolean updateUserAccount(String username,int price);
}

实现类 BookShopDao

@Repository
public class BookShopDaoImpl implements BookShopDao {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Override
    public int findBookPriceByIsbn(String isbn) {
        String sql = "select price from book where isbn = ?";
        return jdbcTemplate.queryForObject(sql, Integer.class, isbn);
    }

   @Override
    public boolean updateBookStock(String isbn) {
        //检查库存是否不够,若不够则抛出异常
        String sql2 = "select stock from book_stock where isbn = ?";
        int stock = jdbcTemplate.queryForObject(sql2, Integer.class, isbn);
        if (stock == 0) {
            throw new BookShopException("库存不足");
        } else {
            String sql = "update book_stock set stock = stock-1 where isbn = ?";
            return (jdbcTemplate.update(sql, isbn)) > 0;
        }


    }

    @Override
    public boolean updateUserAccount(String username, int price) {
        //验证余额不足
        String sql2 = "select balance from account where username = ?";
        int balance = jdbcTemplate.queryForObject(sql2, Integer.class, username);
        if (balance < price) {
            throw new AccountException("余额不足");
        } else {
            String sql = "update account set balance = balance-? where username = ?";
            return (jdbcTemplate.update(sql, price, username)) > 0;
        }
    }
}

service层

public interface BookShopService {
    //某人买一本书
    boolean purchase(String isbn, String username);
}

service实现

@Service
public class BookShopServiceImpl implements BookShopService {

    @Autowired
    private BookShopDao bookShopDao;

    @Override
    public boolean purchase(String isbn, String username) {
        //1.获取书单价
        int price = bookShopDao.findBookPriceByIsbn(isbn);
        //2.更新书的库存
        bookShopDao.updateBookStock(isbn);
        //3.更新用户余额
        return bookShopDao.updateUserAccount(username, price);
    }
}

demo实现(没有事务的情况下,出现只扣库存的操作)

public class TransactionDemo {
    static  ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
    static BookShopService bookShopService  = ctx.getBean(BookShopService.class);

    public static void main(String[] args) {
        testpurchase();
    }

    public static void testpurchase(){
        System.out.println(bookShopService.purchase("0001","Tom"));
    }
    //余额不足
}

为了方便查看是哪个方法的错误,实现了2个自定义异常,重写RuntimeException方法

public class AccountException extends RuntimeException{...}
public class BookShopException extends RuntimeException{...}

9.1 声明式事务 @Transactional

  • 编程式事务管理: 将事务管理代码嵌入到业务方法中来控制事务的提交和回滚. 在编程式管理事务时, 必须在每个事务操作中包含额外的事务管理代码.
  • 声明式事务管理: 大多数情况下比编程式事务管理更好用. 它将事务管理代码从业务方法中分离出来, 以声明的方式来实现事务管理. 事务管理作为一种横切关注点, 可以通过 AOP 方法模块化. Spring 通过 Spring AOP 框架支持声明式事务管理.

bean

<!--配置事务管理器-->
  <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="datasource"></property>
   </bean>

  <!--启用事务注解 http://www.springframework.org/schema/tx-->
  <tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>

在实现类添加@Transactional

@Service
public class BookShopServiceImpl implements BookShopService {

    @Autowired
    private BookShopDao bookShopDao;

    //添加事务注解
    @Transactional
    @Override
    public boolean purchase(String isbn, String username) {
        //1.获取书单价
        int price = bookShopDao.findBookPriceByIsbn(isbn);
        //2.更新书的库存
        bookShopDao.updateBookStock(isbn);
        //3.更新用户余额
        return bookShopDao.updateUserAccount(username, price);
    }
}

疑难:Spring @transaction不起作用,Spring事物注意事项

  1. 在需要事务管理的地方加@Transactional 注解。@Transactional 注解可以被应用于接口定义和接口方法、类定义和类的 public 方法上 。

  2. @Transactional 注解只能应用到 public 可见度的方法上 。 如果你在 protected、private 或者 package-visible 的方法上使用 @Transactional 注解,它也不会报错, 但是这个被注解的方法将不会展示已配置的事务设置。

  3. 注意仅仅 @Transactional 注解的出现不足于开启事务行为,它仅仅 是一种元数据。必须在配置文件中使用配置元素,才真正开启了事务行为。

重点事项:

Spring事物是基于类和接口的(通俗理解即:在调用的时候不能再同一个类里面被调用,必须调用外面的类去做事物操作)

Spring的事物必须是可见的(即:定义的方法必须是public的)

9.1.1 事务传播行为 REQUIRES_NEW

196558-20180821140806080-1785252282.png

定义消费接口

public interface CashierService {
    public boolean checkout(String username,List<String> isbns);
}

实现

@Service
public class CashierServiceImp implements CashierService {

    @Autowired
    private BookShopService bookShopService;

    @Transactional
    @Override
    public boolean checkout(String username, List<String> isbns) {

        try {
            for (String isbn : isbns) {
                bookShopService.purchase(isbn,username);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }
}

测试

static CashierService cashierService = ctx.getBean(CashierService.class);

 public static void main(String[] args) {
        tesCashier();
    }
 public static void tesCashier() {
        List<String> list = Arrays.asList("0001", "0002");
        System.out.println(cashierService.checkout("Tom",list));
    }

196558-20180821151351780-1198637406.png
默认传播模式 propagation = Propagation.REQUIRES
如果同时买0001与0002的价格不够,就不能购买成功。
196558-20180821151931480-1430706348.png

196558-20180821151519975-1557897230.png
现在该为 REQUIRES_NEW 模式:
只要能购买成功0001商品,就能成功,后面不能购买时,报错。
相当于purchase分为一个小的事务。
196558-20180821152121950-397700223.png

9.1.2 属性(隔离级别&回滚&只读&过期)

并发导致的问题

  • 当同一个应用程序或者不同应用程序中的多个事务在同一个数据集上并发执行时, 可能会出现许多意外的问题
  • 并发事务所导致的问题可以分为下面三种类型:
    • 脏读: 对于两个事物 T1, T2, T1 读取了已经被 T2 更新但 还没有被提交的字段. 之后, 若 T2 回滚, T1读取的内容就是临时且无效的.
    • 不可重复读:对于两个事物 T1, T2, T1 读取了一个字段, 然后 T2 更新了该字段. 之后, T1再次读取同一个字段, 值就不同了.
    • 幻读:对于两个事物 T1, T2, T1 从一个表中读取了一个字段, 然后 T2 在该表中插入了一些新的行. 之后, 如果 T1 再次读取同一个表, 就会多出几行.

隔离级别
196558-20180821154958123-949179685.png

    //添加事务注解
    //isolation  隔离级别,常取值 READ_COMMITTED
    //noRollbackFor = {AccountException.class} 对哪个异常不回滚 ,声明为 Class[] 类型的
    //rollbackFor 遇见必须回滚 ,声明为 Class[] 类型的
    //readOnly = true 只读事务属性: 表示这个事务只读取数据但不更新数据, 这样可以帮助数据库引擎优化事务.
    //timeout(s)  指定强制回滚之前事务占用时间(秒)
    @Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.DEFAULT
            , noRollbackFor = {AccountException.class}, readOnly = true, timeout = 3
    )

9.2 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
  http://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 http://www.springframework.org/schema/aop/spring-aop.xsd">

  <!--引用外部文件db.properties-->
  <context:property-placeholder location="classpath:db.properties"/>

  <!--配置jdbc-->
  <bean id="datasource" class="org.apache.commons.dbcp2.BasicDataSource">
    <property name="driverClassName" value="${drivername}"></property>
    <property name="url" value="${url}"></property>
    <property name="username" value="${user}"></property>
    <property name="password" value="${pass}"></property>
  </bean>

  <!--配置Spring的JdbcTemplate-->
  <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
    <property name="dataSource" ref="datasource"></property>
  </bean>

  <!--配置Bean-->
  <bean id="bookShopDao" class="com.cnblogs.tangge.TransactionXML.BookShopDaoImpl">
    <property name="jdbcTemplate" ref="jdbcTemplate"></property>
  </bean>
  <bean id="bookShopService" class="com.cnblogs.tangge.TransactionXML.BookShopServiceImpl">
    <property name="bookShopDao" ref="bookShopDao"></property>
  </bean>
  <bean id="cashierService" class="com.cnblogs.tangge.TransactionXML.CashierServiceImp">
    <property name="bookShopService" ref="bookShopService"></property>
  </bean>

  <!--1.配置事务管理器-->
  <bean id="transactionManager"
    class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="datasource"></property>
  </bean>

  <!--2.配置事务属性-->
  <tx:advice id="txadvice" transaction-manager="transactionManager">
    <tx:attributes>
      <!--根据方法取事务属性-->
      <tx:method name="purchase" propagation="REQUIRES_NEW"/>
      <tx:method name="get*" read-only="true"/>
      <tx:method name="find*" read-only="true"/>
      <tx:method name="*"/>
    </tx:attributes>
  </tx:advice>

  <!--3.配置事务切入点,切入点及事务关联起来-->
  <aop:config>
    <aop:pointcut id="purchase"
      expression="execution(* com.cnblogs.tangge.TransactionXML.BookShopServiceImpl.purchase(..))"/>
    <aop:advisor advice-ref="txadvice" pointcut-ref="purchase"></aop:advisor>
  </aop:config>

  <aop:config>
    <aop:pointcut id="checkout"
      expression="execution(* com.cnblogs.tangge.TransactionXML.CashierServiceImp.checkout(..))"/>
    <aop:advisor advice-ref="txadvice" pointcut-ref="checkout"></aop:advisor>
  </aop:config>
</beans>
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值