Spring框架学习笔记

动力节点官方教程随课笔记

第一章 Spring概述

1.1概述

  ➢Spring全家桶:Spring、SpringMvc、SpringBoot、SpringCloud
  ➢Spring主要作用:为代码“解耦”,降低代码间的耦合度。
  ➢Spring核心技术:IoC、AOP。
   IoC:使用IoC降低业务对象之间耦合度。IoC使业务在相互调用过程中,不用在自己维护关系,即不用再自己创建所需要的对象,而是由Spring容器统一管理,自动“注入”。
   AOP:使系统级服务得到了最大服用,且不用在由程序员手动将系统及服务“混杂”到业务逻辑中,由Spring容器统一完成。
  Spring的主要作用就是为代码“解耦”,降低代码间的耦合度。就是让对象和对象(模块和模块)之间关系不是使用代码关联,而是通过配置来说明。即在Spring中说明对象(模块)的关系。
  Spring根据代码的功能特点,使用Ioc降低业务对象之间耦合度。IoC使得主业务在相互调用过程中,不用再自己维护关系了,即不用再自己创建要使用的对象了。而是由Spring容器统一管理,自动“注入”,注入即赋值。而AOP使得系统级服务得到了最大复用,且不用再由程序员手工将系统级服务“混杂”到主业务逻辑中了,而是由Spring容器统一完成“织入”。
  ➢Spring官网:https://spring.io

1.2 优点

  ➢轻量:Spring的所需要的jar包都非常小,一般1M以下,几百kb。核心功能所需要的jar包总共3M左右。
Spring框架运行占有资源少,运行效率高,不依赖其他jar。
  ➢针对接口编程,解耦合 :Spring 提供了 Ioc 控制反转,由容器管理对象,对象的依赖关系。原来在程序代码中的 对象创建方式,现在由容器完成。对象之间的依赖解耦合。
  ➢AOP 编程的支持 :通过 Spring 提供的 AOP 功能,方便进行面向切面的编程,许多不容易用传统 OOP 实现 的功能可以通过 AOP 轻松应付 在 Spring 中,开发人员可以从繁杂的事务管理代码中解脱出来,通过声明式方式灵活地 进行事务的管理,提高开发效率和质量。
  ➢方便集成各种优秀框架 :Spring 不排斥各种优秀的开源框架,相反 Spring 可以降低各种框架的使用难度,Spring 提供了对各种优秀框架(如 Struts,Hibernate、MyBatis)等的直接支持。简化框架的使用。 Spring 像插线板一样,其他框架是插头,可以容易的组合到一起。需要使用哪个框架,就把 这个插头放入插线板。不需要可以轻易的移除。

1.3 Spring体系结构

Spring 由 20 多个模块组成,它们可以分为数据访问/集成(Data Access/Integration)、 Web、面向切面编程(AOP, Aspects)、提供JVM的代理(Instrumentation)、消息发送(Messaging)、 核心容器(Core Container)和测试(Test)。

第二章 IoC控制反转

2.1 概念

  控制反转(IoC,Inversion of Control),是一个概念,是一种思想。指将传统上由程序代 码直接操控的对象调用权交给容器,通过容器来实现对象的装配和管理。控制反转就是对对 象控制权的转移,从程序代码本身反转到了外部容器。通过容器实现对象的创建,属性赋值, 依赖的管理。

2.2 实现

  ➢依赖注入:DI(Dependency Injection),程序的代码不做定位查询,这些工作由容器自行完成。
  依赖注入: DI 是指程序运行过程中,若需要调用另一个对象协助时,无须在代码中创建 被调用者,而是依赖于外部容器,由外部容器创建后传递给程序。 Spring 的依赖注入对调用者与被调用者几乎没有任何要求,完全支持对象之间依赖关系 的管理。
Spring 框架使用依赖注入(DI)实现 IoC。
  Spring 容器是一个超级大工厂,负责创建、管理所有的 Java 对象,这些 Java 对象被称 为 Bean。Spring 容器管理着容器中 Bean 之间的依赖关系,Spring 使用“依赖注入”的方式 来管理 Bean 之间的依赖关系。使用 IoC 实现对象之间的解耦和。

2.3 Spring的第一个程序

2.3.1 创建maven项目

2.3.2 引入maven依赖 pom.xml

<build>
    <plugins>
      <plugin>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>3.1</version>
        <configuration>
          <source>1.8</source>
          <target>1.8</target>
        </configuration>
      </plugin>
    </plugins>
 </build>
 			

2.3.3 定义接口与实现类

public interface SomeService {
    void doSome();
}
public class SomeServiceImpl implements SomeService {
    public SomeServiceImpl() {
        System.out.println("SomeServiceImpl无参数构造方法调用了");
    }

    @Override
    public void doSome() {
        System.out.println("执行了SomeServiceImpl的doSome方法");
    }
}

2.3.4 创建Spring配置文件

   在 src/main/resources/目录现创建一个 xml 文件,文件名可以随意,但 Spring 建议的名称为 applicationContext.xml。
   spring 配置中需要加入约束文件才能正常使用,约束文件是 xsd 扩展名。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans 
       http://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean id="someService" class="com.bjpowernode.service.impl.SomeServiceImpl"/>
    <bean id="someService1" class="com.bjpowernode.service.impl.SomeServiceImpl"/>
</beans>

< beans/ >:
   用于定义一个实例对象。一个实例对应一个 bean 元素。
   id:该属性是 Bean 实例的唯一标识,程序通过 id 属性访问 Bean,Bean 与 Bean 间的依 赖关系也是通过 id 属性关联的。
   class:指定该 Bean 所属的类,注意这里只能是类,不能是接口。

2.3.5 定义测试类

//spring默认创建对象的时间:在创建spring的容器时,会创建配置文件中的所有对象
@Test
    public void test02(){
        //使用spring容器创建的对象
        //1.指定spring 配置文件的名称
        String config = "beans.xml";
        //2.创建表示spring容器的对象,ApplicationContest
        //ApplicationContext就是表示Spring容器,可以通过容器获取对象了
        //ClassPathXmlApplicationContext表示从类路径加载spring的配置文件
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext(config);
        //从容器中获取某个对象,你要调用对象的方法
        SomeService service = (SomeService) applicationContext.getBean("someService");
        service.doSome();
    }

2.3.6 使用spring创建非自定义类对象

  spring 配置文件加入 java.util.Date 定义:

<bean id="myDate" class="java.util.Date" /> 

  MyTest 测试类中: 调用 getBean(“myDate”); 获取日期类对象。

 	@Test
    public void test04(){
        String config = "beans.xml";
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext(config);
        //使用getBean()
        Date date = (Date) applicationContext.getBean("mydate");
        System.out.println("时间:" + date);
    }

  输出结果:

时间:Fri Sep 04 14:07:01 CST 2020

2.3.7 容器接口和实现类

ApplicationContext接口(容器)
  ApplicationContext用于加载Spring的配置文件,在程序中充当“容器”的角色。其实现类由两个:
  ➢ClassPathXmlApplicationContext
  ➢FileSystemXmlApplicationContext

  ClassPathXmlApplicationContext:配置文件放在项目的类路径下,使用该类进行加载
  FileSystemXmlApplicationContext:配置文件在系统文件夹下,使用该类进行加载
ApplicationContext容器中对象的装配时机
  ApplicationContext容器,会在容器对象初始化时,将其中的所有对象一次性全部装配好。以后代码中若要使用到这些对象,只需要从内存中直接获取即可,执行效率较高,但占用内存较大。
  spring创建对象:默认调用的是无参数的构造方法
使用spring容器创建的java对象
  
在这里插入图片描述

2.4 基于XML的DI

di的实现有两种:
  1.在Spring的配置文件中,使用标签和属性完成,叫做基于XML的di实现
  2.使用Spring中的注解,完成属性赋值,叫做基于注解的di实现。

2.4.1 注入分类

  bean示例在调用无参构造器创建对象后,就要对bean对象的属性进行初始化。初始化是由容器自动完成的,称为注入。
  根据注入的方式不同,常用的由两类:set注入、构造注入

2.4.1.1 set注入

  set注入也叫设值注入,是指:通过setter方法传入被调用者的实例。这种注入方式简单、直观。因而在Spring的依赖注入中大量使用。

A、简单类型

  1.创建相关类

public class Student {
    private String name;
    private int age;

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

    public void setAge(int age) {
        this.age = age;
    }

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

  2.设置配置文件

<bean id="myStudent" class="com.bjpowernode.ba01.Student">
        <property name="name" value="李四"/>
        <property name="age" value="20"/>
</bean>

  3.测试

 	@Test
    public void test01(){
        String config = "ba01/applicationContext.xml";
        ApplicationContext context = new ClassPathXmlApplicationContext(config);
        Student myStudent = (Student) context.getBean("myStudent");
        System.out.println(myStudent);
    }
}

  结果

Student{name='李四', age=20}
B、引用类型

  当指定某属性值为另以bean的实例时,通过ref指定他们之间的引用关系。ref的值必须为某bean的id值。

 <bean id="myStudent" class="com.bjpowernode.ba02.Student">
        <property name="name" value="李四"/>
        <property name="age" value="26"/>
        <property name="school" ref="mySchool"/>
    </bean>
    <bean id="mySchool" class="com.bjpowernode.ba02.School">
        <property name="name" value="北京大兴"/>
        <property name="address" value="动力节点"/>
    </bean>
 @Test
    public void test02(){
        String config = "ba02/applicationContext.xml";
        ApplicationContext context = new ClassPathXmlApplicationContext(config);
        Student student = (Student) context.getBean("myStudent");
        System.out.println(student);
    }
Student{school=School{name='北京大兴', address='动力节点'}, name='李四', age=26}
2.4.1.2构造注入

  构造注入是指:在构造调用者实例的同时,完成被调用者的实例化。即,使用构造器设置依赖关系

//有参构造方法
    public Student(String name, int age, School school) {
        this.name = name;
        this.age = age;
        this.school = school;
    }
 <bean id="myStudent" class="com.bjpowernode.ba03.Student">
        <constructor-arg name="name" value="李四"/>
        <constructor-arg name="age" value="25"/>
        <constructor-arg name="school" value="mySchool"/>
    </bean>
    <bean id="mySchool" class="com.bjpowernode.ba03.School">
        <property name="name" value="动力节点"/>
        <property name="address" value="北京大兴"/>
    </bean>

<constructor-arg />标签中用于指定参数的属性有:
  ➢name:指定参数名称。
  ➢index:指明该参数对应着构造器的第几个参数,从 0 开始。不过,该属性不要也行, 但要注意,若参数类型相同,或之间有包含关系,则需要保证赋值顺序要与构造器中的参数 顺序一致。
  ➢➢value:是指构造方法的形参类型是简单类型的,使用value
  ➢➢ref:构造方法的形参类型是引用类型的,使用ref

 @Test
    public void test02(){
        String config = "ba03/applicationContext.xml";
        ApplicationContext context = new ClassPathXmlApplicationContext(config);
        Student student = (Student) context.getBean("myStudent");
        System.out.println(student);
    }
Student{school=School{name='动力节点', address='北京大兴'}, name='李四', age=25}

2.4.2 引用类型属性自动注入

  对于引用类型属性的注入,也可不在配置文件中显示的注入。可以通过为标签 设置 autowire 属性值,为引用类型属性进行隐式自动注入(默认是不自动注入引用类型属 性)。根据自动注入判断标准的不同,可以分为两种:
  byName:根据名称自动注入
  byType: 根据类型自动注入

2.4.2.1 byName方式自动注入

  当配置文件中被调用者 bean 的 id 值与代码中调用者 bean 类的属性名相同时,可使用 byName 方式,让容器自动将被调用者 bean 注入给调用者 bean。容器是通过调用者的 bean 类的属性名与配置文件的被调用者 bean 的 id 进行比较而实现自动注入的。
  java类中引用类型的属性名和spring容器中(配置文件)的id名称一样,且数据类型是一致的,这样的容器中的bean,spring能够赋值给引用类型。
  语法:<bean id=“xx” class=“yyy” autowire=“byName”>简单类型属性赋值</bean>

<bean id="myStudent" class="com.bjpowernode.ba04.Student"  autowire="byName">
        <property name="name" value="李四" />
        <property name="age" value="26" />
        <!--引用类型-->
        <!--<property name="school" ref="mySchool" />-->
    </bean>

    <!--声明School对象-->
    <bean id="school" class="com.bjpowernode.ba04.School">
        <property name="name" value="清华大学"/>
        <property name="address" value="北京的海淀区" />
    </bean>
	@Test
    public void test01(){
       String config="ba04/applicationContext.xml";
       ApplicationContext ac = new ClassPathXmlApplicationContext(config);
       //从容器中获取Student对象
       Student myStudent =  (Student) ac.getBean("myStudent");
       System.out.println("student对象="+myStudent);
   }
