Spring学习笔记

文章由B站动力节点相关课程视频整理而成,不作为基础入门教程,只作为备忘的学习笔记。

目录

第一章 Spring概述

1.1 概述

Spring:出现在2002年左右,降低企业级开发难度。帮助进行模块之间、类与类之间的管理,帮助开发人员创建对象,管理对象之间的关系。
2003年传入国内,被大量使用。
2017出现新的流行框架SpringBoot,核心思想与Spring相同。
核心技术:IoC、AOP,能使模块之间、类之间解耦合。
依赖:class A使用class B的属性或方法,称之为class A依赖class B。
官网:spring.io

1.2 优点

(1)轻量:Spring的所需要的jar包都非常小,一般1M以下,几百kb。核心功能所需要的jar包总共3M左右。
Spring框架运行占有资源少,运行效率高,不依赖其他jar。
(2) 针对接口编程,解耦合
(3) AOP 编程的支持
(4) 方便集成各种优秀框架
2,3,4的优点在接下来的学习中体会。

1.3 Spring 体系结构

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

第二章 IoC控制反转

控制反转(IoC,Inversion of Control),是一个概念,是一种思想。指将传统上由程序代 码直接操控的对象调用权交给容器,通过容器来实现对象的装配和管理。控制反转就是对对 象控制权的转移,从程序代码本身反转到了外部容器。通过容器实现对象的创建,属性赋值, 依赖的管理。
Ioc 的实现
➢ 依赖查找:DL( Dependency Lookup ),容器提供回调接口和上下文环境给组件。
➢ 依赖注入:DI(Dependency Injection),程序代码不做定位查询,这些工作由容器自行 完成。
Spring 框架使用依赖注入(DI)实现 IoC。

之前学习到的应用控制反转的实例:Servlet对象的创建管理,这一工作完全交给了Web容器。

2.1 Spring 的第一个程序

2.1.1 创建Maven项目,导入Spring依赖

(Maven项目采用quickstart模板,建立好resources目录)

<dependencies>
    <!--单元测试:通过单元测试可以测试每一个方法-->
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.12</version>
      <scope>test</scope>
    </dependency>
     <!--Spring的依赖-->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>5.2.5.RELEASE</version>
    </dependency>
  </dependencies>

2.1.2 创建业务接口与实现类

接口:

package com.bjpowernode.service;

public interface SomeService {
    void doSome();
}

实现类:

package com.bjpowernode.service;

public class SomeServiceImpl implements SomeService {

    /**
     * spring默认调用无参数构造方法创建对象。
     * 如果没有无参数构造方法,报错:No default constructor found
     */
    public SomeServiceImpl() {
        System.out.println("SomeSeviceImpl的无参数构造方法");
    }

    @Override
    public void doSome() {
        System.out.println("====SomeServiceImpl业务方法doSome=====");
    }
}

2.1.3 创建 Spring 配置文件

如同在Servlet中我们需要在web.xml中注册我们希望服务器自动创建管理的servlet对象一样,在Spring中也需要有类似的配置,来自动创建刚才的SomeServiceImpl对象。

在 src/main/resources/目录现创建一个 xml 文件,文件名可以随意,但 Spring 建议的名 称为 applicationContext.xml。 IDEA已经为我们设计好了Spring配置文件的模板:
右击resources–>new–>XML configuration file–>Spring Config
模板如下:

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

</beans>

注意,Spring 配置文件中使用的约束文件为 xsd 文件。作用与Mybatis的sql映射文件的dtd约束文件类似,但xsd约束作用更强:
1)定义一个 xml 文档中都有什么元素
2)定义一个 xml 文档中都有什么属性
3)定义一个 xml 文档中元素可以有哪些子元素,以及元素的顺序。
4)定义一个 xml 文档中元素和属性的数据类型。
<beans/>是配置文件的根标签。在Spring中,java对象称之为bean。在这个标签下进行java对象的注册:

<bean id="service" class="com.bjpowernode.service.SomeServiceImpl" scope="singleton" /> 

1.声明java对象交给Spring创建和管理,这个步骤称之为声明bean。<bean>等同于: SomeService someService = new com.bjpowernode.service.SomeServiceImpl();然后将创建的对象是放入到Spring的容器(Map<id,对象>):
private final Map<String, Object> factoryBeanObjectCache = new ConcurrentHashMap(16);factoryBeanObjectCache.put("service",someService);
2.<bean/>标签的属性:
class:类的全限定名称,不能是接口(Spring使用反射创建对象);class可以是非自定义的对象,例如”java.util.Date“,依然可以被Spring创建对象。
id:自定义的对象名称,要求是唯一值。 表示在Spring中的对象名称,通过这个名称可以从Spring中找到对象,获取对象。
scope:指定bean对象的作用域(对象的存在范围和可见性)。可取值:

  • 1)单例 : singleton , 默认值,表示叫这个名称的对象在spring容器中只有一个。
  • 2)原型 : prototype , 表示每次使用getBean()都创建一个新的对象。

2.1.4 的创建测试方法

@Test
    public void test01(){
    
        //定义Spring的配置文件, 配置文件是在类路径的根目录之下
        String config = "applicationContext.xml";

        //创建Spring的容器对象.根据Spring配置文件的位置,使用接口的不同实现类
        //1.如果Spring的配置文件是在类路径(classpath),使用ClassPathXmlApplicationContext
        //2.如果Spring的配置文件是放在项目的根之下(与src、target同级目录),使用FileSystemXmlApplicationContext
        //创建Spring容器,会读取配置文件中的bean标签,并创建对应的对象。
        ApplicationContext ctx = new ClassPathXmlApplicationContext(config);

        //从容器中获取对象 使用getBean("<bean>的id")
        SomeService service = (SomeService) ctx.getBean("someService");

        //调用业务方法
        service.doSome();
    }

2.2 Bean的装配

2.2.1 默认装配方式

当我们创建ApplicationContext对象时(2.1.4中),Spring会读取配置文件的<bean/>并执行对应类的无参构造器。
在这些类的无参构造器中加一条输出语句可以验证以上结论。

2.2.2 容器中Bean的作用域

当通过 Spring 容器创建一个 Bean 实例时,不仅可以完成 Bean 的实例化,还可以通过 scope 属性,为 Bean 指定特定的作用域。Spring 支持多种作用域。
(1)singleton:单例模式。即在整个 Spring 容器中,使用 singleton 定义的 Bean 将是单例 的,叫这个名称的对象只有一个实例。默认为单例的。
(2)prototype:原型模式。即每次使用 getBean 方法获取的同一个的实例都是一个 新的实例。
(3)request:对于每次 HTTP 请求,都将会产生一个不同的 Bean 实例。 (4)session:对于每个不同的 HTTP session,都将产生一个不同的 Bean 实例。

