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 属性值,为引用类型属性进行隐式自动注入(默认是不自动注入引用类型属性)。根据自动注入判断标准的不同,可以分为两种:

  • byName:根据名称自动注入
  • byType: 根据类型自动注入

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 方法。

@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的编程,常用的有以下几种:

  • 1)Before通知:在目标方法被调用前调用,涉及接口org.springframework.aop.MethodBeforeAdvice;
  • 2)After通知:在目标方法被调用后调用,涉及接口为org.springframework.aop.AfterReturningAdvice;
  • 3)Throws通知:目标方法抛出异常时调用,涉及接口org.springframework.aop.ThrowsAdvice;
  • 4)Around通知:拦截对目标对象方法调用,涉及接口为org.aopalliance.intercept.MethodInterceptor。

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 不同数据库的隔离级别(面试点)

MySQL:mysql默认的事务处理级别是’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的声明式事务控制

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

老衲只用阿道夫

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值