【SSM框架(上)-Spring】

SSM框架【上】

  • 导论
  • Spring
    • 初识Spring
    • Spring系统框架
    • Spring学习路线
    • Spring核心概念
      • 目前项目中的问题
      • IOC、IOC容器、Bean、DI
      • IoC入门案例
      • DI入门案例
    • Bean
      • Bean的配置
      • Bean的实例化
      • Bean的生命周期
    • DI
      • 注入方式
      • 自动装配
      • 集合注入(array、list、set、map、properties)
    • IoC/DI配置管理第三方库
    • 加载properties文件
    • 容器
    • 大总结
    • IOC/DI注解开发
      • 纯注解开发模式
      • 注解开发bean作用范围与生命周期管理
      • 注解开发依赖注入(自动装配)、加载properties文件
      • 注解开发管理第三方bean
      • 注解开发实现为第三方bean注入资源
      • 基本注解用法小结
    • AOP
      • 简介
      • AOP入门案例
      • AOP工作流程(代理对象的创建)
      • AOP切入点表达式
      • AOP通知类型
      • AOP案例之业务层接口万次执行效率
      • AOP通知获取数据
    • 事务
      • 事物简介
      • 事物角色
      • 事物属性
  • SpringMVC、Maven
  • SpringBoot、 MyBatis、 Spring整合

导论

【为什么要学?】

  • Spring技术是JavaEE开发必备技能,企业开发技术选型命中率 >90%
  • Spring的作用
    1)Spring可以简化开发,降低企业级开发的复杂性,使开发变得更简单快捷
    【SSM框架(上)-Spring】_第1张图片

2)Spring可以框架整合,高效整合其他技术,提高企业级应用开发与运行效率

【学什么?】

  • 简化开发: Spring框架中提供了两个大的核心技术,分别是:
    1)、 IoC
    2)、AOP
    ————事务处理
  • 框架整合: Spring在框架整合这块已经做到了极致,它可以整合市面上几乎所有主流框架,比如:
    1)、MyBatis
    2)、 MyBatis-plus
    3)、 Struts
    4)、 Struts2
    5)、 Hibernate
    6)、 ……

【怎么学?】

  • 学习Spring框架设计思想: 对于Spring来说,它能迅速占领全球市场,不只是说它的某个功能比较强大,更重要是在它的思想上。
  • 学习基础操作,思考操作与思想间的联系: 掌握了Spring的设计思想,然后就需要通过一些基础操作来思考操作与思想之间的关联关系。
  • 学习案例,熟练应用操作的同时,体会思想: 会了基础操作后,就需要通过大量案例来熟练掌握框架的具体应用,加深对设计思想的理解。

Spring

初识Spring

Spring发展到今天已经形成了一种开发的生态圈,Spring提供了若干个项目,每个项目用于完成特定的功能。

  • Spring已形成了完整的生态圈,也就是说我们可以完全使用Spring技术完成整个项目的构建、设计与开发。
  • Spring有若干个项目,可以根据需要自行选择,把这些个项目组合起来,起了一个名称叫全家桶,如下图所示
    【SSM框架(上)-Spring】_第2张图片
    这些技术并不是所有的都需要学习,额外需要重点关注Spring Framework、SpringBoot和SpringCloud :
    【SSM框架(上)-Spring】_第3张图片
    1)、Spring Framework:Spring框架,是Spring中最早最核心的技术,也是所有其他技术的基础。
    2)、SpringBoot:Spring是来简化开发,而SpringBoot是来帮助Spring在简化的基础上能更快速进行开发。
    3)、SpringCloud:这个是用来做分布式之微服务架构的相关开发。

Spring系统框架

  • Spring Framework是Spring生态圈中最基础的项目,是其他项目的根基。
  • Spring Framework的发展也经历了很多版本的变更,每个版本都有相应的调整
    【SSM框架(上)-Spring】_第4张图片
  • Spring Framework的5版本目前没有最新的架构图,而最新的是4版本,所以接下来主要研究的是4的架构图
    【SSM框架(上)-Spring】_第5张图片
    1)、核心层:
    Core Container: 核心容器,这个模块是Spring最核心的模块,其他的都需要依赖该模块。
    2)、AOP层
    - AOP:面向切面编程,它依赖核心层容器,目的是在不改变原有代码的前提下对其进行功能增强
    - Aspects:AOP是思想,Aspects是对AOP思想的具体实现
    3)、数据层
    - Data Access:数据访问,Spring全家桶中有对数据访问的具体实现技术
    - Data Integration:数据集成,Spring支持整合其他的数据层解决方案,比如Mybatis
    - Transactions:事务,Spring中事务管理是Spring AOP的一个具体实现,也是后期学习的重点内容
    4)、Web层:这一层的内容将在SpringMVC框架具体学习
    5)、Test层 :Spring主要整合了Junit来完成单元测试和集成测试

Spring学习路线

【SSM框架(上)-Spring】_第6张图片

Spring核心概念

目前项目中的问题

【问题】:
【SSM框架(上)-Spring】_第7张图片

  • 业务层需要调用数据层的方法,就需要在业务层new数据层的对象
  • 如果数据层的实现类发生变化,那么业务层的代码也需要跟着改变,发生变更后,都需要进行编译打包和重部署
  • 所以,现在代码在编写的过程中存在的问题是:耦合度偏高

【解决办法】:
【SSM框架(上)-Spring】_第8张图片
我们就想,如果能把框中的内容给去掉,不就可以降低依赖了么,但是又会引入新的问题,去掉以后程序能运行么?
答案肯定是不行,因为bookDao没有赋值为Null,强行运行就会出空指针异常。
所以现在的问题就是,业务层不想new对象,运行的时候又需要这个对象,该咋办呢?

针对这个问题,Spring就提出了一个解决方案:
使用对象时,在程序中不要主动使用new产生对象,转换为由外部提供对象。而这种对象的控制权由程序转移到外部,这种思想就叫做控制反转IOC(Inversion of Control)

IOC、IOC容器、Bean、DI

【IOC(Inversion of Control)控制反转】:使用对象时,由主动new产生对象转换为由外部提供对象,此过程中对象创建控制权由程序转移到外部,此思想称为控制反转。
Spring技术对IOC思想进行了实现:

  • Spring提供了一个容器,称为IOC容器【即是上面系统框架中提到的Core Container,在容器里创建并管理对象,相当于MVC里的Mode层,用来充当IOC思想中的 “外部”
  • IOC容器负责对象的创建、初始化等一系列工作,其中包含了数据层(Dao)和业务层(Service)的类对象,被创建或被管理的对象在IOC容器中统称为Bean
  • 当IOC容器中创建好service和dao对象后,程序能并不能正确执行因为IOC容器中虽然有service和dao对象但是service对象和dao对象没有任何关系(业务层依赖与数据层的对象和方法),需要把dao对象交给service,也就是说要绑定service和dao对象之间的关系,需要交给DI

【DI(Dependency Injection)依赖注入】:在容器中建立bean与bean之间的依赖关系的整个过程,称为依赖注入
【SSM框架(上)-Spring】_第9张图片

  • IOC容器中哪些bean之间要建立依赖关系呢?
    这个需要程序员根据业务需求提前建立好关系,如业务层需要依赖数据层,service就要和dao建 立依赖关系

【总结】:IoC和DI两个概念的最终目标就是:充分解耦

  • 使用IOC容器管理bean(IOC)
  • 在IOC容器内将有依赖关系的bean进行关系绑定(DI)
  • 最终结果为:使用对象时不仅可以直接从IOC容器中获取,并且获取到的bean已经绑定了所有的依赖关系
  • IOC:控制反转, 控制反转的是对象的创建权;DI:依赖注入, 绑定对象与对象之间的依赖关系
  • Spring创建了一个容器用来存放所创建的对象,这个容器就叫IOC容器
  • 容器中所存放的一个个对象就叫Bean或Bean对象

IoC入门案例

【思路分析:以下说明与实现步骤顺序同步】:

  • 使用Spring导入哪些坐标?
    答:用别人的东西(使用spring框架中的东西),就需要在 pom.xml添加对应的依赖
    【创建Maven项目】:
    【SSM框架(上)-Spring】_第10张图片
    【添加Spring的依赖jar包】:
    pom.xml
<dependencies>
    <dependency>
        <groupId>org.springframeworkgroupId>
        <artifactId>spring-contextartifactId>
        <version>5.2.10.RELEASEversion>
    dependency>
    <dependency>
        <groupId>junitgroupId>
        <artifactId>junitartifactId>
        <version>4.12version>
        <scope>testscope>
    dependency>
dependencies>
  • Spring是使用容器来管理bean对象的,那么管什么?
    答:主要管理 项目中所使用到的类对象,比如(Service和Dao)
    【添加案例中需要的类】:
    创建BookService,BookServiceImpl,BookDao和BookDaoImpl四个类
    【SSM框架(上)-Spring】_第11张图片