student对象=Student{name='李四', age=26, school=School{name='清华大学', address='北京的海淀区'}}
2.4.2. 2 byType方式自动注入

  使用 byType 方式自动注入,要求:配置文件中被调用者 bean 的 class 属性指定的类, 要与代码中调用者 bean 类的某引用类型属性类型同源。即要么相同,要么有 is-a 关系(子 类,或是实现类)。但这样的同源的被调用 bean 只能有一个。多于一个,容器就不知该匹配 哪一个了。
  java类中引用类型的数据类型和spring容器中(配置文件)的class属性 是同源关系的,这样的bean能够赋值给引用类型
  同源就是一类的意思:
  1.java类中引用类型的数据类型和bean的class的值是一样的。
  2.java类中引用类型的数据类型和bean的class的值父子类关系的。
  3.java类中引用类型的数据类型和bean的class的值接口和实现类关系的
  语法:<bean id=“xx” class=“yyy” autowire=“byType”>简单类型属性赋值</bean>
  注意:在byType中,在xml配置文件中声明bean只能有一个符合条件的。

<!--byType-->
    <bean id="myStudent" class="com.bjpowernode.ba05.Student"  autowire="byType">
        <property name="name" value="张飒" />
        <property name="age" value="26" />
        <!--引用类型-->
        <!--<property name="school" ref="mySchool" />-->
    </bean>
    <!--声明School对象-->
    <bean id="mySchool" class="com.bjpowernode.ba05.School">
        <property name="name" value="人民大学"/>
        <property name="address" value="北京的海淀区" />
    </bean>
@Test
    public void test01(){
       String config="ba05/applicationContext.xml";
       ApplicationContext ac = new ClassPathXmlApplicationContext(config);
       //从容器中获取Student对象
       Student myStudent =  (Student) ac.getBean("myStudent");
       System.out.println("student对象="+myStudent);
   }
student对象=Student{name='张飒', age=26, school=School{name='人民大学', address='北京的海淀区'}

2.4.3 为应用指定多个Spring配置文件

 &emslp;在实际应用里,随着应用规模的增加,系统中 Bean 数量也大量增加,导致配置文件变得非常庞大、臃肿。为了避免这种情况的产生,提高配置文件的可读性与可维护性,可以将 Spring 配置文件分解成多个配置文件。
包含关系的配置文件:

多个配置文件中有一个总文件,总配置文件将各其它子文件通过&lt;import/&gt;引入。在 Java 代码中只需要使用总配置文件对容器进行初始化即可。
语法:
<import  resource="其它配置文件的路径"/>

关键字:"classpath:"表示类路径(class文件所在的目录)

在spring的配置文件中需要指定其他文件的位置,不要使用classpath,告诉spring到哪里去加载读取文件
 <!--加载的是文件列表-->
    <import resource="classpath:ba06/spring-school.xml" />
    <import resource="classpath:ba06/spring-student.xml" />

@Test
    public void test01(){
      //加载的是总的文件
       String config= "ba06/total.xml";
       ApplicationContext ac = new ClassPathXmlApplicationContext(config);
       //从容器中获取Student对象
       Student myStudent =  (Student) ac.getBean("myStudent");
       System.out.println("student对象="+myStudent);
   }
student对象=Student{name='张飒', age=30, school=School{name='航空大学', address='北京的海淀区'}}

  在包含关系的配置文件中,可以使用通配符(∗:表示任意字符)。但,此时要求父配置文件名不能满足∗所能匹配的格式,否则将出现 循环递归包含。就本例而言,父配置文件不能匹配 spring-*.xml 的格式,即不能起名为 spring-total.xml。

<import resource="classpath:ba06/spring-*.xml" />

2.5 基于注解的DI

  对于 DI 使用注解,将不再需要在 Spring 配置文件中声明 bean 实例。Spring 中使用注解, 需要在原有 Spring 运行环境基础上再做一些改变。
  需要在 Spring 配置文件中配置组件扫描器,用于在指定的基本包中扫描注解。
  通过注解完成java对象的创建,属性赋值。
  使用注解的步骤:
  1.加入maven的依赖:spring-context,在加入spring-context的同时,也会加入spring-aop的依赖,使用注解必须使用spring-aop依赖

	<!--spring依赖-->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>5.2.5.RELEASE</version>
    </dependency>

  2.在类中加入spring的注解(多个不同功能的注解)
  3.在spring的配置文件中,加入一个组件扫描器的标签,说明注解在你的项目中的位置。

声明组件扫描器(component-scan),组件就是java对象
  base-package:指定注解在你的项目中的包名。
  component-scan工作方式: spring会扫描遍历base-package指定的包,把包中和子包中的所有类,找到类中的注解,按照注解的功能创建对象,或给属性赋值。
加入了component-scan标签,配置文件的变化:
  1.加入一个新的约束文件spring-context.xsd
  2.给这个新的约束文件起个命名空间的名称

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

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

注解分类:
  1.@Component
  @Component: 创建对象的, 等同于<bean>的功能
  属性:value 就是对象的名称,也就是bean的id值,value的值是唯一的,创建的对象在整个spring容器中就一个
  位置:在类的上面
  @Component(value = “myStudent”)等同于<bean id=“myStudent” class=“com.bjpowernode.ba01.Student” />
  spring中和@Component功能一致,创建对象的注解还有:
  1.@Repository(用在持久层类的上面) : 放在dao的实现类上面,表示创建dao对象,dao对象是能访问数据库的。
  2.@Service(用在业务层类的上面):放在service的实现类上面,创建service对象,service对象是做业务处理,可以有事务等功能的。
  3.@Controller(用在控制器的上面):放在控制器(处理器)类的上面,创建控制器对象的,控制器对象,能够接受用户提交的参数,显示请求的处理结果。
以上三个注解的使用语法和@Component一样的。 都能创建对象,但是这三个注解还有额外的功能.@Repository,@Service,@Controller是给项目的对象分层的。
  2.@Respotory
  3.@Service
  4.@Controller
  5.@Value
  6.@Autowired
  7.@Resource

2.5.1 定义 Bean 的注解@Component(掌握)

  需要在类上使用注解@Component,该注解的 value 属性用于指定该 bean 的 id 值。

  @Component: 创建对象的, 等同于<bean>的功能

  属性:value 就是对象的名称,也就是bean的id值,value的值是唯一的,创建的对象在整个spring容器中就一个

  @Component(value = “myStudent”)等同于<bean id=“myStudent” class=“com.bjpowernode.ba01.Student” />
  @Component 不指定 value 属性,bean 的 id 是类名的首字母小写。

@Component("myStudent")
//不指定对象名称,由spring提供默认名称: 类名的首字母小写
//@Component
public class Student {
    private String name;
    private Integer age;
    public Student() {
        System.out.println("==student无参数构造方法===");
    }
    public void setName(String name) {
        this.name = name;
    }
    public void setAge(Integer age) {
        this.age = age;
    }
    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

  另外,Spring 还提供了 3 个创建对象的注解:
  ➢ @Repository 用于对 DAO 实现类进行注解
  ➢ @Service 用于对 Service 实现类进行注解
  ➢ @Controller 用于对 Controller 实现类进行注解
  这三个注解与@Component 都可以创建对象,但这三个注解还有其他的含义,@Service 创建业务层对象,业务层对象可以加入事务功能,@Controller 注解创建的对象可以作为处 理器接收用户的请求。
  @Repository,@Service,@Controller 是对@Component 注解的细化,标注不同层的对 象。即持久层对象,业务层对象,控制层对象。 在这里插入图片描述
  指定多个包的三种方式:
  1)使用多个context:component-scan指定不同的包路径

 <!--第一种方式:使用多次组件扫描器,指定不同的包-->
    <context:component-scan base-package="com.bjpowernode.ba01"/>
    <context:component-scan base-package="com.bjpowernode.ba02"/>

  2)指定base-package的值使用分隔符
  分隔符可以使用逗号(,)分号(;)还可以使用空格,不建议使用空格。

 <!--第二种方式:使用分隔符(;或,)分隔多个包名-->
    <context:component-scan base-package="com.bjpowernode.ba01;com.bjpowernode.ba02" />

  3)base-package是指定到父包名
  base-package的值表是基本包,容器启动会扫描包及其子包中的注解,当然也会扫描到子包下级的子包。所以base-package可以指定一个父包就可以。

  <!--第三种方式:指定父包-->
    <context:component-scan base-package="com.bjpowernode" />

  或者最顶级的父包

 <!--第三种方式:指定父包-->
    <context:component-scan base-package="com" />

  但不建议使用顶级的父包,扫描的路径比较多,导致容器启动时间变慢。指定到目标包和合适的。也就是注解所在包全路径。例如注解的类在com.bjpowernode.beans包中

 <!--第三种方式:指定父包-->
    <context:component-scan base-package="com.bjpowernode.beans" />

2.5.2 简单类型属性注入@Value(掌握)

  需要在属性上使用注解@Value,该注解的value属性用于指定要注入的值。使用该注解完成属性注入时,类中无需setter。当然,若属性有setter,则也可将其加到setter上。

@Component("myStudent")
public class Student {
    /**
     * @Value: 简单类型的属性赋值
     *   属性: value 是String类型的,表示简单类型的属性值
     *   位置: 1.在属性定义的上面,无需set方法,推荐使用。
     *         2.在set方法的上面
     */
    //@Value("李四" )
    @Value("${myname}") //使用属性配置文件中的数据
    private String name;
    @Value("${myage}")  //使用属性配置文件中的数据
    private Integer age;

2.5.3 byType自动注入@Autowired(掌握)

  需要在引用属性上使用注解@Autowired,该注解默认使用按类型自动装配Bean的方式。使用该注解完成属性注入时,类中无需setter。当然,若属性有setter,则也可将其加到setter上。
  spring中通过注解给引用类型赋值,使用的是自动注入原理 ,支持byName, byType。
  @Autowired:默认使用的是byType自动注入。

 	@Component("myStudent")
public class Student {
    @Value("李四" )
    private String name;
    private Integer age;
    /**
     * 引用类型
     * @Autowired: spring框架提供的注解,实现引用类型的赋值。
     * spring中通过注解给引用类型赋值,使用的是自动注入原理 ,支持byName, byType
     * @Autowired:默认使用的是byType自动注入。
     *
     *  位置:1)在属性定义的上面,无需set方法, 推荐使用
     *       2)在set方法的上面
     */
    @Autowired
    private School school;
    public Student() {
        System.out.println("==student无参数构造方法===");
    }
    public void setName(String name) {
        this.name = name;
    }
    @Value("30")
    public void setAge(Integer age) {
        System.out.println("setAge:"+age);
        this.age = age;
    }
    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", school=" + school +
                '}';
    }
}
@Component("mySchool")
public class School {
    @Value("北京大学")
    private String name;
    @Value("北京的海淀区")
    private String address;
    public void setName(String name) {
        this.name = name;
    }
    public void setAddress(String address) {
        this.address = address;
    }
    @Override
    public String toString() {
        return "School{" +
                "name='" + name + '\'' +
                ", address='" + address + '\'' +
                '}';
    }
}
public class MyTest03 {

    @Test
    public void test01(){
        String config="applicationContext.xml";
        ApplicationContext ctx = new ClassPathXmlApplicationContext(config);
        //从容器中获取对象
        Student student = (Student) ctx.getBean("myStudent");

        System.out.println("student="+student);
    }
}

结果

student=Student{name='李四', age=30, school=School{name='北京大学', address='北京的海淀区'}}

2.5.4byName自动注入@Autowired与@Qualifier(掌握)

  需要在引用属性上联合使用注解@Autowired与@Qualifier。@Qualifier的value属性用于指定要匹配的Bean的id值。类中无需set方法,也可加到set方法上。

@Component("myStudent")
public class Student {
    @Value("李四" )
    private String name;
    private Integer age;
    /**
     * 引用类型
     * @Autowired: spring框架提供的注解,实现引用类型的赋值。
     * spring中通过注解给引用类型赋值,使用的是自动注入原理 ,支持byName, byType
     * @Autowired:默认使用的是byType自动注入。
     *  位置:1)在属性定义的上面,无需set方法, 推荐使用
     *        2)在set方法的上面
     *  如果要使用byName方式,需要做的是:
     *  1.在属性上面加入@Autowired
     *  2.在属性上面加入@Qualifier(value="bean的id") :表示使用指定名称的bean完成赋值。
     */
     
    //byName自动注入
    @Autowired
    @Qualifier("mySchool")
    private School school;
    public Student() {
        System.out.println("==student无参数构造方法===");
    }
    public void setName(String name) {
        this.name = name;
    }
    @Value("30")
    public void setAge(Integer age) {
        System.out.println("setAge:"+age);
        this.age = age;
    }
    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", school=" + school +
                '}';
    }
}

  @Autowired还有一个属性required,默认值为true,表示当匹配失败后,会终止程序运行。若将其值设置为false,则匹配失败,将被忽略,未匹配的属性值为null。

//byName自动注入
    @Autowired(required = false)
    @Qualifier("mySchool")
    private School school;

2.5.5 JDK注解@Resource自动注入(掌握)

  Spring提供了对jdk中@Resource注解的支持。@Resource注解既可以按名称匹配Bean,也可以按类型匹配Bean。默认是按名称注入。使用该注解,要求JDK必须是6及以上版本。@Resource可在属性上,也可在set方法上。

