概述

Spring是什么

Spring是分层的java SE/EE 应用一站式的轻量级开源框架,以 IoC(Inverse of Control,控制反转)和 AOP(Aspect Oriented Programming,切面编程)为内核,提供了展示层Spring MVC、持久层Spring JDBC及业务层事务管理等一站式的企业级应用技术。

Spring之父

Rod Johnson

  • 一个技术和商业上的天才。Spring的缔造者。

  • 2004年3月24日发布了Spring1.0正式版并成立了SpringSource公司。

Spring优点

  1. 方便解耦,简化开发。

    通过Spring的IoC容器,用户可以将对象之间的依赖关系交由Spring进行控制,避免硬解码所造成的过度程序耦合。

  2. AOP编程的支持。

    通过Spring提供的AOP功能,方便进行面向切面的编程。

  3. 声明式事务的支持。

    通过声明的方式灵活地管理事务。

  4. 方便进行程序测试。

    可以用非容器依赖的编程方式进行几乎所有的测试工作。

  5. 方便集成各种优秀的框架。

    Spring对其他框架不排斥。更可以降低各种框架的使用难度。它提供了对各种优秀框架的直接支持。(如:Struts、Hibernate、Hessian、Quartz等)

  6. 降低了Java EE API的使用难度。

    Spring对很多难用的Java EE API(如JDBC、JavaMail、远程调用等)提供了薄层封装,大大降低了这些API的使用难度。

  7. Spring源码是经典的学习范例。

    对Spring源码的学习无疑是Java技术的最佳实践范例。

Spring体系结构

Spring核心框架由4000多个类组成,整个框架按其所属功能可以划分为5个模块,如下图所示。

Spring框架结构
Spring框架结构

HelloWorld

  1. 创建一个JavaBean

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    public class HelloWorld {
    private String name;

    public HelloWorld() {
    System.out.println("调用了无参构造器...");
    }

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

    public void hello() {
    System.out.println("hello:" + name);
    }
    }
  2. 在Spring配置文件中注册bean

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    <?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:p="http://www.springframework.org/schema/p"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.1.xsd">

    <!-- 配置Bean -->
    <bean id="helloWorld" class="cn.gxk.spring.beans.HelloWorld">
    <property name="name" value="Spring"></property>
    </bean>

    </beans>
  3. 获取对象

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    /* 传统方法创建对象 */
    HelloWorld helloWorld1 = new HelloWorld();
    helloWorld1.setName("simple");
    helloWorld1.hello();

    /* Spring方法 */
    // 1.创建Spring的IoC容器
    ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
    // 2.获得xml里的对象(xml解析,反射创建对象)
    HelloWorld helloWorld2 = (HelloWorld) ctx.getBean("helloWorld");
    // 3.调用对象的方法
    helloWorld2.hello();

IoC容器

Bean的配置

1
2
3
4
5
6
7
8
9
10
11
<?xml version="1.0" encoding="UTF-8"?>
<beans ...>
<!--
配置Bean:
class:bean的全类名,通过反射的方法在IoC容器中创建对象,所以javabean中必须有无参构造器。
id:bean的唯一标识。
-->
<bean id="helloWorld" class="cn.gxk.spring.beans.HelloWorld">
<property name="name" value="Spring"></property>
</bean>
</beans>

IoC容器的实现

  • BeanFactory:IoC容器的基本实现。(一般不用)

  • ApplicationContext:提供了更多高级的特性,是BeanFactory的子类。

    • ConfigurableApplicationContext:扩展refresh()和close(),让ApplicationContext具有启动、刷新和关闭上下文的能力。

      • ClassPathXmlApplicationContext:从类路径下加载配置文件。

      • FileSystemXmlApplicationContext:从文件系统中加载配置文件。

  • 常用方法:

    • getBean(String)

      通过xml中配置的id来创建唯一对象。

    • getBean(类名.class)

      利用类型返回bean,但要求IoC中只有一个该类型的bean。

bean注入方法

  • 属性注入(setter方法)

    1
    <property name="name" value="Spring"></property>
  • 构造器注入

    1
    2
    3
    4
    5
    6
    7
    8
    <constructor-arg value="v1" index="0" type="java.lang.String"></constructor-arg>
    <constructor-arg index="1" type="int">
    <value>100</value>
    </constructor-arg>
    <!--
    index、type可以不写。
    type用来区分重载的构造器。
    -->
  • 工厂方法注入(很少使用,不推荐)

