JavaWeb开发框架——Spring

目录

1、Spring简介

1.1、Spring是什么

1.2、Spring发展历程

1.3、Spring的优势

1.3.1、方便解耦,简化开发

1.3.2、AOP编程的支持

1.3.3、声明式事务的支持

1.3.4、方便程序的测试

1.3.5、方便继承各种优秀框架

1.3.6、降低JavaEE API 的使用难度

1.3.7、Java源码的经典学习范例

1.4、Spring的体系结构

2、Spring快速入门

2.1、Spring程序开发步骤

2.2、Spring的开发步骤

3、Spring配置文件

3.1、Bean标签基本配置

3.2、Bean标签范围配置

3.3、Bean生命周期

3.3.1、生命周期控制方式1

3.3.2、生命周期控制方式2

3.4、Bean实例化的四种方式

3.5、Bean的依赖注入分析

3.5.1、Bean的依赖注入概念

3.6、Bean的依赖注入方式

3.6.1、setter注入——引用类型

3.6.2、setter注入——简单类型

3.6.3、构造器(构造方法)注入——引用类型

3.6.4、构造器(构造方法)注入——简单类型

3.6.5、p命名空间方式注入

3.6.6、注入集合数据类型

3.6.7、依赖注入方式的选择

3.6.8、案例——数据源对象管理

3.7、依赖自动装配

3.7.1、自动装配方式

3.7.2、依赖自动装配的特征

3.8、Spring加载外部properties文件

3.9、引入其他配置文件(分模块开发)

4、Spring相关API

4.1、ApplicationContext的继承体系

4.2、ApplicationContext的实现类

4.3、getBean()方法使用

5、Spring配置数据源

5.1、数据源(连接池)的作用

5.2、数据源的开发步骤

5.3、Spring配置数据源

5.4、抽取jdbc配置文件

6、Spring注解开发

6.1、Spring原始注解

6.1.1、@Scope & @PostConstruct & @PreDestroy

6.1.2、@Autowired & @Qualifier 实现引用类型依赖注入

6.1.3、@Value 实现简单类型依赖注入

6.2、Spring新注解

6.2.1、@Configuration & @ComponentScan实现替代Spring配置文件

6.2.2、@Import & @Bean 实现配置类分离和第三方bean管理

6.3、XML配置对比注解配置

7、Spring整合MyBatis

8、Spring整合Junit

8.1、原始Junit测试Spring的问题

8.2、上述问题解决思路

8.3、Spring整合Junit步骤

9、Spring集成Web环境

9.1、ApplicationContext应用上下文获取方式

9.2、Spring提供获取应用上下文的工具

9.3、Spring集成web环境步骤

10、Spring JdbcTemplate的基本使用

10.1、JdbcTemplate概述

10.2、JdbcTemplate开发步骤

10.3、Spring产生JdbcTemplate对象

11、Spring AOP

11.1、什么是AOP

10.2、AOP的作用及其优势

10.3、AOP的底层实现

10.4、AOP的动态代理技术

10.4.1、基于JDK的动态代理

10.4.2、基于cglib的动态代理

10.5、AOP相关概念

10.5.1、AOP切入点表达式

10.5.2、AOP通知类型

10.6、AOP开发明确的事项

10.6.1、需要编写的内容

10.6.2、AOP技术实现的内容

10.6.3、AOP底层使用哪种代理方式

10.7、AOP工作流程

10.8、基于XML的AOP开发

10.8.1、快速入门

10.8.2、XML配置AOP详解

10.9、基于注解的AOP开发

10.9.1、快速入门

10.9.2、注解配置AOP详解

10.9.3、AOP通知获取数据

10.10、AOP案例

10.10.1、案例1——测量业务层接口万次执行效率

10.10.2、案例2——百度网盘密码数据兼容处理

11、Spring的事务控制

11.1、事务相关定义

11.1.1、事务角色

11.1.2、事务的隔离级别

11.1.3、事务的传播行为

11.2、编程式事务控制相关对象

11.2.1、PlatformTransactionManager

11.2.2、TransactionDefinition

11.2.3、TransactionStatus

11.3、基于XML的声明式事务控制

11.3.1、什么是声明式事务控制

11.3.2、声明式事务控制的实现

11.4、基于注解的声明式事务控制

11.4.1、使用步骤

11.4.2、注解配置声明式事务控制解析

11.5、Spring事务相关配置

11.5、Spring事务案例

11.5.1、案例1——银行账户转账

11.5.2、案例2——转账业务追加日志


1、Spring简介

1.1、Spring是什么

Spring是分层的Java SE/EE 应用full-stack轻量级开源框架,以IoC(Inversion Of Control:反转控制)和AOP(Aspect Oriented Programming:面向切面编程)为内核。

  • IoC(Inversion of Control):控制反转。使用对象时,由主动new产生对象转换为由外部提供对象,此过程中对象创建控制权由程序转移到外部,此思想称为控制反转。
  • DI(Dependency Injection):依赖注入。

Spring技术对IoC思想进行了实现:

  • Spring提供了一个容器,用来充当IoC思想中的“外部”;
  • IoC容器负责对象的创建、初始化等一系列工作,被创建或被管理的对象在IoC容器中统称为Bean。

提供了展现层SpringMVC和持久层Spring JDBCTemplate以及业务层事务管理等众多的企业级应用技术,还能整合开源世界众多著名的第三方框架和类库,逐渐成为使用最多的JavaEE企业应用开源框架。

1.2、Spring发展历程

1.3、Spring的优势

1.3.1、方便解耦,简化开发

通过Spring提供的IoC容器,可以讲对象间的依赖关系交由Spring进行控制,避免编码所造成的过度耦合,用户也不必在为单例模式类、属性文件解析等这些很底层的需求编写代码,可以更专注上层的应用。

1.3.2、AOP编程的支持

通过Spring的AOP功能,方便进行面向切面编程,许多不容易用传统OOP实现的功能可以通过AOP轻松实现。

1.3.3、声明式事务的支持

可以将我们从单调烦闷的事务管理代码中解脱出来,通过声明式方式灵活地进行事务管理,提高开发效率和质量。

1.3.4、方便程序的测试

可以用非容器依赖的编程方式进行几乎所有的测试工作,测试不再是昂贵的操作,而是随手可做的事情。

1.3.5、方便继承各种优秀框架

Spring对各种优秀框架(Struts、Hibemate、Hessian、Quartz等)的支持。

1.3.6、降低JavaEE API 的使用难度

Spring对JavaEE API(如JDBC、JavaMail、远程调用等)进行了薄薄的封装层,使这些API的使用难度大为降低。

1.3.7、Java源码的经典学习范例

Spring的源代码设计精妙、结构清晰、匠心独用,处处体现着大师对Java设计模式灵活运用以及对Java技术的高深造诣。它的源代码无疑是Java技术的最佳实践的范例。

1.4、Spring的体系结构

JavaWeb开发框架——Spring_第1张图片

  • Data Access:数据访问;
  • Data Integration:数据集成;
  • Web:Web开发;
  • AOP:面向切面编程;
  • Aspects:AOP思想实现;
  • Core Container:核心容器。
  • Test:单元测试与集成测试。

2、Spring快速入门

2.1、Spring程序开发步骤

问题提出:

  • 管理什么?(Service与Dao)
  • 如何将被管理的对象告知IoC容器?(配置)
  • 被管理的对象交给IoC容器,如何获取到IoC容器?(接口)
  • IoC容器得到后,如何从容器中获取bean?(接口方法)
  • 使用Spring导入哪些坐标?(pom.xml)

JavaWeb开发框架——Spring_第2张图片

  1. 导入Spring开发的基本包坐标;
  2. 编写Dao接口和实现类;
  3. 创建Spring核心配置文件;
  4. 在Spring配置文件中配置UserDaoImpl;
  5. 使用Spring的API获得Bean实例。

2.2、Spring的开发步骤

  1. 导入坐标;
  2. 创建Bean;
  3. 创建applicationContext.xml;
  4. 在配置文件中进行配置;
  5. 创建ApplicationContext对象getBean。
applicationContext.xml:




    

注意:bean定义时id属性在同一个上下文中不能重复。

package com.clp.impl;

class UserDaoImpl implements com.clp.UserDao {
    @Override
    public void save() {
        System.out.println("save running ...");
    }
}


package com.clp.demo;

import com.clp.UserDao;
import javafx.application.Application;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class UserDaoDemo {
    public static void main(String[] args) {
        ApplicationContext app = new ClassPathXmlApplicationContext("ApplicationContext.xml");
        UserDao userDao = (UserDao) app.getBean("userDao");
        userDao.save();
    }
}

3、Spring配置文件

3.1、Bean标签基本配置

用于配置对象交由Spring来创建。默认情况下它调用的是类中的无参构造函数,如果没有无参构造函数则不能创建成功。

类别 描述
名称
类型 标签
所属 标签
功能 定义Spring核心容器管理的对象
格式

       

属性列表

id:bean的id,使用容器可以通过id值获取对应的bean,在一个容器中id值唯一。

name:bean的别名,可以设置多个,使用,(逗号)或;(分号)或 (空格)分隔,可以用来代替id。

class:bean的类型,即配置的bean的全路径类名。

scope:定义bean的作用范围。singleton为单例(默认),prototype为非单例。

init-method:生命周期初始化方法。

destroy-method:生命周期销毁方法。

autowire:自动装配类型。

factory-method:bean工厂方法,应用于静态工厂或实例工厂。

factory-bean:实例工厂bean。

lazy-init:控制bean延迟加载。

范例

类别 描述
名称
类型 标签
所属 标签
功能 bean的属性注入
格式

       

属性列表

name:属性名称。

value:注入的普通属性值。

ref:注入的对象引用值。

范例

    
    
    
    
    

注意事项:获取bean无论是通过id还是name获取,如果无法获取到,将抛出异常NoSuchBeanDefinitionException。NoSuchBeanDefinitionException: No bean named 'bookServiceImpl' available 。

3.2、Bean标签范围配置

scope:指对象的作用范围,取值如下:
    singleton:默认值,单例的。
    prototype:多例的。
    request:WEB项目中,Spring创建一个Bean的对象,将对象存入到request域中。
    session:WEB项目中,Spring创建一个Bean的对象,将对象存入到session域中。
    global session:WEB项目中,应用在Portlet环境,如果没有Portlet环境那么globalSession相当于session。

Bean的创建时机:
    当scope的取值为singleton时:
        Bean的实例化个数:1个;
        Bean的实例化时机:当Spring核心文件被加载时,实例化配置的Bean实例。
        Bean的生命周期:
            对象创建:当应用加载,创建容器时,对象就被创建了;
            对象运行:只要容器在,对象一直或者。
            对象销毁:当应用卸载,销毁容器时,对象就被销毁了。
    当scope的取值为prototype时:
        Bean的实例化个数:多个。
        Bean的实例化时机:当调用getBean()方法时实例化Bean。
            对象创建:当使用对象时,创建新的对象实例。
            对象运行:只要对象在使用中,就一直活着。
            对象销毁:当对象长时间不用时,被Java的垃圾回收器回收了。