2.5.5.1 byType注入引用类型属性

  @Resource注解若不带任何参数,采用默认按名称的方式注入,按名称不能注入bean,则会按照类型进行Bean的匹配注入。
在这里插入图片描述

2.5.5.2 byName注入引用类型属性

  @Resource注解指定其name属性,则name的值即为按照名称进行匹配的Bean的id。
在这里插入图片描述

@Component("myStudent")
public class Student {
    @Value("李四" )
    private String name;
    private Integer age;
    /**
     * 引用类型
     * @Resource: 来自jdk中的注解,spring框架提供了对这个注解的功能支持,可以使用它给引用类型赋值
     *            使用的也是自动注入原理,支持byName, byType .默认是byName
     *  位置: 1.在属性定义的上面,无需set方法,推荐使用。
     *         2.在set方法的上面
     * @Resource只使用byName方式,需要增加一个属性 name
     * name的值是bean的id(名称)
     */
    //只使用byName
    @Resource(name = "mySchool")
    private School school;
    public Student() {
        System.out.println("==student无参数构造方法===");
    }
    public void setName(String name) {
        this.name = name;
    }
    @Value("30")
    public void setAge(Integer age) {
        System.out.println("setAge:"+age);
        this.age = age;
    }
    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", school=" + school +
                '}';
    }
}

2.5.6 注解与XML的对比

注解优点是:
  ⚫方便
  ⚫直观
  ⚫高效(代码少,没有配置文件的书写那么复杂)。
  其弊端也显而易见:以硬编码的方式写入到Java代码中,修改是需要重新编译代码的。
XML方式优点是:
  ⚫配置和代码是分离的
  ⚫在xml中做修改,无需编译代码,只需重启服务器即可将新的配置加载。
  xml的缺点是:编写麻烦,效率低,大型项目过于复杂。

第三章 AOP面向切面编程

3.1 动态代理

动态代理是指,程序在整个运行过程中根本就不存在目标类的代理类,目标对象的代理对象只是由代理生成工具(不是真实定义的类)在程序运行时由JVM根据反射等机制动态生成的。代理对象与目标对象的的代理关系在程序运行时才确立

3.1.1 JDK动态代理

动态代理的实现方式常用的有两种:使用JDK的Proxy,与通过CGLIB生成代理。
jdk的动态代理要求目标对象必须实现接口,这是java设计上的要求。
从jdk1.3以来,java语言通过Java.lang.reflect包提供三个类支持代理模式Proxy,Method和InovationHanler。

3.1.2 CGLIB动态代理(了解)

  CGLIB(Code Generation Library)是一个开源项目。是一个强大的,高性能的,高质量的Code生成类库,它可以在运行期扩展Java类与实现Java接口。它广泛的被许多AOP的框架使用,例如:Spring AOP.
  使用JDK的Proxy实现代理,要求目标与代理类实现相同的接口。若目标类不存在接口,则无法使用该方式实现。但对于无接口的类,要为其创建动态代理,就要使用CGLIB来实现。
  CGLIB代理的生成原理是生成目标类的子类,而子类是增强过的,这个子类对象就是代理对象。所以,使用CGLIB生成动态代理,要求目标必须能够被继承,既不能是final的类。
  CGLIB经常被应用在框架中,例如:

3.2 不使用AOP的开发方式(理解)

Step1:项目aop_leadin1
  先定义好接口与一个实现类,该实现类中除了要实现接口中的方法外,还要再写两个非业务方法。非业务方法也称为交叉业务逻辑:
  ➢doTransaction():用于事务处理
  ➢doLog():用于日志处理然后,再使接口方法调用它们。接口方法也称为主业务逻辑。

public interface SomeService {
    void doSome();
    void doOther();
}
public class SomeServiceImpl3 implements SomeService {
    @Override
    public void doSome() {
        doLog();
        System.out.println("执行业务方法doSome");
        doTrans();
    }
    @Override
    public void doOther() {
        doLog();
        System.out.println("执行业务方法doOther");
        doTrans();
    }
    public void doLog(){
        System.out.println("非业务功能,日志功能,在方法开始时输出日志");
    }
    public void doTrans(){
        System.out.println("非业务功能,在业务方法执行之后,加入事务");
    }
}

  Step2:项目aop_leadin2
  当然,也可以有另一种解决方案:将这些交叉业务逻辑代码放到专门的工具类或处理类中,由主业务逻辑调用。

public class ServiceTools {
    public static void doLog(){
        System.out.println("非业务方法,方法的执行时间:"+ new Date());
    }
    public static void doTrans(){
        System.out.println("非业务方法,方法执行完毕后,提交事务");
    }
}
public class SomeServiceImpl2 implements SomeService {
    @Override
    public void doSome() {
        ServiceTools.doLog();
        System.out.println("执行业务方法doSome");
        ServiceTools.doTrans();
    }
    @Override
    public void doOther() {
        ServiceTools.doLog();
        System.out.println("执行业务方法doOther");
        ServiceTools.doTrans();
    }
}

  Step3:项目aop_leadin3以上的解决方案,还是存在弊端:交叉业务与主业务深度耦合在一起。当交叉业务逻辑较多时,在主业务代码中会出现大量的交叉业务逻辑代码调用语句,大大影响了主业务逻辑的可读性,降低了代码的可维护性,同时也增加了开发难度。
  所以,可以采用动态代理方式。在不修改主业务逻辑的前提下,扩展和增强其功能。
功能增强

public class MyIncationHandler implements InvocationHandler {
    //目标对象
    private Object target; //SomeServiceImpl类
    public MyIncationHandler(Object target) {
        this.target = target;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //通过代理对象执行方法时,会调用执行这个invoke()
        System.out.println("执行MyIncationHandler中的invoke()");
        System.out.println("method名称:"+method.getName());
        String methodName = method.getName();
        Object res = null;
        if("doSome".equals(methodName)){ //JoinPoint  Pointcut
            ServiceTools.doLog(); //在目标方法之前,输出时间
            //执行目标类的方法,通过Method类实现
            res  = method.invoke(target,args); //SomeServiceImpl.doSome()
            ServiceTools.doTrans(); //在目标方法执行之后,提交事务
        } else {
            res  = method.invoke(target,args); //SomeServiceImpl.doOther()
        }
        //目标方法的执行结果
        return res;
    }
}
public class MyApp {
    public static void main(String[] args) {
        //使用jdk的Proxy创建代理对象
        //创建目标对象
        SomeService target = new SomeServiceImpl();
        //创建InvocationHandler对象
        InvocationHandler handler = new MyIncationHandler(target);
        //使用Proxy创建代理
        SomeService proxy = (SomeService) Proxy.newProxyInstance(
                target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),handler);
        //com.sun.proxy.$Proxy0
        System.out.println("proxy======"+proxy.getClass().getName());
        //通过代理执行方法,会调用handler中的invoke()
        proxy.doSome();
        System.out.println("==================================================");
        proxy.doOther();
    }
}

3.2AOP概述

  AOP面向切面编程,基于动态代理的,可以使用jdk,cglib两种代理方式。
  AOP就是动态代理的规范化,把动态代理的实现步骤,方式都定义好了,让开发人员用一种统一的方式,使用动态代理。
  怎么理解面向切面编程?
  1.需要在分析项目功能时,找出切面。
  2.合理的安排切面的执行时间(是在目标方法前,还是目标方法后)
  3.合理的安排切面执行的位置,在哪个类,哪个方法增加增强功能

3.3AOP简介

  AOP(Aspect Orient Programming),面向切面编程。面向切面编程是从动态角度考虑程序运行过程。
AOP底层,就是采用动态代理模式实现的。采用了两种代理:JDK的动态代理,与CGLIB的动态代理。

AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,可通过运行期动态代理实现程序功能的统一维护的一种技术。AOP是Spring框架中的一个重要内容。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。

  面向切面编程,就是将交叉业务逻辑封装成切面,利用AOP容器的功能将切面织入到主业务逻辑中。所谓交叉业务逻辑是指,通用的、与主业务逻辑无关的代码,如安全检查、事务、日志、缓存等。
  若不使用AOP,则会出现代码纠缠,即交叉业务逻辑与主业务逻辑混合在一起。这样,会使主业务逻辑变的混杂不清。
  例如,转账,在真正转账业务逻辑前后,需要权限控制、日志记录、加载事务、结束事务等交叉业务逻辑,而这些业务逻辑与主业务逻辑间并无直接关系。但,它们的代码量所占比重能达到总代码量的一半甚至还多。它们的存在,不仅产生了大量的“冗余”代码,还大大干扰了主业务逻辑—转账。

3.4面向切面编程对有什么好处?

1.减少重复;
2.专注业务;注意:面向切面编程只是面向对象编程的一种补充。
使用AOP减少重复代码,专注业务实现:
在这里插入图片描述

3.5 AOP编程术语(掌握)

  (1)切面(Aspect)

  切面泛指交叉业务逻辑。上例中的事务处理、日志处理就可以理解为切面。常用的切面是通知(Advice)。实际就是对主业务逻辑的一种增强。
  常见的切面功能有日志、事务、统计信息、参数检查、权限验证等。

  (2)连接点(JoinPoint)

  连接点指可以被切面织入的具体方法。通常业务接口中的方法均为连接点。

  (3)切入点(Pointcut)

  切入点指声明的一个或多个连接点的集合。通过切入点指定一组方法。被标记为final的方法是不能作为连接点与切入点的。因为最终的是不能被修改的,不能被增强的。

  (4)目标对象(Target)

  目标对象指将要被增强的对象。即包含主业务逻辑的类的对象。上例中的StudentServiceImpl的对象若被增强,则该类称为目标类,该类对象称为目标对象。当然,不被增强,也就无所谓目标不目标了。

  (5)通知(Advice)

  通知表示切面的执行时间,Advice也叫增强。上例中MyInvocationHandler就可以理解为是一种通知。换个角度来说,通知定义了增强代码切入到目标代码的时间点,是目标方法执行之前执行,还是之后执行等。通知类型不同,切入时间不同。切入点定义切入的位置,通知定义切入的时间。
  切面有三个关键要素:
  1.切面的功能代码,切面要干什么
  2.切面的执行位置,使用Pointcut表示切面执行的位置。
  3.切面的执行时间,使用Advice表示时间,在目标方法之前,还是目标方法之后。

3.6AspectJ对AOP的实现(掌握)

  对于AOP这种编程思想,很多框架都进行了实现。Spring就是其中之一,可以完成面向切面编程。然而,AspectJ也实现了AOP的功能,且其实现方式更为简捷,使用更为方便,而且还支持注解式开发。所以,Spring又将AspectJ的对于AOP的实现也引入到了自己的框架中。
  在Spring中使用AOP开发时,一般使用AspectJ的实现方式。
AspectJ简介
  AspectJ是一个优秀面向切面的框架,它扩展了Java语言,提供了强大的切面实现。
  官网地址:http://www.eclipse.org/aspectj/
  AspetJ是Eclipse的开源项目,官网介绍如下:
在这里插入图片描述
a seamless aspect-oriented extension to the Javatm programming language(一种基于Java平台的面向切面编程的语言)
Java platform compatible(兼容Java平台,可以无缝扩展)easy to learn and use(易学易用)
  aspectJ:一个开源的专门做aop的框架。spring框架中集成了aspectj框架,通过spring就能使用aspectj的功能
  aspectJ框架实现aop有两种方式:
  1.使用xml的配置文件
  2.使用注解,我们在项目中要做aop功能,一般都使用注解,aspectj有5个注解。

3.6.1AspectJ的通知类型(理解)

AspectJ中常用的通知有五种类型:
(1)前置通知 @Before
(2)后置通知 @AfterReturning
(3)环绕通知 @Around
(4)异常通知 @AfterThrowing
(5)最终通知 @After

3.6.2AspectJ的切入点表达式(掌握)

AspectJ定义了专门的表达式用于指定切入点。表达式的原型是:

execution(modifiers-pattern? ret-type-pattern
declaring-type-pattern?name-pattern(param-pattern)throws-pattern? )

解释:
   modifiers-pattern]   访问权限类型
   ret-type-pattern  返回值类型
   declaring-type-pattern    包名类名
   name-pattern(param-pattern)  方法名(参数类型和参数个数)
   throws-pattern   抛出异常类型
   ?   表示可选的部分
以上表达式共4个部分。execution(访问权限  方法返回值  方法声明(参数)  异常类型)
  切入点表达式要匹配的对象就是目标方法的方法名。所以,execution表达式中明显就是方法的签名。注意,表达式中黑色文字表示可省略部分,各部分间用空格分开。在其中可以使用以下符号:
在这里插入图片描述
举例:
  execution(public * * (…))
指定切入点为:任意公共方法。
  execution(* set* (…))
指定切入点为:任何一个以“set”开始的方法。
  execution(* com.xyz.service.*.*(…))
指定切入点为:定义在service包里的任意类的任意方法。
  execution(* com.xyz.service…*.*(…))
