Spring学习笔记

本笔记为本人在B站学习时整理而成,为了更好的学习,将其整理成笔记,以防忘记相关知识点。

Spring概述

概述

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

优点

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

(2)(3)(4)的优点将会在接下来学习中体会。

Spring 体系结构

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

spring全家桶:spring , springmvc ,spring boot , spring cloud

框架怎么学: 框架是一个软件,其它人写好的软件。
1)知道框架能做什么, mybatis能访问数据库, 对表中的数据执行增删改查。
2)框架的语法, 框架要完成一个功能,需要一定的步骤支持的,
3)框架的内部实现, 框架内部怎么做。 原理是什么。
4)通过学习,可以实现一个框架。

IoC控制反转

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

Ioc 的实现
➢ 依赖查找:DL( Dependency Lookup ),容器提供回调接口和上下文环境给组件。
➢ 依赖注入:DI(Dependency Injection),程序代码不做定位查询,这些工作由容器自行完成。

Spring 框架使用依赖注入(DI)实现 IoC。

Spring 容器是一个超级大工厂,负责创建、管理所有的 Java 对象,这些 Java 对象被称为 Bean。

Spring 容器管理着容器中 Bean 之间的依赖关系,Spring 使用“依赖注入”的方式 来管理 Bean 之间的依赖关系。使用 IoC 实现对象之间的解耦合。

控制反转的理解

控制反转, 是一个理论,概念,思想。

控制: 创建对象,对象的属性赋值,对象之间的关系管理。

正转:由开发人员在代码中,使用new 构造方法创建对象, 开发人员主动管理对象。

把原来的开发人员管理,创建对象的权限转移给代码之外的容器实现。 由容器代替开发人员管理对象,创建对象,给属性赋值。

为什么要使用 ioc : 目的就是减少对代码的改动, 也能实现不同的功能。 实现解耦合。

底层实现是反射机制。

Spring的第一个程序

实现步骤:

(1)添加module,新建maven工程,选择maven-archetype-quickstart骨架模板

(2)填写工程坐标:

GroupId:com.kwxy
ArtifactId:ch01-hello-spring
Version:1.0-SNAPSHOT

(3)Module name设置为:ch01-hello-spring,点击finish,等待maven项目的构建,控制台显示BUILD SUCCESS字样,则构建成功

(4) 将src/mainsrc/test下的java目录右键Mark Directory as-->Sources Root

(5)在src/main下新建resources目录,右键Mark Directory as-->Resources Root

(6)删除默认创建的APP类文件 ,这些文件分别在main和test目录中

(7)删除pom.xml文件中无关的配置,将编译和运行jdk版本改为1.8

(8)在build标签中添加’指定资源位置’的配置,配置大致如下:

<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.kwxy</groupId>
    <artifactId>ch01-hello-spring</artifactId>
    <version>1.0-SNAPSHOT</version>


    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
    </properties>

    <dependencies>
        <!--单元测试-->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.11</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <resources>
            <resource>
                <directory>src/main/java</directory><!--所在的目录-->
                <includes><!--包括目录下的.properties,.xml 文件都会扫描到-->
                    <include>**/*.properties</include>
                    <include>**/*.xml</include>
                </includes>
                <filtering>false</filtering>
            </resource>
        </resources>
    </build>
</project>

(9)添加Spring依赖(加完最好右键pom.xml->Maven->Reimport

<!--Spring的依赖-->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.2.4.RELEASE</version>
</dependency>
<!--
添加spring-context依赖后,会自动关联以下jar
spring-aop
spring-beans
spring-core
spring-expression
-->

(10)创建业务接口和实现类

接口:

package com.kwxy.service;

public interface SomeService {
    void doSome();
}

实现类:

package com.kwxy.service.impl;

import com.kwxy.service.SomeService;

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

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

(11)创建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="someService" class="com.kwxy.service.SomeServiceImpl" scope="prototype"/> 

1.声明java对象交给Spring创建和管理,这个步骤称之为声明bean

等同于:

SomeService someService = new com.kwxy.service.SomeServiceImpl();

然后将创建的对象是放入到Spring的容器(Map<id,对象>):

private final Map<String, Object> factoryBeanObjectCache = new ConcurrentHashMap(16);

factoryBeanObjectCache.put(“service”,someService);

一个bean标签声明一个对象。

2.<bean/>标签的属性:
class:类的全限定名称,不能是接口(Spring使用反射创建对象);

class可以是非自定义的对象,例如”java.util.Date“,依然可以被Spring创建对象

id:自定义的对象名称,要求是唯一值。 表示在Spring中的对象名称,通过这个名称可以从Spring中找到对象,获取对象。
scope:指定bean对象的作用域(对象的存在范围和可见性)。

scope的可取值为:

1)单例singleton , 默认值,表示叫这个名称的对象在spring容器中只有一个

2)原型prototype , 表示每次使用getBean()都创建一个新的对象