适合交给容器进行管理的bean:表现层对象;业务层对象;数据层对象;工具对象。

不适合交给容器进行管理的对象:封装实体的域对象。

3.3、Bean生命周期

生命周期:从创建到消亡的完整过程。

Bean生命周期:Bean从创建到销毁的整体过程。

- 初始化容器
    1、创建对象(内存分配)
    2、执行构造方法
    3、执行属性注入(set操作)
    4、执行bean初始化方法
- 使用bean
    1、执行业务操作
- 关闭/销毁容器
    1、执行bean销毁方法

Bean的销毁时机:

- 容器关闭前触发bean的销毁

- 关闭容器方式:
    1、手工关闭容器
        ConfigurationApplicationContext接口的close()操作
    2、注册关闭钩子,在虚拟机退出前先关闭容器再退出虚拟机
        ConfigurationApplicationContext接口registerShutdownHook()操作

Bean生命周期控制:在Bean创建后到销毁前做一些事情。

3.3.1、生命周期控制方式1

提供生命周期控制方法:

class UserDaoImpl implements com.clp.UserDao {
    public UserDaoImpl() {
        System.out.println("UserDaoImpl创建..");
    }

    public void init() {
        System.out.println("初始化方法");
    }

    public void destroy() {
        System.out.println("销毁方法");
    }

    @Override
    public void save() {
        System.out.println("save running ...");
    }
}
配置生命周期控制方法:
    init-method:指定类中的初始化方法名称。
    destroy-method:指定类中销毁方法名称。




    
    

public class App {
    public static void main(String[] args) {
        // 3、获取IoC容器
        ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");

        // 4.1、获取bean:BookDao,参数为bean 的id
        BookDao bookDao = (BookDao) ctx.getBean("bookDao");
        bookDao.save();

        ctx.close();
        // Spring容器注册关闭钩子。表示Java虚拟机在关闭之前会先将容器关闭掉
//        ctx.registerShutdownHook();

        // 4.2、获取bean:BookService,参数为bean的id
//        BookService bookService = (BookService) aCtx.getBean("bookService");
//        bookService.save();
    }
}

3.3.2、生命周期控制方式2

提供生命周期控制方法:

/**
 * 实现initializingBean和DisposableBean接口,重写其中的方法
 */
public class BookServiceImpl implements BookService, InitializingBean, DisposableBean {
    public BookDao bookDao;

    public void setBookDao(BookDao bookDao) {
        System.out.println("set ...");
        this.bookDao = bookDao;
    }

    @Override
    public void save() {
        System.out.println("book service save ...");
        bookDao.save();
    }

    /**
     * 在调用完所有setXxx()的setter方法之后会调用该方法,如本类的setBookDao()方法
     * @throws Exception
     */
    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("book service afterPropertiesSet");
    }

    @Override
    public void destroy() throws Exception {
        System.out.println("book service destroy...");
    }
}
applicationContext.xml中配置bean:




    
    
        
    

3.4、Bean实例化的四种方式

bean本质上就是对象,创建bean使用构造方法完成。

- 无参构造方法实例化:
    - 提供可访问的构造方法 public UserDao() {}
        注意:无参构造方法如果不存在,将抛出异常BeanCreationException
    - 配置
    

- 工厂静态方法实例化:
    public class StaticFactory {
        public static UserDao getUserDao() {
            return new UserDaoImpl();
        }
    }

    
            factory-method="getUserDao"
    >

- 工厂实例方法实例化:
    public class UserDaoFactory {
        public UserDao getUserDao() {
            return new UserDaoImpl();
        }
    }

    
    
    
    

- 工厂实例方法实例化-改进版(使用FactoryBean):  
    public class UserDaoFactoryBean implements FactoryBean {
        /**
         * 代替原始实例工厂中创建对象的方法
         * @return
         * @throws Exception
         */
        @Override
        public UserDao getObject() throws Exception {
            return new UserDaoImpl();
        }

        /**
         * 获取对象的类型
         * @return
         */
        @Override
        public Class getObjectType() {
            return UserDao.class;
        }

        /**
         * 该对象是否为单例
         * @return
         */
        @Override
        public boolean isSingleton() {
            return true;
        }
    }

    
    

3.5、Bean的依赖注入分析

目前UserService实例和UserDao实例都存在于Spring容器中,当前的做法是在容器外部获得UserService实例和UserDao实例,然后在程序中进行结合。

JavaWeb开发框架——Spring_第3张图片

因为UserService和UserDao都在Spring容器中,而最终程序直接使用的是UserService,所以可以在Spring容器中,将UserDao设置到UserService内部。

3.5.1、Bean的依赖注入概念

依赖注入(Dependency Injection):它是Spring框架核心IOC的具体实现。

在编写程序时,通过控制反转,把对象的创建交给了Spring,但是代码中不可能出现没有依赖的情况。IOC解耦只是降低它们的依赖关系,但不会消除。例如:业务层仍会调用持久层的方法。

那这种业务层和持久层的依赖关系,在使用Spring之后,就让Spring来维护了。简单地说,就是坐等框架把持久层对象传入业务层,而不用我们自己去获取。

3.6、Bean的依赖注入方式

思考:怎么将UserDao注入到UserService内部呢?/ 向一个类中传递数据的方式有几种?
    1、构造方法
    2、普通方法(set方法)

思考:依赖注入描述了在容器中建立bean与bean之间依赖关系的过程,如果bean运行需要的是数字或字符串呢?
    1、引用类型
    2、简单类型(基本数据类型与String)
    3、集合类型
- 依赖注入方式
    setter注入:
        简单类型
        引用类型
    构造器注入:
        简单类型
        引用类型

3.6.1、setter注入——引用类型

在bean中定义引用类型属性并提供可访问的set方法:

public class BookServiceImpl implements BookService {
    public BookDao bookDao;
    public UserDao userDao;

    public void setBookDao(BookDao bookDao) {
        System.out.println("set ...");
        this.bookDao = bookDao;
    }

    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }
}

配置中使用property标签ref属性注入引用类型对象:

applicationContext.xml:
    ...
    
    
    
        
        
        
    
    ...

3.6.2、setter注入——简单类型

在bean中定义引用类型属性并提供可访问的set方法:

public class BookDaoImpl implements BookDao {
    private int connectionNum;
    private String databaseName;

    public void setConnectionNum(int connectionNum) {
        this.connectionNum = connectionNum;
    }

    public void setDatabaseName(String databaseName) {
        this.databaseName = databaseName;
    }

    @Override
    public void save() {
        System.out.println("book dao save ..." + databaseName + ", " + connectionNum);
    }
}

配置中使用property标签value属性注入简单类型数据:

applicationContext.xml:

    ...
    
        
        
        
    
    ...

3.6.3、构造器(构造方法)注入——引用类型

在bean中定义引用类型属性并提供可访问的构造方法:

public class BookServiceImpl implements BookService, InitializingBean, DisposableBean {
    private BookDao bookDao;
    private UserDao userDao;

    public BookServiceImpl(BookDao bookDao, UserDao userDao) {
        this.bookDao = bookDao;
        this.userDao = userDao;
    }
}

配置中使用constructor-arg标签ref属性注入引用类型对象:

applicationContext.xml:
    ...
    
    
    
        
        
        
    
    ...

3.6.4、构造器(构造方法)注入——简单类型

public class BookDaoImpl implements BookDao {
    private int connectionNum;
    private String databaseName;

    public BookDaoImpl(int connectionNum, String databaseName) {
        this.connectionNum = connectionNum;
        this.databaseName = databaseName;
    }
}
applicationContext.xml:
    ...
    
    
        
        
    

    
    
        
        
    

    
    
        
        
    
    ...

3.6.5、p命名空间方式注入

P命名空间本质也是set()方法注入,但比起上述的set()方法注入更加方便,主要体现在配置文件中,如下:

  • 首先,需要引入P命名空间:xmlns:p="http://www.springframework.org/schema/p"
  • 其次,需要修改注入方式:



    
    

    
        
    
package com.clp.service.impl;

import com.clp.dao.UserDao;
import com.clp.service.UserService;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class UserServiceImpl implements UserService {
    private UserDao userDao;
//    public void setUserDao(UserDao userDao) {
//        this.userDao = userDao;
//    }
    public UserServiceImpl(UserDao userDao) {
        this.userDao = userDao;
    }

    @Override
    public void save() {
        userDao.save();
    }
}

3.6.6、注入集合数据类型

除了对象的引用可以注入,普通数据类型,集合等都可以在容器中进行注入。

public class BookDaoImpl implements BookDao {
    private int[] array;
    private List list;
    private Set set;
    private Map map;
    private Properties properties;

    public void setArray(int[] array) {
        this.array = array;
    }

    public void setList(List list) {
        this.list = list;
    }

    public void setSet(Set set) {
        this.set = set;
    }

    public void setMap(Map map) {
        this.map = map;
    }

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

    @Override
    public void save() {
        System.out.println("book dao save ...");
        System.out.println("遍历数组" + Arrays.toString(array));
        System.out.println("遍历List" + list);
        System.out.println("遍历Set" + set);
        System.out.println("遍历Map" + map);
        System.out.println("遍历Properties" + properties);
    }
}
applicationContext.xml:

    ...
    
        
        
            
                100
                200
                300
                

            
        
        
            
                Laaa
                Lbbb
                Lccc
            
        
        
            
                Saaa
                Sbbb
                
                Sccc
                Sccc
            
        
        
            
                
                
                
            
        
        
            
                Pvalue1
                Pvalue2
                Pvalue3
            
        
    
    ...
public class App {
    public static void main(String[] args) {
        // 3、获取IoC容器
        ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");

        // 4.1、获取bean:BookDao,参数为bean 的id
        BookDao bookDao = (BookDao) ctx.getBean("bookDao");
        bookDao.save();
    }
}

结果:
book dao save ...
遍历数组[100, 200, 300]
遍历List[Laaa, Lbbb, Lccc]
遍历Set[Saaa, Sbbb, Sccc]
遍历Map{Mkey1=Mvalue1, Mkey2=Mvalue2, Mkey3=Mvalue3}
遍历Properties{Pkey3=Pvalue3, Pkey2=Pvalue2, Pkey1=Pvalue1}

3.6.7、依赖注入方式的选择

  • 强制使用构造器进行,使用setter注入有概率不进行注入导致null对象出现;
  • 可选依赖使用setter注入进行,灵活性强;
  • Spring框架倡导使用构造器,第三方框架内部大多数采用构造器注入的形式进行数据初始化,相对严谨;
  • 如果有必要可以两者同时使用,使用构造器完成强制依赖的注入,使用setter注入完成可选依赖的注入;
  • 实际开发过程中还要根据实际情况分析,如果受控对象没有提供setter方法就必须使用构造器注入;
  • 自己开发的模块推荐使用setter注入。