指定切入点为:定义在service包或者子包里的任意类的任意方法。“…”出现在类名中时,后面必须跟“”,表示包、子包下的所有类。
  execution(* *…service.*.*(…))
指定所有包下的serivce子包下所有类(接口)中所有方法为切入点
  execution(* *.service.*.*(…))
指定只有一级包下的serivce子包下所有类(接口)中所有方法为切入点
  execution(* *.ISomeService.*(…))
指定只有一级包下的ISomeSerivce接口中所有方法为切入点e
  xecution(* *…ISomeService.*(…))
指定所有包下的ISomeSerivce接口中所有方法为切入点
  execution(* com.xyz.service.IAccountService.*(…))
指定切入点为:IAccountService 接口中的任意方法。
  execution(* com.xyz.service.IAccountService+.*(…))
指定切入点为:IAccountService 若为接口,则为接口中的任意方法及其所有实现类中的任意方法;若为类,则为该类及其子类中的任意方法。
  execution(* joke(String,int)))
指定切入点为:所有的joke(String,int)方法,且joke()方法的第一个参数是String,第二个参数是int。如果方法中的参数类型是java.lang包下的类,可以直接使用类名,否则必须使用全限定类名,如joke( java.util.List, int)。
  execution(* joke(String,*)))

指定切入点为:所有的joke()方法,该方法第一个参数为String,第二个参数可以是任意类型,如joke(String s1,String s2)和joke(String s1,double d2)都是,但joke(String s1,doubled2,String s3)不是。
  execution(* joke(String,…)))
指定切入点为:所有的joke()方法,该方法第一个参数为String,后面可以有任意个参数且参数类型不限,如joke(String s1)、joke(String s1,String s2)和joke(String s1,double d2,String s3)都是。
  execution(* joke(Object))
指定切入点为:所有的joke()方法,方法拥有一个参数,且参数是Object类型。joke(Object ob)是,但,joke(String s)与joke(User u)均不是。
  execution(* joke(Object+)))
指定切入点为:所有的joke()方法,方法拥有一个参数,且参数是Object类型或该类的子类。不仅joke(Object ob)是,joke(String s)和joke(User u)也是。

3.6.3AspectJ的开发环境(掌握)

(1)maven依赖

	<!--spring依赖-->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>5.2.5.RELEASE</version>
    </dependency>
    <!--aspectj依赖-->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-aspects</artifactId>
      <version>5.2.5.RELEASE</version>
    </dependency>
    <!--插件-->
    <plugins>
      <plugin>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>3.1</version>
        <configuration>
          <source>1.8</source>
          <target>1.8</target>
        </configuration>
      </plugin>
    </plugins>

(2)引入AOP约束
  在AspectJ实现AOP时,要引入AOP的约束。配置文件中使用的AOP约束中的标签,均是AspectJ框架使用的,而非Spring框架本身在实现AOP时使用的。
  AspectJ对于AOP的实现有注解和配置文件两种方式,常用是注解方式。

3.6.4 AspectJ基于注解的AOP实现(掌握)

AspectJ提供了以注解方式对于AOP的实现。
(1)实现步骤
  A、Step1:定义业务接口与实现类

public interface SomeService {
    void doSome(String name,Integer age);
}
public class SomeServiceImpl implements SomeService {
    @Override
    public void doSome(String name,Integer age) {
        //给doSome方法增加一个功能,在doSome()执行之前, 输出方法的执行时间
        System.out.println("====目标方法doSome()====");
    }

  B、Step2:定义切面类
  类中定义了若干普通方法,将作为不同的通知方法,用来增强功能。

/**
 *  @Aspect : 是aspectj框架中的注解。
 *     作用:表示当前类是切面类。
 *     切面类:是用来给业务方法增加功能的类,在这个类中有切面的功能代码
 *     位置:在类定义的上面
 */
@Aspect
public class MyAspect {
	 /**
     * 定义方法,方法是实现切面功能的。
     * 方法的定义要求:
     * 1.公共方法 public
     * 2.方法没有返回值
     * 3.方法名称自定义
     * 4.方法可以有参数,也可以没有参数。
     *   如果有参数,参数不是自定义的,有几个参数类型可以使用。
     */
    /**
     * @Before: 前置通知注解
     *   属性:value ,是切入点表达式,表示切面的功能执行的位置。
     *   位置:在方法的上面
     * 特点:
     *  1.在目标方法之前先执行的
     *  2.不会改变目标方法的执行结果
     *  3.不会影响目标方法的执行。
     */
     /**
     * @Before: 前置通知注解
     *   属性:value ,是切入点表达式,表示切面的功能执行的位置。
     *   位置:在方法的上面
     * 特点:
     *  1.在目标方法之前先执行的
     *  2.不会改变目标方法的执行结果
     *  3.不会影响目标方法的执行。
     */
   @Before(value = "execution(public void com.bjpowernode.ba01.SomeServiceImpl.doSome(String,Integer))")
    public void myBefore(){
        //就是你切面要执行的功能代码
        System.out.println("前置通知, 切面功能:在目标方法之前输出执行时间:"+ new Date());
    }
}

  C、Step3:声明目标对象切面类对象

<!--把对象交给spring容器,由spring容器统一创建,管理对象-->
    <!--声明目标对象-->
    <bean id="someService" class="com.bjpowernode.ba08.SomeServiceImpl" />
    <!--声明切面类对象-->
    <bean id="myAspect" class="com.bjpowernode.ba08.MyAspect" />

  D、Step4:注册AspectJ的自动代理

<!--声明自动代理生成器:使用aspectj框架内部的功能,创建目标对象的代理对象。
        创建代理对象是在内存中实现的, 修改目标对象的内存中的结构。 创建为代理对象
        所以目标对象就是被修改后的代理对象.
        aspectj-autoproxy:会把spring容器中的所有的目标对象,一次性都生成代理对象。-->
    <aop:aspectj-autoproxy />

  <aop:aspectj-autoproxy/>的底层是由AnnotationAwareAspectJAutoProxyCreator实现的。从其类名就可看出,是基于AspectJ的注解适配自动代理生成器。
  其工作原理是,<aop:aspectj-autoproxy/>通过扫描找到@Aspect定义的切面类,再由切面类根据切入点找到目标类的目标方法,再由通知类型找到切入的时间点。
  E、Step5:测试类中使用目标对象的id

public class MyTest01 {
    @Test
    public void test01(){
        String config="applicationContext.xml";
        ApplicationContext ctx = new ClassPathXmlApplicationContext(config);
        //从容器中获取目标对象
        SomeService proxy = (SomeService) ctx.getBean("someService");
        //com.sun.proxy.$Proxy8 :jdk动态代理
        System.out.println("proxy:"+proxy.getClass().getName());
        //通过代理的对象执行方法,实现目标方法执行时,增强了功能
        proxy.doSome("lisi",20);
    }
}

(2)[掌握]@Before前置通知-方法有JoinPoint参数
  在目标方法执行之前执行。被注解为前置通知的方法,可以包含一个JoinPoint类型参数。该类型的对象本身就是切入点表达式。通过该参数,可获取切入点表达式、方法签名、目标对象等。
  不光前置通知的方法,可以包含一个JoinPoint类型参数,所有的通知方法均可包含该参数。


    /**
     * 指定通知方法中的参数 : JoinPoint
     * JoinPoint:业务方法,要加入切面功能的业务方法
     *    作用是:可以在通知方法中获取方法执行时的信息, 例如方法名称,方法的实参。
     *    如果你的切面功能中需要用到方法的信息,就加入JoinPoint.
     *    这个JoinPoint参数的值是由框架赋予, 必须是第一个位置的参数
     */
    @Before(value = "execution(void *..SomeServiceImpl.doSome(String,Integer))")
    public void myBefore(JoinPoint jp){
        //获取方法的完整定义
        System.out.println("方法的签名(定义)="+jp.getSignature());
        System.out.println("方法的名称="+jp.getSignature().getName());
        //获取方法的实参
        Object args [] = jp.getArgs();
        for (Object arg:args){
            System.out.println("参数="+arg);
        }
        //就是你切面要执行的功能代码
        System.out.println("2=====前置通知, 切面功能:在目标方法之前输出执行时间:"+ new Date());
    }

(3)[掌握]@AfterReturning后置通知-注解有returning属性
  在目标方法执行之后执行。由于是目标方法之后执行,所以可以获取到目标方法的返回值。该注解的returning属性就是用于指定接收方法返回值的变量名的。所以,被注解为后置通知的方法,除了可以包含JoinPoint参数外,还可以包含用于接收返回值的变量。该变量最好为Object类型,因为目标方法的返回值可能是任何类型。
  接口增加方法:

public interface SomeService {
    void doSome(String name, Integer age);
    String doOther(String name,Integer age);
    Student doOther2(String name,Integer age);
}

  实现方法:

 @Override
    public String doOther(String name, Integer age) {
        System.out.println("====目标方法doOther()====");
        return "abcd";
    }

  定义切面:

/**
 *  @Aspect : 是aspectj框架中的注解。
 *     作用:表示当前类是切面类。
 *     切面类:是用来给业务方法增加功能的类,在这个类中有切面的功能代码
 *     位置:在类定义的上面
 */
@Aspect
public class MyAspect {
    /**
     * 后置通知定义方法,方法是实现切面功能的。
     * 方法的定义要求:
     * 1.公共方法 public
     * 2.方法没有返回值
     * 3.方法名称自定义
     * 4.方法有参数的,推荐是Object ,参数名自定义
     */

    /**
     * @AfterReturning:后置通知
     *    属性:1.value 切入点表达式
     *         2.returning 自定义的变量,表示目标方法的返回值的。
     *          自定义变量名必须和通知方法的形参名一样。
     *    位置:在方法定义的上面
     * 特点:
     *  1。在目标方法之后执行的。
     *  2. 能够获取到目标方法的返回值,可以根据这个返回值做不同的处理功能
     *      Object res = doOther();
     *  3. 可以修改这个返回值
     *
     *  后置通知的执行
     *    Object res = doOther();
     *    参数传递: 传值, 传引用
     *    myAfterReturing(res);
     *    System.out.println("res="+res)
     *
     */
    @AfterReturning(value = "execution(* *..SomeServiceImpl.doOther(..))",
                    returning = "res")
    public void myAfterReturing(  JoinPoint jp  ,Object res ){
        // Object res:是目标方法执行后的返回值,根据返回值做你的切面的功能处理
        System.out.println("后置通知:方法的定义"+ jp.getSignature());
        System.out.println("后置通知:在目标方法之后执行的,获取的返回值是:"+res);
        if(res.equals("abcd")){
            //做一些功能
        } else{
            //做其它功能
        }
        //修改目标方法的返回值, 看一下是否会影响 最后的方法调用结果
        if( res != null){
            res = "Hello Aspectj";
        }
    }
}

(4)[掌握]@Around环绕通知-增强方法有ProceedingJoinPoint参数
  在目标方法执行之前之后执行。被注解为环绕增强的方法要有返回值,Object类型。并且方法可以包含一个ProceedingJoinPoint类型的参数。接口ProceedingJoinPoint其有一个proceed()方法,用于执行目标方法。若目标方法有返回值,则该方法的返回值就是目标方法的返回值。最后,环绕增强方法将其返回值返回。该增强方法实际是拦截了目标方法的执行。
接口增加方法:

public interface SomeService {
    void doSome(String name, Integer age);
    String doOther(String name, Integer age);
    Student doOther2(String name, Integer age);
    String doFirst(String name,Integer age);
}

接口方法的实现:

@Override
    public String doFirst(String name, Integer age) {
        System.out.println("====业务方法doFirst()====");
        return "doFirst";
    }

定义切面:

public class MyAspect {
    /**
     * 环绕通知方法的定义格式
     *  1.public
     *  2.必须有一个返回值,推荐使用Object
     *  3.方法名称自定义
     *  4.方法有参数,固定的参数 ProceedingJoinPoint
     */
    /**
     * @Around: 环绕通知
     *    属性:value 切入点表达式
     *    位置:在方法的定义什么
     * 特点:
     *   1.它是功能最强的通知
     *   2.在目标方法的前和后都能增强功能。
     *   3.控制目标方法是否被调用执行
     *   4.修改原来的目标方法的执行结果。 影响最后的调用结果
     *
     *  环绕通知,等同于jdk动态代理的,InvocationHandler接口
     *
     *  参数:  ProceedingJoinPoint 就等同于 Method
     *         作用:执行目标方法的
     *  返回值: 就是目标方法的执行结果,可以被修改。
     *
     *  环绕通知: 经常做事务, 在目标方法之前开启事务,执行目标方法, 在目标方法之后提交事务
     */
    @Around(value = "execution(* *..SomeServiceImpl.doFirst(..))")
    public Object myAround(ProceedingJoinPoint pjp) throws Throwable {
        String name = "";
        //获取第一个参数值
        Object args [] = pjp.getArgs();
        if( args!= null && args.length > 1){
              Object arg=  args[0];
              name =(String)arg;
        }
       //实现环绕通知
        Object result = null;
        System.out.println("环绕通知:在目标方法之前,输出时间:"+ new Date());
        //1.目标方法调用
        if( "zhangsan".equals(name)){
            //符合条件,调用目标方法
            result = pjp.proceed(); //method.invoke(); Object result = doFirst();
        }
        System.out.println("环绕通知:在目标方法之后,提交事务");
        //2.在目标方法的前或者后加入功能
        //修改目标方法的执行结果, 影响方法最后的调用结果
        if( result != null){
              result = "Hello AspectJ AOP";
        }
        //返回目标方法的执行结果
        return result;
    }
}

(5) [了解]@AfterThrowing 异常通知-注解中有 throwing 属性
  在目标方法抛出异常后执行。该注解的 throwing 属性用于指定所发生的异常类对象。 当然,被注解为异常通知的方法可以包含一个参数 Throwable,参数名称为 throwing 指定的 名称,表示发生的异常对象。
增加业务方法:

public interface SomeService {
    void doSome(String name, Integer age);
    String doOther(String name, Integer age);
    Student doOther2(String name, Integer age);
    String doFirst(String name, Integer age);
    void doSecond();
    void doThird();
}

方法实现:

 @Override
    public void doSecond() {
        System.out.println("执行业务方法doSecond()" + (10/0));
    }

定义切面

@AfterThrowing(value = "execution(* *..SomeServiceImpl.doSecond(..))",
            throwing = "ex")
    public void myAfterThrowing(Exception ex) {
        System.out.println("异常通知:方法发生异常时,执行:"+ex.getMessage());
        //发送邮件,短信,通知开发人员
    }

(6) [了解]@After 最终通知
  无论目标方法是否抛出异常,该增强均会被执行。
增加方法:

public interface SomeService {
    void doSome(String name, Integer age);
    String doOther(String name, Integer age);
    Student doOther2(String name, Integer age);
    String doFirst(String name, Integer age);
    void doSecond();
    void doThird();
}

方法实现:

 @Override
    public void doThird() {
        System.out.println("执行业务方法doThird()"+ (10/0));
    }

定义切面:

/**
 *  @Aspect : 是aspectj框架中的注解。
 *     作用:表示当前类是切面类。
 *     切面类:是用来给业务方法增加功能的类,在这个类中有切面的功能代码
 *     位置:在类定义的上面
 */
@Aspect
public class MyAspect {
    /**
     * 最终通知方法的定义格式
     *  1.public
     *  2.没有返回值
     *  3.方法名称自定义
     *  4.方法没有参数,  如果还有是JoinPoint,
     */
    /**
     * @After :最终通知
     *    属性: value 切入点表达式
     *    位置: 在方法的上面
     * 特点:
     *  1.总是会执行
     *  2.在目标方法之后执行的
     *  try{
     *      SomeServiceImpl.doThird(..)
     *  }catch(Exception e){
     *
     *  }finally{
     *      myAfter()
     *  }
     */
    @After(value = "execution(* *..SomeServiceImpl.doThird(..))")
    public  void  myAfter(){
        System.out.println("执行最终通知,总是会被执行的代码");
        //一般做资源清除工作的。
     }
}

(7) @Pointcut 定义切入点
  当较多的通知增强方法使用相同的 execution 切入点表达式时,编写、维护均较为麻烦。 AspectJ 提供了@Pointcut 注解,用于定义 execution 切入点表达式。
  其用法是,将@Pointcut 注解在一个方法之上,以后所有的 execution 的 value 属性值均 可使用该方法名作为切入点。代表的就是@Pointcut 定义的切入点。这个使用@Pointcut 注解 的方法一般使用 private 的标识方法,即没有实际作用的方法。

 @After(value = "execution(* *..SomeServiceImpl.doThird(..))")
    public  void  myAfter(){
        System.out.println("执行最终通知,总是会被执行的代码");
        //一般做资源清除工作的。
     }
 /**
     * @Pointcut: 定义和管理切入点, 如果你的项目中有多个切入点表达式是重复的,可以复用的。
     *            可以使用@Pointcut
     *    属性:value 切入点表达式
     *    位置:在自定义的方法上面
     * 特点:
     *   当使用@Pointcut定义在一个方法的上面 ,此时这个方法的名称就是切入点表达式的别名。
     *   其它的通知中,value属性就可以使用这个方法名称,代替切入点表达式了
     */
    @Pointcut(value = "execution(* *..SomeServiceImpl.doThird(..))" )
    private void mypt(){
        //无需代码,
    }

第4章 Spring 集成 MyBatis

  将 MyBatis与 Spring 进行整合,主要解决的问题就是将 SqlSessionFactory 对象交由 Spring 来管理。所以,该整合,只需要将 SqlSessionFactory 的对象生成器 SqlSessionFactoryBean 注 册在 Spring 容器中,再将其注入给 Dao 的实现类即可完成整合。
  实现 Spring 与 MyBatis 的整合常用的方式:扫描的 Mapper 动态代理
  Spring 像插线板一样,mybatis 框架是插头,可以容易的组合到一起。插线板 spring 插 上 mybatis,两个框架就是一个整体。

4.1.1 MySQL 创建数据库 springdb,新建表 Student

在这里插入图片描述

4.1.2 maven 依赖 pom.xml

<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.11</version>
    <scope>test</scope>   
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId> 
    <version>5.2.5.RELEASE</version>
</dependency> 
<dependency> 
	<groupId>org.springframework</groupId> 
	<artifactId>spring-tx</artifactId>    		
	<version>5.2.5.RELEASE</version> 
</dependency>
<dependency>    
	<groupId>org.springframework</groupId>
	<artifactId>spring-jdbc</artifactId>
	<version>5.2.5.RELEASE</version>   
</dependency> 
<dependency>
    <groupId>org.mybatis</groupId> 
    <artifactId>mybatis</artifactId> 
    <version>3.5.1</version> 
    </dependency>
<dependency> 
	<groupId>org.mybatis</groupId>
	<artifactId>mybatis-spring</artifactId>  
	<version>1.3.1</version>
</dependency>
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.9</version>
</dependency>
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.1.12</version> 
</dependency> 

插件:

<build>
 	<resources>
    	<resource> 
        	<directory>src/main/java</directory><!--所在的目录-->         
        	<includes><!--包括目录下的.properties,.xml 文件都会扫描到-->           
        		<include>**/*.properties</include>           
        		<include>**/*.xml</include>         
        	</includes>         
        	<filtering>false</filtering>       
        </resource>  
    </resources> 
 
    <plugins>       
    	<plugin>         
    		<artifactId>maven-compiler-plugin</artifactId>         
    		<version>3.1</version>         
    		<configuration>           
    			<source>1.8</source>           
    			<target>1.8</target>         
    		</configuration>       
    	</plugin>     
    </plugins>   
</build>

4.1.3 定义实体类 Student

public class Student {
    //属性名和列名一样。
    private Integer id;
    private String name;
    private String email;
    private Integer age;

4.1.4 定义 StudentDao 接口

public interface StudentDao {

    int insertStudent(Student student);
    List<Student> selectStudents();

}

4.1.5 定义映射文件 mapper

  在 Dao 接口的包中创建 MyBatis 的映射文件 mapper,命名与接口名相同,本例为StudentDao.xml。mapper 中的 namespace 取值也为 Dao 接口的全限定性名。

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.bjpowernode.dao.StudentDao">

    <insert id="insertStudent">
        insert into student values(#{id},#{name},#{email},#{age})
    </insert>

    <select id="selectStudents" resultType="Student">
        select id,name,email,age from student order by id desc
    </select>
</mapper>

4.1.6 定义 Service 接口和实现类

接口定义:

public interface StudentService {

    int addStudent(Student student);
    List<Student> queryStudents();
}

定义实现类:

public class StudentServiceImpl implements StudentService {

    //引用类型
    private StudentDao studentDao;

    //使用set注入,赋值
    public void setStudentDao(StudentDao studentDao) {
        this.studentDao = studentDao;
    }

    @Override
    public int addStudent(Student student) {
        int nums = studentDao.insertStudent(student);
        return nums;
    }

    @Override
    public List<Student> queryStudents() {
        List<Student> students = studentDao.selectStudents();
        return students;
    }
}

4.1.7 定义 MyBatis 主配置文件

  在 src 下定义 MyBatis 的主配置文件,命名为 mybatis.xml。
  这里有两点需要注意:
(1)主配置文件中不再需要数据源的配置了。因为数据源要交给 Spring 容器来管理了。
(2)这里对 mapper 映射文件的注册,使用标签,即只需给出 mapper 映射文件 所在的包即可。因为 mapper 的名称与 Dao 接口名相同,可以使用这种简单注册方式。这种 方式的好处是,若有多个映射文件,这里的配置也是不用改变的。当然,也可使用原来的 标签方式。

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>

    <!--settings:控制mybatis全局行为-->
    <settings>
        <!--设置mybatis输出日志-->
        <setting name="logImpl" value="STDOUT_LOGGING"/>
    </settings>

    <!--设置别名-->
    <typeAliases>
        <!--name:实体类所在的包名
            表示com.bjpowernode.domain包中的列名就是别名
            你可以使用Student表示com.bjpowenrode.domain.Student
        -->
        <package name="com.bjpowernode.domain"/>
    </typeAliases>


    <!-- sql mapper(sql映射文件)的位置-->
    <mappers>
        <!--
          name:是包名, 这个包中的所有mapper.xml一次都能加载
        -->
        <package name="com.bjpowernode.dao"/>
    </mappers>
</configuration>

4.1.8 修改 Spring 配置文件

(1) 数据源的配置(掌握)
  使用 JDBC 模板,首先需要配置好数据源,数据源直接以 Bean 的形式配置在 Spring 配 置文件中。根据数据源的不同,其配置方式不同:
Druid 数据源 DruidDataSource
  Druid 是阿里的开源数据库连接池。是 Java 语言中最好的数据库连接池。Druid 能 够提供强大的监控和扩展功能。Druid 与其他数据库连接池的最大区别是提供数据库的
官网:https://github.com/alibaba/druid
使用地址:https://github.com/alibaba/druid/wiki/常见问题
配置连接池:
在这里插入图片描述
Spring 配置文件:

<!--
       把数据库的配置信息,写在一个独立的文件,编译修改数据库的配置内容
       spring知道jdbc.properties文件的位置
       配置案例的Druid数据库连接池,无需配置驱动,可以根据url检测出驱动
    -->
    <context:property-placeholder location="classpath:jdbc.properties" />

    <!--声明数据源DataSource, 作用是连接数据库的-->
    <bean id="myDataSource" class="com.alibaba.druid.pool.DruidDataSource"
          init-method="init" destroy-method="close">
        <!--set注入给DruidDataSource提供连接数据库信息 -->
        <!--    使用属性配置文件中的数据,语法 ${key} -->
        <property name="url" value="${jdbc.url}" /><!--setUrl()-->
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.passwd}" />
        <property name="maxActive" value="${jdbc.max}" />
    </bean>

(2) 从属性文件读取数据库连接信息
  为了便于维护,可以将数据库连接信息写入到属性文件中,使 Spring 配置文件从中读取 数据。
  属性文件名称自定义,但一般都是放在 src 下。
在这里插入图片描述
  Spring 配置文件从属性文件中读取数据时,需要在的 value 属性中使用${ }, 将在属性文件中定义的 key 括起来,以引用指定属性的值。

<property name="url" value="${jdbc.url}" /><!--setUrl()-->

  该属性文件若要被 Spring 配置文件读取,其必须在配置文件中进行注册。使用 标签。
context:property-placeholder/方式(掌握)
该方式要求在 Spring 配置文件头部加入 spring-context.xsd 约束文件
context:property-placeholder/标签中有一个属性 location,用于指定属性文件的位置。

<!--引入属性配置文件 spring-context.xsd
        location:指定属性配置文件的位置,使用classpath表示类路径-->
    <context:property-placeholder location="classpath:jdbc.properties" />

(3) 注册 SqlSessionFactoryBean

 <!--声明的是mybatis中提供的SqlSessionFactoryBean类,这个类内部创建SqlSessionFactory的
        SqlSessionFactory  sqlSessionFactory = new ..
    -->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <!--set注入,把数据库连接池付给了dataSource属性-->
        <property name="dataSource" ref="myDataSource" />
        <!--mybatis主配置文件的位置
           configLocation属性是Resource类型,读取配置文件
           它的赋值,使用value,指定文件的路径,使用classpath:表示文件的位置
        -->
        <property name="configLocation" value="classpath:mybatis.xml" />
    </bean>

(4) 定义 Mapper 扫描配置器 MapperScannerConfigurer
  Mapper 扫描配置器 MapperScannerConfigurer 会自动生成指定的基本包中 mapper 的代 理对象。该 Bean 无需设置 id 属性。basePackage 使用分号或逗号设置多个包。

<!--创建dao对象,使用SqlSession的getMapper(StudentDao.class)
        MapperScannerConfigurer:在内部调用getMapper()生成每个dao接口的代理对象。