(12)新建测试类,在测试类中创建测试方法

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

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

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

        //调用业务方法
        service.doSome();
    }
    
    /**
     * 获取spring容器中 java 对象的信息
     */
    @Test
    public void test02(){
        String config="applicationContext.xml";
        ApplicationContext ac = new ClassPathXmlApplicationContext(config);
        //使用spring提供的方法, 获取容器中定义的对象的数量
        int nums  = ac.getBeanDefinitionCount();
        System.out.println("容器中定义的对象数量:"+nums);
        //容器中每个定义的对象的名称
        String names [] = ac.getBeanDefinitionNames();
        for(String name:names){
            System.out.println(name);
        }
    }
}

Bean的装配

默认装配方式

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

ApplicationContext 容器,会在容器对象初始化时,将其中的所有对象一次性全部装配好。

以后代码中若要使用到这些对象,只需从内存中直接获取即可。执行效率较高。但占用内存。

容器中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 实例时才进行装配的。

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

基于XML的DI

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

设值注入(掌握)

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

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

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

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

引用类型

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

例如:创建一个学校类:

package com.kwxy.domain;

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.kwxy.domain;

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;
    }

    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 +
                '}';
    }
}

Spring配置文件中添加如下bean:

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

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

    <!--ref作为子标签-->
    <bean id="myStudent2" class="com.kwxy.domain.Student">
        <property name="name" value="张三" />
        <property name="age" value="22" />
        <property name="school">
            <ref bean="mySchool"/>
        </property>
    </bean>
构造注入(理解)

执行类的有参构造,在构造对象的同时给属性赋值。

1.给Student类添加有参构造器:

//定义有参数构造方法
public Student(String name, int age, School school) {
    	System.out.println("Student有参数构造方法");
        this.name = name;
        this.age = age;
        this.school = school;
    }

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

<!--使用name属性-->
    <bean id="myStu" class="com.kwxy.domain.Student" >
        <constructor-arg name="age" value="22"/>
        <constructor-arg name="name" value="张三" />
        <constructor-arg name="school" ref="mySch" />
    </bean>

<!--声明School-->
    <bean id="mySch" class="com.kwxy.domain.School">
        <property name="name" value="江苏师范大学" />
        <property name="address" value="科文学院" />
    </bean>

<constructor-arg />标签中用于指定参数的属性有:

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

value:构造方法的形参类型是简单类型的,使用value

ref:构造方法的形参类型是引用类型的,使用ref

引用类型的自动注入

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

根据自动注入判断标准的不同,可以分为两种:

(1)byName:根据名称自动注入

(2)byType: 根据类型自动注入

byName 方式自动注入

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

容器是通过调用者的 bean 类的属性名与配置文件的被调用者 bean 的 id 进行比较而实现自动注入的。

例如,在Student类中,School类型的属性名是”school“,那么在xml配置文件中:

<bean id="mySchool" class="com.kwxy.domain.School" autowire="byName">
     <property name="name" value="北京大学" />
     <property name="address" value="北京的海淀区" />
</bean>


<bean id="myStudent" class="com.kwxy.domain.Student" >
     <property name="name" value="李四" />
     <property name="age" value="20" />
    <!--<property name="school" ref="mySchool" />-->
</bean>
byType 方式自动注入

使用 byType 方式自动注入,要求:配置文件中被调用者 bean 的 class 属性指定的类, 要与代码中调用者 bean 类的某引用类型属性类型同源。

即要么相同,要么有 is-a 关系(子 类,或是实现类)。但这样的同源的被调用 bean 只能有一个。多于一个,容器就不知该匹配哪一个了。

    <!--
        byType(按类型注入) : java类中引用类型的数据类型和spring容器中(配置文件)<bean>的class属性
                            是同源关系的,这样的bean能够赋值给引用类型
         同源就是一类的意思:
          1.java类中引用类型的数据类型和bean的class的值是一样的。
          2.java类中引用类型的数据类型和bean的class的值父子类关系的。
          3.java类中引用类型的数据类型和bean的class的值接口和实现类关系的
         语法:
         <bean id="xx" class="yyy" autowire="byType">
            简单类型属性赋值
         </bean>

         注意:在byType中, 在xml配置文件中声明bean只能有一个符合条件的,
              多余一个是错误的
    -->

    <!--byType自动注入-->
    <bean id="myStudent" class="com.kwxy.domain.Student" autowire="byType">
        <property name="name" value="晶哥" />
        <property name="age" value="22"/>
    </bean>

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

    <!--声明School的子类对象-->
    <bean id="xiaoXueSchool" class="com.kwxy.domain.XiaoXueSchool">
        <property name="name" value="中兴小学" />
        <property name="address" value="北京的大兴区"/>
    </bean>
为应用指定多个 Spring 配置文件

在实际应用里,随着应用规模的增加,系统中 Bean 数量也大量增加,导致配置文件变 得非常庞大、臃肿。

为了避免这种情况的产生,提高配置文件的可读性与可维护性,可以将 Spring 配置文件分解成多个配置文件。

包含关系的配置文件: 多个配置文件中有一个总文件,总配置文件将各其它子文件通过<import/>引入。

在 Java 代码中只需要使用总配置文件对容器进行初始化即可。