特殊的赋值

  • 特殊字符(使用<!CDATA[]]>包裹)

    1
    2
    3
    <property name="address">
    <value><!CDATA[<hello>]></value>
    </property>
  • null

    1
    2
    3
    <constructor-arg type="java.lang.String">
    <null/>
    </constructor-arg>
  • 内部bean或外部bean

    • 内部bean,不能被外部引用

      1
      2
      3
      <property name="person">
      <bean id="person1" class="包名.Person"></bean>
      </property>
    • 外部bean,用ref引用

      1
      2
      <bean id="person2" class="包名.Person"></bean>
      <property name="person" ref="person2"></property>
  • 集合

    java.util.List - <list>

    java.util.Set - <set>

    • 内部集合

      1
      2
      3
      4
      5
      6
      <constructor-arg>
      <list>
      <ref bean="person1"/>
      <ref bean="person2"/>
      </list>
      </constructor-arg>
    • 外部集合

      需要在spring配置文件中引入util命名空间。

      1
      2
      3
      4
      <util:list id="persons">
      <ref bean="person1"/>
      <ref bean="person2"/>
      </util:list>
  • Map

    java.util.Map - <map>

    java.util.Properties - <prop>

    • 内部Map

      1
      2
      3
      4
      5
      6
      <constructor-arg  type="java.util.Map">
      <map>
      <entry key="aa" value-ref="person1"></entry>
      <entry key="bb" value-ref="person2"></entry>
      </map>
      </constructor-arg>
    • 外部Map

      需要在spring配置文件中引入util命名空间。

      1
      2
      3
      4
      <util:map id="persons">
      <entry key="aa" value-ref="person1"></entry>
      <entry key="bb" value-ref="person2"></entry>
      </util:map>

使用p命名空间赋值

在spring配置文件中引入p命名空间

1
<bean id="person3" class="包名.person" p:name="zhangsan" p:age="30" p:cars-ref="cars"></bean>

自动装配(了解,一般不用)

  1. byName

    1
    2
    3
    4
    5
    <bean id="car" class="包名.Car" ...></bean>
    <bean id="person4" class="包名.Person" p:name="zhangsan" autowire="byName"></bean>
    <!--
    若person有car属性,设置autowire="byName",IoC容器会自动寻找叫"car"的bean,自动装配。
    -->
  2. byType

    1
    2
    3
    4
    5
    <bean id="car1" class="包名.car" ...></bean>
    <bean id="person4" class="包名.person" p:name="zhangsan" autowire="byType">
    <!--
    若person有car属性,设置autowire="byType",会寻找类car的bean,自动装配(若car有多个bean,会抛异常)
    -->

bean之间的关系

  1. 继承

    1
    2
    3
    <bean id="address" p:city="BeiJing" abstract="true" />

    <bean id="address2" class="包名.Address" p:street="DaZhongShi" parent="address1"/>
    • 先给给出的属性赋值,未给出的属性继承父bean。

    • 设置了abstract=”true”的bean为抽象bean,不能将它实例化,只用于继承。(该抽象bean可以不指定class)

  2. 依赖

    1
    <bean id="person1" p:age="23" depends-on="name"/>

    depends-on=”name”指定了依赖属性,而name未赋值,会抛异常。

bean的作用域

1
2
3
4
5
6
<bean id="car" class="包名.car" scope="?">
<!--
singleton:默认值,单例的,容器初始化时创建bean对象,在整个容器的生命周期中只有这一个对象。

prototype:原型的,每次getBean都会创建一个对象。
-->

外部属性文件(可用于配置数据库连接等)

  1. 新建属性文件person.properties

    1
    2
    pname=zhangsan
    page=20
  2. 引入属性文件

    • Spring2.0

      1
      2
      3
      4
      <bean class="包名.person">
      <property name="location" value="classpath:person.properties"></property>

      </bean>
    • Spring2.5以后

      需要在spring配置文件中引入context工作空间

      1
      <context:property-paceholder location="classpath:person.properties"/>
  3. 创建bean

    1
    2
    3
    4
    <bean id="person" class="包名.person">
    <property name="name" value="${pname}"></property>
    <property name="age" value="${page}"></property>
    </bean>

Spring表达式语言(Spring EL)

  • 一个支持运行时查询和操作对象图的表达式语言。

  • 使用#{…}作为定界符。

  • 为动态赋值提供了便利。

  • 用法:

    • 字面值

      1
      <property name="name" value="#{'ZhangSan'}"/>
    • 引用bean、属性和方法

      • 引用其他bean对象(等同ref属性)

        1
        <property name="car" value="#{car1}"/>
      • 引用其他对象的属性

        1
        <property name="car_price" value="#{car1.price}"/>
      • 调用其他方法,还可以链式操作

        1
        <property name="person" value="#{person1.toString().toUpperCase()}"/>
      • 静态方法或属性,通过T()调用

        1
        <property name="initValue" value="#{T(java.lang.Math).PI}"/>
    • 运算符

      • 算数运算符:+, -, *, /, &, ^

      • 加号进行字符串连接

      • 比较运算符:<, >, ==, <=, >=, lt, gt, eq, le, ge

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

      • if-else运算:

        • a>b ?: (result1), a<=b ?: (result2)

        • a>b ? ‘reslut1’ : ‘result2‘

      • 正则表达式