    -->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <!--指定SqlSessionFactory对象的id-->
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />
        <!--指定包名, 包名是dao接口所在的包名。
            MapperScannerConfigurer会扫描这个包中的所有接口,把每个接口都执行
            一次getMapper()方法,得到每个接口的dao对象。
            创建好的dao对象放入到spring的容器中的。 dao对象的默认名称是 接口名首字母小写
        -->
        <property name="basePackage" value="com.bjpowernode.dao"/>
    </bean>

4.1.9 向 Service 注入接口名

  向 Service 注入 Mapper 代理对象时需要注意,由于通过 Mapper 扫描配置器 MapperScannerConfigurer 生成的 Mapper 代理对象没有名称,所以在向 Service 注入 Mapper 代理时,无法通过名称注入。但可通过接口的简单类名注入,因为生成的是这个 Dao 接口 的对象。

<!--注册StudentService对象,给属性StudentDao注入值-->
    <bean id="studentService" class="com.bjpowernode.service.impl.StudentServiceImpl">
        <property name="studentDao" ref="studentDao" />
    </bean>

4.1.10 Spring 配置文件全部配置

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

    <!--
       把数据库的配置信息,写在一个独立的文件,编译修改数据库的配置内容
       spring知道jdbc.properties文件的位置
       配置案例的Druid数据库连接池,无需配置驱动,可以根据url检测出驱动
    -->
    <!--引入属性配置文件 spring-context.xsd
        location:指定属性配置文件的位置,使用classpath表示类路径-->
    <context:property-placeholder location="classpath:jdbc.properties" />

    <!--声明数据源DataSource, 作用是连接数据库的-->
    <bean id="myDataSource" class="com.alibaba.druid.pool.DruidDataSource"
          init-method="init" destroy-method="close">
        <!--set注入给DruidDataSource提供连接数据库信息 -->
        <!--    使用属性配置文件中的数据,语法 ${key} -->
        <property name="url" value="${jdbc.url}" /><!--setUrl()-->
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.passwd}" />
        <property name="maxActive" value="${jdbc.max}" />
    </bean>

    <!--声明的是mybatis中提供的SqlSessionFactoryBean类,这个类内部创建SqlSessionFactory的
        SqlSessionFactory  sqlSessionFactory = new ..
    -->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <!--set注入,把数据库连接池付给了dataSource属性-->
        <property name="dataSource" ref="myDataSource" />
        <!--mybatis主配置文件的位置
           configLocation属性是Resource类型,读取配置文件
           它的赋值,使用value,指定文件的路径,使用classpath:表示文件的位置
        -->
        <property name="configLocation" value="classpath:mybatis.xml" />
    </bean>

    <!--创建dao对象,使用SqlSession的getMapper(StudentDao.class)
        MapperScannerConfigurer:在内部调用getMapper()生成每个dao接口的代理对象。

    -->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <!--指定SqlSessionFactory对象的id-->
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />
        <!--指定包名, 包名是dao接口所在的包名。
            MapperScannerConfigurer会扫描这个包中的所有接口,把每个接口都执行
            一次getMapper()方法,得到每个接口的dao对象。
            创建好的dao对象放入到spring的容器中的。 dao对象的默认名称是 接口名首字母小写
        -->
        <property name="basePackage" value="com.bjpowernode.dao"/>
    </bean>

    <!--注册StudentService对象,给属性StudentDao注入值-->
    <bean id="studentService" class="com.bjpowernode.service.impl.StudentServiceImpl">
        <property name="studentDao" ref="studentDao" />
    </bean>

</beans>

第5章 Spring 事务

5.1 Spring 的事务管理

  事务原本是数据库中的概念,在 Dao 层。但一般情况下,需要将事务提升到业务层, 即 Service 层。这样做是为了能够使用事务的特性来管理具体的业务。
  在 Spring 中通常可以通过以下两种方式来实现对事务的管理:
  (1)使用 Spring 的事务注解管理事务
  (2)使用 AspectJ 的 AOP 配置管理事务

5.2 Spring 事务管理 API

  Spring 的事务管理,主要用到两个事务相关的接口。
(1) 事务管理器接口(重点)
事务管理器是 PlatformTransactionManager 接口对象。其主要用于完成事务的提交、回 滚,及获取事务的状态信息。
在这里插入图片描述
A、 常用的两个实现类
PlatformTransactionManager 接口有两个常用的实现类:
➢ DataSourceTransactionManager:使用 JDBC 或 MyBatis 进行数据库操作时使用。
➢ HibernateTransactionManager:使用 Hibernate 进行持久化数据时使用。
B、 Spring 的回滚方式(理解)
  Spring 事务的默认回滚方式是:发生运行时异常和 error 时回滚,发生受查(编译)异常时 提交。不过,对于受查异常,程序员也可以手工设置其回滚方式。
C、 回顾错误与异常(理解)
在这里插入图片描述
  Throwable 类是 Java 语言中所有错误或异常的超类。只有当对象是此类(或其子类之一) 的实例时,才能通过 Java 虚拟机或者 Java 的 throw 语句抛出。
   Error 是程序在运行过程中出现的无法处理的错误,比如 OutOfMemoryError、 ThreadDeath、NoSuchMethodError 等。当这些错误发生时,程序是无法处理(捕获或抛出) 的,JVM 一般会终止线程。
   程序在编译和运行时出现的另一类错误称之为异常,它是JVM通知程序员的一种方式。 通过这种方式,让程序员知道已经或可能出现错误,要求程序员对其进行处理。
   异常分为运行时异常与受查异常。
   运行时异常,是 RuntimeException 类或其子类,即只有在运行时才出现的异常。如, NullPointerException、ArrayIndexOutOfBoundsException、IllegalArgumentException 等均属于 运行时异常。这些异常由 JVM 抛出,在编译时不要求必须处理(捕获或抛出)。但,只要代 码编写足够仔细,程序足够健壮,运行时异常是可以避免的。
   受查异常,也叫编译时异常,即在代码编写时要求必须捕获或抛出的异常,若不处理, 则无法通过编译。如 SQLException,ClassNotFoundException,IOException 等都属于受查异常。
  RuntimeException 及其子类以外的异常,均属于受查异常。当然,用户自定义的 Exception 的子类,即用户自定义的异常也属受查异常。程序员在定义异常时,只要未明确声明定义的 为 RuntimeException 的子类,那么定义的就是受查异常。

(2) 事务定义接口
  事务定义接口TransactionDefinition中定义了事务描述相关的三类常量:事务隔离级别、 事务传播行为、事务默认超时时限,及对它们的操作。
在这里插入图片描述
A、 定义了五个事务隔离级别常量(掌握)
这些常量均是以 ISOLATION_开头。即形如 ISOLATION_XXX。
➢ DEFAULT:采用 DB 默认的事务隔离级别。MySql 的默认为 REPEATABLE_READ; Oracle 默认为 READ_COMMITTED。
➢ READ_UNCOMMITTED:读未提交。未解决任何并发问题。
➢ READ_COMMITTED:读已提交。解决脏读,存在不可重复读与幻读。
➢ REPEATABLE_READ:可重复读。解决脏读、不可重复读,存在幻读
➢ SERIALIZABLE:串行化。不存在并发问题。
B、 定义了七个事务传播行为常量(掌握)
  所谓事务传播行为是指,处于不同事务中的方法在相互调用时,执行期间事务的维护情 况。如,A 事务中的方法 doSome()调用 B 事务中的方法 doOther(),在调用执行期间事务的 维护情况,就称为事务传播行为。
  事务传播行为是加在方法上的。 事务传播行为常量都是以 PROPAGATION_ 开头,形如 PROPAGATION_XXX。

PROPAGATION_REQUIRED
PROPAGATION_REQUIRES_NEW
PROPAGATION_SUPPORTS

PROPAGATION_MANDATORY
PROPAGATION_NESTED
PROPAGATION_NEVER
PROPAGATION_NOT_SUPPORTED
a、 PROPAGATION_REQUIRED:
  指定的方法必须在事务内执行。若当前存在事务,就加入到当前事务中;若当前没有事 务,则创建一个新事务。这种传播行为是最常见的选择,也是 Spring 默认的事务传播行为。
  如该传播行为加在 doOther()方法上。若 doSome()方法在调用 doOther()方法时就是在事 务内运行的,则 doOther()方法的执行也加入到该事务内执行。若 doSome()方法在调用 doOther()方法时没有在事务内执行,则 doOther()方法会创建一个事务,并在其中执行。
在这里插入图片描述
b、 PROPAGATION_SUPPORTS
  指定的方法支持当前事务,但若当前没有事务,也可以以非事务方式执行。

在这里插入图片描述

c、 PROPAGATION_REQUIRES_NEW
  总是新建一个事务,若当前存在事务,就将当前事务挂起,直到新事务执行完毕。
在这里插入图片描述
d、 定义了默认事务超时时限
  常量 TIMEOUT_DEFAULT 定义了事务底层默认的超时时限,sql 语句的执行时长。
  注意,事务的超时时限起作用的条件比较多,且超时的时间计算点较复杂。所以,该 值一般就使用默认值即可。

5.3 程序举例环境搭建

举例:购买商品 trans_sale 项目
本例要实现购买商品,模拟用户下订单,向订单表添加销售记录,从商品表减少库存。
实现步骤:
Step0:创建数据库表
创建两个数据库表 sale , goods
sale 销售表
在这里插入图片描述
goods 商品表
在这里插入图片描述
goods 表数据
在这里插入图片描述
Step1: maven 依赖 pom.xml

<dependency>
	<groupId>junit</groupId>    
	<artifactId>junit</artifactId>    
	<version>4.11</version>    
	<scope>test</scope>   
</dependency>   
	<dependency>    
		<groupId>org.springframework</groupId>    
		<artifactId>spring-context</artifactId>    
		<version>5.2.5.RELEASE</version>   
</dependency>   
<dependency> 
	<groupId>org.springframework</groupId>    
	<artifactId>spring-tx</artifactId>    
	<version>5.2.5.RELEASE</version>   
</dependency>   
<dependency>    
	<groupId>org.springframework</groupId>    
	<artifactId>spring-jdbc</artifactId>    
	<version>5.2.5.RELEASE</version>   
</dependency> 
<dependency>    
	<groupId>org.mybatis</groupId>    
	<artifactId>mybatis</artifactId>    
	<version>3.5.1</version>   
</dependency>   
<dependency>    
	<groupId>org.mybatis</groupId>    
	<artifactId>mybatis-spring</artifactId>    
	<version>1.3.1</version>   
</dependency>   
<dependency>    
	<groupId>mysql</groupId>    
	<artifactId>mysql-connector-java</artifactId>    
	<version>5.1.9</version>   
</dependency> 
 <dependency>    
 	<groupId>com.alibaba</groupId>    
 	<artifactId>druid</artifactId>    
 	<version>1.1.12</version>   
 </dependency> 
插件 
<build>     
	<resources>       
		<resource>         
			<directory>src/main/java</directory><!--所在的目录-->         
			<includes><!--包括目录下的.properties,.xml 文件都会扫描到-->           
				<include>**/*.properties</include>           
				<include>**/*.xml</include>         
			</includes>         
			<filtering>false</filtering>       
		</resource>     
	</resources> 
	<plugins>       
		<plugin>         
			<artifactId>maven-compiler-plugin</artifactId>         
			<version>3.1</version>         
			<configuration>           
				<source>1.8</source>           
				<target>1.8</target>         
			</configuration>       
		</plugin>     
	</plugins>  
</build> 
 

Step2:创建实体类

public class Goods {

    private Integer id;
    private String name;
    private Integer amount;
    private Float price;
public class Sale {
    private Integer id;
    private Integer gid;
    private Integer nums;

Step3:定义 dao 接口

public interface GoodsDao {
    //更新库存
    //goods表示本次用户购买的商品信息, id, 购买数量
    int updateGoods(Goods goods);

    //查询商品的信息
    Goods selectGoods(Integer id);
}

public interface SaleDao {
    //增加销售记录
    int insertSale(Sale sale);
}

Step4:定义 dao 接口对应的 sql 映射文件

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.bjpowernode.dao.GoodsDao">
    <select id="selectGoods" resultType="com.bjpowernode.domain.Goods">
        select id,name,amount,price from goods where id=#{gid}
    </select>