3.6.8、案例——数据源对象管理

添加依赖(坐标):

pom.xml:

    ...
        
        
            com.alibaba
            druid
            1.1.16
        
        
        
            c3p0
            c3p0
            0.9.1.2
        
        
        
            mysql
            mysql-connector-java
            5.1.6
        
    ...

配置数据源对象作为Spring容器管理的bean:

applicationContext.xml:
    ...
    
    
        
        
        
        
        
    
    
        
        
        
        
        
    
    ...
测试代码:

public class App {
    public static void main(String[] args) {
        // 3、获取IoC容器
        ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");

        DataSource dataSource1 = (DataSource) ctx.getBean("dataSource1");
        DataSource dataSource2 = (DataSource) ctx.getBean("dataSource2");
        System.out.println(dataSource1);
        System.out.println(dataSource2);
    }
}

3.7、依赖自动装配

IoC容器根据bean所依赖的资源在容器中自动查找并注入到bean中的过程称为自动装配。

3.7.1、自动装配方式

  • 按类型(常用);
  • 按名称;
  • 按构造方法;
  • 不启动自动装配。
public class BookServiceImpl implements BookService {
    private BookDao bookDao;

    public void setBookDao(BookDao bookDao) {
        this.bookDao = bookDao;
    }

    @Override
    public void save() {
        System.out.println("book service save ...");
        bookDao.save();
    }
}
- 配置中使用bean标签的autowire属性设置自动装配的类型

applicationContext.xml:
    ...
    
    
    
    
    
    ...

3.7.2、依赖自动装配的特征

  • 自动装配用于引用类型依赖注入,不能对简单类型进行操作;
  • 使用按类型装配时(byType)必须保障容器中相同类型的bean唯一,推荐使用;
  • 使用按名称装配时(byName)必须保障容器中具有指定名称的bean,因变量名与配置耦合,不推荐使用;
  • 自动装配优先级低于setter注入与构造器注入,同时出现时自动装配配置失效。

3.8、Spring加载外部properties文件

jdbc.properties:

jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/ssmdb
jdbc.username=root
jdbc.password=123456
applicationContext.xml:
    ...
    
    
    
    
    
    
    
    
    
    
        
        
        
        
        
    
    ...

3.9、引入其他配置文件(分模块开发)

实际开发中,Spring的配置内容非常多,这就导致Spring配置很繁杂且体积很大,所以,可以将部分配置拆解到其他的配置文件中,而在Spring主配置文件通过import标签进行加载。

4、Spring相关API

4.1、ApplicationContext的继承体系

JavaWeb开发框架——Spring_第4张图片

applicationContext:接口类型,代表应用上下文,可以通过其实例获得Spring容器中的Bean对象。

  • BeanFactory是Ioc容器的顶层接口,初始化BeanFactory对象时,加载的bean延迟加载。
  • ApplicationContext接口是Spring容器的核心接口,初始化时bean立即加载。
  • ApplicationContext接口提供基础的bean操作相关方法,通过其他接口扩展其功能。
  • ApplicationContext接口常用初始化类:① ClassPathXMLApplicationContext;② FileSystemXMLApplicationContext。

4.2、ApplicationContext的实现类

ClassPathXmlApplicationContext:
    它是从类的根路径下(resource文件夹下)加载配置文件,推荐使用这种。

FileSystemXmlApplicationContext:
    它是从磁盘路径上加载配置文件,配置文件可以在磁盘的任意位置。

AnnotationConfigApplicationContext:
    当使用注解配置容器对象时,需要使用此类来创建Spring容器,它用来读取注解。

4.3、getBean()方法使用

public Object getBean(String name);
    当参数的数据类型是字符串时,标识根据Bean的id从容器中获得Bean实例,返回是Object,需要强转。

public  T getBean(Class requiredType);
    当参数的数据类型是Class类型时,标识根据类型从容器中匹配Bean实例,当容器中相同类型的Bean有多个时,则此方法会报错。

public  T getBean(String beanName, Class beanType);
    使用bean名称获取并指定类型。

5、Spring配置数据源

5.1、数据源(连接池)的作用

  1. 数据源(连接池)是提高程序性能出现的。
  2. 实现实例化数据源,初始化部分连接资源。
  3. 使用连接资源时从数据源中获取。
  4. 使用完毕后将连接资源归还给数据源。

常见的数据源(连接池):DBCP、C3P0、BoneCP、Druid等。

package test;

import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.pool.DruidPooledConnection;
import com.mchange.v2.c3p0.ComboPooledDataSource;
import org.junit.Test;

import java.beans.PropertyVetoException;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.ResourceBundle;

public class DataSourceTest {
    @Test
    //测试手动创建 c3p0 数据源
    public void test1() throws PropertyVetoException, SQLException {
        ComboPooledDataSource dataSource = new ComboPooledDataSource();
        dataSource.setDriverClass("com.mysql.jdbc.Driver");
        dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/bysj?characterEncoding=utf-8");
        dataSource.setUser("root");
        dataSource.setUser("123456");
        Connection connection = dataSource.getConnection();
        System.out.println(connection);
        connection.close();
    }

    @Test
    //测试手动创建 c3p0 数据源(加载properties配置文件)
    public void test3() throws PropertyVetoException, SQLException {
        //读取配置文件
        ResourceBundle rb = ResourceBundle.getBundle("jdbc");
        String driver = rb.getString("jdbc.driver");
        String url = rb.getString("jdbc.url");
        String username = rb.getString("jdbc.username");
        String password = rb.getString("jdbc.password");
        //创建数据源对象,设置连接参数
        ComboPooledDataSource dataSource = new ComboPooledDataSource();
        dataSource.setDriverClass(driver);
        dataSource.setJdbcUrl(url);
        dataSource.setUser(username);
        dataSource.setPassword(password);
        Connection connection = dataSource.getConnection();
        System.out.println(connection);
        connection.close();
    }

    @Test
    //测试手动创建 druid 数据源
    public void test2() throws PropertyVetoException, SQLException {
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setDriverClassName("com.mysql.jdbc.Driver");
        dataSource.setUrl("jdbc:mysql://localhost:3306/bysj?characterEncoding=utf-8");
        dataSource.setUsername("root");
        dataSource.setPassword("123456");
        DruidPooledConnection connection = dataSource.getConnection();
        System.out.println(connection);
        connection.close();
    }
}

5.2、数据源的开发步骤

  1. 导入数据源的坐标和数据库驱动坐标;
  2. 创建数据源对象;
  3. 设置数据源的基本连接数据;
  4. 使用数据源获取连接资源和归还连接资源。

5.3、Spring配置数据源

可以将DataSource的创建权交给Spring去完成。




    
        
        
        
        
    
package test;

import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.pool.DruidPooledConnection;
import com.mchange.v2.c3p0.ComboPooledDataSource;
import javafx.application.Application;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import javax.sql.DataSource;
import java.beans.PropertyVetoException;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.ResourceBundle;

public class DataSourceTest {
    @Test
    //测试Spring容器产生数据源对象
    public void test4() throws PropertyVetoException, SQLException {
        ApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml");
        DataSource dataSource = app.getBean(DataSource.class);
        Connection connection = dataSource.getConnection();
        System.out.println(connection);
        connection.close();
    }
}

5.4、抽取jdbc配置文件

applicationContext.xml加载jdbc.properties配置文件获得连接信息。

首先,需要引入context命名空间和约束路径:
    命名空间:
        xmlns:context="http://www.springframework.org/schema/context"
    约束路径:
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd

Spring容器加载properties文件:
    
    



    
    

    
        
        
        
        
    

6、Spring注解开发

Spring是轻代码而重配置的框架,配置比较繁重,影响开发效率,所以注解开发是一种趋势,注解代替xml配置文件可以简化配置,提高开发效率。

6.1、Spring原始注解

@Component
使用在类上用于实例化Bean

    - Spring提供@Component注解的3个衍生注解:
        @Controller
        使用在web层类上用于实例化Bean

        @Service
        使用在service层上用于实例化Bean

        @Repository
        使用在dao层类上用于实例化Bean

@Autowired
使用在字段上根据类型依赖注入

@Qualifier
结合@Autowired一起使用用于根据名称进行依赖注入

@Resource
相当于@Autowired+@Qualifier,按照名称进行注入。

@Value
注入普通属性

@Scope
标注Bean的作用范围

@PostConstruct
使用在方法上标注该方法是Bean的初始化方法

@PreDestroy
使用在方法上标注该方法是Bean的销毁方法

注意:使用注解进行开发时,需要在applicationContext.xml中配置组件扫描,作用是指定哪个包及其子包下的Bean需要进行扫描以便识别使用注解配置的类、字段和方法。


6.1.1、@Scope & @PostConstruct & @PreDestroy

//
//@Component("userServiceId")
@Service("userServiceId")
@Scope("singleton")
public class UserServiceImpl implements UserService {
    @Value("${jdbc.driver}") //从容器中找键为jdbc.driver的值,并赋给driver
    private String driver;

    //
//    @Autowired  //按照数据类型从Spring容器中进行匹配的
//    @Qualifier("userDaoId")  //按照id名称从Spring容器中进行匹配的,但是注意此处@Qualifier需要结合@Autowired一起使用
    @Resource(name = "userDaoId") //@Resource相当于@Qualifier+@Autowired
    private UserDao userDao;

    //使用xml配置需要set()方法,使用注解方式可以不写set()方法
//    public void setUserDao(UserDao userDao) {
//        this.userDao = userDao;
//    }

    @Override
    public void save() {
        System.out.println(driver);
        userDao.save();
    }

    @PostConstruct
    public void init() {
        System.out.println("service对象的初始化方法");
    }

    @PreDestroy
    public void destroy() {
        System.out.println("service对象的销毁方法");
    }
}

6.1.2、@Autowired & @Qualifier 实现引用类型依赖注入

@Service
public class BookServiceImpl implements BookService {
    /**
     * 使用@Autowired注解开启自动装配模式:按类型依赖注入(通过暴力反射)
     * 如果有多个相同类型的bean,则需再添加@Qualifier指定bean的名称(bean的id)
     */
    @Autowired
    @Qualifier("bookDao1")
    private BookDao bookDao;

    @Override
    public void save() {
        System.out.println("book service save ...");
        bookDao.save();
    }
}

注意:

  • 自动装配基于反射设计创建对象并暴力反射对应属性为私有属性初始化数据,因此无需提供setter方法;
  • 自动装配建议使用无参构造方法创建对象(默认),如果不提供对应构造方法,请提供唯一的构造方法;
  • @Qualifier注解无法单独使用,必须配合@Autowired注解使用。

6.1.3、@Value 实现简单类型依赖注入

@Repository("bookDao")
public class BookDaoImpl implements BookDao {
    @Value("mysql")
    private String dbName;

    @Override
    public void save() {
        System.out.println("book dao save ..." + dbName);
    }
}

注入properties文件中的值:

- 配置类加上@PropertiesSource注解指定要加载的properties文件
    @Configuration
    @ComponentScan("com.clp")
    @PropertySource("classpath:jdbc.properties")    <-这里
    public class SpringConfig {
    }
注意:如果需要加载多个properties文件,使用数组方式加载:
    @PropertiesSource({"jdbc.properties", "xxx.properties"})
注意:路径仅支持单一文件配置,多文件请使用数组格式配置,不允许使用通配符*。允许添加"classpath:"前缀。

- 配置依赖注入
    @Repository("bookDao")
    public class BookDaoImpl implements BookDao {
        @Value("${jdbc.url}")
        private String dbName;

        @Override
        public void save() {
            System.out.println("book dao save ..." + dbName);
        }
    }

6.2、Spring新注解

使用上面的注解还不能全部替代xml配置文件,还需要使用注解替代的配置如下:

  • 非自定义的Bean的配置:
  • 加载properties文件的配置:
  • 组件扫描的配置:
  • 引入其他文件:

Spring 3.0升级了纯注解开发模式,使用Java类代替配置文件,开启了Spring快速开发赛道。在纯注解开发中,Java类代替Spring核心配置文件。

@Configuration
用于指定当前类是一个Spring配置类(相当于一个applicationContext.xml),当创建容器时会从该类上加载注解。

@ComponentScan
用于指定Spring在初始化容器时要扫描的包。
作用和在Spring的xml配置文件中的一样

@Bean
使用在方法上,标注该方法的返回值存储到Spring容器中。

@PropertySource
用于加载.properties文件中的配置

@Import
用于导入其他配置类

6.2.1、@Configuration & @ComponentScan实现替代Spring配置文件



    
    
    

上述配置文件可用以下类替换:

@Configuration
@ComponentScan("com.clp")
public class SpringConfig {
}

@Configuration注解用于设定当前类为配置类。
@ComponentScan注解用于设定扫描路径,此注解只能添加一次,多个数据请用数组格式:
    @ComponentScan({"com.clp.service", "com.clp.dao"})

读取Spring核心配置文件初始化容器对象切换为读取Java配置类初始化容器对象:

public class AppAnno {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
        BookDao bookDao = (BookDao) ctx.getBean("bookDao");
        bookDao.save();
    }
}

测试代码:

public class AppAnno {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
        BookDao bookDao1 = (BookDao) ctx.getBean(BookDao.class);
        BookDao bookDao2 = (BookDao) ctx.getBean(BookDao.class);
        System.out.println(bookDao1);
        System.out.println(bookDao2);
    }
}

6.2.2、@Import & @Bean 实现配置类分离和第三方bean管理

- 使用独立的配置类管理第三方bean:
    public class JdbcConfig {
        // 1、定义一个方法获得要管理的对象
        // 2、添加@Bean表示当前方法的返回值是一个bean
        @Bean
        public DataSource dataSource() {
            DruidDataSource dataSource = new DruidDataSource();
            dataSource.setDriverClassName("com.mysql.jdbc.Driver");
            dataSource.setUrl("jdbc:mysql://localhost:3306/ssmdb");
            dataSource.setUsername("root");
            dataSource.setPassword("123456");
            return dataSource;
        }
    }

- 将独立的配置类加入核心配置:
    - 方式1:导入式。使用@Import注解手动加入配置类到核心配置,此注解只能添加一次,多个数据请用数组格式
        public class JdbcConfig {
            @Bean
            public DataSource dataSource() {
                DruidDataSource dataSource = new DruidDataSource();
                // 相关配置
                return dataSource;
            }
        }

        @Configuration
        @Import({JdbcConfig.class})
        public class SpringConfig {
        }
    - 方式2:扫描式。使用@ComponentScan注解扫描配置类所在的包,加载对应的配置类信息。
        @Configuration
        public class JdbcConfig {
            @Bean
            public DataSource dataSource() {
                DruidDataSource dataSource = new DruidDataSource();
                // 相关配置
                return dataSource;
            }
        }

        @Configuration
        @ComponentScan({"com.clp.config", "com.clp.service", "com.clp.dao"})
        public class SpringConfig {
        }

第三方bean的依赖注入(创建该bean还需要其他东西):

public class JdbcConfig {
    /*
    * 简单类型的依赖注入使用@Value
    * */
    @Value("com.mysql.jdbc.Driver")
    private String driver;
    @Value("jdbc:mysql://localhost:3306/ssmdb")
    private String url;
    @Value("root")
    private String username;
    @Value("123456")
    private String password;

    // 1、定义一个方法获得要管理的对象
    // 2、添加@Bean表示当前方法的返回值是一个bean
    @Bean
    /*
    * 引用类型的依赖注入:主需要为bean定义方法设置形参即可,容器会根据 类型 自动装配对象。
    * */
    public DataSource dataSource(BookDao bookDao) {
        System.out.println(bookDao);
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setDriverClassName(driver);
        dataSource.setUrl(url);
        dataSource.setUsername(username);
        dataSource.setPassword(password);
        return dataSource;
    }
}

6.3、XML配置对比注解配置

功能 XML配置 注解
定义bean

bean标签:

        - id属性

        - class属性

@Component

        @Controller

        @Service

        @Repository

@ComponentScan

设置依赖注入

setter注入(set方法)

        引用/简单

构造器逐日(构造方法)

        引用/简单

自动装配

@Autowired

        @Qualifier

@Value

配置第三方bean

bean标签

静态工厂、实例工厂、FactoryBean

@Bean
作用范围

bean标签:

        - scope属性

@Scope
生命周期

bean标签:

        init-method

        destroy-method

@PostConstruct

@PreDestroy

7、Spring整合MyBatis

MyBatis程序核心对象分析:

// 1、创建SqlSessionFactoryBean对象
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
// 2、加载sqlMapConfig.xml配置文件
InputStream inputStream = Resources.getResourceAsStream("sqlMapConfig.xml");
// 3、创建SqlSessionFactory对象
SqlSessionFactory sqlSessionFactory = sqlSessionFactory.build(inputStream);
// 4、获取SqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();
// 5、执行SqlSession对象执行查询,获取结果User
AccountDao accountDao = sqlSession.getMapper(AccountDao.class);
Account account = accountDao。findById(2);
System.out.println(account);
// 6、释放资源
sqlSession.close();

整合MyBatis:

pom.xml中导入坐标:
    ...
        
            org.springframework
            spring-jdbc
            5.0.5.RELEASE
        
        
            org.mybatis
            mybatis-spring
            1.3.1
        
    ...
编写配置类:
public class JdbcConfig {
    /*
    * 简单类型的依赖注入使用@Value
    * */
    @Value("com.mysql.jdbc.Driver")
    private String driver;
    @Value("jdbc:mysql://localhost:3306/ssmdb")
    private String url;
    @Value("root")
    private String username;
    @Value("123456")
    private String password;

    // 1、定义一个方法获得要管理的对象
    // 2、添加@Bean表示当前方法的返回值是一个bean
    @Bean
    /*
    * 引用类型的依赖注入:主需要为bean定义方法设置形参即可,容器会根据 类型 自动装配对象。
    * */
    public DataSource dataSource(BookDao bookDao) {
        System.out.println(bookDao);
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setDriverClassName(driver);
        dataSource.setUrl(url);
        dataSource.setUsername(username);
        dataSource.setPassword(password);
        return dataSource;
    }
}

public class MyBatisConfig {
    /**
     * SqlSessionFactoryBean专门产生SqlSessionFactory
     * @return
     */
    @Bean
    public SqlSessionFactoryBean sqlSessionFactoryBean(DataSource dataSource) {
        SqlSessionFactoryBean ssfb = new SqlSessionFactoryBean();
        ssfb.setTypeAliasesPackage("com.clp.domain"); // 设置实体类别名
        ssfb.setDataSource(dataSource); // 设置数据源
        return ssfb;
    }

    /**
     * 加载Dao层的映射信息
     * @return
     */
    @Bean
    public MapperScannerConfigurer mapperScannerConfigurer() {
        MapperScannerConfigurer msc = new MapperScannerConfigurer();
        msc.setBasePackage("com.clp.dao"); // 设置映射配置所在的包
        return msc;
    }
}

@Configuration
@Import({JdbcConfig.class, MyBatisConfig.class})
@ComponentScan("com.clp")
@PropertySource("classpath:jdbc.properties")
public class SpringConfig {
}

8、Spring整合Junit

8.1、原始Junit测试Spring的问题

在测试类中,每个测试方法都有以下两行代码:

ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
IAccountService as = ac.getBean("accountService",IAccountService.class);

这两行代码的作用是获取容器,如果不写的话,直接回提示空指针异常,所以又不能轻易删掉。

8.2、上述问题解决思路

  • 让SpringJunit负责创建Spring容器,但是需要将配置文件的名称告诉它。
  • 将需要进行测试Bean直接在测试类中进行注入。

8.3、Spring整合Junit步骤

  1. 导入spring集成Junit的坐标;
    pom.xml:
        ...
            
                junit
                junit
                4.12
                test
            
            
                org.springframework
                spring-test
                5.2.10.RELEASE
            
        ...
  2. 使用@Runwith注解替换原来的类运行器;
  3. 使用@ContextConfiguration指定配置文件或配置类;
  4. 使用@Autowired注入需要测试的对象;
  5. 创建测试方法进行测试。
    // 使用Spring整合JUnit专用的类加载器
    @RunWith(SpringJUnit4ClassRunner.class)
    // 指定Spring的配置类
    @ContextConfiguration(classes = SpringConfig.class)
    public class AccountServiceTest {
        @Autowired
        private AccountService accountService;
    
        @Test
        public void testFindAll() {
            System.out.println(accountService.findAll());
        }
    }

9、Spring集成Web环境

9.1、ApplicationContext应用上下文获取方式

应用上下文是通过new ClasspathXmlApplicationContext(spring配置文件)方式获取的,但是每次从容器中获得Bean时都要编写new ClasspathXmlApplicationContext(spring配置文件),这样的弊端是配置文件加载多次,应用上下文对象创建多次。

在Web项目中,可以使用ServletContextListener监听Web应用的启动,我们可以在Web应用启动时,就加载Spring的配置文件,创建应用上下文对象ApplicationContext,再将其存储到最大的域servletContext域中,这样就可以再任意位置从域中获得应用上下文ApplicationContext对象了。

9.2、Spring提供获取应用上下文的工具

Spring提供了一个监听器ContextLoaderListener,该监听器内部加载Spring配置文件,创建应用上下文对象,并存储到ServletContext域中,提供了一个客户端工具WebApplicationContextUtils供使用者获得应用上下文对象。

所以我们需要做的只有两件事:

在web.xml中配置ContextLoaderListener监听器(要导入spring-web坐标);

使用WebApplicationContextUtils获得应用上下文对象ApplicationContext。

web.xml:




  Archetype Created Web Application

  
  
    contextConfigLocation
    classpath:applicationContext.xml
  

  
  
    org.springframework.web.context.ContextLoaderListener
  