管理bean的生命周期

  • Spring IoC容器可以管理Bean的生命周期,并允许在Bean生命周期的特殊点执行相关方法。

  • Spring IoC容器对Bean的生命周期进行管理的过程:

    1. 通过构造器或工厂方法创建Bean实例

    2. 为Bean的属性设值和对其他Bean的引用

    3. 调用Bean的初始化方法

    4. 使用Bean

    5. 容器关闭时,调用bean的销毁方法

  • 在Bean的声明里设置 init-method 和 destory-method 属性,为bean指定初始化和销毁方法。

  • Bean的后置处理器

    • 创建MyBeanPostProcessor类实现BeanPostProcessor接口

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      public class MyBeanPostProcessor implements BeanPostProcessor {
      //两方法的返回值是用户拿到的bean(可以在这两个方法里对创建的bean进行处理)

      //bean初始化(init-method)前调用
      public Object PostProcessBeforeInitialization(Object bean, String beanName) {...}

      //bean初始化(init-method)后调用
      public Object PostProcessAfterInitialization(Object bean, String beanName) {...}

      }
  • 在xml里配置

    1
    <bean class="包名.MyBeanPostProcessor"></bean>

使用工厂方法创建bean

  • 使用静态工厂方法

    • 创建工厂类

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15

      public class StaticCarFactory{

      private static map<String,Car> cars = nw HashMap<String,Car>();

      static {
      cars.put("audi", new Car("audi"));
      cars.put("ford", new Car("ford"));
      }

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

      }
  • 在applictionContext.xml里配置bean

    1
    2
    3
    <bean id="car1" class="包名.StaticCarFactory" factory-method="getCar">
    <constructor-arg value="audi"></constructor-arg>
    </bean>
  • 使用实例工厂方法

    • 创建工厂类

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      public class InstanceCarFactory{

      private Map<String,Car> cars = null;

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

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

      }
  • 在applictionContext.xml里配置bean

    1
    2
    3
    4
    <bean id="carFactory" class="包名.InstanceCarFactory"></bean>
    <bean id="car2" factory-bean="carFactory" factory-method="getCar">
    <constructor-arg value="ford"></constructor-arg>
    </bean>

使用FactoryBean配置bean

  • 创建CarFactoryBean实现FactoryBean接口

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    public class CarFactoryBean implements FactoryBean<Car>{

    private String name;

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

    @Override
    public Car getObject() throws Exception {
    return new Car(name);
    }

    @Override
    public Class<?> getObjectType() {
    return Car.class;
    }

    @Override
    public boolean isSingleton() {
    return true;
    }

    }
  • 在applictionContext.xml里配置bean

1
2
3
<bean id="car1" class="包名.CarFactoryBean">
<property name="name" value="BMW">
</bean>

基于注解方式配置Bean

  • 组件扫描(component scanning)

    Spring能从classpath下自动扫描,侦测和实例化具有特定注解的组件。

    • 特定组件包括:(这些注解只是用来给编程人员看的,Spring只识别有没有这些注解而不区别)

      • @Component:基本注解,标识了一个受Spring管理的组件

      • @Respository:标识持久层组件

      • @Service:标识服务层(业务层)组件

      • @Controller:标识表现层组件

  • 在applictionContext.xml里配置bean

    在spring配置文件中引入context命名空间。

    • 用法:

      1
      2
      3
      4
      <context:component-scan 
      base-package="包名"
      resource-pattern="a/*.class">
      </context:component-scan>
  • 属性:

    base-package:指定包名,IoC容器扫描该包和子包下的所有类。

    resource-pattern:限制扫描范围。

  • 子标签:

    • <context:include-filter>:包括指定的类

      若实现”只包含指定类”,需要在<context:component-scan>中添加属性use-default-filters=”false”

      • 属性:

        type:用来指定过滤类型。

        • annotation(过滤指定类)

        • assinable(过滤继承或实现了指定类的子类)

    • <context:exclude-filter>:不包括指定的类。

  • bean创建过程:

    IoC容器会扫描指定的包和其子类(经过过滤),对添加了注解的类进行实例化,id为非限定类名(第一个字母小写)。

  • 特殊:

    • 若某被扫描的bean1里有其他bean2,可以在bean2前加 @Autowired 标记,该bean2也会被自动注入。(setter方法也可)

      • 若bean2没有被注解,会抛异常。

        解决方法:可以设置 @Autowired(required=false),容器会对它赋值null。

      • 若有两个bean2,也会抛异常。

        如:UserService是抽象的接口,而项目中有两个实现类userService1和userService2,且都有注解。

        解决方法:

        (1) 在组件中添加名字。(如: @Service(userService1) )

        (2) 在bean2前添加 @Qualifier 注解。(如: @Qualifier(“userService2”) )