例如:有spring-school.xml、spring-student.xml配置文件和一个total.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">

    <!--
         包含关系的配置文件:
         spring-total表示主配置文件 : 包含其他的配置文件的,主配置文件一般是不定义对象的。
         语法:<import resource="其他配置文件的路径" />
         关键字:"classpath:" 表示类路径(class文件所在的目录),
         在spring的配置文件中要指定其他文件的位置, 需要使用classpath,告诉spring到哪去加载读取文件。
    -->

    <!--加载的是文件列表-->
    <!--
    <import resource="classpath:ba06/spring-school.xml" />
    <import resource="classpath:ba06/spring-student.xml" />
    -->

    <!--
       在包含关系的配置文件中,可以通配符(*:表示任意字符),使用通配符的话,配置文件必须放在一个目录中
       即这个配置文件要放在resources下的一个目录中。这个目录自己去创建,比如下面的ba06。
       注意: 主的配置文件名称不能包含在通配符的范围内(不能叫做spring-total.xml)
    -->
    <import resource="classpath:ba06/spring-*.xml" />
</beans>

注意: 主的配置文件名称不能包含在通配符的范围内。

基于注解的DI

通过以下四个步骤实现DI:

(1)创建Maven项目,在pom.xml 加入 AOP 依赖

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

如果你在pom.xml中添加了spring-context,那此依赖自动包含spring-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.kwxy.domain;

import org.springframework.stereotype.Component;

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

    public Student(String name, int age, School school) {
        this.name = name;
        this.age = age;
        this.school = school;
    }

    //引用类型
    private  School school;

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

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

    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 +
                '}';
    }
}

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

    <!--声明组件扫描器(component-scan),组件就是java对象
    base-package:指定注解在你的项目中的包名。
    component-scan工作方式: spring会扫描遍历base-package指定的包,
    把包中和子包中的所有类,找到类中的注解,按照注解的功能创建对象,或给属性赋值。

   加入了component-scan标签,配置文件的变化:
    1.加入一个新的约束文件spring-context.xsd
    2.给这个新的约束文件起个命名空间的名称
-->
    <context:component-scan base-package="com.kwxy.domain" />

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

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

    <!--第三种方式:指定父包-->
    <context:component-scan base-package="com.kwxy" />
定义 Bean 的注解@Component(掌握)

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

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

另外,Spring 还提供了 3 个创建对象的注解:
➢ @Repository 用于对 DAO 实现类进行注解
➢ @Service 用于对 Service 实现类进行注解
➢ @Controller 用于对 Controller 实现类进行注解 这三个注解与@Component 都可以创建对象

但这三个注解还有其他的含义,@Service 创建业务层对象,业务层对象可以加入事务功能,@Controller 注解创建的对象可以作为处理器接收用户的请求。
@Repository,@Service,@Controller 是对@Component 注解的细化,标注不同层的对象。即持久层对象,业务层对象,控制层对象。

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

若想使用这些注解形式的DI,必须在spring配置文件中声明组件扫描器

<context:component-scan base-package="注解类所在包名" />
简单类型属性注入@Value(掌握)
@Component("myStudent")
public class Student {
    /**
     * @Value: 简单类型的属性赋值
     *   属性: value 是String类型的,表示简单类型的属性值
     *   位置: 1.在属性定义的上面,无需set方法,推荐使用。
     *         2.在set方法的上面
     */
    @Value("李晶")
    private String name;

    private int age;

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

    @Value("23")
    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
byType 自动注入@Autowired(掌握)

需要在引用属性上使用注解@Autowired,该注解默认使用按类型自动装配Bean的方式。

使用该注解完成属性注入时,类中无需 setter。当然,若属性有 setter,则也可将其加 到 setter 上。

@Autowired:spring框架提供的注解,实现引用类型的赋值,默认使用byType自动注入;

当有多个类型匹配时,使用要注入的对象变量名称作为 bean 的 id,在 spring 容器查找,找到了也可以注入成功。找不到就报错。

即Autowired默认先按byType,如果发现找到多个bean,则,又按照byName方式比对,如果还有多个name相同,则报出异常。

当不确定注入哪个bean的时候,可以联合使用注解@Autowired 与@Qualifier指定注入某个bean的id。

例如School类:

@Component("mySchool")
public class School {
    @Value("师范大学")
    private  String name;
    @Value("徐州科文")
    private  String address;

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

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

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

Student类:

@Component("myStudent")
public class Student {
    /**
     * @Value: 简单类型的属性赋值
     *   属性: value 是String类型的,表示简单类型的属性值
     *   位置: 1.在属性定义的上面,无需set方法,推荐使用。
     *         2.在set方法的上面
     */
    @Value("李晶")
    private String name;

    @Value("23")
    private int age;