package listener;

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

import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;

@WebListener
public class ContextLoaderListener implements ServletContextListener {
    @Override
    public void contextInitialized(ServletContextEvent sce) {
        //将Spring的应用上下文对象存储到ServletContext域中
        ServletContext servletContext = sce.getServletContext();

        String contextConfigLocation = servletContext.getInitParameter("contextConfigLocation");
        ApplicationContext app = new ClassPathXmlApplicationContext(contextConfigLocation);
        servletContext.setAttribute("app",app);
        System.out.println("spring容器创建完毕");
    }

    @Override
    public void contextDestroyed(ServletContextEvent sce) {

    }
}
package web;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;
import service.UserService;

import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@WebServlet
public class UserServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        this.doPost(req,resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        ServletContext servletContext = req.getServletContext();
        //ApplicationContext app = (ApplicationContext) servletContext.getAttribute("app");
        ApplicationContext app = WebApplicationContextUtils.getWebApplicationContext(servletContext);
        UserService userService = app.getBean(UserService.class);
        userService.save();
    }
}

9.3、Spring集成web环境步骤

  1. 配置ContextLoaderListener监听器;
  2. 使用WebApplicationContextUtils获得应用上下文。

10、Spring JdbcTemplate的基本使用

10.1、JdbcTemplate概述

它是spring框架中提供的一个对象,是对原始繁琐的Jdbc API对象的简单封装。spring框架为我们提供了很多的操作模板类。例如:操作关系型数据的JdbcTemplate和HibernateTemplate,操作nosql数据库的RedisTemplate,操作消息队列的JmsTemplate等等。

10.2、JdbcTemplate开发步骤

  1. 导入spring-jdbc和spring-tx坐标;
  2. 创建数据库表和实体;
  3. 创建JdbcTemplate对象;
  4. 执行数据库操作。
package com.test;

import com.mchange.v2.c3p0.ComboPooledDataSource;
import org.junit.Test;
import org.springframework.jdbc.core.JdbcTemplate;

import java.beans.PropertyVetoException;

public class JdbcTemplateTest {

    @Test
    //测试JdbcTemplate开发步骤
    public void test1() throws PropertyVetoException {
        //创建数据源对象
        ComboPooledDataSource dataSource = new ComboPooledDataSource();
        dataSource.setDriverClass("com.mysql.jdbc.Driver");
        dataSource.setJdbcUrl("jdbc:mysql://127.0.0.1:3306/db?characterEncoding=utf-8");
        dataSource.setUser("root");
        dataSource.setPassword("123456");

        JdbcTemplate jdbcTemplate = new JdbcTemplate();
        //设置数据源对象,知道数据库在哪
        jdbcTemplate.setDataSource(dataSource);
        //执行操作
        int row = jdbcTemplate.update("insert into account values(?,?)", "李四", 5000);
        System.out.println(row);
    }
}

10.3、Spring产生JdbcTemplate对象

我们可以将JdbcTemplate的创建权交给Spring,将数据源DataSource的创建权也交给Spring,在Spring容器内部将数据源DataSource注入到JdbcTemplate模板对象中,配置如下:

jdbc.properties:

jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/db>characterEncoding=utf-8
jdbc.username=root
jdbc.password=123456
applicationContext.xml:




    






    
    
    
        
        
        
        
    

    
    
        
    

    @Test
    //测试spring产生jdbcTemplate模板对象
    public void test2() {
        ClassPathXmlApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml");
        JdbcTemplate jdbcTemplate = app.getBean(JdbcTemplate.class);
        int row = jdbcTemplate.update("insert into account values(?,?)", "王五", 30);
        System.out.println(row);
        app.close();
    }
package com.test;

import com.domain.Account;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import java.util.List;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class JdbcTemplateCRUDTest {
    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Test
    public void test1() {
        jdbcTemplate.update("update account set money=? where name=?",10000,"张三");
    }

    @Test
    public void test2() {
        List accountList = jdbcTemplate.query("select * from account", new BeanPropertyRowMapper(Account.class));
        System.out.println(accountList);
    }

    @Test
    public void test3() {
        Account account = jdbcTemplate.queryForObject("select * from account where name=?",
                new BeanPropertyRowMapper(Account.class), "张三");
        System.out.println(account);
    }

    @Test
    public void test4() {
        Long count = jdbcTemplate.queryForObject("select count(*) from account",
                Long.class);
        System.out.println(count);
    }
}

11、Spring AOP

11.1、什么是AOP

AOP(Aspect Oriented Programming)的缩写,意思为面向切面编程,是通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。

AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间耦合度降低,提高程序的可重用性,同时提高了开发的效率。

10.2、AOP的作用及其优势

作用:在程序运行期间,在不修改源码的情况下对方法进行功能增强。

优势:减少重复代码,提高开发效率,并且易于维护。

10.3、AOP的底层实现

实际上,AOP的底层是通过Spring提供的动态代理技术实现的。在运行期间,Spring通过动态代理技术动态地生成代理对象,代理对象方法执行时进行增强功能地接入,再去调用目标对象地方法,从而完成功能的增强。

10.4、AOP的动态代理技术

常用的动态代理技术:

  • JDK代理:基于接口的动态代理技术。
  • cglib代理:基于父类的动态代理技术。

JavaWeb开发框架——Spring_第5张图片

10.4.1、基于JDK的动态代理

package com.proxy.jdk;

public class Advance {
    public void before() {
        System.out.println("前置增强..");
    }
    public void after() {
        System.out.println("后置增强..");
    }
}
package com.proxy.jdk;

public interface TargetInterface {
    public void save();
}
package com.proxy.jdk;

public class Target implements TargetInterface{
    @Override
    public void save() {
        System.out.println("save running...");
    }
}
package com.proxy.jdk;

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

public class ProxyTest {
    public static void main(String[] args) {
        //创建目标对象
        final Target target = new Target();

        //创建增强对象
        final Advance advance = new Advance();

        //返回值就是动态生成的代理对象
        TargetInterface proxy = (TargetInterface) Proxy.newProxyInstance(
                //目标对象的类加载器
                target.getClass().getClassLoader(),
                //目标对象相同的字节码对象数组
                target.getClass().getInterfaces(),
                new InvocationHandler() {
                    @Override
                    //调用代理对象的任何方法,实质执行的都是invoke方法
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        //前置增强
                        advance.before();

                        //执行目标方法
                        Object invoke = method.invoke(target, args);

                        //后置增强
                        advance.after();

                        return invoke;
                    }
                }
        );

        //调用代理对象的方法
        proxy.save();
    }
}
结果:

前置增强..
save running...
后置增强..

10.4.2、基于cglib的动态代理

package com.proxy.cglib;

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

import java.lang.reflect.Method;

public class ProxyTest {
    public static void main(String[] args) {
        //创建目标对象
        final Target target = new Target();

        //创建增强对象
        final Advance advance = new Advance();

        //返回值就是动态生成的代理对象,基于cglib
        //1、创建增强器
        Enhancer enhancer = new Enhancer();
        //2、设置父类(目标)
        enhancer.setSuperclass(Target.class);
        //3、设置回调
        enhancer.setCallback(new MethodInterceptor() {
            @Override
            public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
                //执行前置
                advance.before();
                //执行目标
                Object invoke = method.invoke(target, args);
                //执行后置
                advance.after();
                return invoke;
            }
        });
        //4、创建代理对象
        Target proxy = (Target) enhancer.create();

        proxy.save();
    }
}

10.5、AOP相关概念

Spring的AOP实现底层就是对上面的动态代理的代码进行了封装,封装后我们只需要对需要关注的部分进行代码编写,并通过配置的方式完成指定目标的方法增强。

AOP常用的术语如下:

  • Target(目标对象):原始功能去掉共性功能对应的类产生的对象,这种对象是无法直接完成最终工作的。
  • Proxy(代理对象):目标对象无法直接完成工作,需要对其进行功能回填,通过原始对象的代理对象实现。SpringAOP的核心本质是采用代理模式实现的。
  • Joinpoint(连接点):程序执行过程中的任意位置,在SpringAOP中,这些点指的是方法,因为Spring只支持方法类型的连接点。
  • Pointcut(切入点):所谓切入点是指我们要对哪些Joinpoint进行拦截的定义(匹配连接点的式子)。在SpringAOP中,一个切入点可以只描述一个具体方法,也可以匹配多个方法:① 一个具体方法:com.itheima.dao包下的BookDao接口中的无形参无返回值的save()方法;② 匹配多个方法:所有的save()方法,所有的get开头的方法,所有以Dao结尾的接口中的任意方法,所有带有一个参数的方法。注意:连接点包含切入点,切入点一定在连接点中。
  • Advice(通知/增强):所谓通知是指拦截到Joinpoint(切入点)之后所要做的操作,也就是共性功能。在SpringAOP中,功能最终以方法的形式呈现。通知类:定义通知的类。
  • Aspect(切面):是切入点和通知(引介)的结合。
  • Weaving(织入):是指把增强应用到目标对象来创建新的代理对象的过程。Spring采用动态代理织入,而AspectJ采用编译期织入和类装载期织入。

10.5.1、AOP切入点表达式

  • 切入点:要进行增强的方法。
  • 切入点表达式:要进行增强的方法的描述形式。
- 描述方式1:执行com.itheima.dao包下的BookDao接口中的无参数update()方法
execution(void com.itheima.dao.BookDao.update())

- 描述方式2:
execution(void com.itheima.dao.impl.BookDaoImpl.update())

1、切点表达式的写法:

- 切入点表达式标准格式:
    动作关键字execution(访问修饰符 返回值类型 包名.类/接口名.方法名(参数) 异常名)
    例:execution(public User com.itheima.service.UserService.findById(int))
        动作关键字:描述切入点的行为动作,例如execution表示执行到指定切入点。
        访问修饰符:public,private等。访问修饰符可以省略;
        返回值类型:可以使用星号*代表任意;
        包名:可以使用星号*代表任意。包名与类名之间一个点.代表当前包下的类;两个点..表示当前包及其子包下的类;
        类/接口名:可以使用星号*代表任意;
        方法名:可以使用星号*代表任意;
        参数:参数列表可以使用两个点..表示任意个数,任意类型的参数列表。
        异常名:方法定义中抛出指定异常,可以省略。

- 可以使用通配符描述切入点,快速描述
    *:单个独立的任意符号,可以独立实现,也可以作为前缀或者后缀的匹配符出现。
        例:execution(public * com.itheima.*.UserService.find* (*))
        匹配com.itheima包下的任意包中的UserService类或接口中所有find开头的带有一个参数的方法。
    ..:多个连续的任意符号,可以独立出现,常用语简化包名与参数的书写。
        例:execution(public User com..UserService.findById (..))
        匹配com包下的任意包中的UserService类或接口中所有名称为findById的方法
    +:专用于匹配子类类型。
        例:execution(* *..*Service+.*(..))