//放在Dao目录下
public interface BookDao {
    public void save();
}
public class BookDaoImpl implements BookDao {
    public void save() {
        System.out.println("book dao save ...");
   }
}
//放在Service目录下
public interface BookService {
    public void save();
}
public class BookServiceImpl implements BookService {
    private BookDao bookDao = new BookDaoImpl();
    public void save() {
        System.out.println("book service save ...");
        bookDao.save();
   }
}
  • 如何将被管理的对象bean告知IOC容器?
    答:使用配置文件
    【添加spring配置文件】:
    resources下添加spring配置文件applicationContext.xml,并完成bean的配置
    【SSM框架(上)-Spring】_第12张图片
    【在配置文件中完成bean的配置】:

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	   xsi:schemaLocation="http://www.springframework.org/schema/beans 
http://www.springframework.org/schema/beans/spring-beans.xsd">

 
  
 <bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl"/>
 <bean id="bookService" class="com.itheima.service.impl.BookServiceImpl"/>
beans>
  • 被管理的对象交给IOC容器,要想从容器中获取对象,就先得思考如何获取到IOC容器?
    答:Spring框架提供相应的 接口
    【获取IOC容器】:
public class App {
    public static void main(String[] args) {
        //获取IOC容器
 ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml"); 
   }
}

【从容器中获取对象进行方法调用】:

public class App {
    public static void main(String[] args) {
        //获取IOC容器
 	ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml"); 
       BookDao bookDao = (BookDao) ctx.getBean("bookDao");
       bookDao.save();
       BookService bookService = (BookService) ctx.getBean("bookService");
       bookService.save();
   }
}
  • IOC容器得到后,如何从容器中获取bean?
    答:调用Spring框架提供对应 接口中的方法
    【从容器中获取对象进行方法调用】:
public class App {
    public static void main(String[] args) {
        //获取IOC容器
 	ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml"); 
       BookDao bookDao = (BookDao) ctx.getBean("bookDao");
       bookDao.save();
       BookService bookService = (BookService) ctx.getBean("bookService");
       bookService.save();
   }
}

【小结】:
Spring的IOC入门案例已经完成,但是在BookServiceImpl的类中依然存在BookDaoImpl对象的new操作,它们之间的耦合度还是比较高,这块该如何解决,就需要用到下面的DI:依赖注入

DI入门案例

  • 要想实现依赖注入,必须要基于IOC管理Bean
    答:DI的入门案例要 依赖于 前面IOC的入门案例
  • Service中使用new形式创建的Dao对象是否保留?
    答:需要删除掉,最终要使用IOC容器中的bean对象(用下面的set方法获取bean对象)
    【去除代码中的new】:
    在BookServiceImpl类中,删除业务层中使用new的方式创建的dao对象
public class BookServiceImpl implements BookService {
    //删除业务层中使用new的方式创建的dao对象
    private BookDao bookDao;//删除 = new BookDaoImpl();

    public void save() {
        System.out.println("book service save ...");
        bookDao.save();
   }
}
  • Service中需要的Dao对象如何进入到Service中?
    答:Service中提供方法(set方法),让Spring的IOC容器可以通过该方法传入bean对象
    【为属性提供setter方法】:
    在BookServiceImpl类中,为BookDao提供setter方法
public class BookServiceImpl implements BookService {
    //删除业务层中使用new的方式创建的dao对象
    private BookDao bookDao;
 
    public void save() {
        System.out.println("book service save ...");
        bookDao.save();
   }
    //提供对应的set方法
    public void setBookDao(BookDao bookDao) {
        this.bookDao = bookDao;
   }
}
  • Service与Dao间的关系如何描述?
    答:使用配置文件
    【修改配置完成注入】:
    在配置文件中添加依赖注入的配置

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans 
http://www.springframework.org/schema/beans/spring-beans.xsd">

    

    <bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl"/>
    <bean id="bookService" class="com.itheima.service.impl.BookServiceImpl">
        
        
        <property name="bookDao" ref="bookDao"/>
    bean>
beans>

【注意】:

  • name="bookDao"中bookDao的作用是让Spring的IOC容器在获取到名称后,将首字母大写,前面加set找对应的setBookDao()方法进行对象注入
  • ref="bookDao"中bookDao的作用是让Spring能在IOC容器中找到id为bookDao的Bean对象给bookService进行注入

【SSM框架(上)-Spring】_第13张图片

Bean

Bean的配置

【bean基础配置】:
【SSM框架(上)-Spring】_第14张图片
【bean别名的配置】:bean的别名(name)相当于id的可选项
【SSM框架(上)-Spring】_第15张图片
【bean的作用范围scope配置】:用来指定该bean是否为单例对象
【SSM框架(上)-Spring】_第16张图片
说明:

public class AppForScope {
    public static void main(String[] args) {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
        BookDao bookDao1 = (BookDao) ctx.getBean("bookDao");
        BookDao bookDao2 = (BookDao) ctx.getBean("bookDao");
        System.out.println(bookDao1);
        System.out.println(bookDao2);
   }
}
  • 将scope设置为singleton:bookDao1与bookDao2是同一个对象

  • 将scope设置为prototype:bookDao1与bookDao2不是同一个对象

  • 为什么bean默认为单例?—>bean对象只有一个就避免了对象的频繁创建与销毁,达到了bean对象的复用,性能高

  • 哪些bean对象适合交给容器进行管理?—>表现层对象、业务层对象、数据层对象、工具对象

  • 哪些bean对象不适合交给容器进行管理?—>封装实例的域对象,因为会引发线程安全问题,所以不适合。

Bean的实例化

  • 【实例化bean的方式一——构造方法(常用)】
    不带参数的构造函数
public class BookDaoImpl implements BookDao {
    public BookDaoImpl() {//修饰词改成private,我们也能从容器中获得对象,说明用了反射构造对象
        System.out.println("book dao constructor is running ....");
   }
    public void save() {
        System.out.println("book dao save ...");
   }
}

运行程序,如果控制台有打印构造函数中的输出,说明Spring容器在创建对象的时候也走的是构造函数
在这里插入图片描述
带参数的构造函数

public class BookDaoImpl implements BookDao {
    public BookDaoImpl(int i) {//修饰词改成private,我们也能从容器中获得对象,说明用了反射构造对象
        System.out.println("book dao constructor is running ....");
   }
    public void save() {
        System.out.println("book dao save ...");
   }
}

结论:运行程序,程序会报错,说明Spring底层使用的是类的无参构造方法

  • 【实例化bean的方式二——静态工厂】
    整体的目录结构:
    【SSM框架(上)-Spring】_第17张图片
    准备一个OrderDao和OrderDaoImpl类:
//在OrderDao接口目录下写
public interface OrderDao {
    public void save();
}
//在OrderDaolmpl下写
public class OrderDaoImpl implements OrderDao {
    public void save() {
        System.out.println("order dao save ...");
   }
}

创建一个工厂类OrderDaoFactory并提供一个静态方法

//静态工厂创建对象
public class OrderDaoFactory {
    public static OrderDao getOrderDao(){
        return new OrderDaoImpl();
   }
}

在spring的配置文件application.properties中添加以下内容:

<bean id="orderDao" class="com.itheima.factory.OrderDaoFactory" factorymethod="getOrderDao"/>

该标签与上面的对应关系如下:
【SSM框架(上)-Spring】_第18张图片

编写AppForInstanceOrder运行类,在类中通过工厂获取对象

public class AppForInstanceOrder {
    public static void main(String[] args) {
        //通过静态工厂创建对象
        //OrderDao orderDao = OrderDaoFactory.getOrderDao();
        //orderDao.save();
        //从容器中获得对象
        ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
        OrderDao orderDao = (OrderDao) ctx.getBean("orderDao");
        orderDao.save();
   }
}码片
  • 【实例化bean的方式三——实例工厂】
    整体的目录结构:
    【SSM框架(上)-Spring】_第19张图片
    准备一个UserDao和UserDaoImpl类
public interface UserDao {
    public void save();
}
 
public class UserDaoImpl implements UserDao {
    public void save() {
        System.out.println("user dao save ...");
   }
}

创建一个工厂类OrderDaoFactory并提供一个普通方法,注意此处和静态工厂的工厂类不一样的地方是方法不是静态方法

public class UserDaoFactory {
    public UserDao getUserDao(){
        return new UserDaoImpl();
   }
}

在spring的配置文件中添加以下内容

<bean id="userFactory" class="com.itheima.factory.UserDaoFactory"/>
<bean id="userDao" factory-method="getUserDao" factory-bean="userFactory"/>

该标签与上面的对应关系如下:
【SSM框架(上)-Spring】_第20张图片