注意

  • 对于 scope 的值 request、session 只有在 Web 应用中使用 Spring 时,该作用域才有效。
  • 对于 scope 为 singleton 的单例模式,该 Bean 是在容器被创建时即被装配好了;对于 scope 为 prototype
    的原型模式,Bean 实例是在代码中使用该 Bean 实例时才进行 装配的。

我们只是自动创建了对象,没有给对象的属性赋值。2.3和2.4介绍了两种给属性赋值的方法。

2.3 基于XML的DI

通过在xml配置文件对对象的属性进行赋值。

2.3.1 设值注入

又称为set注入,通过调用类中属性的setter给属性赋值。

2.3.1.1简单类型(Java中的基本类型和String类型)

<bean/>标签下添加:
<property name="属性名" value="简单类型的属性值" />

每一个property标签,完成一个属性的赋值。
注意:Spring执行property标签的原理,是执行name属性值对应的set方法。而并不关心set方法的具体实现和属性是否真的存在。

2.3.1.2引用类型

当指定 bean 的某属性值为另一 bean 的实例时,通过 ref 指定它们间的引用关系。ref 的 值必须为某 bean 的 id 值。 例如:
创建一个学校类;

package com.bjpowernode;

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

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

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

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

然后再在学生类中添加School类型的属性:

package com.bjpowernode;

public class Student {

    private String name;
    private int age;

    //引用类型
    private  School school;

    public Student() {
        System.out.println("Student的无参数构造方法");
    }

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

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

    public void setSchool(School school) {
        System.out.println("setSchool:"+school);
        this.school = school;
    }

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

在配置文件中:

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

    <!--ref作为属性-->
    <bean id="myStudent" class="com.bjpowernode.Student" >
        <property name="name" value="李四" />
        <property name="age" value="20" />
        <property name="school" ref="mySchool" /> <!--setSchool(mySchool) -->
    </bean>

    <!--ref作为子标签-->
    <bean id="myStudent2" class="com.bjpowernode.Student">
        <property name="name" value="张三" />
        <property name="age" value="22" />
        <property name="school">
            <ref bean="mySchool"/>
        </property>
    </bean>

    <!--声明School-->
    <bean id="mySchool" class="com.bjpowernode.School">
        <property name="name" value="北京大学" />
        <property name="address" value="北京的海淀区" />
    </bean>
</beans>

2.3.2 构造注入(理解)

执行类的有参构造,在构造对象的同时给属性赋值。
1.给Student类添加有参构造器:

//定义有参数构造方法
    public  Student(String myname, int myage, School mySchool){
        System.out.println("Student有参数构造方法");
        //给属性完成赋值
        this.name = myname;
        this.age  = myage;
        this.school = mySchool;
    }

2.对xml配置文件进行相关配置:

<!--使用name属性-->
    <bean id="myStudent" class="com.bjpowernode.Student" >
        <constructor-arg name="myage" value="22"/>
        <constructor-arg name="myname" value="张三" />
        <constructor-arg name="mySchool" ref="myXueXiao" />
    </bean>
    
<!--声明School-->
    <bean id="myXueXiao" class="com.bjpowernode.School">
      <property name="name" value="北京大学" />
      <property name="address" value="北京的海淀区" />
    </bean>

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

 <!--使用index属性-->
    <bean id="myStudent2" class="com.bjpowernode.ba03.Student">
        <constructor-arg index="1" value="28" />
        <constructor-arg index="0" value="李四" />
        <constructor-arg index="2" ref="myXueXiao"/>
    </bean>

    <!--省略index-->
    <bean id="myStudent3" class="com.bjpowernode.ba03.Student">
        <constructor-arg value="周仓" />
        <constructor-arg value="24" />
        <constructor-arg ref="myXueXiao"/>
    </bean>

2.3.3 引用类型的自动注入

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

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

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

例如,在Student类中,School类型的属性名是”school“,那么在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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd">

    <!--byName自动注入,添加属性autowire="byName"-->
    <bean id="myStudent" class="com.bjpowernode.Student" autowire="byName">
        <property name="name" value="李四" />
        <property name="age" value="22"/>
        <!--引用类型-->
        <!--<property name="school" ref="myXueXiao" />-->
    </bean>
    
    <!--声明School-->
    <bean id="school" class="com.bjpowernode.School">
      <property name="name" value="清华大学" />
      <property name="address" value="北京的海淀区" />
    </bean>
    
</beans>
2.3.3.1 byType 方式自动注入

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

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

    <!--
        2.byType:按类型注入,java类中引用类型的数据类型和spring容器(xml配置文件)中<bean>的
                 class属性值是同源关系的,这样的bean能够赋值给引用类型
          同源关系:
           1.java类中引用类型的数据类型和<bean>的class值是一样的。
           2.java类中引用类型的数据类型和<bean>的class值是父子类关系的
           3.java类中引用类型的数据类型和<bean>的class值是接口和实现类关系

          指定byType
          <bean id="xx" class="yyy" autowire="byType">
             简单类型的赋值
          </bean>

          注意:在xml配置文件使用byType,符合条件的对象只能有一个,多余一个是报错的。
     -->

    <!--byType自动注入-->
    <bean id="myStudent" class="com.bjpowernode.Student" autowire="byType">
        <property name="name" value="周武" />
        <property name="age" value="22"/>
        <!--引用类型-->
        <!--<property name="school" ref="myXueXiao" />-->
    </bean>



    <!--声明School-->
    <bean id="mySchool" class="com.bjpowernode.School">
      <property name="name" value="清华大学" />
      <property name="address" value="北京的海淀区" />
    </bean>

    <!--声明School的子类对象-->
    <!--<bean id="xiaoXueSchool" class="com.bjpowernode.ba05.XiaoXueSchool">
        <property name="name" value="中兴小学" />
        <property name="address" value="北京的大兴区"/>
    </bean>-->
</beans>

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

在实际应用里,随着应用规模的增加,系统中 Bean 数量也大量增加,导致配置文件变 得非常庞大、臃肿。为了避免这种情况的产生,提高配置文件的可读性与可维护性,可以将 Spring 配置文件分解成多个配置文件。
包含关系的配置文件: 多个配置文件中有一个总文件,总配置文件将各其它子文件通过<import/>引入。在 Java 代码中只需要使用总配置文件对容器进行初始化即可。例如:
xml文件的目录
在total.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"
      xsi:schemaLocation="http://www.springframework.org/schema/beans
      http://www.springframework.org/schema/beans/spring-beans.xsd">