2、切入点表达式的书写技巧

  • 所有代码按照标准规范开发,否则以下技巧全部失效;
  • 描述切入点通常描述接口,而不描述实现类;
  • 访问控制修饰符针对接口开发均采用public描述(可省略访问控制修饰符描述);
  • 返回值类型对于增删改类使用精准类型加速匹配,对于查询类使用*通配快速描述;
  • 包名书写尽量不使用..匹配,效率过低,常用*做单个包描述匹配,或精准匹配;
  • 接口名/类名书写名称与模块相关的采用*匹配,例如UserService书写成*Service,绑定业务层接口名;
  • 方法名书写以动词进行精准匹配,名词采用*匹配,例如getByI书写成getBy*,selectAll书写成selectAll;
  • 参数规则较为复杂,根据业务方法灵活调整;
  • 通常不使用异常作为匹配规则。

10.5.2、AOP通知类型

AOP通知描述了抽取的共性功能,根据共性功能抽取的位置不同,最终运行代码时要将其加入到合理的位置。

AOP通知功分为5种类型:

  • 前置通知;
  • 后置通知;
  • 环绕通知(重点);
  • 返回后通知(了解);
  • 抛出异常后通知(了解)。

10.6、AOP开发明确的事项

10.6.1、需要编写的内容

  • 编写核心业务代码(目标类的目标方法);
  • 编写切面类,切面类中有通知(增强功能方法);
  • 在配置文件中,配置织入关系,即将哪些通知与哪些连接点进行结合。

10.6.2、AOP技术实现的内容

Spring框架监控切入点方法的执行,一旦监控到切入点方法被运行,使用代理机制,动态创建目标对象的代理对象,根据通知类别,在代理对象的对应位置,将通知对应的功能织入,完成完成的代码逻辑运行。

10.6.3、AOP底层使用哪种代理方式

在Spring中,框架会根据目标类是否实现了接口来决定采用哪种动态代理的方式。

10.7、AOP工作流程

  1. Spring容器启动;
  2. 读取所有切面配置中的切入点;
    /**
     * 切面类
     */
    @Component // 该类受Spring管理
    @Aspect // 说明该类作为AOP处理
    public class MyAdvice {
        /**
         * 切入点1:注解@Pointcut说明该方法是一个切入点方法。method1()注解配置了该切入点,故会被读取
         */
        @Pointcut("execution(void com.clp.dao.BookDao.save())")
        private void pt1() {}
    
        /**
         * 切入点2(没有配置该切入点的切面,故不会读取该切入点)
         */
        @Pointcut("execution(void com.clp.dao.BookDao.update())")
        private void pt2() {}
    
        /**
         * 切面:绑定切点和通知(作为切面)
         */
        @Before("pt1()")
        public void method1() {
            System.out.println(System.currentTimeMillis());
        }
    }
  3. 初始化bean,判定bean对应的类中的方法是否匹配到任意切入点:① 匹配失败,创建对象;② 匹配成功,创建原始对象(目标对象)的代理对象。
  4. 获取bean执行方法:① 获取的bean不是代理对象,调用方法并执行,完成操作;② 获取的bean是代理对象,根据代理对象的运行模式运行原始方法与增强的内容,完成操作。

10.8、基于XML的AOP开发

10.8.1、快速入门

  1. 导入AOP相关坐标;
  2. 创建目标接口和目标类(内部有切点);
  3. 创建切面类(内部有增强方法);
  4. 将目标类和切面类的对象创建权交给Spring;
  5. 在applicationContext.xml中配置织入关系;
  6. 测试代码。
pom.xml:

        ...
        
            org.springframework
            spring-context
            5.0.5.RELEASE
        
        
            org.aspectj
            aspectjweaver
            1.8.4
        
        ...
package com.aop;

public interface TargetInterface {
    public void save();
}
package com.aop;

public class Target implements TargetInterface {
    @Override
    public void save() {
        System.out.println("save running...");
    }
}
package com.aop;

public class MyAspect {
    public void before() {
        System.out.println("前置增强...");
    }
}
applicationContext.xml:

    ...
    
    
    
    
    
    
        
        
            
            
        
    
    ...
package com.test;

import com.aop.TargetInterface;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class AOPTest {
    @Autowired
    private TargetInterface target;

    @Test
    public void test1() {
        target.save();
    }
}

10.8.2、XML配置AOP详解

1、通知的类型:

通知的配置语法:

       

名称 标签 说明
前置通知 用于配置前置通知。指定增强的方法在切入点方法之前执行。
后置通知 用于配置后置通知。指定增强的方法在切入点方法之后执行。
环绕通知 用于配置环绕通知。指定增强的方法在切入点方法之前和之后都执行。
异常抛出通知 用于配置异常抛出通知。指定增强的方法在出现异常时执行。
最终通知 用于配置最终通知。无论增强方式执行是否有异常都会执行。
package com.aop;

import org.aspectj.lang.ProceedingJoinPoint;

public class MyAspect {
    public void before() {
        System.out.println("前置增强...");
    }

    public void afterReturning() {
        System.out.println("后置增强...");
    }

    //ProceedingJoinPoint :正在执行的连接点, 即 切点
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        System.out.println("环绕前增强...");
        //切点方法
        Object proceed = pjp.proceed();
        System.out.println("环绕后增强...");
        return proceed;
    }

    public void afterThrowing() {
        System.out.println("异常抛出增强...");
    }

    private void after() {
        System.out.println("最终增强...");
    }
}

2、切点表达式的抽取

当多个增强的切点表达式相同时,可以将切点表达式进行抽取,在增强中使用pointcut-ref属性代替pointcut属性来引用抽取后的切点表达式。

applicationContext.xml:

    ...
    
    
        
        
            
            
            
        
    
    ...

10.9、基于注解的AOP开发

10.9.1、快速入门

  1. 导入坐标(pom.xml);
    pom.xml: 
        注意:spring-context坐标依赖spring-aop坐标
        ...
            
                org.springframework
                spring-context
                5.2.10.RELEASE
            
            
                org.aspectj
                aspectjweaver
                1.9.4
            
        ...
  2. 制作连接点方法(原始操作,Dao接口与实现类);
    @Repository("bookDaoImpl")
    public class BookDaoImpl implements BookDao {
        @Override
        public void update() {
            System.out.println("book dao update ...");
        }
    }
  3. 制作切面类;
  4. 定义切入点:切入点定义依托一个不具有实际意义的方法进行,即无参数、无返回值,方法无实际逻辑;
  5. 绑定切入点与通知关系(切面),并指定通知添加到原始连接点的具体执行位置(添加@Before注解为连接点方法执行之前执行该通知);
    @Configuration
    @ComponentScan("com.clp")
    @EnableAspectJAutoProxy // 告诉Spring,容器中有注解开发的AOP
    public class SpringConfig {
    }
    
    /**
     * 切面类
     */
    @Component // 该类受Spring管理
    @Aspect // 说明该类作为AOP处理
    public class MyAdvice {
        /**
         * 切入点:注解@Pointcut说明该方法是一个切入点方法
         */
        @Pointcut("execution(void com.clp.dao.BookDao.update())")
        private void pt() {}
    
        /**
         * 切面:绑定切点和通知(作为切面),方法为通知,加上@Before绑定切入点方法pt()
         */
        @Before("pt()")
        public void method1() {
            System.out.println(System.currentTimeMillis());
        }
    
        public void method2() {
    
        }
    }

在applicationContext.xml中配置的方式代码案例:

applicationContext.xml:

    ...
    
    
    
    
    ...
package com.anno;

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

@Component("myAspect")
@Aspect //标注当前MyAspect是一个切面类
public class MyAspect {
    //配置前置增强
    @Before("execution(* com.anno.*.*(..))")
    public void before() {
        System.out.println("前置增强...");
    }

    public void afterReturning() {
        System.out.println("后置增强...");
    }

    //ProceedingJoinPoint :正在执行的连接点, 即 切点
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        System.out.println("环绕前增强...");
        //切点方法
        Object proceed = pjp.proceed();
        System.out.println("环绕后增强...");
        return proceed;
    }

    public void afterThrowing() {
        System.out.println("异常抛出增强...");
    }

    private void after() {
        System.out.println("最终增强...");
    }
}

10.9.2、注解配置AOP详解

1、切点表达式的抽取

同xml配置aop一样,我们可以将切点表达式抽取。抽取方式是在切面内定义方法,在该方法上使用@Pointcut注解定义切点表达式,然后再在增强注解中进行引用。

    @Component("myAspect")
    @Aspect
    public class MyAspect {
        @Before("MyAspect.myPoint()")
        public void before() {
            System.out.println("前置代码增强..");
        }
        
        @Pointcut("execution(* com.anno.*.*(..))")
        public void myPoint() {}
    }

2、AOP注解通知的类型

通知的配置语法:@通知注解("切点表达式")

名称 类型 位置 作用 相关属性
@Before 方法注解 通知方法定义的上方 设置当前通知方法与切入点之间的绑定关系,当前通知方法在原始切入点方法前运行 value(默认):切入点方法名,格式为类名.方法名()
@After 方法注解 通知方法定义的上方 设置当前通知方法与切入点之间的绑定关系,当前通知方法在原始切入点方法后运行 value(默认):切入点方法名,格式为类名.方法名()
@Around 方法注解 通知方法定义的上方 设置当前通知方法与切入点之间的绑定关系,当前通知方法在原始切入点方法前后运行
@AfterReturning 方法注解 通知方法定义的上方 设置当前通知方法与切入点之间的绑定关系,当前通知方法在原始切入点方法正常执行完毕后运行 value(默认):切入点方法名,格式为类名.方法名()
@AfterThrowing 方法注解 通知方法定义的上方 设置当前通知方法与切入点之间的绑定关系,当前通知方法在原始切入点方法运行抛出异常后执行

@Around注解使用注意事项:

  • 环绕通知必须依赖形参ProceedingJoinPoint才能实现对原始方法的调用,进而实现原始方法调用前后同时添加通知;
  • 通知中如果未使用ProceedingJoinPoint对原始方法进行调用将跳过原始方法的执行;
  • 对原始方法的调用可以不接收返回值,通知方法设置成void即可,如果接收返回值,必须设定为Object类型;
  • 原始方法的返回值如果是void类型,通知方法的返回值类型可以设置成void,也可以设置成Object;
  • 由于无法预知原始方法运行后是否会抛出异常,因此环绕通知方法必须抛出Throwable对象。

代码演示:

@Configuration
@ComponentScan("com.clp")
@EnableAspectJAutoProxy // 告诉Spring,容器中有注解开发的AOP
public class SpringConfig {
}

@Repository("bookDaoImpl")
public class BookDaoImpl implements BookDao {
    @Override
    public void update() {
        System.out.println("book dao update is running ...");
    }

    @Override
    public int select() {
        System.out.println("book dao select is running ...");
        return 100;
    }
}

/**
 * 切面类
 */