    /**
     * 引用类型
     * @Autowired: spring框架提供的注解,实现引用类型的赋值。
     * spring中通过注解给引用类型赋值,使用的是自动注入原理 ,支持byName, byType
     * @Autowired:默认使用的是byType自动注入。
     *
     *  位置:1)在属性定义的上面,无需set方法, 推荐使用
     *       2)在set方法的上面
     */
    @Autowired
    private  School school;


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


    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 +
                '}';
    }
}

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

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

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

例如:Student要自动注入School类,只需在school属性前添加注解:

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

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

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

Spring提供了对jdk中@Resource注解的支持。

@Resource注解既可以按名称匹配Bean, 也可以按类型匹配 Bean。

默认是按名称注入。使用该注解,要求 JDK 必须是 6 及以上版本。

@Resource 可在属性上,也可在 set 方法上。

本质就是用@Resource一个注解代替@Autowired和@Qualifier两个注解。

(1)byType 注入引用类型属性

@Resource 注解若不带任何参数,采用默认按名称的方式注入,按名称不能注入bean, 则会按照类型进行 Bean 的匹配注入。
如果Student要自动注入School类,只需在school属性前添加注解:

@Resource
private School school;

(2)byName 注入引用类型属性

@Resource 注解指定其 name 属性,则 name 的值即为按照名称进行匹配的 Bean 的 id。

如果Student要自动注入School类,只需在school属性前添加注解:

@Resource(name = "mySchool")
private School school;

如果JDK是11版本的,学到@Resource注解的时候,加上以下依赖,否则会报错。因为高版本中移除了改模块。

<dependency>
	<groupId>javax.annotation</groupId>
	<artifactId>javax.annotation-api</artifactId>
	<version>1.2</version>
</dependency>
当一个接口有多个实现类时

当一个接口有多个实现时,直接使用@Autowired可能会报异常,原因Autowired默认按照byType注入,找到了多个符合条件的bean,但是不知道注入哪一个,就会报异常。

方式1:

联合使用注解@Autowired 与@Qualifier。@Qualifier 的 value 属性用 于指定要匹配的 Bean 的 id 值。

方式2:

@Resource 注解指定其 name 属性,则 name 的值即为按照名称进行匹配的 Bean 的 id。

注解与 XML 的对比

注解优点是:

(1)方便

(2)直观

(3)高效(代码少,没有配置文件的书写那么复杂)

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

XML方式优点是:

(1)配置和代码是分离的

(2)在 xml 中做修改,无需编译代码,只需重启服务器即可将新的配置加载

xml 的缺点是:编写麻烦,效率低,大型项目过于复杂。

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

AOP面向切面编程

AOP概述

AOP(Aspect Orient Programming),面向切面编程。面向切面编程是从动态角度考虑程序运行过程。

AOP 底层,就是采用动态代理模式实现的。

采用了两种代理:JDK 的动态代理,与 CGLIB 的动态代理,AOP可以看作动态代理的规范化与标准化

面向切面编程,就是将交叉业务逻辑封装成切面,利用 AOP 容器的功能将切面织入到 主业务逻辑中。

所谓交叉业务逻辑是指,通用的、与主业务逻辑无关的代码,如安全检查、 事务、日志、缓存等。

若不使用 AOP,则会出现代码纠缠,即交叉业务逻辑与主业务逻辑混合在一起。这样, 会使主业务逻辑变的混杂不清。

例如,转账,在真正转账业务逻辑前后,需要权限控制、日志记录、加载事务、结束事 务等交叉业务逻辑,而这些业务逻辑与主业务逻辑间并无直接关系。

但,它们的代码量所占比重能达到总代码量的一半甚至还多。它们的存在,不仅产生了大量的“冗余”代码,还大大干扰了主业务逻辑—转账。

AOP的相关术语

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

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

AOP的实现

AOP的技术实现框架:

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

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

Spring框架集成了aspectJ的功能。

aspectJ框架实现AOP有两种方式:

1)使用xml配置文件,一般用于配置全局事务;

2)使用注解。一般在项目开发中使用这种方式。

AspectJ 对 AOP 的实现

AspectJ 的通知类型

切面的执行时间, 这个执行时间在规范中叫做Advice(通知,增强)

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

AspectJ 的切入点表达式

表示切面执行的位置,使用的是切入点表达式

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

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

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

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

例如:

execution(* *..service.*.*(..))

上面表达的意思是返回值任意, 指定所有包下的 serivce 子包下所有类(接口)中所有方法为切入点

技巧:

execution(访问权限 方法返回值 方法声明(参数) 异常类型) 方法返回值和方法声明是必需的。

其实类似于声明一个方法: public void 方法名(参数) throws 异常

AspectJ 的开发环境

(1)引入AspectJ依赖:

<!--aspectj的依赖-->
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-aspects</artifactId>
  <version>5.2.4.RELEASE</version>
</dependency>