编写AppForInstanceUser运行类,在类中通过工厂获取对象

public class AppForInstanceUser {
    public static void main(String[] args) {
        //创建实例工厂对象
        //UserDaoFactory userDaoFactory = new UserDaoFactory();
        //通过实例工厂对象创建对象
        //UserDao userDao = userDaoFactory.getUserDao();
        //userDao.save();
        ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
        UserDao userDao = (UserDao) ctx.getBean("userDao");
        userDao.save();
}

小结:配置方面较为复杂,故使用FactoryBean作为升级版

  • 实例化bean的方式四——方式三的进阶(FactoryBean)
    整体的目录结构:
    【SSM框架(上)-Spring】_第21张图片
    创建一个UserDaoFactoryBean的类用来代替OrderDaoFactory类,实现FactoryBean接口,重写接口的方法
public class UserDaoFactoryBean implements FactoryBean<UserDao> {//该泛型指返回的对象
    //代替原始实例工厂中创建对象的方法
    public UserDao getObject() throws Exception {
        return new UserDaoImpl();
   }
    //返回所创建类的Class对象
    public Class<?> getObjectType() {
        return UserDao.class;
   }
   default boolean isSingleton() {//true是单例模式,false是多例模式
 		return true;
}
}

在Spring的配置文件中进行配置

<bean id="userDao" class="com.itheima.factory.UserDaoFactoryBean"/>

Bean的生命周期

【概述】:
bean生命周期是bean对象从创建到销毁的整体过程bean生命周期控制是在bean创建后到销毁前做一些事情

【生命周期控制的两种方式】:
具体的bean对象:

//BookDaoImpl类BookDao接口
public interface BookDao {
    public void save();
}
public class BookDaoImpl implements BookDao {
    public void save() {
        System.out.println("book dao save ...");
   }
}
//BookServiceImpl类BookeService接口
public interface BookService {
    public void save();
}
public class BookServiceImpl implements BookService{
    private BookDao bookDao;
    public void setBookDao(BookDao bookDao) {
        this.bookDao = bookDao;
   }
    public void save() {
        System.out.println("book service save ...");
        bookDao.save();
  • 在bean对象中添加方法(init和destroy)并且配置好方法,下面以BookDao bean对象为例
    在BookDaoImpl类BookDao接口添加方法(init和destroy)
public class BookDaoImpl implements BookDao {
    public void save() {
        System.out.println("book dao save ...");
   }
    //表示bean初始化对应的操作
    public void init(){
        System.out.println("init...");
   }
    //表示bean销毁前对应的操作
    public void destory(){
        System.out.println("destory...");
   }
}

添加配置,添加的方法成为初始化方法和销毁方法

<bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl" init-method="init" destroy-method="destory"/>

主方法,获取bean对象

public class AppForLifeCycle {
    public static void main( String[] args ) {
        ApplicationContext ctx = new 
        ClassPathXmlApplicationContext("applicationContext.xml");
        BookDao bookDao = (BookDao) ctx.getBean("bookDao");
        bookDao.save();
   }
}

结果:bean对象获取创建后,初始化init方法能实现,但destroy方法不能实现,是因为容器没有关闭,因此我们要使用ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
ctx.close();关闭destroy方法才能实现(或者使用ctx.registerShutdownHook();注册钩子关闭容器)

  • 实现接口关闭,这里以BookServiceImpl类BookeService、InitializingBean、 DisposableBean接口并实现afterPropertiesSet和destroy方法为例
public class BookServiceImpl implements BookService, InitializingBean, DisposableBean {
    private BookDao bookDao;
    public void setBookDao(BookDao bookDao) {
        this.bookDao = bookDao;
   }
     public void save() {
        System.out.println("book service save ...");
        bookDao.save(); 
   }
    public void destroy() throws Exception {
        System.out.println("service destroy");
   }
    public void afterPropertiesSet() throws Exception {
        System.out.println("service init");
   }
}

DI

注入方式

  • Setter注入
    【注入引用数据类型】:上面DI入门案例已经说过了
    【注入简单数据类型】:
    声明属性并提供setter方法:在BookDaoImpl类中声明对应的简单数据类型的属性,并提供对应的setter方法
public class BookDaoImpl implements BookDao {
    private String databaseName;
    private int connectionNum;
 
    public void setConnectionNum(int connectionNum) {
        this.connectionNum = connectionNum;
   }
    public void setDatabaseName(String databaseName) {
        this.databaseName = databaseName;
   }
 
    public void save() {
        System.out.println("book dao save ..."+databaseName+","+connectionNum);
   }
}

配置文件中进行注入配置:在applicationContext.xml配置文件中使用property标签注入


<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	   xsi:schemaLocation="http://www.springframework.org/schema/beans 
http://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl">
        <property name="databaseName" value="mysql"/>
     	<property name="connectionNum" value="10"/>
    bean>
bean>
  • 构造器注入:与setter注入大同小异
    方法:
    1)、将setter注入的set方法删除,改成构造器即可比如: public void setBookDao(BookDao bookDao) {this.bookDao = bookDao;改为public BookServiceImpl(BookDao bookDao) { this.bookDao = bookDao;(它是BookServiceImpl类的构造器)}

    2)、配置方面:将改为【其中name指向的是构造器中的形参bookDao】
  • 两种注入方式的选择:
    1)、强制依赖使用构造器进行【必须要使用其它的类创造对象】,使用setter注入有概率不进行注入导致null对象出现强制依赖指对象在创建的过程中必须要注入指定的参数
    2)、可选依赖使用setter注入进行,灵活性强可选依赖指对象在创建过程中注入的参数可有可无
    3)、Spring框架倡导使用构造器,第三方框架内部大多数采用构造器注入的形式进行数据初始化,相对严谨
    4)、 如果有必要可以两者同时使用,使用构造器注入完成强制依赖的注入,使用setter注入完成可选依赖的注入
    5)、 实际开发过程中还要根据实际情况分析,如果受控对象没有提供setter方法就必须使用构造器注入
    6)、自己开发的模块推荐使用setter注入

自动装配

【简介】:IoC容器根据bean所依赖的资源在容器中自动查找并注入到bean中的过程称为自动装配,即删除了麻烦的配置依赖的操作(将标签删除),并标签中添加autowire属性(让它自动地在容器根据 依赖的对象类型-bean中的class属性(autowire=byType)或者依赖的对象名字-bean标签的id (autowire=byName)里找依赖的对象)
【示例】:


<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	   xsi:schemaLocation="http://www.springframework.org/schema/beans 
http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl"/>
    <bean id="bookService" class="com.itheima.service.impl.BookServiceImpl">
        <property name="bookDao" ref="bookDao"/>
    bean>
beans>

改为:


<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	   xsi:schemaLocation="http://www.springframework.org/schema/beans 
http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean class="com.itheima.dao.impl.BookDaoImpl"/>
    
    <bean id="bookService" class="com.itheima.service.impl.BookServiceImpl" autowire="byType"/>
beans>