Spring4新特性——泛型限定式依赖注入

  1. Spring4.0以前,对于父类里setter方法,必须在子类再写一个setter方法,然后指定注入的具体类型,然后进行注入。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    public abstract class BaseService<M extends Serializable> {

    private BaseRepository<M> repository;

    public void setRepository(BaseRepository<M> repository) {
    this.repository = repository;
    }

    public void save(M m) {
    repository.save(m);
    }

    }

    @Service
    public class UserService extends BaseService<User> {

    @Autowired
    public void setUserRepository(UserRepository userRepository) {
    setRepository(userRepository);
    }

    }

    @Service
    public class OrganizationService extends BaseService<Organization> {

    @Autowired
    public void setOrganizationRepository(OrganizationRepository organizationRepository) {
    setRepository(organizationRepository);
    }

    }
  2. Spring4.0实现了泛型依赖

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    public abstract class BaseService<M extends Serializable> {

    @Autowired
    protected BaseRepository<M> repository;

    public void save(M m) {
    repository.save(m);
    }

    }

    @Service
    public class UserService extends BaseService<User> {}

    @Service
    public class OrganizationService extends BaseService<Organization> {}

AOP

引入AOP

  1. 如何实现一个计算器?(OOP)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    // 接口类
    public interface ArithmeticCalculator {

    int add(int i, int j);

    int sub(int i, int j);

    }

    // 实现接口类
    public class ArithmeticCalculatorImpl implents 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;
    }

    }
  2. 如何在使用计算器过程中打印日志?

    1
    2
    3
    4
    5
    6
    7
    @Override
    public int add(int i, int j) {
    System.out.println("The method add begins with[" + i + "," + j + "]");
    int result = i + j;
    System.out.println("The method add ends with " + result);
    return result;
    }

    上述代码存在的问题

  • 代码混乱:越来越多的非业务需求加入后,原来的业务方法急剧膨胀,每个方法在处理核心逻辑的同时还必须兼顾其他多个关注点。

  • 代码分散:每个模块都必须做相似的动作,而如果需求改变,每个模块都必须修改。

  1. 如何解决?

    使用动态代理(InvocationHandler)—— 使用反射(reflect)对目标对象的每个方法进行模块化操作。

    • 代理类

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      public class ArithmeticCalculatorLoggingProxy {
      // 要代理的对象
      private ArithmeticCalculator target;

      public ArithmeticCalculatorLoggingProxy(ArithmeticCalculator target) {
      this.target = target;
      }

      public ArithmeticCalculator getLoggingProxy() {
      ArithmeticCalculator proxy = null;
      // 代理对象由哪个类加载器负责加载
      ClassLoader loader = target.getClass().getClassLoader();
      // 代理对象的类型,即其中有那些方法
      Class[] interfaces = new Class[]{ArithmeticCalculator.class};
      // 当调用代理对象其中方法时,执行的代码
      InvocationHandler h = new InvocationHandler() {
      /**
      * proxy:正在返回的代理对象,一般情况下,在invoke方法中都不使用该对象
      * —— 如果你想使用这个对象,并调用它的方法,又会调用invoke()方法,直至发生死循环。
      * method:正在调用的方法
      * args:调用方法时传入的参数
      */
      @Override
      public Object invoke(Object proxy, Method method, Object[] args)
      throws Throwable {
      String methodName = method.getName();
      //日志
      System.out.println("The method " + methodName + " begins with [" + args[0] + "," + args[1] + "]");
      //执行方法
      Object result = method.invoke(target, args);
      //日志
      System.out.println("The method " + methodName + " ends with " + result);
      return result;
      }
      }
      proxy = Proxy.newProxyInstance(loader, interfaces, h);
      }
      }
    • 使用代理类创建对象

      1
      2
      3
      4
      5
      6
      7
      public static void main(String[] args) {
      ArithmeticCalculator target = new ArithmeticCalculatorImp();
      ArithmeticCalculator proxy = new ArithmeticCalculatorLoggingProxy(target).getLoggingProxy();

      int result = proxy.add(1, 2);
      System.out.println("-->" + result);
      }
  2. 问题

    这种方法太麻烦,Spring把它封装起来,称为AOP。

什么是AOP

  • AOP (Aspect-Oriented Programming,面向切面编程):是对传统OOP(Object-Oriented Programming,面向对象编程)的补充。

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

  • 在AOP仍需定义公共功能,但不必修改受影响的类,这样横切的关注点就被模块化到特殊的对象(切面)里。

  • 使用AOP的好处:

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

    • 业务模块更简洁,只包含核心业务代码。

在上述的例子(计算器)中:

对于每个业务逻辑(add/sub/mul/div),在实现这些逻辑之后用一个另外的类,对特定的多个逻辑进行额外操作(验证参数、前置/后置日志),这个过程,称为面向切面编程(AOP)。