(2)引入约束(第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标签时会自动引入约束文件。

AspectJ 基于注解的 AOP 实现
AspectJ 基于注解的 AOP 实现步骤

(1)定义业务接口与实现类

package com.kwxy.service;

public interface SomeService {
    void doSome(String name, int age);
}
package com.kwxy.service.impl;

import com.kwxy.service.SomeService;

public class SomeServiceImpl implements SomeService {

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

(2)定义切面类

package com.kwxy.aspect;

import org.aspectj.lang.annotation.Before;

import java.util.Date;

@Aspect
public class MyAspect {
    /**
     * @Before: 前置通知注解
     *   属性:value ,是切入点表达式,表示切面的功能执行的位置。
     *   位置:在方法的上面
     * 特点:
     *  1.在目标方法之前先执行的
     *  2.不会改变目标方法的执行结果
     *  3.不会影响目标方法的执行。
     */

     @Before(value = "execution(public void com.kwxy.service.impl.SomeServiceImpl.doSome(..))")
    public void myBefore(){
        //就是你切面要执行的功能代码
        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:context="http://www.springframework.org/schema/context"
       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/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">

    <!--声明目标对象-->
    <bean id="someService" class="com.kwxy.service.impl.SomeServiceImpl"/>

    <!--声明切面类对象-->
    <bean id="myAspect" class="com.kwxy.aspect.MyAspect"/>

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

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

(4)创建测试方法测试,获取代理对象根据目标对象的id。

@Test
public void test01(){
    //定义Spring的配置文件, 配置文件是在类路径的根目录之下
    String config = "applicationContext.xml";
    ApplicationContext ac = new ClassPathXmlApplicationContext(config);
    //从容器中获取对象 使用getBean("<bean>的id")
    SomeService proxy = (SomeService) ac.getBean("someService");
    System.out.println(proxy.getClass().getName());
    //调用业务方法
    proxy.doSome("李晶",23);
}
/*输出结果
com.sun.proxy.$Proxy8
前置通知, 切面功能:在目标方法之前输出执行时间:Wed Jul 22 19:33:30 CST 2020
SomeSeviceImpl的业务方法doSome
*/
AspectJ 通知注解

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

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

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

@Before(value = "execution(public void com.kwxy.service.impl.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);
        }
    }

/*
测试方法输出结果:
com.sun.proxy.$Proxy8
连接点的方法定义:void com.kwxy.service.SomeService.doSome(String,int)
连接点的方法名称:doSome
李晶
23
*/

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

@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(public String com.kwxy.service.impl.SomeServiceImpl.doOther(..))", returning = "result")
public void myAfterReturning(JoinPoint jp, Object result) {
    //修改目标方法的返回值
    if (result != null) {
    String st = (String) result;
    result = st.toUpperCase();
    }
    System.out.println("后置通知,在目标方法之后执行的。能够获取到目标方法的执行结果:" + result);
}

测试方法:

@Test
public void test03(){
	//定义Spring的配置文件, 配置文件是在类路径的根目录之下
	String config = "applicationContext.xml";
    ApplicationContext ac = new ClassPathXmlApplicationContext(config);
    //从容器中获取对象 使用getBean("<bean>的id")
    SomeService proxy = (SomeService) ac.getBean("someService");
    System.out.println(proxy.getClass().getName());
    //调用业务方法
    String res = proxy.doOther("李晶", 23);
    System.out.println("测试方法中的结果:" + res);
}

/*
测试方法输出结果:
com.sun.proxy.$Proxy8
SomeSeviceImpl的业务方法doOther
后置通知,在目标方法之后执行的。能够获取到目标方法的执行结果:ABCD
测试方法中的结果:abcd
*/
@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;
    }

环绕通知能够控制目标方法是否执行,创建测试方法:

在上述代码中,如果name是zs,则执行,否则不执行。

@Test
public void test03(){
    //定义Spring的配置文件, 配置文件是在类路径的根目录之下
    String config = "applicationContext.xml";
    ApplicationContext ac = new ClassPathXmlApplicationContext(config);
    //从容器中获取对象 使用getBean("<bean>的id")
    SomeService proxy = (SomeService) ac.getBean("someService");
    System.out.println(proxy.getClass().getName());
    //调用业务方法
    String res = proxy.doFirst("李晶", 23);
    System.out.println("测试方法中的结果:" + res);
}
/*
测试结果:
com.sun.proxy.$Proxy8
环绕通知:在目标方法之前加入日志
环绕通知:在目标方法之后加入事务处理
测试方法中的结果:null
*/

将name改为zs后,运行结果为:

/*
com.sun.proxy.$Proxy8
环绕通知:在目标方法之前加入日志
SomeSeviceImpl的业务方法doFirst
环绕通知:在目标方法之后加入事务处理
测试方法中的结果:Hello AspectJ
*/
@AfterThrowing 异常通知-注解中有 throwing 属性

在目标方法抛出异常后执行。该注解的 throwing 属性用于指定所发生的异常类对象。

当然,被注解为异常通知的方法可以包含一个参数 Throwable,参数名称为 throwing 指定的名称,表示发生的异常对象。

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

@After最终通知

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

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

@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(){
        //无需代码
    }
设置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/>

Spring集成myBatis

概述

Spring集成myBatis,其本质工作就是:将使用mybatis框架时用到的一些需要自己创建的对象,交由Spring统一管理。

把mybatis框架和spring集成在一起,像一个框架一样使用。

用的技术是:ioc 。

为什么是ioc:能把mybatis和spring集成在一起,像一个框架, 是因为ioc能创建对象。

可以把mybatis框架中的对象交给spring统一创建, 开发人员从spring中获取对象。

开发人员就不用同时面对两个或多个框架了, 就面对一个spring。

说明:

/*
mybatis使用步骤,对象
1.定义dao接口 ,StudentDao
2.定义mapper文件 StudentDao.xml
3.定义mybatis的主配置文件 mybatis-config.xml
4.创建dao的代理对象, StudentDao dao = SqlSession.getMapper(StudentDao.class);
				    List<Student> students  = dao.selectStudents();

要使用dao对象,需要使用getMapper()方法。
怎么能使用getMapper()方法,需要哪些条件?
1.获取SqlSession对象, 需要使用SqlSessionFactory的openSession()方法。
2.创建SqlSessionFactory对象。 通过读取mybatis的主配置文件,能创建SqlSessionFactory对象

需要SqlSessionFactory对象, 使用Factory能获取SqlSession ,有了SqlSession就能有dao , 目的就是获取dao对象
Factory创建需要读取主配置文件

我们会使用独立的连接池类替换mybatis默认自己带的, 把连接池类也交给spring创建。

主配置文件:
 1.数据库信息
 <environment id="mydev">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <!--数据库的驱动类名-->
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <!--连接数据库的url字符串-->
                <property name="url" value="jdbc:mysql://localhost:3306/ssm"/>
                <!--访问数据库的用户名-->
                <property name="username" value="root"/>
                <!--密码-->
                <property name="password" value="root"/>
            </dataSource>
2. mapper文件的位置
   <mappers>
        <mapper resource="com/kwxy/dao/StudentDao.xml"/>
    </mappers>
*/

通过以上的说明,我们需要让spring创建以下对象:

(1)数据源dataSource。就是保存数据库连接信息的对象。在实际业务开发中,我们放弃使用Mybatis自带的数据库连接池,而采用阿里的Druid,更加高效;

(2)生成sqlSession对象的sqlSessionFactory;

(3)Dao接口的实现类对象。

需要学习就是上面三个对象的创建语法,使用xml的bean标签。

Spring集成myBatis创建项目的流程

(1)新建 mysql数据库,准备数据。( student表)

建表语句

DROP TABLE IF EXISTS `student`;
CREATE TABLE `student` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(80) DEFAULT NULL,
  `email` varchar(100) DEFAULT NULL,
  `age` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1003 DEFAULT CHARSET=utf8;

添加数据

INSERT INTO `student` VALUES ('1001', '李晶', 'lijing@163.com', '23');
INSERT INTO `student` VALUES ('1002', '李慧', 'lihui@qq.com', '25');
mysql> select *from student;
+------+--------+----------------+------+
| id   | name   | email          | age  |
+------+--------+----------------+------+
| 1001 | 李晶   | lijing@163.com |   23 |
| 1002 | 李慧   | lihui@qq.com   |   25 |
+------+--------+----------------+------+

(2)新建maven的module

(3)加入依赖

​ 1)spring依赖
​ 2)mybatis的依赖
​ 3)mybatis-spring依赖, 这个jar是从mybatis官网下载的, mybatis提供在spring中创建对象的类。
​ 4)mysql的驱动
​ 5)druid,数据库连接池的依赖