【注意】:

  • 需要注入属性的类中对应属性的 setter方法不能省略
  • 被注入的对象(依赖的对象)必须要被Spring的IOC容器管理(bean标签必须要配置好,不然spring无法根据标签自动装配
  • 自动装配用于引用类型依赖注入,不能对简单类型进行操作
  • 使用按类型装配时(byType)必须保障容器中相同类型的bean(bean中的class属性唯一)唯一,推荐使用
  • 使用按名称装配时(byName)必须保障容器中具有指定名称的bean, 因变量名与配置耦合,不推
    荐使用
  • 自动装配优先级低于setter注入与构造器注入,同时出现时自动装配配置失效

集合注入(array、list、set、map、properties)

【举例一个array】

public class BookDaoImpl implements BookDao {
    private int[] array;
    public void setArray(int [ ] array){this.array = array}
    public void save(){}

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	   xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

	<bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl">
      	<property name="array">
    		<array>
				<value>100value>
				<value>200value>
        		<value>300value>
			 array>
		property>
    bean>
beans>

【其他】:

  • 注入List:
<property name="list">
    <list>
        <value>itcastvalue>
        <value>itheimavalue>
        <value>boxueguvalue>
        <value>chuanzhihuivalue>
    list>
property>
  • 注入Set:
<property name="set">
    <set>
        <value>itcastvalue>
        <value>itheimavalue>
        <value>boxueguvalue>
        <value>boxueguvalue>
    set>
property>
  • 注入Map:
<property name="map">
    <map>
        <entry key="country" value="china"/>
        <entry key="province" value="henan"/>
        <entry key="city" value="kaifeng"/>
    map>
property>
  • 注入Properties:
<property name="properties">
    <props>
        <prop key="country">chinaprop>
        <prop key="province">henanprop>
        <prop key="city">kaifengprop>
    props>
property>

IoC/DI配置管理第三方库

【场景】: 前面所讲的知识点都是基于我们自己写的类,现在如果有需求让我们去管理第三方jar包中的类,该如何管理?
我们将通过一个案例来学习下对于第三方bean该如何进行配置管理。以后我们会用到很多第三方的bean,本次案例将使用咱们前面提到过的Alibaba的数据源Druid(德鲁伊)和C3P0来配置学习下。

  • 我们要使用Alibaba的数据源Druid(德鲁伊)和C3P0,故需要在pom.xml添加依赖用于配置坐标
<dependencies>
    <dependency>
        <groupId>org.springframeworkgroupId>
        <artifactId>spring-contextartifactId>
        <version>5.2.10.RELEASEversion>
    dependency>
//配置Druid数据源的坐标
<dependency>
    <groupId>com.alibabagroupId>
    <artifactId>druidartifactId>
    <version>1.1.16version>
dependency>
  • 配置第三方bean,由于这是个数据源对象,因此这个数据源要运行必须是要在一个数据库上运行,所以我把本地的数据库的一些配置信息告诉他让他连比如账号,密码,驱动等,所以要配置这些信息放在数据源对象里

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsd">
 

    <bean class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://localhost:3306/spring_db"/>
        <property name="username" value="root"/>
        <property name="password" value="root"/>
    bean>
beans>

说明:driverClassName:数据库驱动;url:数据库连接地址;username:数据库连接用户名;password:数据库连接密码;数据库连接的四要素要和自己使用的数据库信息一致。

  • 从IOC容器中获取对应的bean对象
public class App {
    public static void main(String[] args) {
       ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
       DataSource dataSource = (DataSource) ctx.getBean("dataSource");
       System.out.println(dataSource);
   }
}

加载properties文件

【properties文件简单介绍】:

  • 后缀properties是一种属性文件。
  • 这种文件以key=value格式存储内容。
  • 一般这个文件作为一些参数的存储,代码就可以灵活一点。
  • 通俗点讲就相当于定义一个变量,在这个文件里面定义这些变量的值,在程序里面可以调用这些变量,好处就是,如果程序中的参数值需要变动,直接来改这个.property文件就可以了,不用在去修改源代码。

【问题】:由于上面把数据库信息直接写在数据源bean的配置上不好(以后需要改数据库信息的时候,我们还要改代码),所以我们把他放入properties文件(属性文件,方便修改),所以我们也得把properties文件交给容器管理即加载properties文件
【步骤】:1)、把properties加载进来 2)、改变上面标签数据库信息直接暴露的问题
创建properties文件
【SSM框架(上)-Spring】_第22张图片
在里面加入数据库信息键值对

jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://127.0.0.1:3306/spring_db
jdbc.username=root
jdbc.password=root
  • 把properties加载进来:添加命名空间和使用context标签

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"	   
	   xmlns:context="http://www.springframework.org/schema/context"新加的命名空间
       xsi:schemaLocation=" http://www.springframework.org/schema/beans
            http://www.springframework.org/schema/beans/spring-beans.xsd
            http://www.springframework.org/schema/context新加的命名空间
			http://www.springframework.org/schema/context/springcontext.xsd">
			<context:property-placeholder location="jdbc.properties"/>加载properties文件
  • 改变上面标签数据库信息直接暴露的问题,直接用${properties文件中的属性名(key值)}
 <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="${jdbc.driver}"/>
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
    bean>

注意:普通的bean对象也能访问properties属性文件里的内容,因为这就是注入基本数据类型的方式

  • 注意事项:由于系统会自带一些属性文件,而我们自定义的属性文件内容里的一些键值对会和系统的属性文件冲突,故我们选哟在context标签中加入systemproperties-mode="NEVER"
  • 当我们需要加载多个属性文件时:
   
	 <context:property-placeholder location="jdbc.properties,jdbc2.properties" system-propertiesmode="NEVER"/>
   
     <context:property-placeholder location="*.properties" systemproperties-mode="NEVER"/>
   
     <context:property-placeholder location="classpath:*.properties" system-properties-mode="NEVER"/>
   
     <context:property-placeholder location="classpath*:*.properties" system-properties-mode="NEVER"/>

说明:
方式一:可以实现,如果配置文件多的话,每个都需要配置
方式二: *.properties代表所有以properties结尾的文件都会被加载,可以解决方式一的问题,但是不标准
方式三:标准的写法,classpath:代表的是从根路径下开始查找,但是只能查询当前项目的根路径
方式四:不仅可以加载当前项目还可以加载当前项目所依赖的所有项目的根路径下的properties配置文件

容器

【容器的创建方式】:

  • 配置文件作为参数创建ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
  • 配置文件的绝对路径进行创建ApplicationContext ctx = new FileSystemXmlApplicationContext("D:\\workspace\\spring\\spring_10_container\\src\\main\\resources\\applicationContext.xml");
    【Bean的获取方式】:
  • 目前案例中获取的方式: BookDao bookDao = (BookDao)ctx.getBean("bookDao");
  • 这种方式存在的问题是每次获取的时候都需要进行类型转换,有没有更简单的方式呢? BookDao bookDao = ctx.getBean("bookDao",BookDao.class);
  • 这种方式可以解决类型强转问题,但是参数又多加了一个,相对来说没有简化多少。 BookDao bookDao = ctx.getBean(BookDao.class);

大总结

【Bean】:
【SSM框架(上)-Spring】_第23张图片
【依赖注入】:
【SSM框架(上)-Spring】_第24张图片

IOC/DI注解开发

【注解开发】:用注解的方式代替bean标签达到将对象放入容器的目的

  • 方式一
    【SSM框架(上)-Spring】_第25张图片
    为了让Spring框架能够扫描到写在类上的注解,需要在配置文件上进行包扫描,把bean配置改成:【component-scan相当于扫描component注解,com.itheima相当于在这个包下扫描】

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans 
						   http://www.springframework.org/schema/beans/spring-beans.xsd">

    <context:component-scan base-package="com.itheima"/>
beans>
  • 方式二
    省略掉id
    【SSM框架(上)-Spring】_第26张图片
    必须在获取对象时是按照类型获取bean(因为没有id就不能按照id获取了)
    【SSM框架(上)-Spring】_第27张图片
  • 注解根据对象所属的层次可以选择不同的,但是没有差别就是方便分辨
    【SSM框架(上)-Spring】_第28张图片

纯注解开发模式

【纯注解开发】:在上面的注解开发的基础上直接用一个注解类代替整个配置文件application.xml
【步骤】:

  • 创建配置类SpringConfig
public class SpringConfig {
}
  • 在配置类上添加 @Configuration注解,将其标识为一个配置类,替换applicationContext.xml
@Configuration
public class SpringConfig {
}
  • 在配置类上 添加包扫描注解@ComponentScan替换
@Configuration
@ComponentScan("com.itheima")
public class SpringConfig {
}
  • 创建一个新的运行类AppForAnnotation,读取Spring核心配置文件初始化容器对象切换为读取Java配置类初始化容器对象