相关概念

  • 切面(aspect):横切关注点(跨越应用程序多个模块的功能)被模块化的特殊对象。

  • 通知(advice):切面必须完成的工作。

  • 目标(target):被通知的对象。

  • 代理(proxy):向目标对象应用通知之后创建的对象。

  • 连接点(joinpoint):程序执行的某个特定位置(如:类某个方法调用前、调用后、方法抛出异常后等)。

  • 切点(pointcut):连接点是客观存在的事物。通过切点可以定位到特定的一个或多个连接点。

Spring AOP

在Spring2.0以上版本中,可以使用 基于AspectJ(一种AOP框架)注解 或 基于XML 来配置AOP。

基于AspectJ注解

  • 需要的jar包:aopalliance.jar、aspectj.weaver.jar 和 spring-aspects.jar

  • 配置文件中导入aop命名空间。

  • 使用AspectJ让aop自动动态代理。

    1
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
  • 配置自动扫描的包

    1
    <context:component-scan base-package="cn.gxk.spring.aop.impl"></context:component-scan>切面类
  • 切面类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    //把该类声明为一个切面:需要把该类放入IoC容器,再声明为切面
    @Aspect
    @Component
    public class LoggingAspect {
    /**
    * 前置通知:目标方法之前执行
    */
    @Before("execution(public int cn.gxk.spring.aop.impl.ArithmeticCalculatorImpl.*(int, int))")
    public void beforeMethod(JoinPoint joinPoint){
    String methodName = joinPoint.getSignature().getName();
    List<Object> args = Arrays.asList(joinPoint.getArgs());
    System.out.println("The method " + methodName + " begins with"+args);
    }

    /**
    * 后置通知:目标方法之前执行(无论是否发生异常都执行)
    * 拿不到返回结果
    */
    @After("execution(public int cn.gxk.spring.aop.impl.ArithmeticCalculatorImpl.*(int, int))")
    public void afterMethod(JoinPoint joinPoint){
    String methodName = joinPoint.getSignature().getName();
    System.out.println("The method " + methodName + " ends");
    }

    /**
    * 返回通知:方法正常执行后返回结果
    */
    @AfterReturning(value="execution(public int cn.gxk.spring.aop.impl.ArithmeticCalculatorImpl.*(int, int))",
    returning="result")
    public void afterReturning(JoinPoint joinPoint, Object result){
    String methodName = joinPoint.getSignature().getName();
    System.out.println("The method " + methodName + " return " + result);
    }

    /**
    * 异常通知:在目标方法发生异常时执行
    * 可以访问到异常对象,甚至过滤指定异常
    */
    @AfterThrowing(value="execution(public int cn.gxk.spring.aop.impl.ArithmeticCalculatorImpl.*(int, int))",
    throwing="ex")
    public void afterThrowing(JoinPoint joinPoint, NullPointerException ex){
    String methodName = joinPoint.getSignature().getName();
    System.out.println("The method " + methodName + " occurs exception:" + ex.getStackTrace());
    }

    /**
    * 环绕通知:需要携带ProceedingJoinPoint类型的参数
    * ProceedingJoinPoint参数可以决定是否执行目标方法
    * 且环绕通知必须有返回值,返回值为目标方法的返回值
    * @return
    */
    @Around("execution(public int cn.gxk.spring.aop.impl.ArithmeticCalculatorImpl.*(int, int))")
    public Object aroundMethod(ProceedingJoinPoint pjd){
    Object result = null;
    String methodName = pjd.getSignature().getName();
    //执行目标方法
    try {
    //前置通知
    System.out.println("[环绕]The method " + methodName + " begins with " + Arrays.asList(pjd.getArgs()));
    result = pjd.proceed();
    //返回通知
    System.out.println("[环绕]The method " + methodName + "ends with " + result);
    }catch (Throwable e) {
    //异常通知
    System.out.println("[环绕]The method " + methodName + " occurs exception:" + e.getStackTrace());
    }
    //后置通知
    System.out.println("[环绕]The method " + methodName + "ends");
    return result;
    }
    }
    • 通知的种类:

      • @Before:前置通知

      • @After:后置通知(无论是否发生异常),拿不到返回结果

      • @AfterRunning:返回通知,在方法返回结果后执行

      • @AfterThrowing:异常通知,在方法抛出异常后执行

      • @Around:环绕通知,围绕着方法执行,上述通知能做到的它都能做到。

      注:可以使用通配符(*)来替代方法标志的任意部分(前缀、返回指、方法名等),方法参数则可以用(..)代替。

    注意:动态代理需要使用接口,所以自动动态代理后的bean会自动转化为接口的类型使用 ctx.getBean(接口.class); 得到bean。

    另:若有多个切面 ,可以 @Order(1) 用来指定切面的优先级,值越小优先级越高!

重用切面表达式