    <update id="updateGoods">
        update goods set amount = amount - #{amount} where id=#{id}
    </update>
</mapper>
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.bjpowernode.dao.SaleDao">
    <insert id="insertSale">
        insert into sale(gid,nums) values(#{gid},#{nums})
    </insert>
</mapper>

Step5:定义异常类
定义 service 层可能会抛出的异常类 NotEnoughException

//自定义的运行时异常
public class NotEnoughException extends RuntimeException {
    public NotEnoughException() {
        super();
    }

    public NotEnoughException(String message) {
        super(message);
    }
}

Step6:定义 Service 接口
定义 Service 接口 BuyGoodsService

public interface BuyGoodsService {
    //购买商品的方法, goodsId:购买商品的编号, nums:购买的数量
    void buy(Integer goodsId,Integer nums);
}

Step7:定义 service 的实现类

public class BuyGoodsServiceImpl implements BuyGoodsService {

    private SaleDao saleDao;
    private GoodsDao goodsDao;
    @Override
    public void buy(Integer goodsId, Integer nums) {
        System.out.println("=====buy方法的开始====");
        //记录销售信息,向sale表添加记录
        Sale sale  = new Sale();
        sale.setGid(goodsId);
        sale.setNums(nums);
        saleDao.insertSale(sale);
        //更新库存
        Goods goods  = goodsDao.selectGoods(goodsId);
        if( goods == null){
            //商品不存在
            throw  new  NullPointerException("编号是:"+goodsId+",商品不存在");
        } else if( goods.getAmount() < nums){
            //商品库存不足
            throw new NotEnoughException("编号是:"+goodsId+",商品库存不足");
        }
        //修改库存了
        Goods buyGoods = new Goods();
        buyGoods.setId( goodsId);
        buyGoods.setAmount(nums);
        goodsDao.updateGoods(buyGoods);
        System.out.println("=====buy方法的完成====");
    }
    public void setSaleDao(SaleDao saleDao) {
        this.saleDao = saleDao;
    }
    public void setGoodsDao(GoodsDao goodsDao) {
        this.goodsDao = goodsDao;
    }
}

Step8:修改 Spring 配置文件内容
声明 Mybatis 对象
声明业务层对象

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

    <!--
       把数据库的配置信息,写在一个独立的文件,编译修改数据库的配置内容
       spring知道jdbc.properties文件的位置
    -->
    <context:property-placeholder location="classpath:jdbc.properties" />

    <!--声明数据源DataSource, 作用是连接数据库的-->
    <bean id="myDataSource" class="com.alibaba.druid.pool.DruidDataSource"
          init-method="init" destroy-method="close">
        <!--set注入给DruidDataSource提供连接数据库信息 -->
        <!--    使用属性配置文件中的数据,语法 ${key} -->
        <property name="url" value="${jdbc.url}" /><!--setUrl()-->
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.passwd}" />
        <property name="maxActive" value="${jdbc.max}" />
    </bean>

    <!--声明的是mybatis中提供的SqlSessionFactoryBean类,这个类内部创建SqlSessionFactory的
        SqlSessionFactory  sqlSessionFactory = new ..
    -->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <!--set注入,把数据库连接池付给了dataSource属性-->
        <property name="dataSource" ref="myDataSource" />
        <!--mybatis主配置文件的位置
           configLocation属性是Resource类型,读取配置文件
           它的赋值,使用value,指定文件的路径,使用classpath:表示文件的位置
        -->
        <property name="configLocation" value="classpath:mybatis.xml" />
    </bean>

    <!--创建dao对象,使用SqlSession的getMapper(StudentDao.class)
        MapperScannerConfigurer:在内部调用getMapper()生成每个dao接口的代理对象。

    -->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <!--指定SqlSessionFactory对象的id-->
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />
        <!--指定包名, 包名是dao接口所在的包名。
            MapperScannerConfigurer会扫描这个包中的所有接口,把每个接口都执行
            一次getMapper()方法,得到每个接口的dao对象。
            创建好的dao对象放入到spring的容器中的。 dao对象的默认名称是 接口名首字母小写
        -->
        <property name="basePackage" value="com.bjpowernode.dao"/>
    </bean>

    <!--声明service-->
    <bean id="buyService" class="com.bjpowernode.service.impl.BuyGoodsServiceImpl">
        <property name="goodsDao" ref="goodsDao" />
        <property name="saleDao" ref="saleDao" />
    </bean>

</beans>

Step9:定义测试类
定义测试类 MyTest。现在就可以在无事务代理的情况下运行了

public class MyTest {
    @Test
    public void test01(){
        String config="applicationContext.xml";
        ApplicationContext ctx = new ClassPathXmlApplicationContext(config);
        //从容器获取service
        BuyGoodsService service = (BuyGoodsService) ctx.getBean("buyService");
        //调用方法
        service.buy(1001,200);
    }
}

5.4 使用 Spring 的事务注解管理事务(掌握)

  通过@Transactional 注解方式,可将事务织入到相应 public 方法中,实现事务管理。
@Transactional 的所有可选属性如下所示:
➢ propagation:用于设置事务传播属性。该属性类型为 Propagation 枚举,默认值为Propagation.REQUIRED。
➢ isolation:用于设置事务的隔离级别。该属性类型为 Isolation 枚举,默认值为 Isolation.DEFAULT。
➢ readOnly:用于设置该方法对数据库的操作是否是只读的。该属性为 boolean,默认值 为 false。
➢ timeout:用于设置本操作与数据库连接的超时时限。单位为秒,类型为 int,默认值为 -1,即没有时限。
➢ rollbackFor:指定需要回滚的异常类。类型为 Class[],默认值为空数组。当然,若只有 一个异常类时,可以不使用数组。
➢ rollbackForClassName:指定需要回滚的异常类类名。类型为 String[],默认值为空数组。 当然,若只有一个异常类时,可以不使用数组。
➢ noRollbackFor:指定不需要回滚的异常类。类型为 Class[],默认值为空数组。当然,若 只有一个异常类时,可以不使用数组。
➢ noRollbackForClassName:指定不需要回滚的异常类类名。类型为 String[],默认值为空 数组。当然,若只有一个异常类时,可以不使用数组。
  需要注意的是,@Transactional 若用在方法上,只能用于 public 方法上。对于其他非 public 方法,如果加上了注解@Transactional,虽然 Spring 不会报错,但不会将指定事务织入到该 方法中。因为 Spring 会忽略掉所有非 public 方法上的@Transaction 注解。
  若@Transaction 注解在类上,则表示该类上所有的方法均将在执行时织入事务。
实现注解的事务步骤:
复制 trans_sale 项目,新项目 trans_sale_annotation

  1. 声明事务管理器
 <!--1. 声明事务管理器-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <!--连接的数据库, 指定数据源-->
        <property name="dataSource" ref="myDataSource" />
    </bean>
  1. 开启注解驱动
<!--2. 开启事务注解驱动,告诉spring使用注解管理事务,创建代理对象
           transaction-manager:事务管理器对象的id
    -->
    <tx:annotation-driven transaction-manager="transactionManager" />
  1. 业务层 public 方法加入事务属性
public class BuyGoodsServiceImpl implements BuyGoodsService {

    private SaleDao saleDao;
    private GoodsDao goodsDao;

    /**
     *
     * rollbackFor:表示发生指定的异常一定回滚.
     *   处理逻辑是:
     *     1) spring框架会首先检查方法抛出的异常是不是在rollbackFor的属性值中
     *         如果异常在rollbackFor列表中,不管是什么类型的异常,一定回滚。
     *     2) 如果你的抛出的异常不在rollbackFor列表中,spring会判断异常是不是RuntimeException,
     *         如果是一定回滚。
     *
     */
   /* @Transactional(
            propagation = Propagation.REQUIRED,
            isolation = Isolation.DEFAULT,
            readOnly = false,
            rollbackFor = {
                    NullPointerException.class,  NotEnoughException.class
            }
    )*/

    //使用的是事务控制的默认值, 默认的传播行为是REQUIRED,默认的隔离级别DEFAULT
    //默认抛出运行时异常,回滚事务。
    @Transactional
    @Override
    public void buy(Integer goodsId, Integer nums) {
        System.out.println("=====buy方法的开始====");
        //记录销售信息,向sale表添加记录
        Sale sale  = new Sale();
        sale.setGid(goodsId);
        sale.setNums(nums);
        saleDao.insertSale(sale);

        //更新库存
        Goods goods  = goodsDao.selectGoods(goodsId);
        if( goods == null){
            //商品不存在
            throw  new  NullPointerException("编号是:"+goodsId+",商品不存在");
        } else if( goods.getAmount() < nums){
            //商品库存不足
            throw new NotEnoughException("编号是:"+goodsId+",商品库存不足");
        }
        //修改库存了
        Goods buyGoods = new Goods();
        buyGoods.setId( goodsId);
        buyGoods.setAmount(nums);
        goodsDao.updateGoods(buyGoods);
        System.out.println("=====buy方法的完成====");
    }
    public void setSaleDao(SaleDao saleDao) {
        this.saleDao = saleDao;
    }
    public void setGoodsDao(GoodsDao goodsDao) {
        this.goodsDao = goodsDao;
    }
}

5.5 使用 AspectJ 的 AOP 配置管理事务(掌握)

  使用 XML 配置事务代理的方式的不足是,每个目标类都需要配置事务代理。当目标类 较多,配置文件会变得非常臃肿。
  使用 XML 配置顾问方式可以自动为每个符合切入点表达式的类生成事务代理。其用法 很简单,只需将前面代码中关于事务代理的配置删除,再替换为如下内容即可。
Step1:复制项目
复制 trans_sale 项目,并重命名为 trans_sal_aspectj。在此基础上修改。
Step2:maven 依赖 pom.xml
新加入 aspectj 的依赖坐标

<dependency>  
	<groupId>org.springframework</groupId>  
	<artifactId>spring-aspects</artifactId>  
	<version>5.2.5.RELEASE</version> 
</dependency> 

Step3:在容器中添加事务管理器

<!--声明式事务处理:和源代码完全分离的-->
    <!--1.声明事务管理器对象-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="myDataSource" />
    </bean>

Step4:配置事务通知

<!--2.声明业务方法它的事务属性(隔离级别,传播行为,超时时间)
          id:自定义名称,表示 <tx:advice> 和 </tx:advice>之间的配置内容的
          transaction-manager:事务管理器对象的id
    -->
    <tx:advice id="myAdvice" transaction-manager="transactionManager">
        <!--tx:attributes:配置事务属性-->
        <tx:attributes>
            <!--tx:method:给具体的方法配置事务属性,method可以有多个,分别给不同的方法设置事务属性
                name:方法名称,1)完整的方法名称,不带有包和类。
                              2)方法可以使用通配符,* 表示任意字符
                propagation:传播行为,枚举值
                isolation:隔离级别
                rollback-for:你指定的异常类名,全限定类名。 发生异常一定回滚
            -->
            <tx:method name="buy" propagation="REQUIRED" isolation="DEFAULT"
                       rollback-for="java.lang.NullPointerException,com.bjpowernode.excep.NotEnoughException"/>

            <!--使用通配符,指定很多的方法-->
            <tx:method name="add*" propagation="REQUIRES_NEW" />
            <!--指定修改方法-->
            <tx:method name="modify*" />
            <!--删除方法-->
            <tx:method name="remove*" />
            <!--查询方法,query,search,find-->
            <tx:method name="*" propagation="SUPPORTS" read-only="true" />
        </tx:attributes>
    </tx:advice>

Step5:配置增强器
指定将配置好的事务通知,织入给谁。

 <!--配置aop-->
    <aop:config>
        <!--配置切入点表达式:指定哪些包中类,要使用事务
            id:切入点表达式的名称,唯一值
            expression:切入点表达式,指定哪些类要使用事务,aspectj会创建代理对象

            com.bjpowernode.service
            com.crm.service
            com.service
        -->
        <aop:pointcut id="servicePt" expression="execution(* *..service..*.*(..))"/>

        <!--配置增强器:关联adivce和pointcut
           advice-ref:通知,上面tx:advice哪里的配置
           pointcut-ref:切入点表达式的id
        -->
        <aop:advisor advice-ref="myAdvice" pointcut-ref="servicePt" />
    </aop:config>

Step6:修改测试类
测试类中要从容器中获取的是目标对象。

public class MyTest {

    @Test
    public void test01(){
        String config="applicationContext.xml";
        ApplicationContext ctx = new ClassPathXmlApplicationContext(config);
        //从容器获取service
        BuyGoodsService service = (BuyGoodsService) ctx.getBean("buyService");

        //com.sun.proxy.$Proxy12
        System.out.println("service是代理:"+service.getClass().getName());
        //调用方法
        service.buy(1001,10);
    }

}

第6章 Spring 与 Web

  在 Web 项目中使用 Spring 框架,首先要解决在 web 层(这里指 Servlet)中获取到 Spring 容器的问题。只要在 web 层获取到了 Spring 容器,便可从容器中获取到 Service 对象。

6.1 Web 项目使用 Spring 的问题(了解)

举例:springWeb 项目(在 spring-mybatis 基础上修改)
Step1:新建一个 Maven Project
类型 maven-archetype-webapp
Step2: 复制代码,配置文件,jar
将 spring-mybatis 项目中以下内容复制到当前项目中:
(1)Service 层、Dao 层全部代码
(2)配置文件 applicationContext.xml 及 jdbc.properties,mybatis.xml
(3)pom.xml
(4)加入 servlet ,jsp 依赖
在之前原有的 pom.xml 文件中再加入以下的内容:

<!-- servlet依赖 --> 
<dependency>     
	<groupId>javax.servlet</groupId>     
	<artifactId>javax.servlet-api</artifactId>     
	<version>3.1.0</version>     
	<scope>provided</scope> 
</dependency> 
 
<!-- jsp依赖 --> 
<dependency>     
	<groupId>javax.servlet.jsp</groupId>     
	<artifactId>jsp-api</artifactId>     
	<version>2.2.1-b03</version>     
	<scope>provided</scope> 
</dependency> 

Step3:定义 index 页面

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
     <p>注册学生</p>
     <form action="reg" method="post">
         <table>
             <tr>
                 <td>id</td>
                 <td><input type="text" name="id"></td>
             </tr>
             <tr>
                 <td>姓名:</td>
                 <td><input type="text" name="name"></td>
             </tr>
             <tr>
                 <td>email:</td>
                 <td><input type="text" name="email"></td>
             </tr>
             <tr>
                 <td>年龄:</td>
                 <td><input type="text" name="age"></td>
             </tr>
             <tr>
                 <td></td>
                 <td><input type="submit" value="注册学生"></td>
             </tr>
         </table>
     </form>
</body>
</html>

Step4:定义 RegisterServlet(重点代码)

public class RegisterServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