@Component // 该类受Spring管理
@Aspect // 说明该类作为AOP处理
public class MyAdvice {
    /**
     * 切点
     */
    @Pointcut("execution(void com.clp.dao.BookDao.update())")
    private void pt() {
    }

    @Pointcut("execution(int com.clp.dao.BookDao.select())")
    private void pt2() {
    }

    /**
     * 切点 + 通知 = 切面
     */
    @Before("pt()")
    public void before() {
        System.out.println("before advice ...");
    }

    @After("pt()")
    public void after() {
        System.out.println("after advice ...");
    }

    @After("pt2()")
    public void afterSelect() {
        System.out.println("after advice ...");
    }

    @Around("pt()")
    public void around(ProceedingJoinPoint pjp) throws Throwable {
        System.out.println("around before advice ...");
        pjp.proceed(); // 表示对原始操作的调用
        System.out.println("around after advice ...");
    }

    @Around("pt2()")
    public Object aroundSelect(ProceedingJoinPoint pjp) throws Throwable {
        System.out.println("around before advice ...");
        int res = (int) pjp.proceed();// 表示对原始操作的调用
        System.out.println("around after advice ...");
        return res + 111;
    }

    /**
     * 切点方法正常执行完才会执行该通知
     */
    @AfterReturning("pt2()")
    public void afterReturning() {
        System.out.println("after returning advice ...");
    }

    /**
     * 切点方法抛出异常才会执行该通知
     */
    @AfterThrowing("pt2()")
    public void afterThrowing() {
        System.out.println("afterThrowing advice ...");
    }
}

10.9.3、AOP通知获取数据

获取切入点方法的参数:

  • JoinPoint:适用于前置、后置、返回后、抛出异常后通知;
  • ProceedingJoinPoint:适用于环绕通知。

获取切入点方法返回值:

  • 返回后通知;
  • 环绕通知。

获取切入点方法运行异常信息:

  • 抛出异常后通知;
  • 环绕通知。
@Repository("bookDaoImpl")
public class BookDaoImpl implements BookDao {
    @Override
    public String findName(int id, String password) {
        System.out.println("id: " + id);
        return "itcast";
    }
}

/******************************************************************************/
/**
 * 切面类
 */
@Component // 该类受Spring管理
@Aspect // 说明该类作为AOP处理
public class MyAdvice {
    /**
     * 切点
     */
    @Pointcut("execution(* com.clp.dao.impl.BookDaoImpl.findName(..))")
    private void pt() {
    }

    /**
     * 切点 + 通知 = 切面
     *
     * @param jp :JoinPoint对象描述了连接点方法的运行状态,可以获取到原始方法的调用参数
     */
    @Before("pt()")
    public void before(JoinPoint jp) {
        Object[] args = jp.getArgs();
        System.out.println("before advice ..." + Arrays.toString(args));
    }

    @After("pt()")
    public void after(JoinPoint jp) {
        Object[] args = jp.getArgs();
        System.out.println("after advice ..." + Arrays.toString(args));
    }

    /**
     * ProceedingJoinPoint是JoinPoint的子类
     * 环绕通知中可以手工获取切入点方法中出现的异常信息,得到的结果即为原始方法的返回值
     *
     * @param pjp
     * @return
     * @throws Throwable
     */
    @Around("pt()")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        Object[] args = pjp.getArgs();
        System.out.println("around before advice ..." + Arrays.toString(args));
        args[0] = 666; // 修改切点方法的第1个参数
        Object ret = pjp.proceed(args);// 表示对原始操作的调用
        System.out.println("around after advice ..." + Arrays.toString(args));
        return ret;
    }

    /**
     * 如果原始方法有返回值,那么就将返回值 “装到” 变量ret中
     *
     * @param joinPoint:必须为方法中的第1个参数(可省略)
     * @param ret:原始方法的返回值
     */
    @AfterReturning(value = "pt()", returning = "ret")
    public void afterReturning(JoinPoint joinPoint, Object ret) {
        System.out.println("afterReturning advice ..." + ret); // ret 为返回结果(参数名必须与注解中returning=的值相同)
    }

    /**
     * 抛出异常后通知可以获取切入点方法中出现的异常信息,使用形参可以接收对应的异常对象
     *
     * @param t:方法抛出的异常
     */
    @AfterThrowing(value = "pt()", throwing = "t")
    public void afterThrowing(Throwable t) {
        System.out.println("afterThrowing advice ...");
    }
}

/******************************************************************************/
public class AppAnno {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
        BookDao bookDao = (BookDao) ctx.getBean("bookDaoImpl");
        bookDao.findName(100, "abc");
    }
}

结果:
around before advice ...[100, abc]
before advice ...[666, abc]
id: 666
afterReturning advice ...itcast
after advice ...[666, abc]
around after advice ...[666, abc]

10.10、AOP案例

10.10.1、案例1——测量业务层接口万次执行效率

需求:任意业务层接口执行均可显示其执行效率(执行时长)。

分析:

  • 业务功能:业务层接口执行前后分别记录时间,求差值得到执行效率;
  • 通知类型选择前后均可以增强的类型——环绕通知。

补充说明:当前测试的接口执行效率仅仅是一个理论值,并不是一次完整的执行过程。

代码演示:

public class JdbcConfig {
    /*
    * 简单类型的依赖注入使用@Value
    * */
    @Value("com.mysql.jdbc.Driver")
    private String driver;
    @Value("jdbc:mysql://localhost:3306/ssmdb")
    private String url;
    @Value("root")
    private String username;
    @Value("123456")
    private String password;

    // 1、定义一个方法获得要管理的对象
    // 2、添加@Bean表示当前方法的返回值是一个bean
    /*
     * 引用类型的依赖注入:主需要为bean定义方法设置形参即可,容器会根据 类型 自动装配对象。
     * */
    @Bean
    public DataSource dataSource() {
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setDriverClassName(driver);
        dataSource.setUrl(url);
        dataSource.setUsername(username);
        dataSource.setPassword(password);
        return dataSource;
    }
}

/*********************************************************************************/
public class MyBatisConfig {
    /**
     * SqlSessionFactoryBean专门产生SqlSessionFactory
     * @return
     */
    @Bean
    public SqlSessionFactoryBean sqlSessionFactoryBean(DataSource dataSource) {
        SqlSessionFactoryBean ssfb = new SqlSessionFactoryBean();
        ssfb.setTypeAliasesPackage("com.clp.domain"); // 设置实体类别名
        ssfb.setDataSource(dataSource); // 设置数据源
        return ssfb;
    }

    /**
     * 加载Dao层的映射信息
     * @return
     */
    @Bean
    public MapperScannerConfigurer mapperScannerConfigurer() {
        MapperScannerConfigurer msc = new MapperScannerConfigurer();
        msc.setBasePackage("com.clp.dao");
        return msc;
    }
}

/*********************************************************************************/
@Configuration
@Import({JdbcConfig.class, MyBatisConfig.class})
@ComponentScan("com.clp")
@PropertySource("classpath:jdbc.properties")
@EnableAspectJAutoProxy // 告诉Spring,容器中有注解开发的AOP
public class SpringConfig {
}

/*********************************************************************************/
@Component
@Aspect
public class ProjectAdvice {
    /**
     * 匹配业务层的所有方法
     */
    @Pointcut("execution(* com.clp.service.*Service.*(..))")
    private void servicePc() {}

    @Around("ProjectAdvice.servicePc()")
    public void runSpeed(ProceedingJoinPoint pjp) throws Throwable {
        // 获取执行的签名信息
        Signature signature = pjp.getSignature();
        // 通过签名获取执行类型(接口名)
        String className = signature.getDeclaringTypeName();
        // 通过签名获取执行操作名称(方法名)
        String name = signature.getName();

        // 记录时间
        long start = System.currentTimeMillis();
        for (int i = 0; i < 10000; i++) {
            pjp.proceed();
        }
        long end = System.currentTimeMillis();
        System.out.println("万次执行:"+ className + "." + name + " ----> " + (end - start) + "ms");
    }
}

/*********************************************************************************/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfig.class)
public class AccountServiceTest {
    @Autowired
    private AccountService accountService;

    @Test
    public void testFindById() {
        Account account = accountService.findById(3);
    }

    @Test
    public void testFindAll() {
        accountService.findAll();
    }
}

结果:
万次执行:com.clp.service.AccountService.findAll---->1651ms

10.10.2、案例2——百度网盘密码数据兼容处理

分析:

  1. 在业务方法执行之前对所有的输入参数进行格式处理——trim();
  2. 使用处理后的参数调用原始方法——环绕通知中存在对原始方法的调用。
@Repository
public class ResourcesDaoImpl implements ResourcesDao {
    @Override
    public boolean readResources(String url, String password) {
        System.out.println(password.length());
        // 模拟校验
        return password.equals("root");
    }
}

@Component
@Aspect
public class DataAdvice {
    @Pointcut("execution(boolean com.clp.service.*Service.*(*, *))")
    private void servicePc() {}

    @Around("servicePc()")
    public Object trimStr(ProceedingJoinPoint pjp) throws Throwable {
        Object[] args = pjp.getArgs();
        // 对原始参数的每一个参数进行操作
        for (int i = 0; i < args.length; i++) {
            // 判断参数是不是字符串,如果是,就处理空格
            if (args[i].getClass().equals(String.class)) {
                // 去除数据,trim()操作后,更新数据
                args[i] = args[i].toString().trim();
            }
        }

        Object ret = pjp.proceed(args);

        return ret;
    }
}

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfig.class)
public class ResourcesServiceTest {
    @Autowired
    private ResourcesService resourcesService;

    @Test
    public void test() {
        boolean flag = resourcesService.openUrl("http://pan.baidu.com/aaa", " root ");
        System.out.println(flag);
    }
}

结果:
4
true

11、Spring的事务控制

事务作用:在数据层保障一系列的数据库操作同时成功同时失败。

Spring事务作用:在数据层或业务层保障一系列的数据库操作同时成功同时失败。

11.1、事务相关定义

11.1.1、事务角色

  • 事务管理员:发起事务方,在Spring中通常指代业务层开启事务的方法;
  • 事务协调员:加入事务方,在Spring中通常指代数据层方法,也可以是业务层方法。

11.1.2、事务的隔离级别

设置隔离级别,可以解决事务并发产生的问题,如脏读、不可重复读和虚读。

  • ISOLATION_DEFAULT
  • ISOLATION_READ_UNCOMMITTED
  • ISOLATION_READ_COMMITTED
  • ISOLATION_REPEATABLE_READ
  • ISOLATION_SERIALIZABLE

11.1.3、事务的传播行为