<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>com.kwyx</groupId>
  <artifactId>spring_mybatis</artifactId>
  <version>1.0-SNAPSHOT</version>


  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven.compiler.target>1.8</maven.compiler.target>
  </properties>

  <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.4.RELEASE</version>
    </dependency>

    <!--spring的事务-->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-tx</artifactId>
      <version>5.2.4.RELEASE</version>
    </dependency>

    <!--spring访问数据库-->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-jdbc</artifactId>
      <version>5.2.4.RELEASE</version>
    </dependency>

    <!--aspectj的依赖-->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-aspects</artifactId>
      <version>5.2.4.RELEASE</version>
    </dependency>

    <!--mybatis依赖-->
    <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis</artifactId>
      <version>3.5.1</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>5.1.9</version>
    </dependency>

    <!--数据库连接池-->
    <dependency>
      <groupId>com.alibaba</groupId>
      <artifactId>druid</artifactId>
      <version>1.1.12</version>
    </dependency>
  </dependencies>

  
  <build>
    <resources>
      <resource>
        <directory>src/main/java</directory><!--所在的目录-->
        <includes><!--包括目录下的.properties,.xml 文件都会扫描到-->
          <include>**/*.properties</include>
          <include>**/*.xml</include>
        </includes>
        <filtering>false</filtering>
      </resource>
    </resources>
  </build>
</project>

(4)新建实体类Student

package com.kwyx.domain;

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

    public Student() {
    }

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

    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 String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public Integer getAge() {
        return age;
    }

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

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

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

(5)新建Dao接口和sql映射文件

package com.kwyx.dao;


import com.kwyx.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.kwxy.dao.StudentDao">
    <select id="selectAll" resultType="com.kwxy.domain.Student">
        select id,name,email,age from student
    </select>

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