  <!--
      关键字 "classpath:" , 表示类路径,告诉spring到类路径中查找配置文件
  -->

   <!--包含其他的配置文件-->
   <!--
   <import resource="classpath:ba06/spring-school.xml"/>
   <import resource="classpath:ba06/spring-student.xml"/>
   -->
   <!--使用通配符,指定多个配置文件。
       通配符“*”,表示0或多个字符
       注意:总的文件名称(total.xml),不能包含在通配符的范围内,不能叫做spring-total.xml
   -->
   <import resource="classpath:ba06/spring-*.xml"/>

</beans>

2.4 基于注解的DI

通过以下四个步骤实现DI:
1. 创建Maven项目,在pom.xml 加入 AOP 依赖

<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-aop</artifactId>
   <version>4.3.16.RELEASE</version>
</dependency> 

如果你在pom.xml中添加了spring-context,那此依赖自动包含AOP,无需再次添加。

2.需要更换配置文件头,加入 spring-context.xsd 约束。 约 束 在 %SPRING_HOME%\docs\spring-framework-reference\html\xsd-configuration.html 文件中。

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

</beans>

如果使用IDEA进行编辑,这一步骤可以省略,在执行第四步时根据提示自动添加即可。
3.在类中添加注解

package com.bjpowernode.ba01;

import org.springframework.stereotype.Component;
import org.springframework.stereotype.Repository;

//@Component: 创建类的对象,等同于<bean />,默认是单例对象
@Component("myStudent")
public class Student {

    private String name;
    private int age;

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

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

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

4.声明组件扫描器。在Spring配置文件的<beans/>标签下:

	<!--声明组件扫描器(context:component),指定注解所在的包名,让框架找到注解-->
    <!--
       base-package:指定注解在项目中的包名,框架会扫描这个包和子包中所有类的注解,
                    按照注解的功能,创建对象,赋值属性。
    -->
    <context:component-scan base-package="com.bjpowernode.ba01" />

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

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

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

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

@Component: 创建类的对象,等同于<bean />,默认是单例对象
属性: value 表示对象的名称(的id)
位置: 在类定义的上面,表示创建此类的对象。

例如:@Component(value = "myStudent")等价于<bean id="myStudent" class="com.bjpowernode.ba01.Student"/>

另外,Spring 还提供了 3 个创建对象的注解:
➢ @Repository 用于对 DAO 实现类进行注解
➢ @Service 用于对 Service 实现类进行注解
➢ @Controller 用于对 Controller 实现类进行注解 这三个注解与@Component 都可以创建对象,但这三个注解还有其他的含义,@Service 创建业务层对象,业务层对象可以加入事务功能,@Controller 注解创建的对象可以作为处理器接收用户的请求。
@Repository,@Service,@Controller 是对@Component 注解的细化,标注不同层的对象。即持久层对象,业务层对象,控制层对象。

@Component 不指定 value 属性,bean 的 id 是类名的首字母小写。

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

package com.bjpowernode.ba02;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component("myStudent")
public class Student {

    /**
     * @Value: 简单类型的属性赋值
     *   属性: value 是String类型的, 表示简单类型的属性值
     *   位置:
     *       1.在属性定义的上面, 无需set方法,常用的方式
     *       2.在set方法上面
     */
    @Value("张三01")
    private String name;


    private int age;

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

    @Value("28")
    public void setAge(int age) {
        System.out.println("setAge:"+age);
        this.age = age;
    }

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

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

需要在引用属性上使用注解@Autowired,该注解默认使用按类型自动装配Bean的方式。
使用该注解完成属性注入时,类中无需 setter。当然,若属性有 setter,则也可将其加 到 setter 上。 举例:
School类:

package com.bjpowernode.ba03;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component("mySchool")
public class School {
   @Value("人民大学")
   private  String name;
   @Value("北京的海淀")
   private  String address;

Student类:

package com.bjpowernode.ba03;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component("myStudent")
public class Student {

    @Value("张三")
    private String name;

	@Value("23")
    private int age;


    /**
     * 引用类型
     * @Autowired:引用类型的自动注入, 支持byName, byType.默认是byType
     *       位置:
     *         1.属性定义的上面,无需set方法,常用方式
     *         2.在set方法的上面
     */
    //byType
    @Autowired
    private School school;
    
    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", school=" + school +
                '}';
    }
}

==为了防止空指针异常的产生,推荐@AutoWired注解加在带参构造器上。==具体原因参见:博客.

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

需要在引用属性上联合使用注解@Autowired 与@Qualifier。@Qualifier 的 value 属性用 于指定要匹配的 Bean 的 id 值。类中无需 set 方法,也可加到 set 方法上。 举例:
如果Student要自动注入2.4.3中的School类,只需在school属性前添加注解:

 	//byName
    @Autowired
    @Qualifier("mySchool")//与School类的@component注解value值相同
    private School school;

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

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

Spring提供了对jdk中@Resource注解的支持。@Resource注解既可以按名称匹配Bean, 也可以按类型匹配 Bean。默认是按名称注入。使用该注解,要求 JDK 必须是 6 及以上版本。 @Resource 可在属性上,也可在 set 方法上。
个人认为,本质就是用@Resource一个注解代替@Autowired和@Qualifier两个注解。
一、byType 注入引用类型属性
@Resource 注解若不带任何参数,采用默认按类型的方式注入,按名称不能注入 bean, 则会按照类型进行 Bean 的匹配注入。
如果Student要自动注入2.4.3中的School类,只需在school属性前添加注解:

	@Resource
    private School school;

二、byName 注入引用类型属性
@Resource 注解指定其 name 属性,则 name 的值即为按照名称进行匹配的 Bean 的 id。
如果Student要自动注入2.4.3中的School类,只需在school属性前添加注解:

	@Resource("mySchool")
    private School school;

2.5 注解与 XML 的对比

注解优点是:

  • 方便
  • 直观
  • 高效(代码少,没有配置文件的书写那么复杂)。

其弊端也显而易见:以硬编码的方式写入到 Java 代码中,修改是需要重新编译代码的。

XML 方式优点是:
配置和代码是分离的,在 xml 中做修改,无需编译代码,只需重启服务器即可将新的配置加载。
xml 的缺点是:编写麻烦,效率低,大型项目过于复杂。

个人发现:xml与注解方式还有一个区别在于,xml方式可以通过bean标签创建一个类的多个对象,但注解方式并不能,因为一个类不能存在两个同名的注解。

第三章 AOP面向切面编程

3.1 动态代理

参见我的另一篇文章: 《JDK动态代理和实现原理初步》.

3.2 AOP概述

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

面向切面编程,就是将交叉业务逻辑封装成切面,利用 AOP 容器的功能将切面织入到 主业务逻辑中。所谓交叉业务逻辑是指,通用的、与主业务逻辑无关的代码,如安全检查、 事务、日志、缓存等。 若不使用 AOP,则会出现代码纠缠,即交叉业务逻辑与主业务逻辑混合在一起。这样, 会使主业务逻辑变的混杂不清。

例如,转账,在真正转账业务逻辑前后,需要权限控制、日志记录、加载事务、结束事 务等交叉业务逻辑,而这些业务逻辑与主业务逻辑间并无直接关系。但,它们的代码量所占 比重能达到总代码量的一半甚至还多。它们的存在,不仅产生了大量的“冗余”代码,还大 大干扰了主业务逻辑—转账。

3.3 AOP的相关术语

(1) 切面(Aspect)
切面泛指交叉业务逻辑。上例中的事务处理、日志处理就可以理解为切面。常用的切面 是通知(Advice)。实际就是对主业务逻辑的一种增强。
(2) 连接点(JoinPoint)
连接点指可以被切面织入的具体方法。通常业务接口中的方法均为连接点。
(3) 切入点(Pointcut)
切入点指声明的一个或多个连接点的集合。通过切入点指定一组方法。 被标记为 final 的方法是不能作为连接点与切入点的。因为最终的是不能被修改的,不能被增强的。
(4) 目标对象(Target)
目标对象指将要被增强的对象。即包含主业务逻辑的类的对象。上例中的 StudentServiceImpl 的对象若被增强,则该类称为目标类,该类对象称为目标对象。当然,不 被增强,也就无所谓目标不目标了。
(5) 通知(Advice)
通知是切面的一种实现,可以完成简单织入功能(织入功能就是在这里完成的)。Advice 也叫增强。上例中的 MyInvocationHandler 就可以理解为是一种通知。换个角度来说,通知定义了增强代码切入到目标代码的时间点,是目标方法执行之前执行,还是之后执行等。通知类型不同,切入时间不同。

切入点定义切入的位置,通知定义切入的时间。

3.4 AOP的实现

AOP的技术实现框架:

  1. Spring:Spring的AOP实现较为笨重,一般用在事务处理。

  2. aspectJ:一个开源,专门做AOP的框架,隶属于Eclipse基金会。

    Spring框架集成了aspectJ的功能。
    aspectJ框架实现AOP有两种方式:
    1)使用xml配置文件,一般用于配置全局事务;
    2)使用注解。一般在项目开发中使用这种方式。

3.5 AspectJ 对 AOP 的实现

3.5.1 AspectJ 的通知类型

AspectJ 中常用的通知有五种类型,体现在五个不同的添加在切面的注解:
(1)前置通知
(2)后置通知
(3)环绕通知
(4)异常通知
(5)最终通知

3.5.2 AspectJ 的切入点表达式

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

execution ( [modifiers-pattern]  访问权限类型
	 ret-type-pattern 返回值类型
	  [declaring-type-pattern]  全限定性类名 
	  name-pattern(param-pattern) 方法名(参数类型和参数个数) 
	 [throws-pattern]  抛出异常类型 ) 

切入点表达式要匹配的对象就是目标方法的方法名。所以,execution 表达式中明显就是方法的签名。注意,表达式中加[ ]的部分表示可省略部分,各部分间用空格分开。

在其中可以使用以下符号:
*:0至多个任意字符
..: 用在方法参数中,表示任意多个参数;用在包名后,表示当前包及其子包
+:用在类名后,表示当前类及其子类;用在接口名后,表示当前接口及其实现类

3.5.3 AspectJ 的开发环境