1
2
3
4
5
6
// 定义一个方法,用于声明切入点表达式,一般地,该方法不需要添加其他代码
@Pointcut("execution(...)")
public void declareJointPointExpression(){}

// 在通知注解中调用(不同包的不同类下需要声明包名和类名)
@Before("包名.类名.declareJointPointExpression()");

基于XML文件

  • 切面(Aspect)类写法与上述一致,不需要使用注释

  • xml里配置(返回或异常通知的returning和throwing属性需要与切面类的参数名相同)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    <!--bean-->
    <bean id="arithmeticCalculator"
    class="cn.gxk.spring.aop.xml.ArithmeticCalculatorImpl">
    </bean>

    <!--切面的bean-->
    <bean id="loggingAspect"
    class="cn.gxk.spring.aop.xml.LoggingAspect">
    </bean>

    <aop:config>
    <!--配置切点表达式-->
    <aop:pointcut expression="execution(* cn.gxk.spring.aop.xml.ArithmeticCalculator.*(int, int))"
    id="pointcut"/>
    <!--配置切面及通知-->
    <aop:aspect ref="loggingAspect" order="1">
    <aop:before method="beforeMethod" pointcut-ref="pointcut"/>
    </aop:aspect>
    <aop:aspect ref="loggingAspect">
    <aop:after method="afterMethod" pointcut-ref="pointcut"/>
    </aop:aspect>
    <aop:aspect ref="loggingAspect">
    <aop:after-returning method="afterReturning" pointcut-ref="pointcut" returning="result"/>
    </aop:aspect>
    <aop:aspect ref="loggingAspect">
    <aop:after-throwing method="afterThrowing" pointcut-ref="pointcut" throwing="ex"/>
    </aop:aspect>
    <aop:aspect ref="loggingAspect">
    <aop:around method="aroundMethod" pointcut-ref="pointcut"/>
    </aop:aspect>
    </aop:config>

JDBC相关

JDBC相关工具

c3p0配置数据源

  1. 数据源配置文件 db.properties

    1
    2
    3
    4
    jdbc.password = 123456
    jdbc.driverClass = com.mysql:///spring4
    jdbc.initPoolSize = 5
    jdbc.maxPoolSize = 10
  2. 在applicationContext.xml里配置c3p0数据源

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    <!-- 导入资源文件 -->
    <context:property-placeholder location="classpath:db.properties"/>
    <!-- 配置C3P0数据源 -->
    <bean id="dataSource"
    class="com.mchange.v2.c3p0.ComboPooledDataSource">
    <property name="user" value="${jdbc.user}"/>
    <property name="password" value="${jdbc.password}"/>
    <property name="driverClass" value="${jdbc.driverClass}"/>
    <property name="initialPoolSize" value="${jdbc.initialPoolSize}"/>
    <property name="maxPoolSize" value="${jdbc.maxPoolSize}"/>
    </bean>
  3. 测试连接数据源

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    public class JDBCTest {
    private ApplicationContext ctx = null;

    {
    ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
    }

    @Test
    public void testDataSource() throws SQLException {
    DataSource dataSource = ctx.getBean(DataSource.class);
    System.out.println(dataSource.getConnection());
    }
    }