public class AppForAnnotation {
    public static void main(String[] args) {
        ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
        BookDao bookDao = (BookDao) ctx.getBean("bookDao");
        System.out.println(bookDao);
        BookService bookService = ctx.getBean(BookService.class);
        System.out.println(bookService);
   }
}
  • 注意:@ComponentScan注解用于设定扫描路径,此注解只能添加一次,多个数据请用数组格式@ComponentScan({com.itheima.service","com.itheima.dao"})
  • 小结:
    @Configuration
    【SSM框架(上)-Spring】_第29张图片
    @ComponentScan
    【SSM框架(上)-Spring】_第30张图片

注解开发bean作用范围与生命周期管理

【作用范围】:bean是单例还是多例,通过注解@Scope来设置,有singletonprototype两种可选值
示例:

@Repository
//@Scope设置bean的作用范围
@Scope("prototype")
public class BookDaoImpl implements BookDao {
    public void save() {
        System.out.println("book dao save ...");
   }
}

@Scope的用法:
【SSM框架(上)-Spring】_第31张图片

【生命周期管理】:在BookDaoImpl添加init和destory方法(方法名可以任意取),只需要在对应的方法上添加@PostConstruct和@PreDestroy注解即可对方法进行标识,哪个是初始化方法哪个是销毁方法。
举例:

@Repository
public class BookDaoImpl implements BookDao {
    public void save() {
        System.out.println("book dao save ...");
   }
    @PostConstruct //在构造方法之后执行,替换 init-method
    public void init() {
        System.out.println("init ...");
   }
    @PreDestroy //在销毁方法之前执行,替换 destroy-method
    public void destroy() {
        System.out.println("destroy ...");
   }
}

注意:

  • 要想看到两个方法执行,需要注意的是destroy只有在容器关闭的时候,才会执行,所以需要修改App的类【具体使用方法看上面bean的生命周期管理如何关闭容器】
  • @PostConstruct@PreDestroy注解如果找不到,需要在pom.xml导入下面的jar包
<dependency>
  <groupId>javax.annotationgroupId>
  <artifactId>javax.annotation-apiartifactId>
  <version>1.3.2version>
dependency>

小结:
【SSM框架(上)-Spring】_第32张图片

注解开发依赖注入(自动装配)、加载properties文件

【环境】:

//添加一个配置类SpringConfig
@Configuration
@ComponentScan("com.itheima")
	public class SpringConfig {
}
……………………………………………………………………………………………………………………………………………………………………………………
//添加数据层bean(BookDao、BookDaoImpl类)
public interface BookDao {
    public void save();
}
@Repository
public class BookDaoImpl implements BookDao {
    public void save() {
        System.out.println("book dao save ..." );
   }
}
public interface BookService {
    public void save();
}
添加服务层bean(BookServiceBookServiceImpl类)
@Service
public class BookServiceImpl implements BookService {
    private BookDao bookDao;
 public void setBookDao(BookDao bookDao) {
        this.bookDao = bookDao;
   }
    public void save() {
        System.out.println("book service save ...");
        bookDao.save();
   }
}
……………………………………………………………………………………………………………………………………………………………………………………
//创建运行类App
public class App {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
        BookService bookService = ctx.getBean(BookService.class);
        bookService.save();
   }
}
  • 按照类型依赖注入(默认的)
    在BookServiceImpl类的bookDao属性上添加@Autowired注解,并且可以删除set方法(一般删除)
@Service
public class BookServiceImpl implements BookService {
    @Autowired
    private BookDao bookDao;
    
// public void setBookDao(BookDao bookDao) {
//       this.bookDao = bookDao;
//   }
    public void save() {
        System.out.println("book service save ...");
        bookDao.save();
   }
}

注意@Autowired是默认按照类型注入,那么对应BookDao接口如果有多个实现类,比如添加下面的实现BookDao的另一个实现类BookDaoImpl2,它就无法注入,这时就必须使用第二种注入方式按名称注入

@Repository
public class BookDaoImpl2 implements BookDao {
    public void save() {
        System.out.println("book dao save ...2");
   }
}
  • 按照名称依赖注入
    先给两个Dao类分别起个名称:
@Repository("bookDao")
public class BookDaoImpl implements BookDao {
    public void save() {
        System.out.println("book dao save ..." );
   }
}
@Repository("bookDao2")
public class BookDaoImpl2 implements BookDao {
    public void save() {
        System.out.println("book dao save ...2" );
   }
}

以上其实就可以了,但是想要指定哪个对象注入,就必须使用注解@@Qualifier如下:

@Service
public class BookServiceImpl implements BookService {
    @Autowired
    @Qualifier("bookDao1")
    private BookDao bookDao;
 
    public void save() {
        System.out.println("book service save ...");
        bookDao.save();
   }
}
  • 简单类型注入:直接在属性的上方加入@Value(“值”)即可
@Repository("bookDao")
public class BookDaoImpl implements BookDao {
    @Value("itheima")
    private String name;
    public void save() {
        System.out.println("book dao save ..." + name);
   }

这样又把值暴露在了代码里,因此我们还需要加载属性(properties)文件

  • 加载属性(properties)文件
    步骤1:resource下准备properties文件,jdbc.properties文件内容如下
name=itheima888

步骤2: 使用注解加载properties配置文件:在配置类上添加@PropertySource(properties文件名数组)注解【这里不支持*.properties

@Configuration
@ComponentScan("com.itheima")
@PropertySource("jdbc.properties")
public class SpringConfig {
}

步骤3:使用@Value读取配置文件中的内容

@Repository("bookDao")
public class BookDaoImpl implements BookDao {
    @Value("${name}")
    private String name;
    public void save() {
        System.out.println("book dao save ..." + name);
   }
}
  • 小结:
    【SSM框架(上)-Spring】_第33张图片

【SSM框架(上)-Spring】_第34张图片

注解开发管理第三方bean

  • 在pom.xml中导入第三方数据源Druid的依赖
<dependency>
    <groupId>com.alibabagroupId>
    <artifactId>druidartifactId>
    <version>1.1.16version>
dependency>
  • 引入(新建一个与原来SpringConfig配置类同级别)外部配置类JdbcConfig用于专门管理数据源的配置类,对于数据源的bean,我们把数据源配置到该类下。
public class JdbcConfig {
	 @Bean//用来表示这个方法返回的是bean对象
    public DataSource dataSource(){
        DruidDataSource ds = new DruidDataSource();
        ds.setDriverClassName("com.mysql.jdbc.Driver");
        ds.setUrl("jdbc:mysql://localhost:3306/spring_db");
        ds.setUsername("root");
        ds.setPassword("root");
        return ds;
   }
}
  • 在Spring配置类SpringConfig类中引入@import({JdbcConfig.class})
@Configuration
//@ComponentScan("com.itheima.config")
@Import({JdbcConfig.class})
public class SpringConfig {
}

注意:扫描注解可以移除;@Import参数需要的是一个数组,可以引入多个配置类;@Import注解在配置类中只能写一次,下面的方式是不允许的

  • 小结:
    【SSM框架(上)-Spring】_第35张图片

注解开发实现为第三方bean注入资源

【问题】:对于下面代码关于数据库的四要素不应该写死在代码中,应该是从properties配置文件中读取。如何来优化下面的代码?

public class JdbcConfig {
 @Bean
    public DataSource dataSource(){
        DruidDataSource ds = new DruidDataSource();
        ds.setDriverClassName("com.mysql.jdbc.Driver");
        ds.setUrl("jdbc:mysql://localhost:3306/spring_db");
        ds.setUsername("root");
        ds.setPassword("root");
        return ds;
   }
}
  • 注入简单数据类型,后面@Value的值可以通过${properties文件中的属性名}获取
public class JdbcConfig {
    @Value("com.mysql.jdbc.Driver")
    private String driver;
    @Value("jdbc:mysql://localhost:3306/spring_db")
    private String url;
    @Value("root")
    private String userName;
    @Value("password")
    private String password;
 @Bean

    public DataSource dataSource(){
        DruidDataSource ds = new DruidDataSource();
        ds.setDriverClassName(driver);
        ds.setUrl(url);
        ds.setUsername(userName);
        ds.setPassword(password);
        return ds;
   }
}
  • 注入引用数据类型(下面以BookDao类型为例)
    在SpringConfig中扫描BookDao (在Spring配置类SpringConfog类下使用注解@ComponentScan("com.itheima.dao") )扫描的目的是让Spring能管理到BookDao,也就是说要让IOC容器中有一个bookDao对象
@Configuration
@ComponentScan("com.itheima.dao")
@Import({JdbcConfig.class})
public class SpringConfig {
}

在JdbcConfig类的方法上添加参数(下面的BookDao bookDao)

@Bean
public DataSource dataSource(BookDao bookDao){
    System.out.println(bookDao);
    DruidDataSource ds = new DruidDataSource();
    ds.setDriverClassName(driver);
    ds.setUrl(url);
    ds.setUsername(userName);
    ds.setPassword(password);
    return ds;
}

基本注解用法小结

【SSM框架(上)-Spring】_第36张图片
【SSM框架(上)-Spring】_第37张图片

AOP

简介

Spring有两个核心的概念,一个是IOC/DI,一个是AOP。AOP(Aspect Oriented Programming)面向切面编程,一种编程范式,指导开发者如何组织程序结构
作用:在不惊动原始设计的基础上为其进行功能增强,前面咱们有技术就可以实现这样的功能即代理模式,如何理解呢?见下面代码和说明:

@Repository
public class BookDaoImpl implements BookDao {
    public void save() {
        //记录程序当前执行执行(开始时间)
    Long startTime = System.currentTimeMillis();
        //业务执行万次
        for (int i = 0;i<10000;i++) {
            System.out.println("book dao save ...");
       }
        //记录程序当前执行时间(结束时间)
        Long endTime = System.currentTimeMillis();
        //计算时间差
        Long totalTime = endTime-startTime;
        //输出信息
        System.out.println("执行万次消耗时间:" + totalTime + "ms");
   }
    public void update(){
        System.out.println("book dao update ...");
   }
    public void delete(){
        System.out.println("book dao delete ...");
   }
    public void select(){
        System.out.println("book dao select ...");
   }
}

说明: AOP就是在不改变上边代码的同时也能使得updat、delete、select方法实现save中的代码功能,总体上说就是 在不惊动(改动)原有设计(代码)的前提下,想给谁(Spring一般指给方法添加)添加功能就给谁添加。

【概念说明】:
在这里插入图片描述

  • 前面一直在强调,Spring的AOP是对一个类的方法在不进行任何修改的前提下实现增强。对于上面的案例中BookServiceImpl中有save , update , delete和select方法,这些方法我们给起了一个名字叫连接点。
  • 在BookServiceImpl的四个方法中,update和delete只有打印没有计算万次执行消耗时间,但是在运行的时候已经有该功能,那也就是说update和delete方法都已经被增强,所以对于需要增强的方法我们给起了一个名字叫切入点
  • 执行BookServiceImpl的update和delete方法的时候都被添加了一个计算万次执行消耗时间的功能,将这个功能抽取到一个方法中,换句话说就是存放共性功能的方法,我们给起了个名字叫通知
  • 通知是要增强的内容,会有多个,切入点是需要被增强的方法,也会有多个,那哪个切入点需要添加哪个通知,就需要提前将它们之间的关系描述清楚,那么对于通知和切入点之间的关系描述,我们给起了个名字叫切面

AOP入门案例

【环境配置】:

//添加BookDao和BookDaoImpl类
public interface BookDao {
    public void save();
    public void update();
}
@Repository
public class BookDaoImpl implements BookDao {
    public void save() {
        System.out.println(System.currentTimeMillis());
        System.out.println("book dao save ...");
   }
    public void update(){
        System.out.println("book dao update ...");
   }
}
//创建Spring的配置类
@Configuration
@ComponentScan("com.itheima")
public class SpringConfig {
}
//编写App运行类
public class App {
    public static void main(String[] args) {
        ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
        BookDao bookDao = ctx.getBean(BookDao.class);
        bookDao.save();
        bookDao.update();
   }
}

说明:目前打印save方法的时候,因为方法中有打印系统时间,所以运行的时候是可以看到系统时间,对于update方法来说,就没有该功能,我们要使用SpringAOP的方式在不改变update方法的前提下让其具有打印系统时间的功能。

【步骤】:

  • 在pom.xml中添加依赖
<dependency>
    <groupId>org.aspectjgroupId>
    <artifactId>aspectjweaverartifactId>
    <version>1.9.4version>
dependency>

说明:因为spring-context中已经导入了spring-aop ,所以不需要再单独导入spring-aop;导入AspectJ的jar包,AspectJ是AOP思想的一个具体实现,Spring有自己的AOP实现,但是相比于AspectJ来说比较麻烦,所以我们直接采用Spring整合ApsectJ的方式进行AOP开发。

  • 定义接口与实现类
    环境准备的时候,BookDaoImpl已经准备好,不需要做任何修改
  • 定义通知类和通知
    创建一个与Dao包同级别的AOP包,并在包下创建一个类(MyAvice),并把通知写进去,通知就是将共性功能抽取出来后形成的方法,共性功能指的就是当前系统时间的打印。
    此时当前的目录结构如下
    【SSM框架(上)-Spring】_第38张图片
public class MyAdvice {
    public void method(){
        System.out.println(System.currentTimeMillis());
   }
}
  • 定义切入点:BookDaoImpl中有两个方法,分别是save和update,我们要增强的是update方法,该如何定义呢?---->加入@Pointcut标签和pt(){}方法
public class MyAdvice {
    @Pointcut("execution(void com.itheima.dao.BookDao.update())")
    private void pt(){}
    public void method(){
        System.out.println(System.currentTimeMillis());
   }
}

说明:切入点定义依托一个不具有实际意义的方法进行,即无参数、无返回值、方法体无实际逻辑。execution及后面编写的内容,后面会有章节专门去学习。

  • 制作切面,切面是用来描述通知和切入点之间的关系,如何进行关系的绑定?—>绑定切入点与通知关系,并指定通知添加到原始连接点的具体执行位置
    【SSM框架(上)-Spring】_第39张图片
    说明:@Before翻译过来是之前,也就是说通知会在切入点方法执行之前执行,除此之前还有其他四种类型,后面会讲。

  • 将通知类配给容器(@Component)并标识其为切面类(@Aspect)

@Component
@Aspect
public class MyAdvice {
    @Pointcut("execution(void com.itheima.dao.BookDao.update())")
    private void pt(){}
    @Before("pt()")
    public void method(){
    System.out.println(System.currentTimeMillis());
   }
}
  • 开启注解格式(@EnableAspectJAutoProxy)AOP功能
@Configuration
@ComponentScan("com.itheima")
@EnableAspectJAutoProxy
public class SpringConfig {
}
  • 运行程序
public class App {
    public static void main(String[] args) {
        ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
        BookDao bookDao = ctx.getBean(BookDao.class);
        bookDao.update();
   }
}

说明:看到在执行update方法之前打印了系统时间戳,说明对原始方法进行了增强,AOP编程成功。

  • 小结
    【SSM框架(上)-Spring】_第40张图片

AOP工作流程(代理对象的创建)

【流程1:Spring容器启动】:

  • 容器启动就需要去加载bean,哪些类需要被加载呢?
  • 需要被增强的类,如:BookServiceImpl
  • 通知类,如:MyAdvice
  • 注意此时bean对象还没有创建成功

【流程2:读取所有切面配置中的切入点】:下面这个例子中有两个切入点的配置,但是第一个ptx()并没有被使用,所以不会被读取。
【SSM框架(上)-Spring】_第41张图片
【流程3:初始化bean】:
判定bean对应的类中的方法是否匹配到任意切入点

  • 注意第1步在容器启动的时候,bean对象还没有被创建成功。
  • 要被实例化bean对象的类中的方法和切入点进行匹配 【通俗得讲就是通知类MyAdvice中切入点标签@Pointcut中的参数是否对应到了bean对象类中的方法】
    【SSM框架(上)-Spring】_第42张图片
    - 若匹配失败,创建原始对象,如UserDao—>匹配失败说明不需要增强,直接调用原始对象(原本的对象也就是是没有加强方法的对象) 的方法即可。
    -匹配成功,创建原始对象(目标对象)的代理对象,如: BookDao—>1)、匹配成功说明需要对其进行增强;2)、对哪个类做增强,这个类对应的对象就叫做目标对象;3)、因为要对目标对象进行功能增强,而采用的技术是动态代理,所以会为其==创建一个代理对象;==4)、最终运行的是代理对象的方法,在该方法中会对原始方法进行功能增强

