Spring学习笔记

1.Spring概述

1.1 Spring是什么

  • Spring是轻量级的、开源的JavaEE框架
  • Spring可以解决企业应用开发的复杂性
  • Spring的核心是IOC(控制反转)和AOP(面向切面编程)

1.2 Spring框架的特点

  • 方便解耦,简化开发
    Spring 提供了 Ioc 控制反转,由容器管理对象,对象的依赖关系。原来在程序代码中的对象创建方式,现在由容器完成。对象之间的依赖解耦合。
  • 方便集成各种优秀框架
    Spring 不排斥各种优秀的开源框架,其内部提供了对各种优秀框架(如 Struts2、Hibernate、MyBatis 等)的直接支持
  • 降低 Java EE API 的使用难度
    Spring 对 Java EE 开发中非常难用的一些 API(JDBC、JavaMail、远程调用等)都提供了封装,使这些 API 应用的难度大大降低。
  • 方便程序的测试
    Spring 支持 JUnit4,可以通过注解方便地测试 Spring 程序。
  • AOP 编程的支持
    Spring 提供面向切面编程,可以方便地实现对程序进行权限拦截和运行监控等功能
  • 声明式事务支持
    只需要通过配置就可以完成对事务的管理,而无须手动编程。

1.3 Spring的体系结构

Spring 有可能成为所有企业应用程序的一站式服务点,然而,Spring 是模块化的,允许你挑选和选择适用于你的模块,不必要把剩余部分也引入。下面的部分对在 Spring 框架中所有可用的模块给出了详细的介绍。

Spring 框架提供约 20 个模块,可以根据应用程序的要求来使用。

在这里插入图片描述

核心容器

核心容器由 spring-core,spring-beans,spring-context,spring-context-support和spring-expression(SpEL,Spring 表达式语言,Spring Expression Language)等模块组成,它们的细节如下:

  • spring-core 模块提供了框架的基本组成部分,包括 IOC 和依赖注入功能
  • spring-beans 模块提供 beanfactory,工厂模式的微妙实现,它移除了编码式单例的需要,并且可以把配置和依赖从实际编码逻辑中解耦。
  • context 模块建立在由 core和 beans 模块的基础上建立起来的,它以一种类似于 JNDI 注册的方式访问对象。Context 模块继承自 Bean 模块,并且添加了国际化(比如,使用资源束)、事件传播、资源加载和透明地创建上下文(比如,通过 Servelet 容器)等功能。Context 模块也支持 Java EE 的功能,比如 EJB、JMX 和远程调用等。ApplicationContext 接口是 Context 模块的焦点。spring-context-support 提供了对第三方集成到 Spring 上下文的支持,比如缓存(EhCache, Guava, jcache)、邮件(JavaMail)、调度(CommonJ, Quartz)、模板引擎(FreeMarker, JasperReports, VeLocity)等。
  • spring-expression 模块提供了强大的表达式语言,用于在运行时查询和操作对象图。它是 JSP2.1 规范中定义的统一表达式语言的扩展,支持 set 和 get 属性值、属性赋值、方法调用、访问数组集合及索引的内容、逻辑算术运算、命名变量、通过名字从 Spring IOC 容器检索对象,还支持列表的投影、选择以及聚合等。
    它们的完整依赖关系如下图所示:

    在这里插入图片描述


    数据访问/集成

数据访问/集成层包括 JDBC,ORM,OXM,JMS 和事务处理模块,它们的细节如下:
(注:JDBC=Java Data Base Connectivity,ORM=Object Relational Mapping,OXM=Object XML Mapping,JMS=Java Message Service)
JDBC 模块提供了 JDBC 抽象层,它消除了冗长的 JDBC 编码和对数据库供应商特定错误代码的解析。

  • ORM 模块提供了对流行的对象关系映射 API 的集成,包括 JPA、JDO 和 Hibernate 等。通过此模块可以让这些 ORM 框架和 spring的其它功能整合,比如前面提及的事务管理。
  • OXM 模块提供了对 OXM 实现的支持,比如 JAXB、Castor、XML Beans、jibX、XStream 等。
  • JMS 模块包含生产(produce)和消费(consume)消息的功能。从 Spring 4.1 开始,集成了 spring-messaging 模块。
  • 事务模块为实现特殊接口类及所有的 POJO 支持编程式和声明式事务管理。(注:编程式事务需要自己写 beginTransaction()、commit()、rollback() 等事务管理方法声明式事务是通过注解或配置由 spring 自动处理,编程式事务粒度更细)

Web

Web 层由 Web,Web-MVC,Web-Socket 和 Web-Portlet 组成,它们的细节如下:

  • Web 模块提供面向 web 的基本功能和面向 web 的应用上下文,比如多部分(multipart)文件上传功能、使用 Servlet 监听器初始化 IoC 容器等。它还包括 HTTP 客户端以及 Spring 远程调用中与 web 相关的部分。
  • Web-MVC 模块为 web 应用提供了模型视图控制(MVC)和 REST Web服务的实现。Spring 的 MVC 框架可以使领域模型代码和 web 表单完全地分离,且可以与 Spring 框架的其它所有功能进行集成。
  • Web-Socket 模块为 WebSocket-based 提供了支持,而且在 web 应用程序中提供了客户端和服务器端之间通信的两种方式。
  • Web-Portlet 模块提供了用于 Portlet 环境的 MVC 实现,并反映了 spring-webmvc 模块的功能

Test模块
Test 模块:Spring 支持 Junit 和 TestNG 测试框架,而且还额外提供了一些基于 Spring 的测试功能,比如在测试 Web 框架时,模拟 Http 请求的功能

其他
还有其他一些重要的模块,像 AOP,Aspects,Instrumentation,Web 和测试模块,它们的细节如下:

  • AOP 模块提供了面向方面(切面)的编程实现,允许你定义方法拦截器和切入点对代码进行干净地解耦,从而使实现功能代码彻底的解耦出来。使用源码级的元数据,可以用类似于.Net属性的方式合并行为信息到代码中。
  • Aspects 模块提供了与 AspectJ 的集成,这是一个功能强大且成熟的面向切面编程(AOP)框架。
  • Instrumentation 模块在一定的应用服务器中提供了类 instrumentation 的支持和类加载器的实现。
  • Messaging 模块为 STOMP 提供了支持作为在应用程序中 WebSocket 子协议的使用。它也支持一个注解编程模型,它是为了选路和处理来自 WebSocket 客户端的 STOMP 信息。
  • 测试模块支持对具有 JUnit 或 TestNG 框架的 Spring 组件的测试。

2.控制反转(IOC)

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

  • Spring 的依赖注入对调用者与被调用者几乎没有任何要求,完全支持对象之间依赖关系的管理。Spring 框架使用依赖注入(DI)实现 IOC。
  • Spring 容器是一个超级大工厂,负责创建、管理所有的 Java 对象,这些 Java 对象被称为 Bean。Spring 容器管理着容器中 Bean 之间的依赖关系,Spring 使用“依赖注入”的方式来管理 Bean 之间的依赖关系。使用 IOC 实现对象之间的解耦和。

2.1 Spring ApplicationContext 容器

ApplicationContext 是 beanfactory 的子接口,也被称为 Spring 上下文。

Application Context 是 spring 中较高级的容器。和 beanfactory 类似,它可以加载配置文件中定义的 bean,将所有的 bean 集中在一起,当有请求的时候分配 bean。 另外,它增加了企业所需要的功能,比如,从属性文件中解析文本信息和将事件传递给所指定的监听器。这个容器在 org.springframework.context.ApplicationContext interface 接口中定义。