(6)新建mybatis主配置文件mybatis-config.xml

由于使用阿里的数据库连接池,所以不需要<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>

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

    <!--设置别名-->
    <typeAliases>
        <!--name:实体类所在的包名-->
        <package name="com.kwxy.domain"/>
    </typeAliases>


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

(7)新建Service接口和实现类, 在实现类中有Dao的属性

在实际项目中,我们在对数据库前需要一些其他的业务代码,例如逻辑判断、身份认证等,这些放在Service中

package com.kwyx.service;

import com.kwyx.domain.Student;

import java.util.List;

public interface StudentService {
    List<Student> selectAll();

    int insertStudent(Student student);
}
package com.kwyx.service.impl;

import com.kwyx.service.StudentService;
import com.kwyx.dao.StudentDao;
import com.kwyx.domain.Student;

import java.util.List;

public class StudentServiceImpl implements StudentService {

    private StudentDao studentDao;

    //使用ioc,设值注入,在配置文件中给dao赋值
    public void setStudentDao(StudentDao studentDao) {
        this.studentDao = studentDao;
    }

    @Override
    public List<Student> selectAll() {
        return studentDao.selectAll();
    }

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

(8)创建数据库连接配置文件jdbc.properties

jdbc.url=jdbc:mysql://localhost:3306/ssm
jdbc.user=root
jdbc.password=root
jdbc.maxActive=20

不需要配置数据库驱动

(9)新建Spring的配置文件applicationContext.xml(重要)

1)声明Druid的数据源DruidDataSource对象
2)声明SqlSessionFactoryBean,创建SqlSessionFactory对象
3)声明MyBatis的扫描器MapperScannerConfigurer,创建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 https://www.springframework.org/schema/context/spring-context.xsd">

    <!--读取jdbc配置文件
        location:指定属性配置文件的路径
        "classpath:":关键字表示类文件,也就是class文件所在的目录
    -->
    <context:property-placeholder location="classpath:jdbc.properties"/>

    <!--声明数据源DataSource-->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <!--读取属性配置文件的key的值,使用 ${key}-->
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.user}"/>
        <property name="password" value="${jdbc.password}"/>
        <property name="maxActive" value="${jdbc.maxActive}"/>
    </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="dataSource"/>
        <!--指定mybatis的主配置文件-->
        <property name="configLocation" value="classpath:mybatis-config.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.kwxy.dao"/>
    </bean>

    <!--
       从spring中获取SqlSessionFacotory,因为spring是一个容器(Map)
       SqlSessionFactory factory  = map.get("sqlSessionFactory");
       SqlSession session = factory.openSession();

       for(接口:com.kwxy.dao)
       {
           Dao对象 =  session.getMapper(接口)
           //把创建好的对象放入到spring容器中
           spring的Map.put( 接口名的首字母小写, Dao对象 )

       }

       创建好的dao对象放入到spring的容器中的。 dao对象的默认名称是 接口名首字母小写

   -->
    <!--声明Service-->
    <bean id="studentService" class="com.kwxy.service.impl.StudentServiceImpl">
        <property name="studentDao" ref="studentDao"/>
    </bean>
</beans>

(10)新建测试类, 从spring容器中获取Service,调用Service的方法,完成数据库的操作

public class MyTest {

    @Test
    public void test01() {
        ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
        StudentService studentService = (StudentService) ac.getBean("studentService");
        List<Student> students = studentService.selectAll();
        for (Student student : students) {
            System.out.println("学生 :" + student);
        }
    }

    /**结果
     * JDBC Connection [com.mysql.jdbc.JDBC4Connection@662b4c69] will not be managed by Spring
     * ==>  Preparing: select id,name,email,age from student
     * ==> Parameters:
     * <==    Columns: id, name, email, age
     * <==        Row: 1001, 李晶, lijing@163.com, 23
     * <==        Row: 1002, 李慧, lihui@qq.com, 25
     * <==      Total: 2
     * Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@53aad5d5]
     * 学生 :Student{id=1001, name='李晶', email='lijing@163.com', age=23}
     * 学生 :Student{id=1002, name='李慧', email='lihui@qq.com', age=25}
     */

    @Test
    public void test02() {
        ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
        StudentService studentService = (StudentService) ac.getBean("studentService");
        Student student = new Student(3, "张飞", "zhangfei@163.com", 24);
        int nums = studentService.insertStudent(student);
        System.out.println("添加了 " + nums + " 位学生");
    }
    /**结果
     * JDBC Connection [com.mysql.jdbc.JDBC4Connection@662b4c69] will not be managed by Spring
     * ==>  Preparing: insert into student values (?,?,?,?)
     * ==> Parameters: 3(Integer), 张飞(String), zhangfei@163.com(String), 24(Integer)
     * <==    Updates: 1
     * Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@76f2bbc1]
     * 添加了 1 位学生
     */
}

Spring与事务

理论知识

(1)什么是事务

讲mysql的时候,提出了事务。 事务是指一组sql语句的集合, 集合中有多条sql语句,