Spring的JDBCTemplate

  1. 在applicationContext.xml里配置JDBCTemplate

    1
    2
    3
    4
    <bean id="jdbcTemplate"
    class="org.springframework.jdbc.core.JdbcTemplate">
    <property name="dataSource" ref="dataSource"></property>
    </bean>
  2. JDBCTest

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    public class JDBCTest {
    private ApplicationContext ctx = nulll;
    private JdbcTemplate jdbcTemplate;
    {
    ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
    jdbcTemplate = (JdbcTemplate) ctx.getBean("jdbcTemplate");
    }

    /**
    * 单条更新
    */
    @Test
    public void testUpdate() {
    String sql = "UPDATE employee SET last_name = ? WHERE id=?";
    jdbcTemplate.update(sql, "Jack", 12);
    }

    /**
    * 批量更新
    */
    @Test
    public void testBatchUpdate() {
    String sql = "INSERT INTO employee(last_name, email, dept_id) VALUES(?, ?, ?)";
    List<Object[]> betchArgs = new ArrayList<>();
    batchArgs.add(new Object[]{"Zhang", "zhang@1.com", 1});
    batchArgs.add(new Object[]{"Li", "li@2.com", 2});
    batchArgs.add(new Object[]{"Wang", "wang@3.com", 3});

    jdbcTemplate.batchUpdate(sql, batchArgs);
    }

    /**
    * 得到数据库里一条记录,并生成对象
    * 注意:并不是使用queryForObject(String sql, Class<Employee> requiredType, Object...args)
    * 而是 queryForObject(String sql, RowMapper<Employee> requiredType, Object...args)
    * 1. 其中RowMapper指定如何去映射结果集的行,常用的实现类为BeanPropertyRowMapper
    * 2. 使用SQL中列的别名完成列名和类的属性名的映射。例如:last_name lastName
    * 3. 并不支持级联属性,JdbcTemplate只是JDBC的小工具,并非ORM框架
    */
    @Test
    public void testQueryForObject() {
    String sql = "SELECT id,last_name lastName, email FROM employees WHERE id = ?";
    RowMapper<Employee> rowMapper = new BeanPropertyRowMapper<>(Employee.class);
    Employee employee = jdbcTemplate.queryForObject(sql, rowMapper, 1);
    System.out.println(employee);
    }

    /**
    * 获得单列的值,或做统计查询
    */
    @Test
    public void testQueryForObject2() {
    String sql = "SELECT count(id) FROM employees";
    long count = jdbcTemplate.queryForObject(sql, Long.class);
    System.out.println(count);
    }

    /**
    * 查询实体类的集合
    */
    @Test
    public void testQueryForList() {
    String sql = "SELECT id, last_name lastName, email FROM employees WHERE id> ?";
    RowMapper<Employee> rowMapper = new BeanPropertyRowMapper<>(Employee.class);
    List<Employee> employees = jdbcTemplate.query(sql, rowMapper, 5);
    System.out.println(employees);
    }
    }
    • 说明:

      • 每次使用都创建一个JdbcTemplate的新实例,这种做法效率低下。

      • JdbcTemplate类被设计成线程安全的,所有可以在IOC中声明它的单个实例,并把这个实例注入到所有的Dao里。

    • 代码实例:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      @Respository
      public class DepartmentDao extends JdbcDaoSupport {
      @Autowired
      private JdbcTemplate jdbcTemplate;

      public Employee get(Integer id) {
      String sql = "SELECT id, last_name lastName, email FROM employees WHERE id= ?";
      RowMapper<Employee> rowMapper = new BeanPropertyRowMapper<>(Employee.class);
      Employee employee = jdbcTemplate.queryForObject(sql, rowMapper, id);
      return employee;
      }
      }

扩展JdbcDaoSupport

使用起来更麻烦,不推荐!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Respository
public class DepartmentDao extends JdbcDaoSupport {

/**
* 需要提供数据源(或使用JdbcTemplate)才能连接数据库
* 但由于JdbcDaoSupport父类中已经有JdbcTemplate变量,
* 且setDataSource()已经被设计成final类型,
* 所以并不能直接注入数据源
*/
@Autowired
public void setDataSource2(DataSource dataSource){
setDataSource(dataSource);
}
/**
* 需要通过getJdbcTemplate()得到 JdbcTemplate对象
*/
public DepartMent get(Integer id) {
String sql = "SELECT id, dept_name name FROM departments WHERE id= ?";
RowMapper<DepartMent> rowMapper = new BeanPropertyRowMapper<>(DepartMent.class);
DepartMent departMent = getJdbcTemplate().queryForObject(sql, rowMapper, id);
return departMent;
}
}

使用具名参数

不需要再在java代码里绑定dataSource。

  • 配置NamedParameterJdbcTemplate

    该对象可以使用具名参数,其没有无参构造器,必须对其构造器指定参数。

    1
    2
    3
    4
    <bean id="namedParameterJdbcTemplate"
    class="org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate">
    <constructor-arg ref="dataSource"></constructor-arg>
    </bean>
  • 测试使用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    public class JDBCTest {
    private ApplicationContext ctx = nulll;
    private NamedParameterJdbcTemplate namedParameterJdbcTemplate;
    {
    ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
    namedParameterJdbcTemplate = ctx.getBean(NamedParameterJdbcTemplate.class);
    }

    /**
    * 可以给参数起名字
    * 1.好处:若有多个参数,不需要再去对应位置,直接对应参数名,不方便维护。
    * 2.缺点:书写复杂。
    */
    @Test
    public void testNamedParameterJdbcTemplate(){
    String sql = "INSERT INTO employees(last_name, email, dept_id) VALUES(:ln, :email, :deptid)";
    Map<String,Object> paramMap = new HashMap<>();
    paramMap.put("ln","Alice");
    paramMap.put("email","Alice@123.com");
    paramMap.put("deptid","2");
    namedParameterJdbcTemplate.update(sql, paramMap);
    }

    /**
    * update(String sql, SqlParameterSource paramSource);
    * 1.sql语句中的参数名与类的属性名保持一致!
    * 2.使用SqlParameterSource的实现类BeanPropertySqlParameterSource作为参数。
    */
    @Test
    public void testNamedParameterJdbcTemplate(){
    String sql = "INSERT INTO employees(last_name, email, dept_id) VALUES(:lastName, :email, :deptId)";
    Employee employee = new Employee();
    employee.setLastName("zhang");
    employee.setEmail("zhang@1.com");
    employee.setDeptId(1);

    SqlParameterSource paramSource = new BeanPropertySqlParameterSource(Employee);
    namedParameterJdbcTemplate.update(sql, paramSource);
    }
    }