  1. 创建Maven项目时,引入AspectJ依赖:
    <dependencies>
    <!--单元测试:通过单元测试可以测试每一个方法-->
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.12</version>
      <scope>test</scope>
    </dependency>
    <!--Spring的依赖-->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>4.3.16.RELEASE</version>
    </dependency>
    <!--aspectj的依赖-->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-aspects</artifactId>
      <version>4.3.16.RELEASE</version>
    </dependency>
    
``` ![](https://img-blog.csdnimg.cn/20200604223245659.PNG#pic_center)
  1. 引入约束(第4、7、8行)
    <?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:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/aop
       http://www.springframework.org/schema/aop/spring-aop.xsd">
       
    </beans>
    
    在IDEA中开发,可以省略这一步。在添加aop标签时会自动引入约束文件。

3.5.4 AspectJ 基于注解的 AOP 实现

3.5.4.1 AspectJ 基于注解的 AOP 实现步骤
  1. 定义业务接口与实现类

    package com.bjpowernode.service;
    
    public interface SomeService {
    	void doSome(String name, int age);
    }
    
    package com.bjpowernode.service;
    
    public class SomeServiceImpl implements SomeService {
    
    	@Override
    	public void doSome(String name,int age) {
        	System.out.println("SomeSeviceImpl的业务方法doSome");
    	}
    }
    
  2. 定义切面类

    package com.bjpowernode.aspect;
    
    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.annotation.Before;
    
    import java.util.Date;
    
    @Aspect
    public class TestAspect {
    
    	@Before("execution(public * *(..))")
    	public void printTime(){
        	System.out.println("方法执行时间=" + new Date());
    	}
    }
    
  3. 声明目标对象与切面类对象,注册AspectJ的自动代理

     <?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:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
    
    	<bean id="someService" class="com.bjpowernode.service.SomeServiceImpl" />
    
    	<bean id="testAspect" class="com.bjpowernode.aspect.TestAspect" />
    
    	<aop:aspectj-autoproxy />
    </beans>
    

    <aop:aspectj-autoproxy/>通过扫描找到@Aspect 定义的切面类,再由切面类根据切入点找到目标类的目标方法,再由通知类型找到切入的时间点。

  4. 测试。注意获取代理对象根据目标对象的id。

    @Test
    public void test01(){
       ApplicationContext ac = new ClassPathXmlApplicationContext("test.xml");
       SomeService proxy = (SomeService) ac.getBean("someService");
    
       proxy.doSome("zhangsna",25);
    }
    
3.5.4.2 AspectJ 通知注解

**通知注解:**在切面类中修饰方法的注解,这些注解体现了通知类型。例如上面例子中@Before就是一个通知注解。通知注解修饰的方法称之为通知方法。
一共有五种通知类型,就对应了五种通知注解。下面一一介绍。

3.5.4.2.1 @Before前置通知-方法有JoinPoint参数

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

 @Before(value = "execution(* *..SomeServiceImpl.doSome(..))")
    public void myBefore(JoinPoint jp){
        //在方法中,实现功能的增强,例如日志的代码

        //获取方法的定义
        System.out.println("连接点的方法定义:"+jp.getSignature());
        System.out.println("连接点的方法名称:"+jp.getSignature().getName());
        //获取方法执行时的参数
        Object args [] = jp.getArgs();
        for (Object arg : args) {
            System.out.println(arg);
        }

不光前置通知的方法,可以包含一个 JoinPoint 类型参数,所有的通知方法均可包含该 参数。

3.5.4.2.2 @AfterReturning 后置通知-注解有 returning 属性

在目标方法执行之后执行。由于是目标方法之后执行,所以可以获取到目标方法的返回 值。该注解的 returning 属性就是用于指定接收方法返回值的变量名的。所以,被注解为后 置通知的方法,除了可以包含 JoinPoint 参数外,还可以包含用于接收返回值的变量。该变 量最好为 Object 类型,因为目标方法的返回值可能是任何类型。
例如,在业务接口中定义一个有返回值的抽象方法:

String doOther(String name,int age);

再在业务类中实现:

@Override
    public String doOther(String name, int age) {
        System.out.println("SomeSeviceImpl的业务方法doOther");
        return "abcd";
    }

然后在切面类中定义一个切面:

@AfterReturning(value = "execution(* *..SomeServiceImpl.doOther(..))",returning = "result")
    public void afterReturing(JoinPoint jp,Object result){
        //修改目标方法的返回值
        if( result != null){
            String st = (String)result;
            result = st.toUpperCase();
        }
        System.out.println("后置通知,在目标方法之后执行的。能够获取到目标方法的执行结果:"+result);
    }
3.5.4.2.3 @Around 环绕通知-增强方法有 ProceedingJoinPoint 参数

在目标方法执行之前之后执行。
被注解为环绕增强的方法要有返回,Object 类型。并 且方法可以包含一个 ProceedingJoinPoint 类型的参数。接口 ProceedingJoinPoint 继承于JoinPoint,因此可以根据它获取方法的信息。其有一个 proceed()方法,用于执行目标方法。若目标方法有返回值,则该方法的返回值就是目标方法的返回值。最后,环绕增强方法将其返回值返回。该增强方法实际是拦截了目标方法的执行。
接口增加方法:

 String doFirst(String name,int age);

在实现类中实现此方法:

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

增加切面:

@Around(value = "execution(* *..SomeServiceImpl.doFirst(..))")
   public Object myAround(ProceedingJoinPoint pjp) throws Throwable {

       //ProceedingJoinPoint能获取连接点方法的定义,参数等信息
       String name = "";
       Object args [] = pjp.getArgs();
       if( args.length > 1){
           name = (String)args[0];
       }

       Object result = null;
       System.out.println("环绕通知:在目标方法之前加入日志");

       //控制目标方法是否执行
       if( "zs".equals(name)){
           //执行目标方法
           result  = pjp.proceed(); //doFirst  result = method.invoke(target, args);
       }

       System.out.println("环绕通知:在目标方法之后加入事务处理");

       //返回目标方法的执行结果(可以是修改后的结果)
       //修改目标方法的执行结果
       if(result != null){
           result= "Hello AspectJ";
       }
       return result;
   }
3.5.4.2.4 @AfterThrowing 异常通知-注解中有 throwing 属性

在目标方法抛出异常后执行。该注解的 throwing 属性用于指定所发生的异常类对象。 当然,被注解为异常通知的方法可以包含一个参数 Throwable,参数名称为 throwing 指定的名称,表示发生的异常对象。

在效果上相当于一个try…catch语句。目标方法的方法体在try语句块中,而切面方法的方法体放在了catch子句中。

3.5.4.2.5 @After最终通知

无论目标方法是否抛出异常,该增强均会被执行。

在执行效果上,相当于将切面方法的方法体放在了try…catch…finally…语句发finally子句中。

3.5.4.2.6 @Pointcut 定义切入点

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

 @After(value = "mypt()")
    public void myAfter(){
        System.out.println("最终通知,总是会被执行的,可以做程序最后要做的工作,例如资源回收,内存释放");
    }

    /**
     * @Pointcut: 定义和管理切入点
     *     属性:value 切入点表达式
     *     位置:在自定义的方法上面。
     * 作用:@Pointcut定义在方法的上面, 这个方法的名称就是切入点的别名
     * 其他的通知注解的value属性可以使用方法名称,表示切入点。
     */
    @Pointcut(value = "execution(* *..SomeServiceImpl.doThird(..))")
    private void mypt(){
        //无需代码
    }
3.5.4.3 设置AspectJ 实现 AOP的方式

在Spring配置文件中,通过<aop:aspectj-autoproxy/>的proxy-target-class属性设置选择通过JDK动态代理还是cglib动态代理实现AOP。

<!--声明自动代理生成器:使用aspectj把spring容器中目标类对象生成代理
        proxy-target-class="true"表示使用cglib动态代理

        目标类有接口,默认使用jdk动态代理。
        目标类没有接口,默认时候cglib动态代理
        目标类有接口,也可以使用cglib动态代理,需要设置proxy-target-class="true"
    -->
    <!-- <aop:aspectj-autoproxy proxy-target-class="true" />-->
    <aop:aspectj-autoproxy/>

3.5.4 AspectJ 基于XML的 AOP 实现

课程未介绍,日后补充

3.6 Spring实现AOP

课程未介绍,日后补充

第四章 Spring集成myBatis

4.1 概述

Spring集成myBatis,其本质工作就是:将使用mybatis框架时用到的一些需要自己创建的对象,交由Spring统一管理。主要有一下对象:
1.数据源dataSource。就是保存数据库连接信息的对象。在实际业务开发中,我们放弃使用Mybatis自带的数据库连接池,而采用阿里的Druid,更加高效;
2.生成sqlSession对象的sqlSessionFactory;
3.Dao接口的实现类对象。

4.2 Spring集成myBatis创建项目的流程

  1. 新建 mysql的库,准备数据。( student表)
    本人之前学习MySql用到的一张表:
    建表语句:
    CREATE TABLE t_student (
     id int(11) NOT NULL AUTO_INCREMENT,
     stdno int(11) DEFAULT NULL,
     stdname varchar(255) DEFAULT NULL,
     classno int(11) DEFAULT NULL,
     PRIMARY KEY (id)
    ) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
    
    添加数据:
    INSERT INTO t_student (id,stdno,stdname,classno) VALUES (1,10101,'张三',101);
    INSERT INTO t_student (id,stdno,stdname,classno) VALUES (2,10102,'李四',101);
    INSERT INTO t_student (id,stdno,stdname,classno) VALUES (3,20101,'王五',201);
    INSERT INTO t_student (id,stdno,stdname,classno) VALUES (4,20202,'赵六',201);
    INSERT INTO t_student (id,stdno,stdname,classno) VALUES (5,10201,'王力宏',102);
    INSERT INTO t_student (id,stdno,stdname,classno) VALUES (6,10202,'黄晓明',102);
    INSERT INTO t_student (id,stdno,stdname,classno) VALUES (7,20201,'张靓颖',202);
    INSERT INTO t_student (id,stdno,stdname,classno) VALUES (8,20202,'刘德华',202);
    
    mysql> select * from t_student;
    +----+-------+---------+---------+
    | id | stdno | stdname | classno |
    +----+-------+---------+---------+
    |  1 | 10101 | 张三    |     101 |
    |  2 | 10102 | 李四    |     101 |
    |  3 | 20101 | 王五    |     201 |
    |  4 | 20202 | 赵六    |     201 |
    |  5 | 10201 | 王力宏  |     102 |
    |  6 | 10202 | 黄晓明  |     102 |
    |  7 | 20201 | 张靓颖  |     202 |
    |  8 | 20202 | 刘德华  |     202 |
    +----+-------+---------+---------+
    8 rows in set (0.00 sec)
    
  2. 加入依赖
    1)spring依赖
    2)mybatis的依赖
    3)mybatis-spring依赖, 这个jar是从mybatis官网下载的, mybatis提供在spring中创建对象的类。
    4)mysql的驱动
    5)druid,数据库连接池的依赖
    <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>4.3.16.RELEASE</version>
    </dependency>
    <!--spring的事务-->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-tx</artifactId>
      <version>4.3.16.RELEASE</version>
    </dependency>
    <!--spring访问数据库-->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-jdbc</artifactId>
      <version>4.3.16.RELEASE</version>
    </dependency>
    <!--mybatis的依赖-->
    <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis</artifactId>
      <version>3.4.5</version>
    </dependency>
    <!--mybatis整合spring的依赖:创建mybatis对象-->
    <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.16</version>
    </dependency>
    <!--数据库连接池-->
    <dependency>
      <groupId>com.alibaba</groupId>
      <artifactId>druid</artifactId>
      <version>1.1.12</version>
    </dependency>
    
  1. 新建实体类Student

    package com.yang.domain;
    
    public class Student {
    
    	private int id;
    	private int stdno;
    	private String stdname;
    	private int classno;
    
    	public Student() {
    	}
    
    	public Student(int id, int stdno, String stdname, int classno) {
        	this.id = id;
        	this.stdno = stdno;
        	this.stdname = stdname;
        	this.classno = classno;
    	}
    
    	public int getId() {
        	return id;
    	}
    
    	public void setId(int id) {
        	this.id = id;
    	}
    
    	public int getStdno() {
        	return stdno;
    	}
    
    	public void setStdno(int stdno) {
        	this.stdno = stdno;
    	}
    
    	public String getStdname() {
        	return stdname;
    	}
    
    	public void setStdname(String stdname) {
        	this.stdname = stdname;
    	}
    
    	public int getClassno() {
            return classno;
    	}
    
    	public void setClassno(int classno) {
            this.classno = classno;
        }
    
        @Override
        public String toString() {
            return "Student{" +
                    "id=" + id +
                    ", stdno=" + stdno +
                    ", stdname='" + stdname + '\'' +
                    ", classno=" + classno +
                    '}';
        }
    }
    
    

    记得在pom.xml中添加资源插件!

  2. 新建Dao接口和sql映射文件

    package com.yang.dao;
    
    import com.yang.domain.Student;
    
    import java.util.List;
    
    public interface StudentDao {
        List<Student> selectAll();
        int insertStudent(Student student);
    }
    
    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <mapper namespace="com.yang.dao.StudentDao">
        <select id="selectAll" resultType="Student">
          select * from t_student
        </select>
        <insert id="insertStudent">
            insert into t_student values(#{id},#{stdno},#{stdname},#{classno})
        </insert>
    </mapper>
    
  3. 新建mybatis主配置文件
    由于使用阿里的数据库连接池,所以不需要<environments/>标签:

    <?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>
        <typeAliases>
            <package name="com.bjpowernode.beans"/>
        </typeAliases>
    
        <mappers>
            <package name="com.bjpowernode.dao"/>
        </mappers>
    </configuration>
    
  4. 新建Service接口和实现类, 在实现类中有Dao的属性
    在实际项目中,我们在对数据库前需要一些其他的业务代码,例如逻辑判断、身份认证等,这些放在Service中:

    package com.yang.service;
    
    import com.yang.domain.Student;
    
    import java.util.List;
    
    public interface SomeService {
        int addStudent(Student student);
        List<Student> queryStudents();
    }
    
    package com.yang.service.impl;
    
    import com.yang.dao.StudentDao;
    import com.yang.domain.Student;
    import com.yang.service.SomeService;
    
    import java.util.List;
    
    public class SomeServiceImpl implements SomeService {
    
        private StudentDao stuDao;
    
        //使用ioc,设值注入,在配置文件中给dao赋值
        public SomeServiceImpl(StudentDao stuDao) {
            this.stuDao = stuDao;
        }
    
        @Override
        public int addStudent(Student student) {
            return stuDao.insertStudent(student);
        }
    
        @Override
        public List<Student> queryStudents() {
            return stuDao.selectAll();
        }
    }
    
    
  5. 新建spring的配置文件(重要)
    1)声明数据源DataSource对象
    2)声明SqlSessionFactoryBean,创建SqlSessionFactory对象
    3)声明MyBatis的扫描器,创建Dao接口的实现类对象
    4)声明自定义的Service ,把3)中的Dao对象注入赋值给Service的属性

    <?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 http://www.springframework.org/schema/context/spring-context.xsd">
        <!--读取配置文件
            location:指定属性配置文件的路径
            "classpath:":关键字表示类文件,也就是class文件所在的目录
        -->
        <context:property-placeholder location="classpath:jdbc.properties" />
    
        <!--声明数据源DataSource-->
        <bean id="myDataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
            <!--读取属性配置文件的key的值,使用 ${key}-->
            <!--数据库的uri-->
            <property name="url" value="${jdbc.url}"/> <!--setUrl()-->
            <!--数据库的用户名-->
            <property name="username" value="${jdbc.username}"/> <!--setUsername()-->
            <!--访问密码-->
            <property name="password" value="${jdbc.password}" /><!--setPassoword()-->
        </bean>
        <!--
            DruidDataSource myDataSource = new DruidDataSource();
            myDataSource.setUrl();
            myDataSource.setUsername();
            myDataSource.setPassword();
            myDataSource.init();
        -->
    
        <!--声明SqlSessionFactoryBean,创建SqlSessionFactory对象-->
        <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
            <!--数据源-->
            <property name="dataSource" ref="myDataSource" />
            <!--指定mybatis的主配置文件-->
            <property name="configLocation" value="classpath:mybatis.xml" />
        </bean>
    
        <!--声明MyBatis的扫描器,创建Dao接口的实现类对象-->
        <bean  class="org.mybatis.spring.mapper.MapperScannerConfigurer">
            <!--指定SqlSessionFactory对象,能获取SqlSession-->
            <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />
            <!--指定Dao接口的包名,框架会把这个包中的所有接口一次创建出Dao对象-->
            <property name="basePackage" value="com.yang.dao" />
        </bean>
        <!--
            从spring中获取SqlSessionFacotory,因为spring是一个容器(Map)
            SqlSessionFactory factory  = map.get("sqlSessionFactory");
            SqlSession session = factory.openSession();
    
            for(接口:com.bjpowernode.dao)
            {
                Dao对象 =  session.getMapper(接口)
                //把创建好的对象放入到spring容器中
                spring的Map.put( 接口名的首字母小写, Dao对象 )
    
            }
    
        -->
        <!--声明Service-->
        <bean id="someService" class="com.yang.service.impl.SomeServiceImpl">
            <constructor-arg ref="studentDao"/>
        </bean>
    </beans>
    

    jdbc.properties:

    jdbc.url=jdbc:mysql://localhost:3306/web01?serverTimezone=UTC
    jdbc.username=root
    jdbc.password=******
    
  6. 新建测试类, 从spring容器中获取Service,调用Service的方法,完成数据库的操作

    package com.yang;
    
    import static org.junit.Assert.assertTrue;
    
    import com.yang.domain.Student;
    import com.yang.service.SomeService;
    import org.junit.Test;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    
    import java.util.List;
    
    /**
     * Unit test for simple App.
     */
    public class AppTest 
    {
        /**
         * Rigorous Test :-)
         */
        @Test
        public void test01()
        {
            ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
            SomeService someservice = (SomeService) ac.getBean("someService");
            List<Student> students = someservice.queryStudents();
            for(Student student:students){
                System.out.println(student);
            }
        }
    
        @Test
        public void test02()
        {
            ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
            SomeService someservice = (SomeService) ac.getBean("someService");
    
            //准备数据
            Student student = new Student();
            student.setId(9);
            student.setStdno(30101);
            student.setStdname("姜岑");
            student.setClassno(301);
    
            int num = someservice.addStudent(student);
            System.out.println(num);
        }
    }
    

第五章 Spring与事务

5.1 理论知识

5.1.1 事务管理器接口

事务管理器是 PlatformTransactionManager 接口对象。其主要用于完成事务的提交、回滚,及获取事务的状态信息。
PlatformTransactionManager 接口有两个常用的实现类
➢ DataSourceTransactionManager:使用 JDBC 或 MyBatis 进行数据库操作时使用。
➢ HibernateTransactionManager:使用 Hibernate 进行持久化数据时使用。

5.1.2 Spring 的回滚方式

Spring 事务的默认回滚方式是:发生运行时异常和 error 时回滚,发生受查(编译)异常时 提交。不过,对于受查异常,程序员也可以手工设置其回滚方式。

运行时异常,是 RuntimeException 类或其子类,即只有在运行时才出现的异常。如, NullPointerException、ArrayIndexOutOfBoundsException、IllegalArgumentException 等均属于运 行时异常。这些异常由 JVM 抛出,在编译时不要求必须处理(捕获或抛出)。但,只要代码 编写足够仔细,程序足够健壮,运行时异常是可以避免的。

受查异常,也叫编译时异常,即在代码编写时要求必须捕获或抛出的异常,若不处理, 则无法通过编译。如 SQLException,ClassNotFoundException,IOException 等都属于受查异常。 RuntimeException 及其子类以外的异常,均属于受查异常。

5.1.3 事务定义接口

事务定义接口TransactionDefinition中定义了事务描述相关的三类常量:事务隔离级别、 事务传播行为、事务默认超时时限,及对它们的操作。

  1. 定义了五个事务隔离级别常量
    这些常量均是以 ISOLATION_开头。即形如 ISOLATION_XXX。 ➢ DEFAULT:采用 DB 默认的事务隔离级别。MySql 的默认为 REPEATABLE_READ; Oracle 默 认为 READ_COMMITTED。
    ➢ READ_UNCOMMITTED:读未提交。未解决任何并发问题。
    ➢ READ_COMMITTED:读已提交。解决脏读,存在不可重复读与幻读。
    ➢ REPEATABLE_READ:可重复读。解决脏读、不可重复读,存在幻读 。
    ➢ SERIALIZABLE:串行化。不存在并发问题。

  2. 定义了七个事务传播行为常量
    所谓事务传播行为是指,处于不同事务中的方法在相互调用时,执行期间事务的维护情 况。如,A 事务中的方法 doSome()调用 B 事务中的方法 doOther(),在调用执行期间事务的 维护情况,就称为事务传播行为。事务传播行为是加在方法上的。 事务传播行为常量都是以 PROPAGATION_ 开头,形如 PROPAGATION_XXX。

    PROPAGATION_REQUIRED
    PROPAGATION_REQUIRES_NEW
    PROPAGATION_SUPPORTS

    PROPAGATION_MANDATORY
    PROPAGATION_NESTED
    PROPAGATION_NEVER
    PROPAGATION_NOT_SUPPORTED

    1) PROPAGATION_REQUIRED:
    指定的方法必须在事务内执行。若当前存在事务,就加入到当前事务中;若当前没有事 务,则创建一个新事务。这种传播行为是最常见的选择,也是 Spring 默认的事务传播行为。 如该传播行为加在 doOther()方法上。若 doSome()方法在调用 doOther()方法时就是在事 务内运行的,则 doOther()方法的执行也加入到该事务内执行。若 doSome()方法在调用 doOther()方法时没有在事务内执行,则 doOther()方法会创建一个事务,并在其中执行。

    2) PROPAGATION_SUPPORTS
    指定的方法支持当前事务,但若当前没有事务,也可以以非事务方式执行。

    3)PROPAGATION_REQUIRES_NEW
    总是新建一个事务,若当前存在事务,就将当前事务挂起,直到新事务执行完毕。

5.2 使用 Spring 的事务注解管理事务

  1. 开启注解驱动
     <tx:annotation-driven transaction-manager="transactionManager" />
    

2.声明事务管理器

	<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
	        <property name="dataSource" ref="myDataSource" />
	</bean>

可视为固定写法,其中property标签的ref是配置文件中数据源对象的id属性值。

3.业务层 public 方法加入事务属性

//    @Transactional(propagation = Propagation.REQUIRED,
//            isolation = Isolation.DEFAULT, timeout = 20,
//            rollbackFor = {NullPointerException.class,NotEnoughException.class})
    @Transactional

@Transactional 的所有可选属性如下所示:
➢ propagation:用于设置事务传播属性。该属性类型为 Propagation 枚举,默认值为 Propagation.REQUIRED。
➢ isolation:用于设置事务的隔离级别。该属性类型为 Isolation 枚举,默认值为 Isolation.DEFAULT。
➢ readOnly:用于设置该方法对数据库的操作是否是只读的。该属性为 boolean,默认值 为 false。
➢ timeout:用于设置本操作与数据库连接的超时时限。单位为秒,类型为 int,默认值为 -1,即没有时限。在实际业务开发中一般不设置。
➢ rollbackFor:指定需要回滚的异常类。类型为 Class[],默认值为空数组。当然,若只有 一个异常类时,可以不使用数组。
➢ rollbackForClassName:指定需要回滚的异常类类名。类型为 String[],默认值为空数组。 当然,若只有一个异常类时,可以不使用数组。
➢ noRollbackFor:指定不需要回滚的异常类。类型为 Class[],默认值为空数组。当然,若 只有一个异常类时,可以不使用数组。
➢ noRollbackForClassName:指定不需要回滚的异常类类名。类型为 String[],默认值为空 数组。当然,若只有一个异常类时,可以不使用数组。

需要注意的是,@Transactional 若用在方法上,只能用于 public 方法上。对于其他非 public 方法,如果加上了注解@Transactional,虽然 Spring 不会报错,但不会将指定事务织入到该 方法中。因为 Spring 会忽略掉所有非 public 方法上的@Transaction 注解。 若@Transaction 注解在类上,则表示该类上所有的方法均将在执行时织入事务。

5.3 使用 AspectJ 的 AOP 配置管理事务

一般大型项目使用。在不更改源代码的条件下管理事务。
1.添加Maven依赖

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

2.添加事务管理器

与Spring的注解方式配置内容相同。

3.配置事务通知
为事务通知设置相关属性。用于指定要将事务以什么方式织入给哪些方法。

<!--声明事务的通知
        指定业务方法的事务属性(传播行为,隔离级别,超时,回滚等)
    -->
    <tx:advice id="buyAdvice" transaction-manager="transactionManager">
        <tx:attributes>
            <!--给指定的业务方法,设置事务属性
                name:业务方法的名称,可以使用通配符(*:任意字符)
            -->
            <tx:method name="buyGoods" propagation="REQUIRED" isolation="DEFAULT"
                       rollback-for="java.lang.NullPointerException,com.bjpowernode.excep.NotEnoughException"/>
            <!--设置addXXX方法的事务-->
            <tx:method name="add*" propagation="REQUIRED" isolation="DEFAULT" rollback-for="java.lang.Exception"/>
            <!--设置updateXXX方法的事务-->
            <tx:method name="update*" propagation="REQUIRED" isolation="DEFAULT" rollback-for="java.lang.Exception"/>
            <!--设置removeXXX方法的事务-->
            <tx:method name="remove*" propagation="REQUIRED" isolation="DEFAULT" rollback-for="java.lang.Exception"/>

            <!--其他方法的事务-->
            <tx:method name="*" propagation="SUPPORTS" read-only="true" />
        </tx:attributes>

    </tx:advice>

4.配置增强器
指定将配置好的事务通知,织入给谁。 在第三步中我们指定了事务管理的方法。现在来指定这些方法所在的类

<aop:config>
        <!--声明切入点表达式:指定一些类和方法要加入切面的功能-->
        <aop:pointcut id="servicePt" expression="execution(* *..service..*.*(..))" />

        <!--声明增强器对象(通知+切入点)
            advice-ref:事务通知对象的id
            pointcut-ref:切入点表达式
        -->
        <aop:advisor advice-ref="buyAdvice" pointcut-ref="servicePt"/>

    </aop:config>

第六章 Spring与Web

这一章主要介绍了一个核心知识点:解决不同Servlet中重复创建ApplicationContext对象,造成内存浪费的问题。

解决这个问题的一个思路是,创建一个ServletContextListener,在ServletContext初始化的时候创建ApplicationContext对象,并将它保存在ServletContext中。
这样,在每个servlet中,只要调用当前servlet的ServletContext对象getAttribute方法就可以获取这个webapp中共享的一个ApplicationContext对象。

spring-web框架已经帮我们创建好了这样一个监听器。我们只需要在web.xml注册这个监听器就可以使用了。

1.添加Maven依赖

<!--spring-web依赖:有监听器-->
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-web</artifactId>
  <version>4.3.16.RELEASE</version>
</dependency>

2.注册监听器在注册监听器时需要为监听器提供Spring的配置文件路径信息。

<!--注册spring框架提供的监听器
    在监听器启动的时候,会寻找/WEB-INF/applicatoinContext.xml,为什么找这个文件?
    在监听器的初始方法中,会创建spring的容器对象, 在创建容器对象时,需要读取配置文件
    监听器默认是找/WEB-INF/applicatoinContext.xml。
-->
<!--自定义spring配置文件的位置和名称-->
<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath:spring.xml</param-value>
</context-param>
<listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

4.在Sevlet中获取ApplicationContext对象

WebApplicationContext ctx = null;
String key = WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE;
Object attr = getServletContext().getAttribute(key);
if( attr != null){
    ctx = (WebApplicationContext)attr;
}

webApplicationContext是ApplicationContext的子类,是在web项目中使用的Spring容器对象。

为了不使用框架给出的难记的key值获取webApplicationContext,这个框架还提供了一个工具类。使用工具类获取webApplicationContext:

//获取ServletContext中的容器对象,spring提供了一个方法,获取容器对象
WebApplicationContext ctx = WebApplicationContextUtils.getRequiredWebApplicationContext(
getServletContext());
  • 72
    点赞
  • 277
    收藏
    觉得还不错? 一键收藏
  • 11
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值