事务传播行为:事务协调员对事务管理员所携带事务的处理态度。

  1. REQUIRED:如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。一般的选择(默认值)。
  2. SUPPORTS:支持当前事务,如果当前没有事务,就以非事务方式执行(没有事务)。
  3. MANDATORY:使用当前的事务,如果当前没有事务,就抛出异常。
  4. REQUERS_NEW:新建事务,如果当前在事务中,把当前事务挂起。
  5. NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
  6. NEVER:以非事务方式运行,如果当前存在事务,抛++出异常。
  7. NESTED:如果当前存在事务,则在嵌套事务内执行;如果当前没有事务,则执行REQUIRED类似的操作。
  8. 超时时间:默认值是-1,没有超时限制。如果有,以秒为单位进行设置。
  9. 是否只读:建议查询时设置为只读。
传播属性 事务管理员 事务协调员
REQUIRED(默认)

开启T

加入T
未开启T 新建T2
REQUIRES_NEW

开启T

新建T2

未开启T 新建T2
SUPPORTS 开启T 加入T
未开启T
NOT_SUPPORTED 开启T
未开启T
MANDATORY 开启T 加入T
未开启T ERROR
NEVER 开启T ERROR
未开启T
NESTED 设置savePoint,一旦事务回滚,事务将回滚到savePoint处,交由客户端响应提交/回滚

11.2、编程式事务控制相关对象

11.2.1、PlatformTransactionManager

PlatformTransactionManager接口是Spring的事务管理器,它里面提供了我们常用的操作事务的方法。

方法 说明
TransactionStatus getTransaction(TransactionDefination defination) 获取事务的状态信息
void commit(TransactionStatus status) 提交事务
void rollback(TransactionStatus) 回滚事务

注意:PlatformTransactionManager是接口类型,不同的Dao层技术则有不同的实现类,例如:Dao层技术是jdbc或mybatis时:org.springframework.jdbc.datasource.DataSourceTransactionManager;Dao层技术是hibernate时:org.springframework.orm.hibernate5.HibernateTransactionManager

11.2.2、TransactionDefinition

TransactionDefinition是事务的定义信息对象,里面有如下方法:

方法 说明
int getIsolationLevel() 获得事务的隔离级别
int getPropogationBehavior() 获得事务的传播行为
int getTimeout() 获得超时时间
boolean isReadOnly() 是否只读

11.2.3、TransactionStatus

TransactionStatus接口提供的是事务具体的运行状态,方法介绍如下:

方法 说明
boolean hasSavepoint() 是否存储回滚点
boolean isCompleted() 事务是否完成
boolean isNewTransaction() 是否是新事务
boolean isRollbackOnly() 事务是否回滚

11.3、基于XML的声明式事务控制

11.3.1、什么是声明式事务控制

Spring 的声明式事务控制顾名思义就是采用声明的方式来处理事务。这里所说的声明,就是指在配置文件中声明,用在Spring配置文件中声明式地处理事务来代替代码式的处理事务。

声明式事务处理的作用

  • 事务管理不侵入开发的组件。具体来说,业务逻辑对象不会意识到正在事务管理之中,事实上也应该如此,因为事务管理是属于系统层面的服务,而不是业务逻辑的一部分,如果要改变事务管理策划的话,也只需要在定义文件中重新配置即可。
  • 在不需要事务管理的时候,只要在设定文件上修改一下,即可移去事务管理服务,无需改变代码重新编译,这样维护起来极其方便。

注意:Spring声明式事务控制底层就是AOP。

11.3.2、声明式事务控制的实现

声明式事务控制明确事项:

  • 谁是切点?
  • 谁是通知?
  • 配置切面?
applicationContext.xml:

    ...
    
    
        
    
    
    
        
    
    
    
        
        
            
            
        
    
    
    
        
    
    ...

其中,代表切点方法的事务参数的配置。

  • name:切点方法的名称。
  • isolation:事务的隔离级别。
  • propagation:事务的传播行为。
  • timeout:超时时间。
  • read-only:是否只读。

11.4、基于注解的声明式事务控制

11.4.1、使用步骤

步骤:

  1. 在业务层接口上添加Spring事务管理。注意:Spring注解式事务通常添加在业务层接口中而不会添加到业务层实现类中,降低耦合。注解式事务可以添加到业务方法上表示当前方法开启事务,也可以添加到接口上表示当前接口所有方法开启事务。
    - 方式1:在实现类配置:
    @Service("accountService")
    @Transactional(isolation = Isolation.REPEATABLE_READ) //表示该对象下的所有方法都为这种配置,若方法有自己的配置,则方法用自己的配置
    public class AccountServiceImpl {
        @Autowired
        private AccountDaoImpl accountDao;
    
    //    
    //    
    //        
    //        
    //            
    //            
    //        
    //    
        @Transactional(isolation = Isolation.READ_COMMITTED,propagation = Propagation.REQUIRED)
        public void transfer(String outMan, String inMan, double money) {
            accountDao.out(outMan, money);
            accountDao.in(inMan,money);
        }
    
        @Transactional(isolation = Isolation.DEFAULT)
        public void xxx() {}
    }
    
    - 方式2:在接口配置
    @Transactional(isolation = Isolation.REPEATABLE_READ) //表示该接口下的所有方法都为这种配置,若方法有自己的配置,则方法用自己的配置
    public interface AccountService {
        /**
         * 转账操作
         * @param out:转出方
         * @param in:转入方
         * @param money:金额
         */
        @Transactional
        void transfer(String out, String in, Double money);
    }
  2. 配置事务管理器。
    - 配置平台事务管理器方式1:
    applicationContext.xml:
        ...
        
        
            
        
    
        
        
        
        
        
        ...
    
    - 配置平台事务管理器方式2:
    public class JdbcConfig {
        /*
        * 简单类型的依赖注入使用@Value
        * */
        @Value("com.mysql.jdbc.Driver")
        private String driver;
        @Value("jdbc:mysql://localhost:3306/ssmdb")
        private String url;
        @Value("root")
        private String username;
        @Value("123456")
        private String password;
    
        // 1、定义一个方法获得要管理的对象
        // 2、添加@Bean表示当前方法的返回值是一个bean
        /*
         * 引用类型的依赖注入:主需要为bean定义方法设置形参即可,容器会根据 类型 自动装配对象。
         * */
        @Bean
        public DataSource dataSource() {
            DruidDataSource dataSource = new DruidDataSource();
            dataSource.setDriverClassName(driver);
            dataSource.setUrl(url);
            dataSource.setUsername(username);
            dataSource.setPassword(password);
            return dataSource;
        }
    
        /**
         * 创建平台事务管理器
         * @param dataSource
         * @return
         */
        @Bean
        public PlatformTransactionManager transactionManager(DataSource dataSource) {
            DataSourceTransactionManager dtm = new DataSourceTransactionManager();
            dtm.setDataSource(dataSource);
    
            return dtm;
        }
    }
  3. 开启注解式事务驱动。
    @Configuration
    @Import({JdbcConfig.class, MyBatisConfig.class})
    @ComponentScan("com.clp")
    @PropertySource("classpath:jdbc.properties")
    @EnableTransactionManagement // 开启注解式事务驱动
    public class SpringConfig {
    }
    
    public class MyBatisConfig {
        /**
         * SqlSessionFactoryBean专门产生SqlSessionFactory
         * @return
         */
        @Bean
        public SqlSessionFactoryBean sqlSessionFactoryBean(DataSource dataSource) {
            SqlSessionFactoryBean ssfb = new SqlSessionFactoryBean();
            ssfb.setTypeAliasesPackage("com.clp.domain"); // 设置实体类别名
            ssfb.setDataSource(dataSource); // 设置数据源
            return ssfb;
        }
    
        /**
         * 加载Dao层的映射信息
         * @return
         */
        @Bean
        public MapperScannerConfigurer mapperScannerConfigurer() {
            MapperScannerConfigurer msc = new MapperScannerConfigurer();
            msc.setBasePackage("com.clp.dao");
            return msc;
        }
    }

11.4.2、注解配置声明式事务控制解析

  • 使用@Transactional在需要进行事务控制的类或者方法上修饰,注解可用的属性同xml配置方式,例如:隔离级别、传播行为等。
  • 注解使用在类上,那么该类下的所有方法都使用同一套注解参数配置。
  • 使用在方法上,不同的方法可以采用不同的事务参数配置。
  • XML配置文件(applicationContext.xml)中要开启事务的注解驱动

注解声明式事务控制的配置要点:

  • 平台事务管理器配置(xml方式/配置类方式);
  • 事务通知的配置(@Transactional注解配置);
  • 事务注解驱动的配置

11.5、Spring事务相关配置

属性 作用 示例
readOnly 设置是否为只读事务 readOnly=true(只读事务)
timeout 设置事务超时时间 timeout=-1(永不超时)
rollbackFor 设置事务回滚异常(class) rollbackFor=如NullPointException.class
rollbackForClassName 设置事务回滚异常(String) 同上,格式为字符串
noRollbackFor 设置事务不回滚异常(class) noRollbackFor=如NullPointException.class
noRollbackForClassName 设置事务不回滚异常(String) 同上,格式为字符串
propagation 设置事务传播行为 ...
public interface AccountService {
    @Transactional(readOnly = true, timeout = -1, rollbackFor = {IOException.class})
    void transfer(String out, String in, Double money);
}

11.5、Spring事务案例

11.5.1、案例1——银行账户转账

模拟银行账户间转账业务。

需求:实现任意两个账户间转账操作。

需求微缩:A账户减钱,B账户加钱。

分析:

  • 数据层提供基础操作,指定账户减钱(outMoney),指定账户加钱(inMoney);
  • 业务层提供转账操作(transfer),调用减钱与加钱的操作;
  • 提供2个账号和操作金额执行转账操作;
  • 基于Spring整合MyBatis环境搭建上述操作。
@Service
public class AccountServiceImpl implements AccountService {
    @Autowired
    private AccountDao accountDao;

    @Override
    public void transfer(String out, String in, Double money) {
        accountDao.outMoney(out, money);
        accountDao.inMoney(in, money);
    }
}

结果分析:

  • 程序正常执行时,账户金额A减B加,没有问题;
  • 程序出现异常后,转账失败,但是异常之前操作成功,异常之后操作失败,整体业务失败。

11.5.2、案例2——转账业务追加日志

需求:实现任意两个账户间转账操作,并对每次转账操作在数据库进行留痕。

需求微缩:A账户减钱,B账户加钱,数据库记录日志。

分析:

  • 基于转账操作案例添加日志模块,实现数据库中记录日志;
  • 业务层转账操作(transfer),调用减钱、加钱与记录日志功能。

实现效果预期:无论转账是否成功,均进行转账操作的日志留痕。

步骤:

  1. 在业务层接口上添加Spring事务,设置事务传播行为为REQUIRES_NEW(即需要新事务)。
    @Service
    public class LogServiceImpl implements LogService {
        @Autowired
        private LogDao logDao;
    
        @Override
        @Transactional(propagation = Propagation.REQUIRES_NEW)
        public void log(String out, String in, Double money) {
            logDao.log("转账操作由" + out + "到" + in + ", 金额:" + money);
        }
    }

你可能感兴趣的:(SSM,java,spring,学习)