AOP切入点表达式

【切入点表达式】:动作关键字 (访问修饰符 返回值 包名.类/接口名.方法名(参数) 异常名)
【SSM框架(上)-Spring】_第43张图片对于AOP中切入点表达式,我们总共会学习三个内容,分别是语法格式、通配符和书写技巧。
【语法格式】:
【SSM框架(上)-Spring】_第44张图片
对于切入点表达式的语法为:

切入点表达式标准格式:动作关键字 (访问修饰符 返回值 包名.类/接口名.方法名(参数) 异常名)
【SSM框架(上)-Spring】_第45张图片
【通配符】:
【SSM框架(上)-Spring】_第46张图片
【书写技巧】:
【SSM框架(上)-Spring】_第47张图片

AOP通知类型

【通知类型的说明】:前面的案例中,@Before("pt()")它所代表的含义是将通知(通知中有方法体)添加到切入点方法执行(方法体)的前面。这就是前置通知类型
【类型介绍】:共提供了5种通知类型—>不同的注解表示通知方法体加入到切入方法体的哪个位置
【SSM框架(上)-Spring】_第48张图片
–上图是切入点的方法体

  • 前置通知 @Before("pt()"),追加功能到方法执行前,类似于在代码1或者代码2添加内容
  • 后置通知@After("pt()"),追加功能到方法执行后,不管方法执行的过程中有没有抛出异常都会执行,类似于在代码5添加内容
  • 环绕通知@Around("pt()"),环绕通知功能比较强大,它可以追加功能到方法执行的前后,这也是比较常用的方式,它可以实现其他四种通知类型的功能,具体是如何实现的,需要我们往学习。
  • 返回后通知@AfterReturning("pt2()"),追加功能到方法执行后,只有方法正常执行结束后才进行,类似于在代码3添加内容,如果方法执行抛出异常,返回后通知将不会被添加
  • 抛出异常后通知@AfterThrowing,追加功能到方法抛出异常后,只有方法执行出异常才进行,类似于在代码4添加内容,只有方法抛出异常后才会被添加

下面以环绕通知类型举例:

  • (原始方法没有返回值即切入点方法体没返回值)切入点以及方法体:
public interface BookDao {
    public void update();
}
 
@Repository
public class BookDaoImpl implements BookDao {
    public void update(){//切入点
        System.out.println("book dao update ...");//切入点方法体
   }
}

环绕型通知配置:其中原始操作(即是上面的System.out.println("book dao update ...");被下面的pjp.proceed();代替了)