        String strId = request.getParameter("id");
        String strName = request.getParameter("name");
        String strEmail = request.getParameter("email");
        String strAge = request.getParameter("age");

        //创建spring的容器对象
        //String config= "spring.xml";
        //ApplicationContext ctx = new ClassPathXmlApplicationContext(config);
        WebApplicationContext ctx = null;
        //获取ServletContext中的容器对象,创建好的容器对象,拿来就用
        /*String key =  WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE;
        Object attr  = getServletContext().getAttribute(key);
        if( attr != null){
            ctx = (WebApplicationContext)attr;
        }*/

        //使用框架中的方法,获取容器对象
        ServletContext sc = getServletContext();
        ctx = WebApplicationContextUtils.getRequiredWebApplicationContext(sc);
        System.out.println("容器对象的信息========"+ctx);

        //获取service
        StudentService service  = (StudentService) ctx.getBean("studentService");
        Student student  = new Student();
        student.setId(Integer.parseInt(strId));
        student.setName(strName);
        student.setEmail(strEmail);
        student.setAge(Integer.valueOf(strAge));
        service.addStudent(student);

        //给一个页面
        request.getRequestDispatcher("/result.jsp").forward(request,response);

    }

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

    }
}

Step5:定义 success 页面

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
result.jsp 注册成功
</body>
</html>

Step6:web.xml 注册 Servlet

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">
    <servlet>
        <servlet-name>RegisterServlet</servlet-name>
        <servlet-class>com.bjpowernode.controller.RegisterServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>RegisterServlet</servlet-name>
        <url-pattern>/reg</url-pattern>
    </servlet-mapping>
</web-app>

Step7:运行结果分析
  当表单提交,跳转到 success.jsp 后,多刷新几次页面,查看后台输出,发现每刷新一次 页面,就 new 出一个新的 Spring 容器。即,每提交一次请求,就会创建一个新的 Spring 容 器。对于一个应用来说,只需要一个 Spring 容器即可。所以,将 Spring 容器的创建语句放 在 Servlet 的 doGet()或 doPost()方法中是有问题的。
在这里插入图片描述
  此时,可以考虑,将 Spring 容器的创建放在 Servlet 进行初始化时进行,即执行 init()方 法时执行。并且,Servlet 还是单例多线程的,即一个业务只有一个 Servlet 实例,所有执行 该业务的用户执行的都是这一个 Servlet 实例。这样,Spring 容器就具有了唯一性了。
  但是,Servlet 是一个业务一个 Servlet 实例,即 LoginServlet 只有一个,但还会有 StudentServlet、TeacherServlet 等。每个业务都会有一个 Servlet,都会执行自己的 init()方法, 也就都会创建一个 Spring 容器了。这样一来,Spring 容器就又不唯一了。

6.2 使用 Spring 的监听器 ContextLoaderListener(掌握)

  举例:springweb-2 项目(在 spring-web 项目基础上修改)
  对于 Web 应用来说,ServletContext 对象是唯一的,一个 Web 应用,只有一个 ServletContext 对象,该对象是在 Web 应用装载时初始化的。若将 Spring 容器的创建时机, 放在 ServletContext 初始化时,就可以保证 Spring 容器的创建只会执行一次,也就保证了 Spring 容器在整个应用中的唯一性。
  当 Spring 容器创建好后,在整个应用的生命周期过程中,Spring 容器应该是随时可以被访问的。即,Spring 容器应具有全局性。而放入 ServletContext 对象的属性,就具有应用的 全局性。所以,将创建好的 Spring 容器,以属性的形式放入到 ServletContext 的空间中,就 保证了 Spring 容器的全局性。
  上述的这些工作,已经被封装在了如下的 Spring 的 Jar 包的相关 API 中:
  spring-web-5.2.5.RELEASE
Step1:maven 依赖 pom.xml

<dependency>   \
	<groupId>org.springframework</groupId>   
	<artifactId>spring-web</artifactId>   
	<version>5.2.5.RELEASE</version>  
</dependency> 

Step2:注册监听器 ContextLoaderListener
  若要在 ServletContext 初 始 化 时 创 建 Spring 容 器 , 就 需 要 使 用 监 听 器 接 口 ServletContextListener 对 ServletContext 进行监听。在 web.xml 中注册该监听器。

 <!--注册监听器ContextLoaderListener
        监听器被创建对象后,会读取/WEB-INF/spring.xml
        为什么要读取文件:因为在监听器中要创建ApplicationContext对象,需要加载配置文件。
        /WEB-INF/applicationContext.xml就是监听器默认读取的spring配置文件路径

        可以修改默认的文件位置,使用context-param重新指定文件的位置


        配置监听器:目的是创建容器对象,创建了容器对象, 就能把spring.xml配置文件中的所有对象都创建好。
        用户发起请求就可以直接使用对象了。
    -->
    <context-param>
        <!-- contextConfigLocation:表示配置文件的路径  -->
        <param-name>contextConfigLocation</param-name>
        <!--自定义配置文件的路径-->
        <param-value>classpath:spring.xml</param-value>
    </context-param>
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

  Spring 为该监听器接口定义了一个实现类 ContextLoaderListener,完成了两个很重要的 工作:创建容器对象,并将容器对象放入到了 ServletContext 的空间中。
  打开 ContextLoaderListener 的源码。看到一共四个方法,两个是构造方法,一个初始化 方法,一个销毁方法。
在这里插入图片描述
所以,在这四个方法中较重要的方法应该就是 contextInitialized(),context 初始化方法。
在这里插入图片描述
跟踪 initWebApplicationContext()方法,可以看到,在其中创建了容器对象。
在这里插入图片描述
并且,将创建好的容器对象放入到了 ServletContext 的空间中,key 为一个常量: WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE。
在这里插入图片描述
Step3:指定 Spring 配置文件的位置
  ContextLoaderListener 在对 Spring 容器进行创建时,需要加载 Spring 配置文件。其默认 的 Spring 配置文件位置与名称为:WEB-INF/applicationContext.xml。但,一般会将该配置文 件放置于项目的 classpath 下,即 src 下,所以需要在 web.xml 中对 Spring 配置文件的位置及 名称进行指定。

<context-param>
        <!-- contextConfigLocation:表示配置文件的路径  -->
        <param-name>contextConfigLocation</param-name>
        <!--自定义配置文件的路径-->
        <param-value>classpath:spring.xml</param-value>
    </context-param>

Step4:获取 Spring 容器对象
  在 Servlet 中获取容器对象的常用方式有两种:
(1) 直接从 ServletContext 中获取
  从对监听器 ContextLoaderListener 的源码分析可知,容器对象在 ServletContext 的中存 放的 key 为 WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE。所以,可 以直接通过 ServletContext 的 getAttribute()方法,按照指定的 key 将容器对象获取到。

//创建spring的容器对象
        //String config= "spring.xml";
        //ApplicationContext ctx = new ClassPathXmlApplicationContext(config);
        WebApplicationContext ctx = null;
        //获取ServletContext中的容器对象,创建好的容器对象,拿来就用
        String key =  WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE;
        Object attr  = getServletContext().getAttribute(key);
        if( attr != null){
            ctx = (WebApplicationContext)attr;
        }

(2)通过WebApplicationContextUtils获取
  工具类WebApplicationContextUtils有一个方法专门用于从ServletContext中获取Spring容器对象:getRequiredWebApplicationContext(ServletContext sc)

public class RegisterServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        String strId = request.getParameter("id");
        String strName = request.getParameter("name");
        String strEmail = request.getParameter("email");
        String strAge = request.getParameter("age");
        //创建spring的容器对象
        //String config= "spring.xml";
        //ApplicationContext ctx = new ClassPathXmlApplicationContext(config);
        WebApplicationContext ctx = null;
        //获取ServletContext中的容器对象,创建好的容器对象,拿来就用
        /*String key =  WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE;
        Object attr  = getServletContext().getAttribute(key);
        if( attr != null){
            ctx = (WebApplicationContext)attr;
        }
*/
        //使用框架中的方法,获取容器对象
        ServletContext sc = getServletContext();
        ctx = WebApplicationContextUtils.getRequiredWebApplicationContext(sc);
        System.out.println("容器对象的信息========"+ctx);
        //获取service
        StudentService service  = (StudentService) ctx.getBean("studentService");
        Student student  = new Student();
        student.setId(Integer.parseInt(strId));
        student.setName(strName);
        student.setEmail(strEmail);
        student.setAge(Integer.valueOf(strAge));
        service.addStudent(student);
        //给一个页面
        request.getRequestDispatcher("/result.jsp").forward(request,response);
    }
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    }
}

  查其源码,看其调用关系,就可看到其是从ServletContext中读取的属性值,即Spring容器。
在这里插入图片描述
  以上两种方式,无论使用哪种获取容器对象,刷新success页面后,可看到代码中使用的Spring容器均为同一个对象。
在这里插入图片描述

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
第一课:面向抽象编程 4 第二课:Jdom的基本使用 4 第三课:模拟Spring功能 5 第四课:搭建sping的运行环境 8 一、 建立一个新的项目 8 二、 建立spring的配置文件 8 三、 引入spring的jar包 8 四、 测试代码: 8 五、 注意接口的使用: 8 第五课:IOC(DI)配置及应用 9 一、 什么是IOC、DI 9 二、 编辑xml文件时,没有提示 9 三、 注入类型(Injecting dependencies) 9 (一) setter注入类型Setter Injection 9 (二) 构造方法Constructor Injection 10 四、 id、name 11 五、 简单属性的注入 11 六、 Bean的作用范围scope 12 七、 集合注入 12 八、 自动装配autowire 13 (一) byName 13 (二) byType 14 (三) 注意 14 九、 生命周期 15 (一) lazy-init/default-lazy-init 15 (二) init-method destroy-method 不要和prototype一起用(了解) 15 第六课:annotation方式Spring 16 一、 开始使用annotation配置Spring 16 二、 @Autowired、@Qualifier 16 (一) @Autowired 16 (二) @Qualifier 17 三、 @Resource(重要、推荐) 17 (一) JSR-250 17 (二) @Resource 17 四、 @Componet 18 五、 @Scope、@PostConstruct、@PreDestroy 19 六、 注解对应的jar包 19 第七课:AOP(面向切面编程) 19 一、 AOP概念 19 二、 利用动态代理实现面向切面编程 20 第八课:Spring AOP配置选项 21 一、 AOP配置annotation方式 21 (一) 搭建annotation开发环境 21 (二) aspectJ类库 22 (三) AOP的annotation实例 22 (四) AspectJ的专业术语 23 (五) 织入点语法 23 (六) Advice 24 (七) Pointcut 26 (八) annotatin方式的AOP实例 26 二、 AOP配置xml方式 27 三、 AOP实现动态代理注意 28 第九课:DataSource 28 一、 Sping配置数据源: 28 二、 注入使用 29 三、 dbcp.BasicDataSource 29 第十课 Spring整合Hiberante3 30 一、 Spring配置hibernate3的SessionFactory 30 (一) xml形式的SessionFactory 30 (二) annotation注解方式的SessionFactory 30 二、 引入hibernate所需要使用的jar 31 (一) 基本jar 31 (二) 加入annotation功能的jar包 31 (三) 搭建日志环境并配置显示DDL语句jar包 31 三、 Spring整合hibernate3事务 31 (一) Annotation注解方式配置事务管理 31 (二) Spring事务选项 35 (三) XML文件形式配置Spring事务管理 37 四、 HibernateTemplate 38 (一) HibernateTemplate 38 (二) HibernateDaoSupport 39 第十一课:Spring整合-SSH 40 一、 第一步:加入jar包(需要的jar包列表) 40 二、 第二步: 首先整合Spring + Hibernate 41 三、 第三步:再来整合Struts2 41 四、 struts的读常量: 43 第十二课:DTO、VO 43 一、 DTO 43 二、 VO 43 第十二课:SSH整合存在的问题 43 一、 Jsp中访问Session时,Session已经关闭 43 二、 如果不配置事务,openSessionView出现异常 44 三、 中文乱码问题: 44 第十三课:SSH整合的jar包 45 一、 Struts2 45 二、 Hibernate3.3.2 45 三、 Spring 46

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值