ApplicationContext 包含 beanfactory 所有的功能,一般情况下,相对于 beanfactory,ApplicationContext 会更加优秀。当然,beanfactory 仍可以在轻量级应用中使用,比如移动设备或者基于 applet 的应用程序。
最常被使用的 ApplicationContext 接口实现:

  • FileSystemXmlApplicationContext:该容器从 XML 文件中加载已被定义的 bean。在这里,你需要提供给构造器 XML 文件的完整路径。
  • ClasspathXmlApplicationContext:该容器从 XML 文件中加载已被定义的 bean。在这里,你不需要提供 XML 文件的完整路径,只需正确配置 CLAsspATH 环境变量即可,因为,容器会从 CLAsspATH 中搜索 bean 配置文件
  • WebXmlApplicationContext:该容器会在一个 web 应用程序的范围内加载在 XML 文件中已被定义的 bean。

2.2 Spring Bean 作用域

当在 Spring 中定义一个 bean 时,你必须声明该 bean 的作用域的选项。例如,为了强制 Spring 在每次需要时都产生一个新的 bean 实例,你应该声明 bean 的作用域的属性prototype。同理,如果你想让 Spring 在每次需要时都返回同一个bean实例,你应该声明 bean 的作用域的属性singleton

Spring 框架支持以下五个作用域,分别为 singleton、prototype、request、session 和 global session,5种作用域说明如下所示,
注意,如果你使用 web-aware ApplicationContext 时,其中三个是可用的。

作用域 描述
singleton 在spring IoC容器仅存在一个Bean实例,Bean以单例方式存在,认值
prototype 每次从容器中调用Bean时,都返回一个新的实例,即每次调用getBean()时,相当于执行newXxxBean()
request 每次HTTP请求都会创建一个新的Bean,该作用域仅适用于WebApplicationContext环境
session 一个HTTP Session共享一个Bean,不同Session使用不同的Bean,仅适用于WebApplicationContext环境
singleton 作用域:

singleton 是认的作用域,也就是说,当定义 Bean 时,如果没有指定作用域配置项,则 Bean 的作用域被认为 singleton。
当将一个 bean 定义设置为 singleton 作用域的时候,Spring IoC 容器只会创建该 bean 定义的唯一实例。Singleton 是单例类型,就是在创建起容器时就同时自动创建了一个 bean 的对象,不管你是否使用,他都存在了,每次获取到的对象都是同一个对象。注意,Singleton 作用域是 Spring 中的缺省作用域。

验证:
在pom.xml中添加依赖

<dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.11</version>
      <scope>test</scope>
    </dependency>
    <!--添加spring依赖-->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>5.2.6.RELEASE</version>
    </dependency>
  </dependencies>

创建Student实体类

public class Student {
    public Student() {
        System.out.println("Student类的无参构造");
    }
    public void showScope() {
        System.out.println("Student的作用域是singleton");
    }
}

创建applicationContext.xml

<!--作用域是singleton-->
    <!--创建学生对象-->
    <bean id="student1" class="com.xin.pojo.Student" scope="singleton"></bean>

测试:

@Test
    public void test1() {
        //由spring容器进行对象的创建
        //如果想从spring容器中取出对象,则要先创建容器对象,并启动才可以取对象
        ApplicationContext context=new ClasspathXmlApplicationContext("applicationContext.xml");
        Student stu1=(Student) context.getBean("student1");
        Student stu2=(Student) context.getBean("student1");
        stu1.showScope();
        stu2.showScope();
        System.out.println(stu1==stu2);

    }

结果:

在这里插入图片描述

prototype 作用域

一个 bean 的作用域为 Prototype,表示一个 bean 定义对应多个对象实例。Prototype 作用域的 bean 会导致在每次对该 bean 请求(将其注入到另一个 bean 中,或者以程序的方式调用容器的 getBean() 方法)时都会创建一个新的 bean 实例。Prototype 是原型类型,它在我们创建容器的时候并没有实例化,而是当我们获取bean的时候才会去创建一个对象,而且我们每次获取到的对象都不是同一个对象。

验证
Student实体类

public class Student {
    public Student() {
        System.out.println("Student类的无参构造");
    }
    public void showScope1() {
        System.out.println("Student的作用域是prototype");
    }
}

applicationContext.xml

<!--作用域是prototype-->
    <!--创建学生对象-->
    <bean id="student2" class="com.xin.pojo.Student" scope="prototype"></bean>

测试:

@Test
    public void test1() {
        //由spring容器进行对象的创建
        //如果想从spring容器中取出对象,则要先创建容器对象,并启动才可以取对象
        ApplicationContext context=new ClasspathXmlApplicationContext("applicationContext.xml");
        Student stu3=(Student) context.getBean("student2");
        Student stu4=(Student) context.getBean("student2");
        stu3.showScope1();
        stu4.showScope1();
        System.out.println(stu3==stu4);

    }

在这里插入图片描述

2.3 Spring 依赖注入

Spring框架的核心功能之一就是通过依赖注入的方式来管理Bean之间的依赖关系。

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