@Component
@Aspect
public class MyAdvice {
    @Pointcut("execution(void com.itheima.dao.BookDao.update())")
    private void pt(){}
   
    @Around("pt()")
    public void around(ProceedingJoinPoint pjp) throws Throwable{
        System.out.println("around before advice ...");
        //表示对原始操作的调用
        pjp.proceed();
        System.out.println("around after advice ...");
   }
}
  • (原始方法有返回值即切入点方法体有返回值)切入点以及方法体:
public interface BookDao {
    public void select();
}
 
@Repository
public class BookDaoImpl implements BookDao {
    public int select(){//切入点
        System.out.println("book dao select ...");//切入点方法体
        return 100}
}

其中通知的返回类型从void要改为Object(可以是int但Object更通用),并且返回值需要被ret接收

@Component
@Aspect
public class MyAdvice {
    @Pointcut("execution(int com.itheima.dao.BookDao.select())")
    private void pt2(){}
    
    @Around("pt2()")
    public Object aroundSelect(ProceedingJoinPoint pjp) throws Throwable {
        System.out.println("around before advice ...");
        //表示对原始操作的调用
        Object ret = pjp.proceed();
        System.out.println("around after advice ...");
        return ret;
   }
}

修改App类,调用select方法

public class App {
    public static void main(String[] args) {
        ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
        BookDao bookDao = ctx.getBean(BookDao.class);
        int num = bookDao.select();
        System.out.println(num);
   }
}

【小结】:
【SSM框架(上)-Spring】_第49张图片

AOP案例之业务层接口万次执行效率

【需求】:任意业务层接口执行均可以显示其执行效率(执行时长)
【分析】:

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

【环境以及目录结构】:
【SSM框架(上)-Spring】_第50张图片
【代码(所在目录与上面标号匹配)以及注释说明】:
配置依赖:
【SSM框架(上)-Spring】_第51张图片
具体代码:

//对应目录结构1:业务类实现接口,定义一些业务方法
public interface AccountService {
    void save(Account account);
    void delete(Integer id);
    void update(Account account);
    List<Account> findAll();
    Account findById(Integer id);
}
//对应目录结构2:业务类实现类,实现业务方法
@Service  //注释业务类bean
public class AccountServiceImpl implements AccountService {
    @Autowired //自动注入AccountDao对象,实现具体的操作数据库方法
    private AccountDao accountDao;
 
    public void save(Account account) {
        accountDao.save(account);
   }
    public void update(Account account){
        accountDao.update(account);
   }
    public void delete(Integer id) {
        accountDao.delete(id);
   }
    public Account findById(Integer id) {
        return accountDao.findById(id);
   }
    public List<Account> findAll() {
        return accountDao.findAll();
   }
}
//对应目录结构3:数据层实现接口,实现类通过注释来实现
public interface AccountDao {
    @Insert("insert into tbl_account(name,money)values(#{name},#{money})")//注释代替实现类方法的具体操作,通过自动代理实现
    void save(Account account);
    @Delete("delete from tbl_account where id = #{id} ")
    void delete(Integer id);
    @Update("update tbl_account set name = #{name} , money = #{money} 
where id = #{id} ")
    void update(Account account);
    @Select("select * from tbl_account")
    List<Account> findAll();
    @Select("select * from tbl_account where id = #{id} ")
    Account findById(Integer id);
}
//具体的实体对象定义
public class Account implements Serializable {
	private Integer id;
    private String name;
    private Double money;
    //setter..getter..toString方法省略
}
//jdc.properties属性文件,用来定义数据库连接配置属性
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/spring_db?useSSL=false
jdbc.username=root
jdbc.password=root
//Spring配置类:SpringConfig
@Configuration//配置类定义
@ComponentScan("com.itheima")//扫描
@PropertySource("classpath:jdbc.properties")//属性文件配置
@Import({JdbcConfig.class,MybatisConfig.class})//导入其他相关类
public class SpringConfig {
}
//JdbcConfig配置类
public class JdbcConfig {
    @Value("${jdbc.driver}")//读入jsbc.properties文件的属性内容
    private String driver;
    @Value("${jdbc.url}")
    private String url;
    @Value("${jdbc.username}")
    private String userName;
    @Value("${jdbc.password}")
    private String password;
    //Druid数据源bean放入容器
    @Bean
    public DataSource dataSource(){
        DruidDataSource ds = new DruidDataSource();
        ds.setDriverClassName(driver);
        ds.setUrl(url);
        ds.setUsername(userName);
        ds.setPassword(password);
        return ds;
   }
}
//MybatisConfig配置类,SqlSessionFactoryBean、MapperScannerConfigurer配置
public class MybatisConfig {
    @Bean
    public SqlSessionFactoryBean sqlSessionFactory(DataSource dataSource){
        SqlSessionFactoryBean ssfb = new SqlSessionFactoryBean();
        ssfb.setTypeAliasesPackage("com.itheima.domain");
        ssfb.setDataSource(dataSource);
        return ssfb;
   }
    @Bean
    public MapperScannerConfigurer mapperScannerConfigurer(){
        MapperScannerConfigurer msc = new MapperScannerConfigurer();
        msc.setBasePackage("com.itheima.dao");
        return msc;
   }
}
//测试类
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfig.class)//注释配置测试对象配置文件
public class AccountServiceTestCase {
    @Autowired//自动注入
    private AccountService accountService;
    @Test
    public void testFindById(){
        Account ac = accountService.findById(2);
   }
    @Test//测试方法注释
    public void testFindAll(){
        List<Account> all = accountService.findAll();
   }
}
//AOP实现通知,切入点,切入面,通知类
@Component//bean对象
@Aspect//注释AOP
public class ProjectAdvice {
    //配置业务层的所有方法
    @Pointcut("execution(* com.itheima.service.*Service.*(..))")
    private void servicePt(){}
    //@Around("ProjectAdvice.servicePt()") 可以简写为下面的方式
    @Around("servicePt()")
    public void runSpeed(ProceedingJoinPoint pjp){
        //获取执行签名信息
        Signature signature = pjp.getSignature();
        //通过签名获取执行操作名称(接口名)
        String className = signature.getDeclaringTypeName();
        //通过签名获取执行操作名称(方法名)
        String methodName = signature.getName();
        long start = System.currentTimeMillis();
        for (int i = 0; i < 10000; i++) {
           pjp.proceed();
       }
        long end = System.currentTimeMillis();
        System.out.println("万次执行:"+ className+"."+methodName+"---->" +(end-start) + "ms");
   } 
}

AOP通知获取数据

【环境配置】:切入点方法有形参id和password

//添加BookDao和BookDaoImpl类
public interface BookDao {
    public String findName(int id);
}
@Repository
//切入点方法含有形参
public class BookDaoImpl implements BookDao {
    public String findName(int id,String password) {
        System.out.println("id:"+id);
        return "itcast";
   }
}
//编写App运行类
public class App {
    public static void main(String[] args) {
        ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
        BookDao bookDao = ctx.getBean(BookDao.class);
        String name = bookDao.findName(100,itheima);
        System.out.println(name);
   }
}
  • 获取切入点方法的参数,所有的通知类型都可以获取参数,并且可以对参数进行修改
    - JoinPoint: 适用于前置、后置、返回后、抛出异常后通知,也可以修改参数使(原始方法的传入参数进行修改,用来判断参数的规范性并改正),下面的ProceedingJionPiont也一样
//通知(环绕通知和其他通知)方法获取切入点方法的参数数组
@Component
@Aspect
public class MyAdvice {
    @Pointcut("execution(* com.itheima.dao.BookDao.findName(..))")
    private void pt(){}
    @Before("pt()")
    public void before(JoinPoint jp) //定义参数JoinPiont jp
        Object[] args = jp.getArgs();//获取切入点方法对象jp的参数(用getArgs()方法)
        args[0]=666;//对第一个参数进行修改
        System.out.println(Arrays.toString(args));//打印参数数组·1
        System.out.println("before advice ..." );
   }
}

- ProceedingJoinPoint: 适用于环绕通知

@Component
@Aspect
public class MyAdvice {
    @Pointcut("execution(* com.itheima.dao.BookDao.findName(..))")
    private void pt(){}
    @Around("pt()")
    public Object around(ProceedingJoinPoint pjp)throws Throwable {
        Object[] args = pjp.getArgs();
        System.out.println(Arrays.toString(args));
        Object ret = pjp.proceed();//获取返回值
        return ret;
   }
 //其他的略
}
  • 获取切入点方法返回值,前置和抛出异常后通知是没有返回值,后置通知可有可无,所以不做研究
    - 返回后通知:@AfterReturning(value = “pt()”,returning = “ret”)//让pt{}表示的切入点返回值传给ret