可能是insert , update ,select ,delete, 我们希望这些多个sql语句都能成功,

或者都失败, 这些sql语句的执行是一致的,作为一个整体执行。

(2)在什么时候想到使用事务

当我的操作,涉及得到多个表,或者是多个sql语句的insert,update,delete。

需要保证这些语句都是成功才能完成我的功能,或者都失败,保证操作是符合要求的。

在java代码中写程序,控制事务,此时事务应该放在哪里呢?

service类的业务方法上,因为业务方法会调用多个dao方法,执行多个sql语句。

(3)通常使用JDBC访问数据库, 还是mybatis访问数据库怎么处理事务

jdbc访问数据库,处理事务 Connection conn ; conn.commit(); conn.rollback();
mybatis访问数据库,处理事务, SqlSession.commit(); SqlSession.rollback();
hibernate访问数据库,处理事务, Session.commit(); Session.rollback();

(4)上面事务的处理方式,有什么不足

不同的数据库访问技术,处理事务的对象,方法不同,需要了解不同数据库访问技术使用事务的原理

要掌握多种数据库中事务的处理逻辑,什么时候提交事务,什么时候回顾事务

处理事务需要多种方法

(5)怎么解决不足

spring提供一种处理事务的统一模型, 能使用统一步骤,方式完成多种不同数据库访问技术的事务处理。

使用spring的事务处理机制,可以完成mybatis访问数据库的事务处理。

使用spring的事务处理机制,可以完成hibernate访问数据库的事务处理。

spring处理事务的模型,使用的步骤都是固定的。把事务使用的信息提供给spring就可以了。

事务管理器接口

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

Spring 的回滚方式

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

运行时异常,是 RuntimeException 类或其子类,即只有在运行时才出现的异常。

如, NullPointerException、ArrayIndexOutOfBoundsException、IllegalArgumentException 等均属于运 行时异常。

这些异常由 JVM 抛出,在编译时不要求必须处理(捕获或抛出)。

但,只要代码 编写足够仔细,程序足够健壮,运行时异常是可以避免的。

受查异常,也叫编译时异常,即在代码编写时要求必须捕获或抛出的异常,若不处理, 则无法通过编译。

如 SQLException,ClassNotFoundException,IOException 等都属于受查异常。 RuntimeException 及其子类以外的异常,均属于受查异常。

事务定义接口

事务定义接口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

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

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

(1)开启注解驱动,告诉Spring框架现在使用注解处理事务,在spring配置文件applicationContext中添加如下代码:

<!--开启注解驱动,开启事务支持,以tx开头,可以看出是用于事务的-->
<tx:annotation-driven transaction-manager="transactionManager"/>

(2)声明事务管理器

 <!--声明事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"/>
</bean>

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

(3)业务层 public 方法加入事务注解

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

    //使用的是事务控制的默认值, 默认的传播行为是REQUIRED,默认的隔离级别DEFAULT
    //默认抛出运行时异常,回滚事务。
    @Transactional
    @Override
    public int insertStudent(Student student) {
        return studentDao.insertStudent(student);
    }

@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 注解在类上,则表示该类上所有的方法均将在执行时织入事务。

使用 AspectJ 的 AOP 配置管理事务

一般大型项目使用。在不更改源代码的条件下管理事务。代码和事务的配置完全是分离的,不需要在代码上加注解,全部是在xml中配置。

(1)添加Maven依赖

<!--aspectj的依赖-->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-aspects</artifactId>
      <version>5.2.4.RELEASE</version>
    </dependency>

(2)声明事务管理器

 <!--声明事务管理器对象-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"/>
</bean>

(3)配置事务通知

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

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

    </tx:advice>

(4)配置aop,配置增强器

指定哪些哪类要创建代理

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

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

Spring与Web

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

解决这个问题的一个思路是,创建一个ServletContextListener,在ServletContext初始化的时候创建ApplicationContext对象,并将它保存在ServletContext中。

这样,在每个servlet中,只要调用当前servlet的ServletContext对象getAttribute方法就可以获取这个webapp中共享的一个ApplicationContext对象。

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

(1)maven创建web模块,加入servlet,jsp依赖,拷贝之前spring和mybatis中所用的依赖

<!--以下是新增依赖,其它依赖到之前做的项目中拷贝-->
<!-- servlet依赖 -->
<dependency>
 <groupId>javax.servlet</groupId>
 <artifactId>javax.servlet-api</artifactId>
 <version>3.1.0</version>
 <scope>provided</scope>
</dependency>
<!-- jsp依赖 -->
<dependency> 
 <groupId>javax.servlet.jsp</groupId> 
 <artifactId>jsp-api</artifactId> 
 <version>2.2.1-b03</version> 
 <scope>provided</scope>
</dependency>
<!--spring-web依赖:有监听器-->
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-web</artifactId>
  <version>5.2.4.RELEASE</version>
</dependency>

(2)需要为监听器提供Spring的配置文件路径信息

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

(3)在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());
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

筱晶哥哥

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

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

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

打赏作者

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

抵扣说明:

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

余额充值