2.3.1 set注入(实体类中必须有参数方法

(1)简单类型
实体类

public class School {
    private String name;
    private String address;

    public String getName() {
        return name;
    }

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

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

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

applicationContext.xml

<!--创建学校对象,并赋值-->
<bean id="school" class="com.xin.pojo.School">
        <!--name:实体类中的属性,value:为属性赋值-->
        <property name="name" value="北京大学"></property><!--setName("李四")-->
        <property name="address" value="北京"></property><!--setAge("18")-->
</bean>

测试:

 @Test
    public void test1() {
        //由spring容器进行对象的创建
        //如果想从spring容器中取出对象,则要先创建容器对象,并启动才可以取对象
        ApplicationContext context=new ClasspathXmlApplicationContext("applicationContext.xml");
        //取出对象
        School school=(School) context.getBean("school");
        System.out.println(school);


    }

结果:

在这里插入图片描述


(2)引用类型
实体类

public class Student {
    private String name;
    private Integer age;
    private School school;

    public String getName() {
        return name;
    }

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

    public Integer getAge() {
        return age;
    }

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

    public School getSchool() {
        return school;
    }

    public void setSchool(School school) {
        this.school = school;
    }

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

applicationContext.xml

     <!--创建学校对象,并赋值-->
    <bean id="school" class="com.xin.pojo.School">
        <property name="name" value="北京大学"></property><!--setName("李四")-->
        <property name="address" value="北京"></property><!--setAge("18")-->
    </bean>
    <!--创建学生对象,并赋值-->
    <bean id="student" class="com.xin.pojo.Student">
        <property name="name" value="李四"></property>
        <property name="age" value="18"></property>
        <!--对于其它 Bean 对象的引用,使用<bean/>标签的 ref 属性。-->
        <property name="school" ref="school"></property>
    </bean>

测试:

@Test
    public void test1() {
        //由spring容器进行对象的创建
        //如果想从spring容器中取出对象,则要先创建容器对象,并启动才可以取对象
        ApplicationContext context=new ClasspathXmlApplicationContext("applicationContext.xml");
        //取出对象
        Student stu=(Student) context.getBean("student");
        System.out.println(stu);
    }

结果:

在这里插入图片描述

2.3.2 构造方法注入

构造注入是指,在构造调用者实例的同时,完成被调用者的实例化。即,使用构造器设置依赖关系。在实体类中必须提供相应参数的构造方法
< constructor-arg />标签中用于指定参数的属性有:

  • name:指定参数名称(与实体类中的属性名一致)。
  • index:指明该参数对应着构造器的第几个参数,从 0 开始。不过,该属性不要也行, 但要注意,若参数类型相同,或之间有包含关系,则需要保证赋值顺序要与构造器中的参数顺序一致。

(1)使用构造方法的参数名称注入值

public School(String name, String address) {
        this.name = name;
        this.address = address;
    }
 public Student(String name, int age, School school) {
        this.name = name;
        this.age = age;
        this.school = school;
    }
<bean id="school" class="com.xin.pojo.School">
       <!--name:实体类中的属性,value:为属性赋值-->
       <constructor-arg name="name" value="清华大学"></constructor-arg>
        <constructor-arg name="address" value="北京"></constructor-arg>
    </bean>
    <bean id="student" class="com.xin.pojo.Student">
        <constructor-arg name="name" value="李四"></constructor-arg>
        <constructor-arg name="age" value="18"></constructor-arg>
        <constructor-arg ref="school"></constructor-arg>
    </bean>

(2)使用构造方法的参数索引下标注入值

    <bean id="student" class="com.xin.pojo.Student">
        <constructor-arg index="0" value="李四"></constructor-arg>
        <constructor-arg index="1" value="18"></constructor-arg>
        <constructor-arg index="2" ref="school"></constructor-arg>
    </bean>

(3)不指定名称和下标索引的注入

    <bean id="student" class="com.xin.pojo.Student">
        <constructor-arg value="李四"></constructor-arg>
        <constructor-arg value="18"></constructor-arg>
        <constructor-arg ref="school"></constructor-arg>
    </bean>

注意:此种方式的注入一定要按类中构造方法的参数的顺序来进行注入。

2.3.3 Spring 注入集合

现在如果你想传递多个值,如 Java Collection 类型 List、Set、Map 和 Properties,应该怎么做呢。为了处理这种情况,Spring 提供了四种类型的集合的配置元素,如下所示:

元素 描述
< list> 它有助于连线,如注入一列值,允许重复。
< set> 它有助于连线一组值,但不能重复。
< map> 它可以用来注入名称-值对的集合,其中名称和值可以是任何类型。
< props> 它可以用来注入名称-值对的集合,其中名称和值都是字符串类型。

你可以使用或来连接任何 java.util.Collection 的实现或数组。
你会遇到两种情况(a)传递集合中直接的值(b)传递一个 bean 的引用作为集合的元素。

实体类

public class JavaCollection {
    List list;
    Set set;
    Map map;
    Properties prop;

    public List getList() {
        System.out.println("List:"+list);
        return list;
    }

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

    public Set getSet() {
        System.out.println("Set:"+set);
        return set;
    }

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

    public Map getMap() {
        System.out.println("Map:"+map);
        return map;
    }

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

    public Properties getProp() {
        System.out.println("Properties:"+prop);
        return prop;
    }

    public void setProp(Properties prop) {
        this.prop = prop;
    }

applicationContext.xml

<bean id="javaCollection" class="com.xin.pojo.JavaCollection">
        <property name="list">
            <list>
                <value>java</value>
                <value>js</value>
                <value>MysqL</value>
            </list>
        </property>
        <property name="set">
            <set>
                <value>java</value>
                <value>js</value>
                <value>MysqL</value>
            </set>
        </property>
        <property name="map">
            <map>
                <entry key="1" value="java"></entry>
                <entry key="2" value="js"></entry>
                <entry key="3" value="MysqL"></entry>
            </map>
        </property>
        <property name="prop">
            <props>
                <prop key="one">java</prop>
                <prop key="two">js</prop>
                <prop key="three">MysqL</prop>
            </props>
        </property>
    </bean>

测试:

@Test
    public void test1() {
        //由spring容器进行对象的创建
        //如果想从spring容器中取出对象,则要先创建容器对象,并启动才可以取对象
        ApplicationContext context=new ClasspathXmlApplicationContext("applicationContext.xml");
        //取出对象
        JavaCollection jc=(JavaCollection) context.getBean("javaCollection");
        jc.getList();
        jc.getSet();
        jc.getMap();
        jc.getProp();
    }

结果:

在这里插入图片描述


注入 Bean 引用
下面的 Bean 定义将帮助你理解如何注入 bean 的引用作为集合的元素。甚至你可以将引用和值混合在一起。

<bean id="..." class="...">

      <!-- Passing bean reference  for java.util.List -->
      <property name="addressList">
         <list>
            <ref bean="address1"/>
            <ref bean="address2"/>
            <value>Pakistan</value>
         </list>
      </property>

      <!-- Passing bean reference  for java.util.Set -->
      <property name="addressSet">
         <set>
            <ref bean="address1"/>
            <ref bean="address2"/>
            <value>Pakistan</value>
         </set>
      </property>

      <!-- Passing bean reference  for java.util.Map -->
      <property name="addressMap">
         <map>
            <entry key="one" value="INDIA"/>
            <entry key ="two" value-ref="address1"/>
            <entry key ="three" value-ref="address2"/>
         </map>
      </property>

   </bean>

如果你需要传递一个空字符串作为值,

<bean id="..." class="exampleBean">
   <property name="email" value=""/>
</bean>

相当于 Java 代码:exampleBean.setEmail(“”)。

如果你需要传递一个 NULL 值

<bean id="..." class="exampleBean">
   <property name="email"><null/></property>
</bean>

相当于 Java 代码:exampleBean.setEmail(null)。

2.4 Spring Beans自动装配

自动装配指的就是使用将 Spring 容器中的 bean 自动的和我们需要这个 bean 的类组装在一起。

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

Spring Beans自动装配 byName
(1)byName 方式自动注入
配置文件中被调用者 bean 的 id 值与代码调用者 bean 类的属性名相同时,可使用byName 方式,让容器自动将被调用者 bean 注入给调用者 bean。容器是通过调用者的 bean类的属性名与配置文件的被调用者 bean 的 id 进行比较而实现自动注入的。

在这里插入图片描述


在这里插入图片描述

<!--创建学校对象,并赋值-->
    <bean id="school" class="com.xin.pojo.School">
        <property name="name" value="北京大学"></property><!--setName("李四")-->
        <property name="address" value="北京"></property><!--setAge("18")-->
    </bean>
    <!--创建学生对象,并赋值-->
    <bean id="student" class="com.xin.pojo.Student" autowire="byName">
        <property name="name" value="李四"></property>
        <property name="age" value="18"></property>
        <!--对于其它 Bean 对象的引用,使用<bean/>标签的 ref 属性。-->
        <!--<property name="school" ref="school"></property>-->
    </bean>

Spring Beans自动装配 byType
(2)byType 方式自动注入
使用 byType 方式自动注入,要求:配置文件中被调用者 bean 的 class 属性指定的类, 要与代码调用者 bean 类的某引用类型属性类型同源。即要么相同,要么有 is-a 关系(子类,或是实现类)。但这样的同源的被调用 bean 只能有一个。多于一个,容器就不知该匹配哪一个

<!--创建学校对象,并赋值-->
    <bean id="school" class="com.xin.pojo.School">
        <property name="name" value="北京大学"></property><!--setName("李四")-->
        <property name="address" value="北京"></property><!--setAge("18")-->
    </bean>
    <!--创建学生对象,并赋值-->
    <bean id="student" class="com.xin.pojo.Student" autowire="byType">
        <property name="name" value="李四"></property>
        <property name="age" value="18"></property>
        <!--对于其它 Bean 对象的引用,使用<bean/>标签的 ref 属性。-->
        <!--<property name="school" ref="school"></property>-->
    </bean>

2.5 Spring基于注解的配置

注解认情况下在 Spring 容器中不打开。因此需要在Spring 配置文件中启用它。

(1)< context:annotation-config />:仅能够在已经在已经注册过的bean上面起作用。对于没有在spring容器注册的bean,它并不能执行任何操作。

(2)< context:component-scan base-package=“XX.XX”/> :除了具有@autowire,@resource等注入功能之外,还具有自动将带有@Component,@Service,@Repository等注解的对象注册spring容器中的功能

如果同时使用这两个配置会不会出现重复注入的情况呢?
答案:因为< context:annotation-config />和 < context:component-scan>同时存在的时候,前者会被忽略。如@autowire,@resource等注入注解只会被注入一次!

一旦 被配置后,你就可以开始注解你的代码,表明 Spring 应该自动连接值到属性方法和构造函数

2.5.1 Spring @required 注解

@required 注解应用于 bean 属性的 setter 方法,它表明受影响的 bean 属性在配置时必须放在 XML 配置文件,否则容器就会抛出一个 BeanInitializationException 异常。

2.5.2 Spring @Autowired 注解

我们使用最多的注解应该就是 @Autowired 注解了。这个注解的功能就是为我们注入一个定义好的 bean。

  • @Autowired 注解认使用按类型(byType)自动装配 Bean 的方式,可以应用到 bean 属性的 setter 方法,非 setter 方法,构造函数属性
  • @Autowired 还有一个属性 required认值为 true,表示当匹配失败后,会终止程序运行。若将其值设置为 false,则匹配失败,将被忽略,未匹配的属性值为 null。注意:如果可注入的类型多于一个,则按名称进行二次匹配.如果有匹配到则注入,如果没有匹配到,则报错。

2.5.2 byName 自动注入@Qualifier 注解

@Qualifier 的 value 属性用于指定要匹配的 Bean 的 id 值,可能会有这样一种情况,当你创建多个具有相同类型的 bean 时,并且想要用一个属性只为它们其中的一个进行装配,在这种情况下,你可以使用 @Qualifier 注解和 @Autowired 注解通过指定哪一个真正的 bean 将会被装配来消除混乱

在这里插入图片描述

2.5.3 简单类型属性注入@Value

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

在这里插入图片描述

2.5.4 注解@Resource

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

(1)byType 注入引用类型属性
@Resource 注解若不带任何参数,采用认按名称的方式注入,按名称不能注入 bean, 则会按照类型进行 Bean 的匹配注入。

在这里插入图片描述


(2)byName 注入引用类型属性
@Resource 注解指定其 name 属性,则 name 的值即为按照名称进行匹配的 Bean 的 id。

在这里插入图片描述

2.5.5 (1)创建对象的注解

@Component :创建所有对象都可以使用此注解,除了控制器,业务逻辑层,数据访问层的对象
@Controller:创建控制器层的对象,此对象可以接收用户请求,返回处理结果
@Service:创建业务逻辑层的对象,此对象可施事务控制,向上给控制器返回数据,向下调用数据访问层
@Repository:创建数据访问层的对象 ,对数据库中的数据进行增删改查操作

3 面向切面编程(AOP)

3.1 AOP概述

  • AOP(Aspect Orient Programming),面向切面编程。面向切面编程是从动态角度考虑程序运行过程。AOP 底层,就是采用动态代理模式实现的。采用了两种代理:JDK 的动态代理,与 cglib的动态代理。
  • AOP 为 Aspect Oriented Programming 的缩写,意为:面向切面编程,可通过运行期动态代理实现程序功能的统一维护的一种技术。AOP 是 Spring 框架中的一个重要内容。利用 AOP 可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
  • 面向切面编程,就是将交叉业务逻辑封装成切面,利用 AOP 容器的功能将切面织入到主业务逻辑中。所谓交叉业务逻辑是指,通用的、与主业务逻辑无关的代码,如安全检查、事务、日志、缓存等。若不使用 AOP,则会出现代码纠缠,即交叉业务逻辑与主业务逻辑混合在一起。这样, 会使主业务逻辑变的混杂不清。例如,转账,在真正转账业务逻辑前后,需要权限控制、日志记录、加载事务、结束事务等交叉业务逻辑,而这些业务逻辑与主业务逻辑间并无直接关系。但,它们的代码量所占比重能达到总代码量的一半甚至还多。它们的存在,不仅产生了大量的“冗余”代码,还大大干扰了主业务逻辑—转账。

3.2 面向切面编程对有什么好处

1.减少重复;
2.专注业务;
注意:面向切面编程只是面向对象编程的一种补充。用 AOP 减少重复代码,专注业务实现。

3.3 Spring的AOP通知类型

Spring支持AOP的编程,常用的有以下几种:

Spring的AOP利用一种称为“横切”的技术,剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其名为“Aspect”,即方面。所谓“方面”,简单地说,就是将那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可操作性和可维护性。AOP代表的是一个横向的关系,如果说“对象”是一个空心的圆柱体,其中封装的是对象的属性和行为;那么面向方面编程的方法,就仿佛一把利刃,将这些空心圆柱体剖开,以获得其内部的消息。而剖开的切面,也就是所谓的“方面”了。然后它又以巧夺天功的妙手将这些剖开的切面复原,不留痕迹。
总结:AOP的核心思想就是“将应用程序中的商业逻辑同对其提供支持的通用服务进行分离。”

3.4 AOP 编程术语

(1)切面(Aspect)
切面泛指交叉业务逻辑,或是公共的,通用的业务。上例中的事务处理、日志处理就可以理解为切面。常用的切面是通知(Advice)。实际就是对主业务逻辑的一种增强。
(2)连接点(JoinPoint)
连接点指可以被切面织入的具体方法。通常业务接口中的方法均为连接点。

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

(4)目标对象(Target)
目标对象指 将要被增强 的对象。 即包含主业 务逻辑的 类的对象。 上例中 的
BookServiceImpl 的对象若被增强,则该类称为目标类,该类对象称为目标对象。当然, 不被增强,也就无所谓目标不目标了。
(5)通知(Advice)
通知表示切面的执行时间,Advice 也叫增强。上例中的 MyInvocationHandler 就可以理解为是一种通知。换个角度来说,通知定义了增强代码切入到目标代码的时间点,是目标方法执行之前执行,还是之后执行等。通知类型不同,切入时间不同。
切入点定义切入的位置,通知定义切入的时机。

3.5 AspectJ 对 AOP 的实现

对于 AOP 这种编程思想,很多框架都进行了实现。Spring 就是其中之一,可以完成面向切面编程。然而,AspectJ 也实现了 AOP 的功能,且其实现方式更为简捷,使用更为方便, 而且还支持注解式开发。所以,Spring 又将 AspectJ 的对于 AOP 的实现也引入到了自己的框架中。
在 Spring 中使用 AOP 开发时,一般使用 AspectJ 的实现方式。

3.5.1 AspectJ 简介

  • AspectJ 是一个优秀面向切面的框架,它扩展了 Java 语言,提供了强大的切面实现。

3.5.2 AspectJ 的通知类型(理解)

AspectJ 中常用的通知有四种类型:
(1)前置通知@Before
(2)后置通知@AfterReturning
(3)环绕通知@Around
(4)最终通知@After
(5)定义切入点@pointcut(了解)

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

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

在这里插入图片描述


解释:
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.impl..(…))
    指定切入点为:定义在 service 包里的任意类的任意方法
  • execution(* com.xyz.service….(…)) * com.xyz.service.power2.aa..(…)
    指定切入点为:定义在 service 包或者子包里的任意类的任意方法。“…”出现在类名中时,后面必须跟“*”,表示包、子包下的所有类。
  • execution(* …service..(…)) a.b.service..(…) a.b.c.d.service..*(…)
    指定所有包下的 serivce 子包下所有类(接口)中所有方法为切入点
  • execution(* .service..*(…))
    指定只有一级包下的 serivce 子包下所有类(接口)中所有方法为切入点
  • execution(* .ISomeService.(…))
    指定只有一级包下的 ISomeSerivce 接口中所有方法为切入点
  • execution(* …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,double d2,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.5.4 AspectJ 的开发环境

1.添加maven依赖

<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-aspects</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>

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

3.6.5 AspectJ 基于注解的 AOP 实现

(1)@Before前置通知实现
第一步:定义接口和实现类

public interface SomeService {
    public void doSome();
    public String show(String name,int age);
}
@Service("someService")
public class SomeServiceImpl implements SomeService{
    @Override
    public void doSome() {
        System.out.println("执行了业务方法doSome()");
    }

    @Override
    public String show(String name, int age) {
        System.out.println("show():"+"姓名:"+name+",年龄:"+age);
        return name;
    }
}

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

@Aspect//交给AspectJ框架去识别切面类,来进行切面方法调用
@Component
public class MyAspectj {
/**
     * 前置通知中的切面方法的规范
     * 1)访问权限是public
     * 2)没有返回值void
     * 3)切面方法名称自定义
     * 4)切面方法可以没有参数,如果有也是固定的类型JoinPoint
     * 5)使用@Before注解表明是前切功能
     * 6)@Before的参数:
     *   value:指定切入点表达式
     *    public String doSome(String name, int age)
     */
    //@Before(value="execution(public void com.xin.demo.someServiceImpl.doSome())")
   //@Before(value = "execution(* com.xin.demo.someServiceImpl.*(String,int))")    
   // @Before(value = "execution(* *...someServiceImpl.*(..))")    
  //@Before(value = "execution(* *.*(..))")
    @Before(value="execution(* com.xin.demo.*.*(..))")
    public void myAspect() {
        System.out.println("前置日志");
    }
}

第三步:声明目标对象切面类对象
在pom.xml文件中配置

    <!--开启组件扫描,用于使用注解创建对象-->
    <context:component-scan base-package="com.xin.demo"></context:component-scan>

Step4:注册 AspectJ 的自动代理
在定义好切面 Aspect 后,需要通知 Spring 容器,让容器生成“目标类+ 切面”的代理对象。这个代理是由容器自动生成的。只需要在 Spring 配置文件注册一个基于 aspectj 的自动代理生成器,其就会自动扫描到@Aspect 注解,并按通知类型与切入点,将其织入,并生成代理。

<!--开启AspectJ对于 AOP 的注解实现-->
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>

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

@Test
    public void test1() {
        //由spring容器进行对象的创建
        //如果想从spring容器中取出对象,则要先创建容器对象,并启动才可以取对象
        ApplicationContext context=new ClasspathXmlApplicationContext("applicationContext.xml");
        SomeService someService1=(SomeService) context.getBean("someService");
        System.out.println(someService1.getClass());
        someService1.doSome();
        System.out.println("返回值:"+someService1.show("李四",12));

    }

结果:

在这里插入图片描述


在测试用例中,

SomeService someService1=(SomeService)context.getBean(“someService”);
System.out.println(someService1.getClass());
得到的结果是com.sun.proxy.$Proxy14,底层实现的是JDK动态代理。

如果使用
SomeServiceImpl someService1=(SomeServiceImpl) context.getBean(“someService”);
System.out.println(someService1.getClass());
得到的结果是java.lang.classCastException: com.sun.proxy.$Proxy14 cannot be cast to com.xin.demo.someServiceImpl。原因:不能用接口的实现类(SomeServiceImplImpl)来转换Proxy的实现类,因为代理对象和实现类对象是同级的,应该用共同的接口来转换。

若想要底层使用cjlib代理,只需要将标签< aop:aspectj-autoproxy>< /aop:aspectj-autoproxy>中添加proxy-target-class即可

<!--开启AspectJ注解-->
    <aop:aspectj-autoproxy proxy-target-class="true"></aop:aspectj-autoproxy>

proxy-target-class属性值决定是基于接口的还是基于类的代理被创建。首先说明下proxy-target-class="true"和proxy-target-class="false"的区别,为true则是基于类的代理将起作用(需要cglib库),为false或者省略这个属性,则标准的JDK 基于接口的代理将起作用。
proxy-target-class在spring事务、aop、缓存这几块都有设置,其作用都是一样的。
进行测试:

 @Test
    public void test1() {
        //由spring容器进行对象的创建
        //如果想从spring容器中取出对象,则要先创建容器对象,并启动才可以取对象
        ApplicationContext context=new ClasspathXmlApplicationContext("applicationContext.xml");
        SomeService someService1=(SomeService) context.getBean("someService");
        System.out.println(someService1.getClass());
        SomeServiceImpl someService2=(SomeServiceImpl) context.getBean("someService");
        System.out.println(someService2.getClass());
        someService1.doSome();
        System.out.println("返回值:"+someService1.show("李四",12));
    }

结果:

在这里插入图片描述

SomeService someService1=(SomeService) context.getBean(“someService”);
System.out.println(someService1.getClass());
SomeServiceImpl someService2=(SomeServiceImpl) context.getBean(“someService”);
System.out.println(someService2.getClass());
可以看到,someService1.getClass()和someService2.getClass()得到的结果是一样的,class
com.xin.demo.someServiceImpl$$ EnhancerBySpringcglib$ $8cd333bc,说明底层使用的是cjlib代理,因为cjlib代理是使用子类来进行代理的,可以使用父类SomeServiceImpl来接,也可以使用接口SomeService(子类间接实现SomeService接口)

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

@Aspect
@Component
public class MyAspectj {
//    @Before(value="execution(public void com.xin.demo.someServiceImpl.doSome())")
    @Before(value="execution(* *..demo.*.*(..))")
    public void myAspect(JoinPoint joinPoint) {
        System.out.println("目标方法的签名:"+joinPoint.getSignature());
        System.out.println("目标方法的所有参数:"+ Arrays.toString(joinPoint.getArgs()));
        System.out.println("前置日志");
    }
}

在这里插入图片描述


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

public interface SomeService {
    String doSome(String name, int age);
    Student change();
}

实现方法

@Component
public class SomeServiceImpl implements SomeService {
    @Override
    public String doSome(String name, int age) {
        System.out.println(name+"doSome方法调用 (主业务方法)");
        return "abcd";
    }
    @Override
    public Student change() {
        return new Student("张三");
    }
}

定义切面:

@Aspect  //交给AspectJ框架扫描识别切面类
@Component
public class MyAspect {
    /**
     * 后置通知切面方法的规范
     * 1)访问权限是public
     * 2)切面方法没有返回值void
     * 3)方法自定义
     * 4)切面方法可以没有参数,如果有参数则是目标方法的返回值,也可以包含参数JoinPoint,它必须是第一个参数
     * 5)使用@AfterReturning注解
     * 6)参数value:指定切入点表达式
     *      returning:指定目标方法返回值的形参名称,此名称必须与切面方法的参数名称一致.
     */
    @AfterReturning(value = "execution(* com.bjpowernode.s02.someServiceImpl.*(..))",returning = "obj")
    public void myAfterReturning(Object obj){
        System.out.println("后置通知..........");
        //改变目标方法的返回值
        if(obj != null){
            if(obj instanceof String){
               String s =  ((String) obj).toupperCase();//转为大写
                System.out.println("在切面方法中的输出:"+s);
            }
            if(obj instanceof Student){
               ((Student) obj).setName("李四");
                System.out.println("在切面方法中的输出"+(Student)obj);
            }
        }
    }
}

测试类:

@Test
public void test01(){
    ApplicationContext ac = new ClasspathXmlApplicationContext("s02/applicationContext.xml");
    SomeServiceImpl someService = (SomeServiceImpl) ac.getBean("someServiceImpl");
    System.out.println(someService.getClass());
    String s = someService.doSome("张三",22);
    System.out.println("在测试类中输出目标方法的返回值---"+s);
}

运行结果:

在这里插入图片描述


如果目标方法的返回值是8种基本数据类型或String类型,则目标方法的返回值不可改变
如果目标方法的返回值是引用类型,则可改变

@Test
 public void test03(){
     ApplicationContext ac = new ClasspathXmlApplicationContext("s02/applicationContext.xml");
     SomeServiceImpl someService = (SomeServiceImpl) ac.getBean("someServiceImpl");
     System.out.println(someService.getClass());
     Student stu = someService.change();
     System.out.println("在测试类中输出目标方法的返回值---"+stu);
 }

运行结果:

在这里插入图片描述


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

public interface SomeService {
    public String doSome(String name,int age);
}

实现类:

@Service
public class SomeServiceImpl implements SomeService{
    @Override
    public String doSome(String name, int age) {
        System.out.println("业务功能doSome()方法执行----");
        return "abcd";
    }

切面:

@Aspect
@Component
public class MyAspect {
 /**
     * 环绕通知方法的规范:
     * 访问权限:public
     * 切面方法有返回值,此返回值就是目标方法的返回值
     * 方法名称自定义
     * 方法有参数,此参数是目标方法
     * 回避异常:Throwable
     * 使用@Around注解声明是环绕通知
     *    参数:value:指定切入点表达式
     * @param pjp
     * @return
     * @throws Throwable
     */
    @Around(value = "execution(* com.xin.service.s03.*.*(..))")
    public Object myAround(ProceedingJoinPoint pjp) throws Throwable {
        //前切功能增强
        System.out.println("环绕通知中前切功能----");
        //调用目标方法
        /**
         * obj:目标方法的返回值
         * pjp:目标方法本身
         * proceed():调用目标方法
         * getArgs():相当于传参
         */
        Object obj = pjp.proceed(pjp.getArgs());
        //后切功能增强
        System.out.println("环绕通知中后切功能----");
        return obj.toString().toupperCase();
    }
}

测试:

@Test
    public void test() {
        ApplicationContext ac=new ClasspathXmlApplicationContext("s03/applicationContext.xml");
        SomeService s=(SomeService) ac.getBean("someServiceImpl");
        System.out.println(s.getClass());
        String s1 = s.doSome("张三", 12);
        System.out.println(s1);
    }

运行结果:

在这里插入图片描述

(5)@After 最终通知
无论目标方法是否抛出异常,该增强均会被执行。
接口方法

public interface SomeService {
    String doSome(String name, int age);
}

方法实现:

@Component
public class SomeServiceImpl implements SomeService {
    @Override
    public String doSome(String name, int age) {
        System.out.println(name+"doSome方法调用 (主业务方法)");
        System.out.println(1/0);
        return "abcd";
    }
}

定义切面:

@Aspect
@Component
public class MyAspect {
    /**
     * 最终方法的规范
     * 1)访问权限是public
     * 2)切面方法没有返回值void
     * 3)方法名称自定义
     * 4)方法可以没有参数,也可以有,则JoinPoint.
     * 5)使用@After注解
     * 6)参数:value:指定切入点表达式
     */
    @After(value = "execution(* com.bjpowernode.s04.someServiceImpl.*(..))")
    public void myAfter(){
        System.out.println("最终通知被执行.............");
    }
}

测试类:

@Test
public void test01(){
    ApplicationContext ac = new ClasspathXmlApplicationContext("s04/applicationContext.xml");
    SomeServiceImpl someService = (SomeServiceImpl) ac.getBean("someServiceImpl");
    System.out.println(someService.getClass());
    String s = someService.doSome("张三",22);
    System.out.println("在测试类中输出目标方法的返回值---"+s);
}

运行结果:

在这里插入图片描述


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

@Aspect
@Component
public class MyAspect {
    /**
     * 最终方法的规范
     * 1)访问权限是public
     * 2)切面方法没有返回值void
     * 3)方法名称自定义
     * 4)方法可以没有参数,也可以有,则JoinPoint.
     * 5)使用@After注解
     * 6)参数:value:指定切入点表达式
     */
    @After(value = "mycut()")
    public void myAfter(){
        System.out.println("最终通知被执行.............");
    }
    @Before(value = "mycut()")
    public void myBefore(){
        System.out.println("前置通知被执行.............");
    }
    @AfterReturning(value = "mycut()",returning = "obj")
    public void myAfterReturning(Object obj){
        System.out.println("后置通知被执行.............");
    }
    //给切入点表达式起别名
    @pointcut(value = "execution(* com.bjpowernode.s04.someServiceImpl.*(..))")
    public void mycut(){}
}

3.6.6 AspectJ 基于XML的 AOP 实现

    <aop:config>
        <!--设置一个公共的切入点表达式-->
        <aop:pointcut id="ponitcut" expression="execution(* com.xin.service.someServiceImpl.*(..))"/>
        <!--ref标签:引入切面类的id-->
        <aop:aspect id="myaspect" ref="myaspect">
            <!--method:切面类中的通知方法的名字-->
            <aop:before method="myBefore" pointcut-ref="ponitcut"></aop:before>
            <aop:after-returning method="myAfterRuturning" pointcut-ref="ponitcut" returning="obj"></aop:after-returning>
            <aop:after method="myAfter" pointcut-ref="ponitcut"></aop:after>
            <aop:after-throwing method="myAfterThrowing" pointcut-ref="ponitcut" throwing="ex"></aop:after-throwing>
            <aop:around method="myAround" pointcut-ref="ponitcut"></aop:around>
        </aop:aspect>
    </aop:config>

3.6.7 SpringAOP与AspectJ的区别

在这里插入图片描述

4. Spring JDBC 框架

4.1 JDBC 框架概述

  • 在使用普通的 JDBC 数据库时,就会很麻烦的写不必要的代码来处理异常,打开和关闭数据库连接等。但 Spring JDBC 框架负责所有的低层细节,从开始打开连接,准备和执行 sql 语句,处理异常,处理事务,到最后关闭连接。

  • 所以当从数据库获取数据时,你所做的是定义连接参数,指定要执行的 sql 语句,每次迭代完成所需的工作。

  • Spring JDBC 提供几种方法数据库中相应的不同的类与接口。我将给出使用 JdbcTemplate 类框架的经典和最受欢迎的方法。这是管理所有数据库通信和异常处理的中央框架类。

4.2 JdbcTemplate 类

  • JdbcTemplate 类执行 sql 查询、更新语句和存储过程调用,执行迭代结果集和提取返回参数值。它也捕获 JDBC 异常并转换它们到 org.springframework.dao 包中定义的通用类、更多的信息、异常层次结构。
  • JdbcTemplate 类的实例是线程安全配置的。所以你可以配置 JdbcTemplate 的单个实例,然后将这个共享的引用安全地注入到多个 DAOs 中。
  • 使用 JdbcTemplate 类时常见的做法是在你的 Spring 配置文件配置数据源,然后共享数据源 bean 依赖注入到 DAO 类中,并在数据源的设值函数中创建了 JdbcTemplate。

第一步:设置依赖

<dependencies>
     <!--添加Spring依赖-->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>5.2.6.RELEASE</version>
    </dependency>
     <!--添加orm依赖,其中包含了spring-jdbc:提供JdbcTemplate,spring-tx:负责在spring框架中实现事务管理功能-->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-orm</artifactId>
      <version>5.3.1</version>
    </dependency>
    <!--
    提供@RunWith(SpringJUnit4ClassRunner.class) 和 @ContextConfiguration({"classpath:applicationContext.xml"})  ,
    让测试运行于Spring测试环境,不用手动加载Spring配置文件,使其自动的初始化IOC容器
    -->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-test</artifactId>
      <version>5.2.6.RELEASE</version>
    </dependency>
    <!--添加单元测试依赖-->
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.12</version>
      <scope>test</scope>
    </dependency>
    <!--添加MysqL驱动-->
    <dependency>
      <groupId>MysqL</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>8.0.16</version>
    </dependency>
    <!--添加连接池-->
    <dependency>
      <groupId>com.alibaba</groupId>
      <artifactId>druid</artifactId>
      <version>1.1.12</version>
    </dependency>
  </dependencies>

第二步:配置spring-jdbc.xml

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

    <!--引入jdbc.properties文件-->
    <context:property-placeholder location="classpath:jdbc.properties"></context:property-placeholder>
    <!--配置数据源-->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="${jdbc.driverClassName}"></property>
        <property name="url" value="${jdbc.url}"></property>
        <property name="username" value="${jdbc.username}"></property>
        <property name="password" value="${jdbc.password}"></property>
    </bean>
    <!--创建jdbcTemplate对象-->
    <bean class="org.springframework.jdbc.core.JdbcTemplate">
         <!--需要引入数据源-->
        <property name="dataSource" ref="dataSource"></property>
    </bean>
</beans>

jdbc.properties文件

jdbc.driverClassName=com.MysqL.cj.jdbc.Driver
jdbc.url=jdbc:MysqL://localhost:3306/imooc_reader?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf8
jdbc.username=root jdbc.password=root
第三步:创建Student实体类

public class Student {
    private Integer id;
    private String name;
    private Integer age;
    private String addr;

    public Student() {
    }

    public Student(Integer id, String name, Integer age, String addr) {
        this.id = id;
        this.name = name;
        this.age = age;
        this.addr = addr;
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

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

    public Integer getAge() {
        return age;
    }

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

    public String getAddr() {
        return addr;
    }

    public void setAddr(String addr) {
        this.addr = addr;
    }

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

测试:

//因为当前的测试用例需要依托与SpringIOC容器,
//JUnit4在运行的时候,会自动的初始化IOC容器,此时就可以通过注入的方式直接获取IOC容器中的bean
@RunWith(SpringJUnit4ClassRunner.class)
//JUnit4初始化的时候,IOC容器就会根据spring-jdbc.xml配置文件完成初始化的工作
@ContextConfiguration("classpath:spring-jdbc.xml")
public class AppTest 
{
    @Autowired
    private JdbcTemplate jdbcTemplate;
    @Test
    //添加
    public void testInsert() {
        String sql="insert into student values(null,?,?,?)";
        jdbcTemplate.update(sql,"李明",2,"海南");
    }
    @Test
    //修改
    public void testUpdate() {
        String sql="update student set age=? where id=?";
        jdbcTemplate.update(sql,0,2);
    }
    @Test
    //删除
    public void testDelete() {
        String sql="delete from student where id=?";
        jdbcTemplate.update(sql,4);
    }
    @Test
    //查询数据个数
    public void testCount() {
        String sql="select count(*) from student";
        Integer integer = jdbcTemplate.queryForObject(sql, Integer.class);
        System.out.println("count="+integer);
    }
    @Test
    //查询数据信息
    public void testList() {
        String sql="select * from student";
        List<Student> list = jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(Student.class));
        for(Student stu:list) {
            System.out.println(stu);
        }
    }
    @Test
    //根据id查询Student对象
    public void testSelectById() {
        String sql="select * from student where id=?";
        Student student = jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<>(Student.class), 1);
        System.out.println(student);
    }
    @Test
    //批量增加
    public void testBatchAdd() {
        List<Object[]> batchArgs = new ArrayList<>();
        Object[] o1 = {"小明", 1,"天"};
        Object[] o2 = {"小龙",2, "南"};
        Object[] o3 = {"小林", 3,"地"};
        Object[] o4 = {"小李",4, "北"};
        batchArgs.add(o1);
        batchArgs.add(o2);
        batchArgs.add(o3);
        batchArgs.add(o4);
        String sql="insert into student values(null,?,?,?)";
        jdbcTemplate.batchUpdate(sql,batchArgs);
    }
    @Test
    public void testBatchDelete() {
        List<Object[]> batchArgs = new ArrayList<>();
        Object[] o1 = {1};
        Object[] o2 = {2,};
        Object[] o3 = {3,};
        Object[] o4 = {4,};
        batchArgs.add(o1);
        batchArgs.add(o2);
        batchArgs.add(o3);
        batchArgs.add(o4);
        String sql="delete from student where age=?";
        jdbcTemplate.batchUpdate(sql,batchArgs);
    }
}

在这里插入图片描述

5. Spring 集成 MyBatis

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

5.1 Spring的事务管理

事务原本是数据库中的概念,在实际项目的开发中,进行事务的处理一般是在业务逻辑层, 即 Service 层。这样做是为了能够使用事务的特性来管理关联操作的业务。
在 Spring 中通常可以通过以下两种方式来实现对事务的管理:
(1)使用 Spring 的事务注解管理事务
(2)使用 AspectJ 的 AOP 配置管理事务(声明式事务管理)

5.2 Spring中事务的五大隔离级别

  • 1.未提交读(Read Uncommitted):允许脏读,也就是可能读取到其他会话中未提交事务修改的数据
  • 2.提交读(Read Committed):只能读取到已经提交的数据。Oracle等多数数据库认都是该级别 (不重复读)
  • 3.可重复读(Repeated Read):可重复读。在同一个事务内的查询都是事务开始时刻一致的,InnoDB认级别。在sql标准中,该隔离级别消除了不可重复读,但是还存在幻象读,但是innoDB解决了幻读
  • 4.串行读(Serializable):完全串行化的读,每次读都需要获得表级共享锁,读写相互都会阻塞

    在这里插入图片描述

4.3 不同数据库的隔离级别(面试点)

MysqLMysqL认的事务处理级别是’REPEATABLE-READ’,也就是可重复读
Oracle:oracle数据库支持READ COMMITTED 和 SERIALIZABLE这两种事务隔离级别。
认系统事务隔离级别是READ COMMITTED,也就是读已提交

4.4 Spring事务的传播特性

在这里插入图片描述


总结:
常用
PROPAGATION_required:必被包含事务
PROPAGATION_REQUIRES_NEW:自己新开事务,不管之前是否有事务
PROPAGATION_SUPPORTS:支持事务,如果加入的方法有事务,则支持事务,如果没有,不单开事务
PROPAGATION_NEVER:不能运行中事务中,如果包在事务中,抛异常
PROPAGATION_NOT_SUPPORTED:不支持事务,运行在非事务的环境

不常用
PROPAGATION_MANDATORY:必须包在事务中,没有事务则抛异常
PROPAGATION_nesTED:嵌套事务

4.5 @Transactional的参数讲解

在这里插入图片描述

4.7 Spring中事务的实现

Spring中事务的实现有两种方式,一种是基于xml文件的实现,一种是基于注解方式实现。在SSM的开发中,多使用注解方式实现事务的处理。

4.7.1 基于注解方式的实现

第一步:添加依赖

<dependencies>
    <!--单元测试-->
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.11</version>
      <scope>test</scope>
    </dependency>
    <!--Aspectj依赖-->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-aspects</artifactId>
      <version>5.2.5.RELEASE</version>
    </dependency>
    <!--spring核心ioc-->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>5.2.5.RELEASE</version>
    </dependency>
    <!--做spring事务用到的-->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-tx</artifactId>
      <version>5.2.5.RELEASE</version>
    </dependency>
    <!--提供JdbcTemplate框架-->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-jdbc</artifactId>
      <version>5.2.5.RELEASE</version>
    </dependency>
    <!--mybatis依赖-->
    <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis</artifactId>
      <version>3.5.6</version>
    </dependency>
    <!--mybatis和spring集成的依赖-->
    <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis-spring</artifactId>
      <version>1.3.1</version>
    </dependency>
    <!--MysqL驱动-->
    <dependency>
      <groupId>MysqL</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>8.0.17</version>
    </dependency>
    <!--阿里公司的数据库连接池-->
    <dependency>
      <groupId>com.alibaba</groupId>
      <artifactId>druid</artifactId>
      <version>1.1.12</version>
    </dependency>
  </dependencies>
  <build>
    <!--目的是把src/main/java目录中的xml文件包含到输出结果中。输出到classes目录中-->
    <resources>
      <resource>
        <directory>src/main/java</directory><!--所在的目录-->
        <includes><!--包括目录下的.properties,.xml 文件都会扫描到-->
          <include>**/*.properties</include>
          <include>**/*.xml</include>
        </includes>
        <filtering>false</filtering>
      </resource>
      <resource>
        <directory>src/main/resources</directory><!--所在的目录-->
        <includes><!--包括目录下的.properties,.xml 文件都会扫描到-->
          <include>**/*.properties</include>
          <include>**/*.xml</include>
        </includes>
        <filtering>false</filtering>
      </resource>
    </resources>
  </build>

第二步:创建com.xin.mapper包

public interface AccountsMapper {
    //增加账户信息
    /*private int aid;
    private String aname;
    private String acontent;*/
    @Insert("insert into accounts values(#{aid},#{aname},#{acontent})")
    int insert(Accounts accounts);
}

创建service

public interface AccountsService {
    int insert(Accounts accounts);
}

创建serviceImpl

@Service
@Transactional(propagation= Propagation.required,//事务的传播特性
        norollbackForClassName = "ArithmeticException",//指定发生什么异常不回滚,使用的是异常的名称
        norollbackFor = ArithmeticException.class,//指定发生什么异常不回滚,使用的是异常的类型
        rollbackForClassName = "",//指定发生什么异常必须回滚,使用的是异常的名称
        rollbackFor = ArithmeticException.class,//指定发生什么异常必须回滚,使用的是异常的类型
        readOnly = false,//认是false,如果是查询操作,必须设置为true
        timeout = -1,//连接超时设置,认是-1,表示永不超时
        isolation = Isolation.DEFAULT//使用数据库自己的隔开级别
)
public class AccountsServiceImpl implements AccountsService {
    @Autowired
    private AccountsMapper accountsMapper;
    @Override
    public int insert(Accounts accounts) {
        int num=0;
        num=accountsMapper.insert(accounts);
        System.out.println("账户增加成功!num="+num);
        //手工抛出异常
        System.out.println(1/0);
        return num;
    }

第三步:创建配置文件
jdbc.properties

jdbc.driverClassName=com.MysqL.cj.jdbc.Driver
jdbc.url=jdbc:MysqL://localhost:3306/ssm?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf8
jdbc.username=root jdbc.password=root

mybatis的配置文件
sqlMapConfig.xml

<?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>

    <!--读取属性文件数据库的配置-->
<!--
    <properties resource="db.properties"></properties>
-->
    <!--设置日志输出语句,显示相应操作的sql语名-->
    <settings>
        <setting name="logImpl" value="STDOUT_LOGGING"/>
    </settings>
    <!--<typeAliases>
        <package name="com.bjpowernode.pojo"></package>
    </typeAliases>-->
    <!--<environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.MysqL.cj.jdbc.Driver"/>
                <property name="url"
                          value="jdbc:MysqL://localhost:3308/ssm?useSSL=false&amp;serverTimezone=Asia/Shanghai&amp;allowPublicKeyRetrieval=true"/>
                <property name="username" value="root"/>
                <property name="password" value="root"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <package name="mapper文件所在的包名"></package>
    </mappers>-->
</configuration>

Spring 的配置文件
applicationContext_mapper.xml

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

    <!--读取属性文件jdbc.properties-->
    <context:property-placeholder location="classpath:jdbc.properties"></context:property-placeholder>
    <!--创建数据源-->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="${jdbc.driverClassName}"></property>
        <property name="url" value="${jdbc.url}"></property>
        <property name="username" value="${jdbc.username}"></property>
        <property name="password" value="${jdbc.password}"></property>
    </bean>
    <!--配置sqlSessionFactorBean类-->
    <bean class="org.mybatis.spring.sqlSessionfactorybean">
        <!--配置数据源-->
        <property name="dataSource" ref="dataSource"></property>
        <!--配置MyBatis核心配置文件-->
        <property name="configLocation" value="sqlMapConfig.xml"></property>
        <!--注册实体类别名-->
        <property name="typeAliasesPackage" value="com.xin.pojo"></property>
     </bean>
    <!--注册mapper.xml-->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="basePackage" value="com.xin.mapper"></property>
    </bean>
</beans>

applicationContext_service.xml

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

    <!--导入applicationContext_mapper.xml文件-->
    <import resource="applicationContext_mapper.xml"></import>
    <!--sm整合是基于注解的,所以要添加包扫描-->
    <context:component-scan base-package="com.xin.service.impl"></context:component-scan>

    <!--事务处理-->

    <!--1.添加事务管理器-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <!--因为事务必须关联数据库处理,所以要配置数据源-->
        <property name="dataSource" ref="dataSource"></property>
    </bean>
    <!--2.添加事务的注解驱动-->
    <tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>
</beans>

测试:

@Test
    public void testAccounts() {
        ApplicationContext ac=new ClasspathXmlApplicationContext("applicationContext_service.xml");
        AccountsService useRSService=(AccountsService) ac.getBean("accountsServiceImpl");
        int num=useRSService.insert(new Accounts(4,"zhangsan4","账户安全2"));
    }

Spring 事务控制 – 基于XML的声明式事务控制

相关文章

显卡天梯图2024最新版,显卡是电脑进行图形处理的重要设备,...
初始化电脑时出现问题怎么办,可以使用win系统的安装介质,连...
todesk远程开机怎么设置,两台电脑要在同一局域网内,然后需...
油猴谷歌插件怎么安装,可以通过谷歌应用商店进行安装,需要...
虚拟内存这个名词想必很多人都听说过,我们在使用电脑的时候...