@Component
@Aspect
public class MyAdvice {
    @Pointcut("execution(* com.itheima.dao.BookDao.findName(..))")
    private void pt(){}
    @AfterReturning(value = "pt()",returning = "ret")//让pt{}表示的切入点返回值传给ret
    public void afterReturning(Object ret) {//获得返回值参数
        System.out.println("afterReturning advice ..."+ret);
   }
}

- 环绕通知:切入点方法返回值直接由pjp.proceed获取

@Component
@Aspect
public class MyAdvice {
    @Pointcut("execution(* com.itheima.dao.BookDao.findName(..))")
    private void pt(){}
    @Around("pt()")
    public Object around(ProceedingJoinPoint pjp) throws Throwable{
        Object ret = pjp.proceed(args);
        return ret;
   }
  • 获取切入点方法运行异常信息,前置和返回后通知是不会有,后置通知可有可无,所以不做研究
    - 抛出异常后通知:
@Component
@Aspect
public class MyAdvice {
    @Pointcut("execution(* com.itheima.dao.BookDao.findName(..))")
    private void pt(){}
    @AfterThrowing(value = "pt()",throwing = "t")
    public void afterThrowing(Throwable t) {
        System.out.println("afterThrowing advice ..."+t);
   }
}

- 环绕通知:这块比较简单,以前我们是抛出异常,现在只需要将异常捕获,就可以获取到原始方法的异常信息了,在catch方法中就可以获取到异常,至于获取到异常以后该如何处理,这个就和你的业务需求有关了。

@Component
@Aspect
public class MyAdvice {
    @Pointcut("execution(* com.itheima.dao.BookDao.findName(..))")
    private void pt(){}
    @Around("pt()")
    public Object around(ProceedingJoinPoint pjp){
        Object[] args = pjp.getArgs();
        System.out.println(Arrays.toString(args));
        args[0] = 666;
        Object ret = null;
        try{
            ret = pjp.proceed(args);
       }catch(Throwable throwable){
            t.printStackTrace();
       }
        return ret;
   }
}

事务

事物简介

【相关概念介绍】:转账业务会有两次数据层的调用,一次是加钱一次是减钱;把事务放在数据层,加钱和减钱就有两个事务;没办法保证加钱和减钱同时成功或者同时失败;这个时候就需要将事务放在业务层进行处理。

  • 事务作用:在数据层保障一系列的数据库操作同成功同失败
  • Spring事务作用:在数据层或业务层保障一系列的数据库操作同成功同失败
    在这里插入图片描述

【案例】:
需求:
【SSM框架(上)-Spring】_第52张图片

环境搭建:

  • 在Mybatis创建两个表
create database spring_db character set utf8;
use spring_db;
create table tbl_account(
   id int primary key auto_increment,
   name varchar(35),
   money double
);
insert into tbl_account values(1,'Tom',1000);`在这里插入代码片`
insert into tbl_account values(2,'Jerry',1000);
  • 其他准备
    项目目录结构:【SSM框架(上)-Spring】_第53张图片
//根据表创建模型类
public class Account implements Serializable {
    private Integer id;
    private String name;
    private Double money;
 //setter...getter...toString...方法略    
}
//创建Dao接口
public interface AccountDao {
    @Update("update tbl_account set money = money + #{money} where name = #
{name}")
    void inMoney(@Param("name") String name, @Param("money") Double money);
    @Update("update tbl_account set money = money - #{money} where name = #
{name}")
    void outMoney(@Param("name") String name, @Param("money") Double money);
}
//创建Service接口和实现类
public interface AccountService {
    /**
     * 转账操作
     * @param out 传出方
     * @param in 转入方
     * @param money 金额
     */
    public void transfer(String out,String in ,Double money) ;
}
@Service
public class AccountServiceImpl implements AccountService {
    @Autowired
    private AccountDao accountDao;
    public void transfer(String out,String in ,Double money) {
        accountDao.outMoney(out,money);
        accountDao.inMoney(in,money);
   }
}
//添加jdbc.properties文件
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/spring_db?useSSL=false
jdbc.username=root
jdbc.password=root
//创建JdbcConfig配置类
public class JdbcConfig {
    @Value("${jdbc.driver}")
    private String driver;
    @Value("${jdbc.url}")
    private String url;
    @Value("${jdbc.username}")
    private String userName;
    @Value("${jdbc.password}")
    private String password;
    @Bean
    public DataSource dataSource(){
        DruidDataSource ds = new DruidDataSource();
        ds.setDriverClassName(driver);
        ds.setUrl(url);
        ds.setUsername(userName);
        ds.setPassword(password);
        return ds;
   }
}
//创建MybatisConfig配置类
public class MybatisConfig {
    @Bean
    public SqlSessionFactoryBean sqlSessionFactory(DataSource dataSource){
        SqlSessionFactoryBean ssfb = new SqlSessionFactoryBean();
        ssfb.setTypeAliasesPackage("com.itheima.domain");
        ssfb.setDataSource(dataSource);
        return ssfb;
   }
    @Bean
    public MapperScannerConfigurer mapperScannerConfigurer(){
        MapperScannerConfigurer msc = new MapperScannerConfigurer();
        msc.setBasePackage("com.itheima.dao");
        return msc;
   }
}
//创建SpringConfig配置类
@Configuration
@ComponentScan("com.itheima")
@PropertySource("classpath:jdbc.properties")
@Import({JdbcConfig.class,MybatisConfig.class})
public class SpringConfig {
}
//编写测试类
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfig.class)
public class AccountServiceTest {
    @Autowired
    private AccountService accountService;
    @Test
    public void testTransfer() throws IOException {
        accountService.transfer("Tom","Jerry",100D);
   }
}

问题出现:
【SSM框架(上)-Spring】_第54张图片

步骤:

  • 在需要被事务管理的方法上添加注解 @Transactional
public interface AccountService {
    /**
     * 转账操作
     * @param out 传出方
     * @param in 转入方
     * @param money 金额
     */
    //配置当前接口方法具有事务
    public void transfer(String out,String in ,Double money) ;
}
@Service
public class AccountServiceImpl implements AccountService {
 	@Autowired
    private AccountDao accountDao;
 	@Transactional//**********
    public void transfer(String out,String in ,Double money) {
        accountDao.outMoney(out,money);
        int i = 1/0;
        accountDao.inMoney(in,money);
   }
}

注意:@Transactional可以写在接口类上、接口方法上、实现类上和实现类方法上
- 写在接口类上,该接口的所有实现类的所有方法都会有事务
- 写在接口方法上,该接口的所有实现类的该方法都会有事务
- 写在实现类上,该类中的所有方法都会有事务
- 写在实现类方法上,该方法上有事务
- 建议写在实现类或实现类的方法上

  • 在JdbcConfig类中配置事务管理器,PlatformTransactionManager是接口事务管理器接口,DataSourceTransactionManager是实现类,具体写法如下基本上是固定的
public class JdbcConfig {
    @Value("${jdbc.driver}")
    private String driver;
    @Value("${jdbc.url}")
    private String url;
    @Value("${jdbc.username}")
    private String userName;
    @Value("${jdbc.password}")
    private String password;
    @Bean
    public DataSource dataSource(){
        DruidDataSource ds = new DruidDataSource();
        ds.setDriverClassName(driver);
        ds.setUrl(url);
        ds.setUsername(userName);
        ds.setPassword(password);
        return ds;
   }
    //配置事务管理器,mybatis使用的是jdbc事务
    @Bean
    public PlatformTransactionManager transactionManager(DataSource dataSource){
           DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
           transactionManager.setDataSource(dataSource);
           return transactionManager;
   }
}
  • 开启事务注解:在SpringConfig的配置类中开启,使用@EnableTransactionManagemen
@Configuration
@ComponentScan("com.itheima")
@PropertySource("classpath:jdbc.properties")
@Import({JdbcConfig.class,MybatisConfig.class
//开启注解式事务驱动
@EnableTransactionManagement
public class SpringConfig {
}
  • 小结:
    【SSM框架(上)-Spring】_第55张图片

事物角色

【SSM框架(上)-Spring】_第56张图片

事物属性

【事务回滚】:已经出错的事务回到原来正常的时候,即上面加钱的操作由于异常并未执行但减钱的操作已经执行了所以减钱的操作需要同步回退到原来正常的时候
【事务传播行为案例】:事务传播行为:事务协调员对事务管理员所携带事务的处理态度(事务协调员(添加日志信息)决定是否加入事务管理员与其他事务成为一个事务的态度,若设置为不加入,则该事务协调员称为一个单独的事务)。
【SSM框架(上)-Spring】_第57张图片

SpringMVC、Maven

见【SSM框架中-SpringMVC、Maven】

SpringBoot、 MyBatis、 Spring整合

见【SSM框架下-SpringBoot、 MyBatisPlusPlus】

你可能感兴趣的:(spring,java,mybatis)