JDBC事务

什么是事务

  • 事务管理是企业级应用程序开发中必不可少的技术,用来确保数据的完整性和一致性。

  • 事务管理就是一系列动作,它们作为一个单独的工作单元。这些动作需要全部完成后才能都起作用。

  • 事务的四个属性:

    • 原子性(atomicity)

    • 一致性(consistency)

    • 隔离性(isolation)

    • 持久性(durability)

JDBC事务操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public class JDBCTest {
public void test() {
Connection conn = null;
try {
conn = dataSource.getConnection();
conn.setAutoCommit(false);
//...
conn.commit();
} catch (SQLException e) {
e.printStackTrace();
if (conn != null) {
try {
conn.rollback();
} catch (SQLException e1) {
e1.printStackTrace();
}
}
throw new RuntimeException();
} finally {
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
e.printStackTrance();
}
}
}
}
}

Spring中的事务管理

  • 编程式事务管理

  • 声明式事务管理

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

    • 事务管理作为一种横切关注点,可以通过AOP方法模块化。Spring通过Spring AOP框架支持声明式事务管理。

    • Spring从不同的事务管理API中抽象了一整套的事务机制。从而让事务管理代码独立于特定的事务之外。

    • 使用方法:

      • applicationContext.xml配置

        1
        2
        3
        4
        5
        6
        7
        <!-- 配置事务管理器 -->
        <bean id="transactionManager"
        class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"></property>
        </bean>
        <!-- 启动事务 -->
        <tx:annotation-driven transaction-manager="transactionManager"/>
      • 使用方法

        在相应方法前增加注释 @Transactional,这个方法就会变成事务方法,IoC会让这个方法拥有事务的特性。

事务传播属性

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

  • 传播行为(propagation)

    • REQUIRED(默认):共用一个事务操作,回滚时都不执行。(例如:买了两本书,第二本购买失败,第一本也会失败)

    • REQUIRES_NEW:开启一个新的事务。(买了两本书,第二本购买失败,不影响第一本)

  • 隔离级别(isolation)

    • DEFAULT

      默认隔离级别,每种数据库支持的事务隔离级别不一样。

    • READ_UNCOMMITTED

      读未提交,即能够读取到没有被提交的数据,所以很明显这个级别的隔离机制无法解决脏读、不可重复读、幻读中的任何一种,因此很少使用。

    • READ_COMMITED

      读已提交,即能够读到那些已经提交的数据,自然能够防止脏读,但是无法限制不可重复读和幻读

    • REPEATABLE_READ

      重复读取,即在数据读出来之后加锁,类似”select * from XXX for update”,明确数据读取出来就是为了更新用的,所以要加一把锁,防止别人修改它。REPEATABLE_READ的意思也类似,读取了一条数据,这个事务不结束,别的事务就不可以改这条记录,这样就解决了脏读、不可重复读的问题,但是幻读的问题还是无法解决。

    • SERLALIZABLE

      串行化,最高的事务隔离级别,不管多少事务,挨个运行完一个事务的所有子事务之后才可以执行另外一个事务里面的所有子事务,这样就解决了脏读、不可重复读和幻读的问题了

    事务隔离等级的并发问题

  • 回滚类型

    • 默认情况下对所有运行时异常进行回滚。

    • 对某些异常不会滚:noRollbackFor={UserAccountException.class}

    • 对哪些异常回滚:rollbackFor={UserAccountException.class}

  • 只读(readOnly=true)

    • 标明这个事务只读取数据而不更新,这样可以帮助数据库引擎优化事务
  • 强制回滚时间(timeout=1)

    • 如果事务执行时间超过1s,会强制回滚。以保证不占用太多数据库连接时间。
  • 使用实例

    1
    2
    3
    4
    5
    6
    7
    8
    9
    @Transactional(propagation=Propagation.REQUIRES_NEW)
    public void purchase(String username, String isbn) {
    //查询书的单价
    int price = bookShopDao.findBookPriceByIsbn(isbn);
    //更新库存
    bookShopDao.updateBookStock(isbn);
    //更新账户余额
    bookShopDao.updateUserAccount(username, price);
    }

使用xml配置声明式事务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<!-- 配置事务管理器 -->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>

<!-- 配置事务属性 -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<!-- 根据方法名置顶事务的属性 -->
<tx:method name="purchase" propagation="REQUIRES_NEW">
<tx:method name="*"/>
</tx:attributes>
</tx:advice>

<!-- 配置切入点 -->
<aop:config>
<aop:pointcut expression="execution(* cn.gxk.spring4.xml.service.*.*(...))" id="txPointCut"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="txPointCut"/>
</aop:config>