spring学习笔记

Spring

文章目录


请点击这里 获取本文代码

Spring概览

什么是spring?

  • spring是一个框架,核心技术是ioc,aop,实现解耦合

  • spring也是一个容器, 容器中存放的是java对象,需要做的是把对象放入容器中

怎么使用spring?

  • spring是一个容器,把项目中要用的对象放入容器中.

  • 让容器完成创建对象,对象之间关系的管理(属性赋值)

  • 我们在程序中之间从容器中获取要使用的对象

什么样的对象放入容器中?

  • dao类,service类,controller类型,工具类
  • spring中的对象默认都是单例的,在容器中叫这个名称的对象只有一个

不放入spring容器中的对象

  • 实体类对象, 实体类数据来自数据库.
  • servlet,listener,filter等.

第一章 Spring 概述

spring: 出现在2002年左右,解决企业开发的难度.减轻对项目模块之间的管理,类和类之间的管理,帮助开发人员创建对象,管理对象之间的关系.

Spring的核心技术, ioc ,aop.能实现模块之间,类之间的解耦合.

依赖: class A 中使用Class B 的属性或者方法,叫做 class A依赖class B

Spring的优点

  1. 轻量

    Spring 框架运行占用的资源少,运行效率高.不依赖其他jar.

  2. 针对接口编程,解耦合.

    Spring 提供了Ioc控制反转, 由容器管理对象,对象的依赖关系. 原来在程序代码中的对象创建方式,现在由容器完成, 对象之间的依赖解耦合.

  3. aop编程的支持

    ​ 通过Spring提供的AOP功能,方便进行面向切面的编程, 许多不容易使用 传统oop实现的功能 都可以通过 AOP 轻松应对

    ​ 在Spring中,开发人员可以从繁杂的事务管理代码中解脱出来,通过声明式方式灵活地进行事务管理, 提高开发效率和质量.

  4. 方便集成各种优秀框架

    可以将spring和其他框架一起使用

Spring体系结构

在这里插入图片描述

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

第二章 IOC 控制反转

IOC (Inversion Of Control) : 控制反转

​ Ioc是一个理论,思想.描述的是 : 把对象的创建,赋值,管理工作都交给代码之外的容器实现. 也就是对象的创建是由其他外部资源完成的.

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

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

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

public static void main(String args[]){
    Student student = new Student();// 在代码中,创建对象.-- 正转
}

容器:是一个服务器软件,一个框架(Spring)

为什么使用ioc: 目的是减少对代码的改动,也能实现不同的功能.也就是实现解耦合.ioc能够实现业务对象之间的解耦合,例如service和dao对象之间的解耦合

java中创建对象的方式

  1. 构造方法, new Student()

  2. 反射

  3. 序列化

  4. 克隆

  5. ioc : 容器创建对象

  6. 动态代理

ioc的体现

​ 比如: servlet

  1. 创建类继承 HttpServlet

  2. 在web.xml中注册servlet,

    <servlet-name>myservlet</servlet-name>
    <servlet-class>com.xxx.MyServlet</servlet-class>
    
  3. 没有创建Servlet对象, 即没有 MyServlet myServlet = new MyServlet()

  4. Servlet 是Tomcat 服务器创建的, Tomcat也称为容器.

    Tomcat作为容器,里面存放的有Servlet对象,Listener,Filter对象.

IOC的技术实现

​ IOC的技术实现 是 依赖注入:DI(Dependency Injection),程序代码不做定位查询,这些工作由容器自行 完成。即只需要在程序中提供要使用的对象名称就可以, 至于对象如何在容器中创建,赋值,查找都由容器内部实现.

​ Spring是使用DI实现了ioc的功能, spring是一个容器,管理对象,给属性赋值.底层是反射创建对象.

基于配置文件的DI

Spring创建对象实现步骤
  1. 创建maven项目

  2. 加入maven的依赖

    spring的依赖 和 junit依赖

    pom.xml

    <dependencies>
        <dependency>
          <groupId>junit</groupId>
          <artifactId>junit</artifactId>
          <version>4.11</version>
          <scope>test</scope>
        </dependency>
        <dependency>
          <groupId>org.springframework</groupId>
          <artifactId>spring-context</artifactId>
          <version>5.2.5.RELEASE</version>
        </dependency>
      </dependencies>
    
  3. 创建类(接口类和他的实现类)

    和没有使用框架一样,就是一个普通的类型

    public interface SomeService {
       void doSome();
    }
    
    public class SomeServiceImpl implements SomeService {
        public SomeServiceImpl(){
            System.out.println("SomeServiceImpl的构造方法执行了");
        }
    
        @Override
        public void doSome() {
            System.out.println("执行SomeServiceImpl的doSome()方法");
        }
    }
    
  4. 创建spring需要使用的配置文件

    声明类的信息,这些类由spring创建和管理

    beans.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创建对象
            声明bean,就是告诉spring要创建某个类的对象.
            id:对象的自定义名称,唯一值.spring通过这个名称找到对象.
            class:类的全限定名称,(不能是接口,因为spring是反射机制创建对象的.必须使用类)
    
            spring 就完成 SomeService someService = new SomeServiceImpl();
            spring 是把创建好的对象放入到map中,spring框架有一个map存放对象的.
                springMap.put(id的值,对象);
                springMap.put("someService",new SomeServiceImpl());
    
            一个bean标签声明一个java对象.
    -->
        <bean id="someService" class="com.nguyenxb.service.impl.SomeServiceImpl"/>
    
        <bean id="someService1" class="com.nguyenxb.service.impl.SomeServiceImpl"/>
    
        <bean id="mydate" class="java.util.Date"/>
    </beans>
    
    <!--
        spring的配置文件
        1,beans: 是根标签,spring把java对象称为bean.
        2.spring-beans.xsd 是约束文件, 和mybatis指定 dtd是一样的.
    -->
    

测试spring创建的对象是否成功

测试1 , 手工创建对象
@Test
public void test01(){
    SomeService someService = new SomeServiceImpl();
    someService.doSome();
}
/*
	输出: 
	SomeServiceImpl的构造方法执行了
	执行SomeServiceImpl的doSome()方法
*/
测试2, 使用spring创建对象
@Test
public void test02(){
    // 使用spring 容器创建的对象
    // 1. 指定spring配置文件的名称
    String config = "beans.xml";
    // 2. 创建表示spring 容器的对象. ApplicationContext
    // ApplicationContext就是表示spring容器,就能通过容器获取对象了.
    ApplicationContext ac = new ClassPathXmlApplicationContext(config);

    // 3. 从容器获取某个对象,你要调用对象的方法.
    // getBean("配置文件中bean的id的值")
    SomeService someService = (SomeService) ac.getBean("someService");

    // 4. 使用spring 创建好的对象
    someService.doSome();

}
/*
	SomeServiceImpl的构造方法执行了
	SomeServiceImpl的构造方法执行了
	执行SomeServiceImpl的doSome()方法
*/
测试3, 获取spring容器中, java对象的信息
/**
 * 获取spring容器中, java对象的信息
 * */
@Test
public void test03(){
    String config = "beans.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);
    }
}
测试4, 创建非自定义的对象
// 创建非自定义的对象
@Test
public void test04(){
    String config = "beans.xml";
    ApplicationContext ac = new ClassPathXmlApplicationContext(config);
    // 使用getBean();
    Date mydate = (Date) ac.getBean("mydate");
    System.out.println("Date:"+mydate);
}
小结

spring 默认创建对象的时间: 在创建spring的容器时,会创建配置文件中的所有对象. spring 创建对象, 默认调用的是无参数构造方法.

用spring, 给java对象属性赋值(注入)

spring默认调用java对象的无参数构造方法.

di: 依赖注入,表示创建对象,给属性赋值.

di实现的语法有两种:

  1. 在spring的配置文件中,使用标签和属性完成赋值, 叫做基于xml的di实现.
  2. 使用spring中注解,完成属性赋值,叫做基于注解的di实现.

di语法分类:

  1. set注入(设置注入): spring调用类的set方法,在set方法可以实现属性的赋值.80%都是使用set注入的.
  2. 构造注入,spring调用类的有参数构造方法,创建对象,在构造方法中完成赋值.
项目文件目录:

项目根目录
    |--src
    	|--main
    	|	|--java
    	|	|	|--com
    	|	|		|--nguyenxb
    	|	|			|--ba01
    	|	|				|--自定义的类 如Student.java
    	|	|			|--ba02
    	|	|				|--自定义的类 如Student.java
    	|	|			....
    	|	|			|--baxx
    	|	|				|--自定义的类 如Student.java
    	|	|--resources
    	|		|--ba01 (不同的知识点使用不同包下的spring的配置文件)
    	|			|--applicationContext.xml
    	|		|--ba02 (不同的知识点使用不同包下的spring的配置文件)
    	|			|--applicationContext.xml
    	|		....
    	|		|--baXX (不同的知识点使用不同包下的spring的配置文件)
    	|			|--applicationContext.xml
    	|--test (存放测试方法的目录)
    		|--java
    			|--com
    				|--nguyenxb
    					|--MyTest01.java
    					|--MyTest02.java
    					...
    					|--MyTestxx.java

java文件一律放在java目录下的响应包中,applicationContext.xml一律放在resources目录下的相应目录下.并且java包,资源包的编号相对应.

set注入

​ 注入就是赋值的意思

set注入 (设值注入): spring调用类的set方法, 可以在set方法中完成赋值.注意: 必需有set方法,否则spring会报错

简单类型的set注入
简单类型: spring中规定java的基本数据类型和String都是简单类型
di: 给属性赋值

简单类型的set注入语法:
<bean id="xxx" class="yyy">
    <property name="属性名字" value="此属性的值"/>
    一个property只能给一个属性赋值.如果要给多个属性赋值,只能用多个property标签
</bean>

ba01\applicationContext.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">
<!--    声明student对象
-->
    <bean id="mystudent" class="com.nguyenxb.ba01.Student">
        <property name="name" value="李四"/><!--setName("李四")-->
        <property name="age" value="20"/><!--setAge(20)-->

    </bean>
</beans>

ba01\Student.java

public class Student {
    private String name;
    private int age;
    
    // 已省略set|get|toString方法;
}

测试

@Test
public void test01(){
   String config = "ba01/applicationContext.xml";
    // 创建spring容器
    ApplicationContext ac = new ClassPathXmlApplicationContext(config);
    // 从容器中获取对象
    Student mystudent = (Student) ac.getBean("mystudent");

    System.out.println("student对象="+mystudent);
}
// 输出:student对象=Student{name='李四', age=20}
引用类型的set注入
引用类型的set注入 : spring 调用类的set方法.
语法:
<bean id="xxx" class="yyy">
    <property name="属性名字" ref="bean的id(对象的名称)"/>
</bean>

ba02\applicationContext.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">
    <bean id="mystudent" class="com.nguyenxb.ba02.Student">
        <property name="name" value="李四"/><!--setName("李四")-->
        <property name="age" value="20"/><!--setAge(20)-->
<!--        引用类型赋值-->
        <property name="school" ref="myschool"/>
    </bean>

<!--    声明School对象-->
    <bean id="myschool" class="com.nguyenxb.ba02.School">
        <property name="name" value="xx大学"/>
        <property name="address" value="xx大学路"/>
    </bean>
</beans>

ba02\Student.java

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

    // 声明一个引用类型
    private School school;
    
    // 已省略set|get|toString方法;
}

ba02\School.java

public class School {
    private String name;
    private String address;
    // 已省略set|get|toString方法;
}

测试

@Test
public void test02(){
    String config = "ba02/applicationContext.xml";
    // 创建spring容器
    ApplicationContext ac = new ClassPathXmlApplicationContext(config);
    // 从容器中获取对象
    Student mystudent = (Student) ac.getBean("mystudent");

    System.out.println("student对象="+mystudent);
}
/*输出:
student对象=Student{name='李四', age=20, school=School{name='xx大学', address='xx大学路'}}
*/
构造注入

​ 构造注入: spring调用类的有参数构造方法,在创建对象的同时,在构造方法中给属性赋值.

​ 构造注入使用<constructor-arg>标签

<constructor-arg>标签:一个<constructor-arg>表示构造方法的一个参数.

<constructor-arg>标签属性:

​ name: 表示构造方法的形参名

​ index:表示构造方法的参数的位置, 参数从左往右位置是0,1,2…的顺序.

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

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

构造注入可以通过构造方法的参数名,构造方法的参数位置实现创建对象.并且这两种方式不用考虑赋值的顺序, 而省略构造方法的参数位置必须从上到下依次赋值给 0…n的位置.

ba03\applicationContext.xml

<!--    使用name属性实现构造方法-->
    <bean id="mystudent" class="com.nguyenxb.ba03.Student">
        <constructor-arg name="myname" value="张三"/><!--setName("李四")-->
        <constructor-arg name="myage" value="23"/><!--setAge(20)-->
<!--        引用类型赋值 -->
        <constructor-arg name="myschool" ref="myschool11"/>
    </bean>

<!--    使用index属性实现构造方法-->
    <bean id="mystudent2" class="com.nguyenxb.ba03.Student">
        <constructor-arg index="0" value="张1"/>
        <constructor-arg index="1" value="12"/>
        <constructor-arg index="2" ref="myschool11"/>
    </bean>
    <!--    省略index属性实现构造方法: 默认从上到下是 0 , 1, 2 的参数位置-->
    <bean id="mystudent3" class="com.nguyenxb.ba03.Student">
        <constructor-arg value="周瑜"/>
        <constructor-arg value="18"/>
        <constructor-arg ref="myschool11"/>
    </bean>

<!--    声明School对象-->
    <bean id="myschool11" class="com.nguyenxb.ba03.School">
        <property name="name" value="03xx大学"/>
        <property name="address" value="03xx大学路"/>
    </bean>

​ ba03\School.java

public class School {
    private String name;
    private String address;
     // 已省略set|get|toString方法;
}

ba03\Student.java

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

    // 声明一个引用类型
    private School school;

    public Student(String myname, 
                   int myage, School myschool) {
        this.name = myname;
        this.age = myage;
        this.school = myschool;
    }
      // 已省略set|get|toString方法;
}

测试:

@Test
public void test03(){
    String config = "ba03/applicationContext.xml";
    // 创建spring容器
    ApplicationContext ac = new ClassPathXmlApplicationContext(config);
    // 从容器中获取对象, 使用name属性创建的对象
    Student mystudent = (Student) ac.getBean("mystudent");
   System.out.println("使用name创建student对象="+mystudent);

    // 从容器中获取对象,使用index属性创建的对象
    Student mystudent2 = (Student) ac.getBean("mystudent2");
    System.out.println("使用index创建student对象="+mystudent2);

    // 从容器中获取对象,直接省略index而创建的对象
    Student mystudent3 = (Student) ac.getBean("mystudent3");
    System.out.println("省略index创建student对象="+mystudent3);
}
/*
student对象=Student{name='张三', age=23, school=School{name='03xx大学', address='03xx大学路'}}

student对象=Student{name='张1', age=12, school=School{name='03xx大学', address='03xx大学路'}}

student对象=Student{name='周瑜', age=18, school=School{name='03xx大学', address='03xx大学路'}}
*/
小案例

在 src\main\resources\ba04目录下新建一个haha.txt文件,通过构造注入的方式获取文件对象.

ba04\applicationContext.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">
<!--    使用name属性实现构造方法-->
    <bean id="myfile" class="java.io.File">
        <constructor-arg name="parent" value="src\main\resources\ba04"/>
        <constructor-arg name="child" value="haha.txt"/>
    </bean>
</beans>

测试

@Test
public void test04(){
    String config = "ba04/applicationContext.xml";
    ApplicationContext ac = new ClassPathXmlApplicationContext(config);

    File myfile = (File) ac.getBean("myfile");
    System.out.println("文件名称:"+myfile.getName());
}
//输出: 文件名称:haha.txt
自动注入

​ 引用类型的自动注入: spring框架根据某些规则库给引用类型赋值.不用手动赋值了.使用的常用规则是byName,byType.

byName
byName(按照名字注入) : java 类中引用类型的属性名和spring容器中(配置文件)<bean>标签的id名称一样,并且数据数据类型是一样的,这样spring就能够自动赋值给<bean>标签中的引用类型.
     语法规则:
    <bean id="xx" class="yyy" autowire="byName>
        简单类型属性赋值
    </bean>

School.java

public class School {
    private String name;
    private String address;
     // 已省略set|get|toString方法;
}

Student.java

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

    // 声明一个引用类型
    private School school;

      // 已省略set|get|toString方法;
}

applicationContext.xml

<!--    使用name属性实现构造方法-->
    <bean id="mystudent" class="com.nguyenxb.ba05.Student" autowire="byName">
       	<property name="name" value="张三"/>
        <property name="age" value="28"/>
    </bean>

<!--    声明School对象-->
    <bean id="school" class="com.nguyenxb.ba05.School">
        <property name="name" value="05xx大学"/>
        <property name="address" value="05xx大学路"/>
    </bean>
<!--如上诉例子中, School的对象名为school,那么Student的属性名中必须要有一个叫school的属性名,并且属性的类型为School -->

测试:

@Test
public void test05(){
    String config = "ba05/applicationContext.xml";
    // 创建spring容器
    ApplicationContext ac = new ClassPathXmlApplicationContext(config);
    // 从容器中获取对象, 使用name属性创建的对象
    Student mystudent = (Student) ac.getBean("mystudent");
    System.out.println("student对象="+mystudent);
}

/*输出:
student对象=Student{name='张三', age=28,school=School{name='05xx大学', address='05xx大学路'}}

*/
byType
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只能有一个符合条件的,多于一个就会发生错误.

applicationContext.xml

<!--byType -->
    <bean id="mystudent" class="com.nguyenxb.ba06.Student" autowire="byType">
       <property name="name" value="张三asdasd"/>
        <property name="age" value="288"/>
    </bean>

<!--    声明School对象-->
<!--    <bean id="MySchool" class="com.nguyenxb.ba06.School">-->
<!--        <property name="name" value="06xx大学"/>-->
<!--        <property name="address" value="06xx大学路"/>-->
<!--    </bean>-->
<!--    School的子类-->
    <bean id="primarySchool" class="com.nguyenxb.ba06.PrimarySchool">
        <property name="name" value="06xx小学"/>
        <property name="address" value="06xx小学路"/>
    </bean>
<!--实现类关系同理-->
@Test
public void test06(){
    String config = "ba06/applicationContext.xml";
    ApplicationContext ac = new ClassPathXmlApplicationContext(config);

    Student myStudent = (Student) ac.getBean("mystudent");

    System.out.println(myStudent);

}

多个配置文件

优点:

  1. 每个文件的大小比一个文件要小很多,效率高.
  2. 避免多人操作同一个文件而产生冲突.

多文件的分配方式

  1. 按照功能模块,一个功能模块一个配置文件.
  2. 按照类的功能,比如数据库相关的配置一个配置文件,事务相关的一个配置文件等等.
  3. spring支持两种加载其他配置文件的方式,第一种是文件列表加载,第二种是使用通配符加载.

ba07/spring-student.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">
<!--        学生模块:所有bean的声明-->
    <bean id="mystudent" class="com.nguyenxb.ba07.Student" autowire="byType">
        <property name="name" value="张三asdasd"/>
        <property name="age" value="288"/>
    </bean>
</beans>

ba07/spring-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">
    <!--    声明School对象-->
    <bean id="MySchool" class="com.nguyenxb.ba07.School">
        <property name="name" value="06xx大学"/>
        <property name="address" value="06xx大学路"/>
    </bean>
</beans>

ba07/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:ba07/spring-school.xml"/>-->
<!--    <import resource="classpath:ba07/spring-student.xml"/>-->

<!--  加载方式二:   在包含关系的配置文件中,可以使用通配符(*:表示任意字符)
        注意:主配置文件名称不能包含在通配符的范围内(比如不能叫做spring-total.xml)
            使用通配符的文件必须在同一个目录中,否则会读取不到
-->
    <import resource="classpath:ba07/spring-*.xml"/>
</beans>

测试

@Test
public void test07(){
    String config = "ba07/total.xml";
    ApplicationContext ac = new ClassPathXmlApplicationContext(config);

    Student myStudent = (Student) ac.getBean("mystudent");

    System.out.println(myStudent);

}

基于注解的DI

通过注解完成java对象的创建,属性赋值.

使用注解的步骤:
  1. 加入maven的依赖 spring-context, 在你加入spring-context的同时,会间接加入spring-aop的依赖,使用注解必须使用spring-aop依赖.
  2. 在类中加入spring的注解(多个不同功能的注解)
  3. 在spring的配置文件中,加入一个注解扫描器的标签,说明注解在你的项目的位置.
如何通过spring的注解完成对象的创建,属性赋值.代替xml文件.

实现步骤:

  1. 加入依赖
  2. 创建类,在类中加入注解
  3. 创建spring配置文件,声明组件扫描器的标签,指明注解在项目中的位置.
  4. 使用注解创建对象, 创建容器ApplicationContext
学习的注解:
  1. @Component
  2. @Respotory
  3. @Service
  4. @Controller
  5. @Value
  6. @Autowired
  7. @Resource
如何配置注解扫描器
<?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">
<!--    声明 组件扫描器(component-scan), 组件就是java对象
        base-package: 指定注解在你的项目的包名.
        component-scan工作方式:spring会扫描遍历base-package指定的包,
            把包和子包中的所有类,找到类中的注解,安装注解的功能创建对象,或者给属性赋值.

        加入了 component-scan标签,配置文件的变化:
            1.加入了一个新的约束文件 spring-context.xsd
            2.给这个新的约束文件起了一个命名空间的名称 context
                而 context 代表的是 http://www.springframework.org/schema/context
                而该地址指向了一个 https://www.springframework.org/schema/context/spring-context.xsd 文件

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

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

<!--    第三种方式:指定父包,就是把父包里面所有的注解都扫描完-->
    <context:component-scan base-package="com.nguyenxb"/>

</beans>
如何使用注解创建对象
@Component: 创建对象的 等同于<bean>的功能
  属性: value 就是对象的名称, 也就是bean的id值
          value的值是唯一的.创建的对象在整个spring容器中就一个
  写法位置: 写在类的上面

1. @component(value="myStudent") 等同于
 <bean id="myStudent" class="com.nguyenxb.ba01.Student"></bean>

2. 可以将value省略即: @component("myStudent")

3.也可以使用spring的默认注解 @component ,生成的对象名就是student,即类的首字母是小写,等价于: 
<bean id="student" class="com.nguyenxb.ba01.Student"></bean>
    
    
 spring中和@component功能一致,创建对象的注解还有:
1.@Repository(用在持久层类的上面): 放在dao的实现类上面
    	表示创建dao对象,dao对象是能访问数据库的.
2.@Service(用在业务层类的上面):放在service的实现类上面,
		表示创建service对象,service对象是做业务处理,可以有事务等功能的.
3.@Controller(用在控制器的上面):放在控制器(处理器的上面)
        表示创建控制器对象,能够接受用户提交的参数,显示请求的处理结果.
以上三个注解的使用语法和@Component一样的.都能创建对象,但是这三个注解还有额外的功能. @Repository,@Service,@Controller是给项目的对象分层的.都是创建对象的注解,但是他们的功能都是不一样的.
 
如何使用注解给对象赋值
给简单类型赋值
@value : 简单类型的属性赋值
    属性: value 是String类型的,表示简单类型的属性值
    位置: 1.在属性定义的上面,无需set方法,推荐使用.
         2.在set方法的上面
给引用类型赋值
第一种方式 :
@Autowired: spring 中通过注解给引用类型赋值, 使用的是自动注入的原理.支持  byName, byType
@Autowired:默认使用的是byType自动注入
@Autowired 的属性: required, 是一个boolean 类型的,默认true
  required=true : 表示引用类型赋值失败,程序报错,并终止执行
  required=false: 表示 引用类型如果赋值失败.程序正常执行,引用类型是null
    用法: @Autowired(required=true)
        
@Autowired:默认使用的是byType自动注入
 位置: 1.在属性定义的上面,无需set方法,推荐使用
	  2.在set方法上面
     
如果要使用byName方式,需要做的是:
1.在属性上面加入 @Autowired
2.在属性上面加入@Qualifier(value="bean的id"):
表示使用指定名称的bean完成赋值.
    
第二种方式:
@Resource: 来自jdk中的注解,sprin框架提供了对这个注解的功能支持,可以使用它给引用类型赋值使用的也是自动注入原理,支持byName,byType.默认是byName.
   位置: 1.在属性定义的上面,无需set方法,推荐使用
	  2.在set方法上面
       
工作原理: 先使用byName自动注入,如果byName注入失败,再使用byType
	使用方式:  @Resource   

如何让@Resource只使用byName方式?
    需要增加一个属性,name的值是 bean的id(名称): @Resource(name="mySchool")//如果找不到,直接报错
     
@Autowried与@Value和Qualifier的注解综合案例:

通过注解创建一个学生类,并且给引用类型赋值

spring 配置文件

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

<!--    使用多次组件扫描器,扫描对应的包下的注解-->
    <context:component-scan base-package="com.nguyenxb.ba03"/>
</beans>

School类

@Component("mySchool")
//@Component
public class School {

    @Value("03xxx学校")
    private String name;
    @Value("03xxx地址")
    private String address;

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

}

Student类

@Component("myStudent")
public class Student {
    @Value("张三")// 在属性上使用注解赋值
    private String name;
    private int age;

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

    /*
    * 如果要使用byName方式,需要做的是:
    *   1.在属性上面加入 @Autowired
    *   2.在属性上面加入@Qualifier(value="bean的id"):
    *         表示使用指定名称的bean完成赋值.
    * */

    // 两个没有先后顺序, 按名称自动赋值
    @Autowired 
    @Qualifier("mySchool")// 即把这个注释掉了就是默认的byType,
    private School school;

    @Value("24")// 在set方法上使用注解赋值
    public void setAge(int age) {
        this.age = age;
    }
}

测试:

@Test
public void test03(){
    String config = "ba03/applicationContext.xml";
    ApplicationContext ac = new ClassPathXmlApplicationContext(config);

    Student myStudent = (Student) ac.getBean("myStudent");

    System.out.println(myStudent);
}
@Resource的用法:
@Component("mySchool")
//@Component
public class School {

    @Value("04xxx学校")
    private String name;
    @Value("04xxx地址")
    private String address;

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

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

import javax.annotation.Resource;

@Component("myStudent")
public class Student {
    @Value("张五")
    private String name;
    private int age;

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", school=" + school +
                '}';
    }
//    @Resource //先使用byName自动注入,如果byName注入失败,再使用byType
    @Resource(name="mSchool")//只使用byName自动注入,如果找不到,直接报错
    private School school;

    @Value("30")
    public void setAge(int age) {
        this.age = age;
    }
}
注解与${}结合使用

用法: 在只能在赋值的时候使用,无法用于定义

resources/ba05目录下

applicationContext.xml

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

    <context:component-scan base-package="com.nguyenxb.ba05"/>
<!--    加载属性的配置文件-->
    <context:property-placeholder location="classpath:ba05/test.properties"/>
</beans>

test.properties

schoolClassName=mySchool
age=25
schoolAddress=xxx05address

java代码

School类

package com.nguyenxb.ba05;

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

@Component("mySchool")
//@Component
public class School {

    @Value("05xxx学校")
    private String name;
    @Value("${schoolAddress}")
    private String address;

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

}

Student类

package com.nguyenxb.ba05;

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

import javax.annotation.Resource;

@Component("MyStudent")
public class Student {
    @Value("张五")
    private String name;
    private int age;

    //    @Resource //先使用byName自动注入,如果byName注入失败,再使用byType
    @Resource(name="${schoolClassName}")//只使用byName自动注入,如果找不到,直接报错
    private School school;

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

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

测试

package com.nguyenxb;

import com.nguyenxb.ba05.Student;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class MyTest05 {
    @Test
    public void test05(){
        String config = "ba05/applicationContext.xml";
        ApplicationContext ac = new ClassPathXmlApplicationContext(config);
        Student myStudent = (Student) ac.getBean("MyStudent");
        System.out.println(myStudent);
    }
}
// 运行结果: Student{name='张五', age=25, school=School{name='05xxx学校', address='xxx05address'}}

注解与xml配置文件的对比

注解优点是:

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

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

XML 方式优点是:

  • 配置和代码是分离的
  • 在 xml 中做修改,无需编译代码,只需重启服务器即可将新的配置加载。

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

第三章 AOP面向切面编程

动态代理

项目aop_leadin1

先定义好接口与一个实现类,该实现类中除了要实现接口中的方法外,还要再写两个非 业务方法。非业务方法也称为交叉业务逻辑:

  • doTrans():用于事务处理
  • doLog():用于日志处理

然后,再使接口方法调用它们。接口方法也称为主业务逻辑

public interface SomeService {
    void doSome();
    void doOther();
}
public class SomeServiceImpl implements SomeService {
    @Override
    public void doSome() {
        doLog();
        System.out.println("执行了业务方法doSome");
        doTrans();
    }

    @Override
    public void doOther() {
        doLog();
        System.out.println("执行了业务方法doOther");
        doTrans();
    }
    public void doLog(){
        System.out.println("非业务功能,日志功能,在方法开始是输出日志"+new Date());
    }
    public void doTrans(){
        System.out.println("非业务事务功能,在方法执行后,加入事务");
    }
}
项目 aop_leadin2

​ 另一种解决方案:将这些交叉业务逻辑代码放到专门的工具类或处理类 中,由主业务逻辑调用

public interface SomeService2 {
    void doSome();
    void doOther();
}
public class SomeServiceImpl2 implements SomeService2 {
    @Override
    public void doSome() {
        ServiceTool.doLog();
        System.out.println("执行了业务方法doSome");
        ServiceTool.doTrans();
    }

    @Override
    public void doOther() {
        ServiceTool.doLog();
        System.out.println("执行了业务方法doOther");
        ServiceTool.doTrans();
    }
}
public class ServiceTool {
    public static void doLog(){
        System.out.println("非业务功能,日志功能,在方法开始是输出日志"+new Date());
    }
    public static void doTrans(){
        System.out.println("非业务事务功能,在方法执行后,加入事务");
    }
}
项目 aop_leadin3 (动态代理的实现)

​ 以上的解决方案,还是存在弊端:交叉业务与主业务深度耦合在一起。当交叉业务逻辑 较多时,在主业务代码中会出现大量的交叉业务逻辑代码调用语句,大大影响了主业务逻辑 的可读性,降低了代码的可维护性,同时也增加了开发难度。 所以,可以采用动态代理方式。在不修改主业务逻辑的前提下,扩展和增强其功能。下面代码中,使用动态代理实现doSome()方法时才输出日志,否则就正常输出.

public interface SomeService {
    void doSome();
    void doOther();
}
public class SomeServiceImpl implements SomeService {
    @Override
    public void doSome() {
        System.out.println("执行了业务方法doSome");
    }

    @Override
    public void doOther() {
        System.out.println("执行了业务方法doOther");
    }
}
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class MyInvocationHandler implements InvocationHandler {
    // 目标对象
    private Object target;

    public MyInvocationHandler(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 通过代理对象执行方法时,会调用这个invoke()
        Object res = null;
        // 只有执行doSome()时, 才输出日志
        if ("doSome".equals(method.getName())){
            ServiceTool.doLog();
            // 执行目标类的方法,通过Method类实现
            res = method.invoke(target,args);// SomeServiceImpl.doSome() , doOther()
            ServiceTool.doTrans();
        }

        // 目标方法执行结果
        return res;
    }
}

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;

public class Main {
    public static void main(String[] args) {
        // 使用jad的Proxy创建代理对象
        // 创建目标对象
        SomeService target =  new SomeServiceImpl();

        // 创建InvocationHandler对象
        InvocationHandler handler = new MyInvocationHandler(target);

        // 使用Proxy创建代理
        /*
        * 传入参数:
        * 1, 目标类的类加载器
        * 2,目标类实现的接口
        * 3,代理对象,要执行的功能
        * */
        SomeService proxy = (SomeService) Proxy.newProxyInstance(
                target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),
                handler);
        // 通过代理对象执行业务方法,会调用handler中的invoke(),即实现日志,事务的增强
        proxy.doSome();
        System.out.println("==============================");
        proxy.doOther();
    }
}
jdk动态代理

实现方式: jdk动态代理,使用jdk中Proxy,Method,InvocationHandler创建代理对象.

jdk的动态代理要求目标对象必须实现接口,这是java设计上的要求.

CGLIB动态代理

​ CGLIB动态代理: 是第三方工具库,创建代理对象,原理是继承.

​ CGLIB代理的生成原理是生成目标类的子类,而子类是增强过的,这个子类对象就是代理对象.所以,使用CGLIB生成动态代理,要求目标类必须能够被继承,即不能是final的类,方法也不能时final的.

​ CGLIB经常被应用在框架中,如spring,Hibernate等. CGLIB的代理效率高于Jdk. 项目中之间使用动态代理的地方不多,一般都使用框架提供的功能.

动态代理的作用
  1. 在目标类源代码不改变的情况下,增加功能.

  2. 减少代码的重复.

  3. 专注业务逻辑代码

  4. 实现解耦合, 让业务的功能和日志,事务等非业务功能分离.

AOP概述

AOP(Aspect Orient Programming):面向切面编程,基于动态代理的,可以使用jdk,cglib两种代理方式.Aop就是动态代理的规范化,把动态代理的实现步骤,方式都定义好了,让开发人员用一种统一的方式来使用动态代理

Aspect : 切面,给你的目标类增加功能,就是切面. 像上面用的日志,事务都是切面.

切面的特点: 一般都是非业务方法,独立使用的.

怎么理解面向切面编程?
  1. 需要在分析项目功能时,找出切面
  2. 合理的安排切面的执行时间(在目标方法前,还是目标方法后)
  3. 合理的安排切面执行的位置,在哪个类,哪个方法增加增强功能

术语:

  1. Aspect:切面 , 表示 增强的功能,就是一段代码完成某一个非业务功能.

     常见的切面功能有日志功能, 事务功能, 统计功能,参数检查, 权限验证.
    
  2. JoinPoint: 连接点, 连接业务方法和切面的位置.就是某类中的业务方法

  3. Pointcut: 切入点, 指给多个连接点增加功能, 这个类就是目标对象

  4. 目标对象: 给哪个类的方法增加功能,这个类就是目标对象.

  5. Advice : 通知, 表示切面功能执行的时间.

切面的三要素
  1. 切面的代码功能,干什么样的功能.
  2. 切面的执行位置,使用Pointcut表示切面的执行位置.
  3. 切面的执行时间, 使用Advice表示时间,在目标方法之前,还是目标方法之后.
AOP的实现

aop是一个规范 , 是动态的一个规范化, 一个标准

aop的技术实现框架:

  1. spring: spring在内部实现了一个aop规范,可以做aop的工作.

    ​ spring主要在事务处理时使用aop.

    ​ 而在实际项目开发中很少使用spring的aop实现. 因为spring的aop比较笨重.

  2. aspectJ: 一个开源的专门做aop的框架, spring 框架中集成了 aspectj 框架,通过spring就能使用aspectJ的功能.aspectJ框架实现aop有两种方式:

    1. 使用xml的配置文件: 配置全局事务
    2. 使用注解,我们在项目中使用aop功能时,一般使用注解,aspectJ有5个注解.
aspectJ框架的使用语法
  1. ​ 切面的执行时间, 这个执行时间在规范中叫做Advice(通知,增强) , 在aspectj框架中使用注解表示的.也可以使用xml配置文件中的标签.

    1. @Before : 前置通知
    2. @AfterReturning : 后置通知
    3. @Around : 环绕通知
    4. @AftterThrowing : 异常通知
    5. @After : 最终通知
  2. 表示切面执行的位置,使用的是切入点表达式.

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

    execution(modifiers-pattern? ret-type-pattern  
              declaring-type-pattern?name-pattern(param-pattern)             throws-pattern?)
    
    解释:
    modifiers-pattern] 访问权限类型
    ret-type-pattern 返回值类型
    declaring-type-pattern 包名类名
    name-pattern(param-pattern) 方法名(参数类型和参数个数)
    throws-pattern 抛出异常类型
    ?表示可选的部分
        
        注意: 方法的返回值类型和方法名(参数类型和参数个数)是必须要有的
    
     即以上表达式共 4 个部分。
     execution(访问权限 方法返回值 方法声明(参数) 异常类型
    

​ 切入点表达式要匹配的对象就是目标方法的方法名。所以,execution 表达式中明显就 是方法的签名。注意,表达式中黑色文字表示可省略部分,各部分间用空格分开。在其中可 以使用以下符号:

符号意义
*0 至多个任意字符
..用在方法参数中,表示任意多个参数
用在包名后,表示当前包及其子包路径
+用在类名后,表示当前类及其子类
用在接口后, 表示当前接口及其实现类

举例:

1. execution(public * *(..)) 
  //指定切入点为:任意公共方法。
    
2. execution(* set*(..)) 
  //指定切入点为:任何一个以“set”开始的方法。
    
3. execution(* com.xyz.service.*.*(..)) 
  //指定切入点为:定义在 service 包里的任意类的任意方法。
    
4. execution(* com.xyz.service..*.*(..))
  //指定切入点为:定义在 service 包或者子包里的任意类的任意方法。“..”出现在类名中时,后面必须跟“*”,表示包、子包下的所有类。
    
5. execution(* *..service.*.*(..))
  //指定所有包下的 serivce 子包下所有类(接口)中所有方法为切入点
    
6. execution(* *.service.*.*(..))
  //指定只有一级包下的 serivce 子包下所有类(接口)中所有方法为切入点
    
7. execution(* *.ISomeService.*(..))
  //指定只有一级包下的 ISomeSerivce 接口中所有方法为切入点
    
8. execution(* *..ISomeService.*(..))
  //指定所有包下的 ISomeSerivce 接口中所有方法为切入点
    
9. execution(* com.xyz.service.IAccountService.*(..)) 
  //指定切入点为:IAccountService 接口中的任意方法。
    
10. execution(* com.xyz.service.IAccountService+.*(..)) 
  //指定切入点为:IAccountService 若为接口,则为接口中的任意方法及其所有实现类中的任意方法;若为类,则为该类及其子类中的任意方法。
    
11. execution(* joke(String,int)))
  //指定切入点为:所有的 joke(String,int)方法,且 joke()方法的第一个参数是 String,第二个参数是 int。如果方法中的参数类型是 java.lang 包下的类,可以直接使用类名,否则必须使用全限定类名,如 joke( java.util.List, int)。
    
12. execution(* joke(String,*))) 
  //指定切入点为:所有的 joke()方法,该方法第一个参数为 String,第二个参数可以是任意类型,如joke(String s1,String s2)和joke(String s1,double d2)都是,但joke(String s1,double d2,String s3)不是
    
13. execution(* joke(String,..))) 
  //指定切入点为:所有的 joke()方法,该方法第一个参数为 String,后面可以有任意个参数且参数类型不限,如 joke(String s1)、joke(String s1,String s2)和 joke(String s1,double d2,String s3)都是。
    
14. execution(* joke(Object))
  //指定切入点为:所有的 joke()方法,方法拥有一个参数,且参数是 Object 类型。joke(Object ob)是,但,joke(String s)与 joke(User u)均不是。
    
15. execution(* joke(Object+))) 
    //指定切入点为:所有的 joke()方法,方法拥有一个参数,且参数是 Object 类型或该类的子类。不仅 joke(Object ob)是,joke(String s)和 joke(User u)也是。

使用aspectj框架实现aop

目的: 给已经存在的一些类和方法,增加额外的功能.前提是不改变原来类的代码.

使用aspectj实现aop的基本步骤:
  1. 新建maven项目

  2. 加入依赖

    1. spring依赖

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

      <dependency>
          <groupId>org.springframework</groupId>
          <artifactId>spring-aspects</artifactId>
          <version>5.2.5.RELEASE</version>
      </dependency>
      
  3. 创建目标类:接口和它的实现类.要做的就是给类中的方法增加功能.

  4. 创建切面类型:普通类

    1. 在类的上面加入@Aspect

    2. 在类中定义方法, 方法里面封装封装的就是切面要执行的功能代码

      要在方法上面加入aspectj中的通知注解,例如@Before,还需要指定切入点表达式execution()

  5. 创建spring的配置文件: 声明对象,把对象交给容器统一管理,声明对象可以使用注解或者xml配置文件<bean>

    1. 声明目标对象

    2. 声明切面类对象

    3. 声明aspectj框架中的自动代理生成器标签.

      自动代理生成器: 用来完成对象的自动创建功能.

  6. spring使用什么代理?

    1. 当目标类有接口的时候, 使用的是jdk动态代理,

    2. 没有接口使用的是spring框架提供的cglib动态代理

    3. 当有接口时, 可以也可以使用spring提供的动态代理 , 即在配置文件中加入:

      <!--    proxy-target-class="true" : 告诉 spring 框架使用 cglib动态代理-->
          <aop:aspectj-autoproxy proxy-target-class="true"/>
      

前置通知实例:@Before

applicationContext.xml

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

<!--    把对象交给spring容器,由spring容器统一创建,管理对象-->
<!--    声明目标对象-->
    <bean id="someService" class="com.nguyenxb.ba01.SomeServiceImpl"/>

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

<!--    声明自动代理生成器: 使用aspectj框架内部的功能,创建目标对象的代理对象.
        创建代理对象是哎内存中实现的,修改目标对象的内存中的结果.创建为代理对象
        所有目标对象就是被修改后的代理对象.
       如果下面这些东西没有,需要自己手动加
        xmlns:aop="http://www.springframework.org/schema/aop"
        http://www.springframework.org/schema/aop
       https://www.springframework.org/schema/aop/spring-aop.xsd

       aspectj-autoproxy:会把spring容器中的所有的目标对象,一次性都生成代理对象,
-->
    <aop:aspectj-autoproxy/>

</beans>

目标接口定义:

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

目标类实现

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

切面类

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


    /**
     * @Before: 前置通知注解,可以有多个
     *      属性: value, 是切入点表达式.表示切面的功能执行的位置.
     *      位置:在方法的上面
     *
     *    特点:
     *      1.在目标方法之前先执行的.
     *      2. 不会改变目标方法的执行结果
     *      3.不会影响目标方法的执行.
     */
  //变形 1
//    @Before(value = "execution(public void com.nguyenxb.ba01.SomeServiceImpl.doSome(String,Integer))")
    //变形 2
//    @Before(value = "execution(void com.nguyenxb.ba01.SomeServiceImpl.doSome(String,Integer))")
    //变形 3
//    @Before(value = "execution(void *..SomeServiceImpl.doSome(String,Integer))")
    //变形 4
//    @Before(value = "execution(void *..SomeServiceImpl.doSome(..))")
    //变形 5
    @Before(value = "execution(void *..SomeServiceImpl.do*(..))")

    public void myBefore(){
        // 就是你切面要执行的功能代码
        System.out.println("前置通知,切面功能: 在目标方法执行之前输出执行时间:"+new Date());
    }
}

测试

public class MyTest01 {
    @Test
    public void test01(){
        String config="applicationContext.xml";
        // 获取容器
        ApplicationContext ac = new ClassPathXmlApplicationContext(config);
        SomeService proxy = (SomeService) ac.getBean("someService");
        System.out.println("proxy=="+proxy.getClass().getName());//该类类型为com.sun.proxy.$Proxy8
        proxy.doSome("haha",12);
    }
}
切面类方法获取目标类的数据:joinPoint
/**
 * 指定通知方法的参数: joinPoint
 * JoinPoint: 业务方法, 要加入切面功能的业务方法
 *  作用是: 可以在通知方法中获取方法执行的信息, 例如方法名称,方法的实参
 *  这个JoinPoint参数的值是框架赋予的,必须是第一个位置的参数
 * */
public void myBefore(JoinPoint joinPoint){
    // 就是你切面要执行的功能代码
    System.out.println("方法的签名(定义):"+joinPoint.getSignature());
    System.out.println("方法的名称"+joinPoint.getSignature().getName());
    // 获取方法的实参
    Object args[] = joinPoint.getArgs();
    for (Object arg:args){
        System.out.println("参数:"+arg);
    }

    System.out.println("前置通知,切面功能: 在目标方法执行之前输出执行时间:"+new Date());
}

/** 以前面的例子举例,输出结果为 :

方法的签名(定义):void com.nguyenxb.ba01.SomeService.doSome(String,Integer)
方法的名称doSome
参数:haha
参数:12

*/

后置通知@AfterReturning
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;


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


    /**
     * @AfterReturning: 前置通知注解,可以执行多个
     *      属性: 1.value, 是切入点表达式.表示目标方法的返回值.
     *            2. returning 自定义的变量,表示目标方法的返回值的,
     *                  自定义变量名必须和通知方法的形参名一样
     *      位置:在方法定义的上面
     *
     *    特点:
     *      1.在目标方法之后执行的.
     *      2. 能够获取到目标放啊的返回值,可以根据这个返回值做不同的处理功能
     *      3.可以修改这个返回值
     *
     *     后置通知执行的顺序
     *      Object res = doOther()
     *      myAfterReturning(res);
     */

    @AfterReturning(value = "execution(* *..SomeServiceImpl.doOther(..))",returning = "res")
   
    // 如果要使用JoinPoint, 只需要改成 :   public void myAfterReturning(JoinPoint jp,Object res) 即可
    
    public void myAfterReturning(Object res){
        // Object res : 是目标方法执行后的返回值,根据返回值做你的切面的功能处理
        System.out.println("后置通知,在目标之后执行的,获取的返回值是:"+res);
        // 如果是普通值,在这里更改数据后,目标方法返回的结果不会改变,
        // 如果是引用类型,在这里修改数据后,目标方法会发生改变
        res = "asdasd";
    }
}
环绕通知: @Around
package com.nguyenxb.ba03;


import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;

import java.util.Date;


/**
 * @Aspect : 是aspectj框架中的注解.
 *      作用: 表示当前类是切面类.
 *      切面类: 是用来给业务方法增加功能的类, 在这个类中有切面的功能代码.
 *      位置:在类定义的上面
 */
@Aspect
public class MyAspect {
   /**
    * 环绕通知方法的定义格式
    *  1,public
    *  2,必须有一个返回值,推荐使用Object
    *  3,方法名称定义
    *  4,方法有参数,固定的参数 ProceedingJoinPoint
    * */

   /**
    * @Around: 环绕通知
    *   属性: value 切入点表达式
    *   位置: 在方法定义上面
    *
    *  特点:
    *    1. 他是功能最强的通知
    *    2. 在目标方法的前和后都能增强功能.
    *    3. 控制目标方法是否被调用执行.
    *    4. 修改原来的目标方法的执行结果. 影响最好的调用结果
    *
    *    环绕通知,等同与jdk的动态代理的,InvocationHandler接口
    *     参数: ProceedingJoinPoint 就等同于 Method
    *           作用 : 执行目标方法的
*           返回值 : 就是目标方法的执行结果,结果可以被修改
    *
    * 环绕通知经常做事务, 在目标放之前开启事务,执行目标方法之后提交事务.
    *
    * */

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

        // 获取目标类的第一个参数值
        Object args[] = proceedingJoinPoint.getArgs();
        for (Object arg: args){
            System.out.println("参数:"+arg);
        }

       // 实现环绕通知
        Object result = null;
        System.out.println("环绕通知: 在目标方法之前,输出时间:"+new Date());
        // 1. 目标方法的调用
        result = proceedingJoinPoint.proceed(); // 等同于method.invoke(); Object result = doFirst();
        System.out.println("环绕通知: 在目标方法之后,如提交事务");
        // 2. 在目标方法的前或者后加入功能

        // 返回目标方法的执行结果,可以改变结果,并且会影响返回的结果
        return result;

   }

}
异常通知:@AfterThrowing
package com.nguyenxb.ba04;


import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;

import java.util.Date;


/**
 * @Aspect : 是aspectj框架中的注解.
 *      作用: 表示当前类是切面类.
 *      切面类: 是用来给业务方法增加功能的类, 在这个类中有切面的功能代码.
 *      位置:在类定义的上面
 */
@Aspect
public class MyAspect {
   /**
   *  异常通知方法的定义格式:
    *   1.public
    *   2.没有返回值
    *   3.方法名称自定义
    *   4.方法有一个Exception,如果还有就是JoinPoint
    *
   * */

   /**
    * @AfterThrowing : 异常通知
    *   属性: 1.value 切入点表达式
    *       2. throwing 自定义变量名, 表示目标方法抛出的异常对象,
    *           变量名必须和方法的参数名一样
    *
    *   执行类比于:
    *       try{
    *           doSecond()
    *       }catch(Exception ex){
    *                  System.out.println("异常通知: 方法发生异常时, 执行: "+ex);
    *       }
    *
    * */

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

}
最终通知:@After
package com.nguyenxb.ba05;


import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;


/**
 * @Aspect : 是aspectj框架中的注解.
 *      作用: 表示当前类是切面类.
 *      切面类: 是用来给业务方法增加功能的类, 在这个类中有切面的功能代码.
 *      位置:在类定义的上面
 */
@Aspect
public class MyAspect {
 /**
  * 最终通知方法的定义格式
  *  1.public
  *  2. 没有返回值
  *  3.方法名称自定义
  *  4. 方法没有参数 , 如果还有是JoinPoint,
  *
  * */

 /**
  * @After : 最终通知
  *     属性: value 切入点表达式
  *     位置: 在方法的上面
  *  特点:
  *     1. 总是会执行
  *     2. 在目标方法之后执行的
  *
  *
  *     其实执行就等价于
  *      try{
  *
  *      }catch(Exception e){
  *
  *      }finally{
  *
  *      }
  * */

 @After(value = "execution(* *..SomeServiceImpl.doThird(..))")
    public void myAfter(){
     System.out.println("执行最终通知,总是会被执行的代码");
     // 一般是做资源清除工作的.

 }

}
切入点表达式别名 @Pointcut
package com.nguyenxb.ba06;


import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;


/**
 * @Aspect : 是aspectj框架中的注解.
 *      作用: 表示当前类是切面类.
 *      切面类: 是用来给业务方法增加功能的类, 在这个类中有切面的功能代码.
 *      位置:在类定义的上面
 */
@Aspect
public class MyAspect {
 /**
  * 最终通知方法的定义格式
  *  1.public
  *  2. 没有返回值
  *  3.方法名称自定义
  *  4. 方法没有参数 , 如果还有是JoinPoint,
  *
  * */

 /**
  * @After : 最终通知
  *     属性: value 切入点表达式
  *     位置: 在方法的上面
  *  特点:
  *     1. 总是会执行
  *     2. 在目标方法之后执行的
  *
  *
  *     其实执行就等价于
  *      try{
  *
  *      }catch(Exception e){
  *
  *      }finally{
  *
  *      }
  * */

// @After(value = "execution(* *..SomeServiceImpl.doThird(..))")
    @After(value = "myPointcut()")
    public void myAfter(){
     System.out.println("执行最终通知,总是会被执行的代码");
     // 一般是做资源清除工作的.

 }
// @Before(value = "execution(* *..SomeServiceImpl.doThird(..))")
    @Before(value = "myPointcut()")
    public void myBefore(){
     System.out.println("前置通知,总是在目标方法之前执行");
     // 一般是做资源清除工作的.

 }
 /**
  * @Pointcut : 定义和管理切入点, 如果项目中有多个切入点表达式是重复的,可以复用的,
  *         那么可以使用 @Pointcut
  *      属性: value  切入点表达式
  *      位置: 在自定义的方法的上面
  *    特点: 当使用 @Pointcut定义在一个方法的上面,次数这个方法的名称就是切入点的别名
  *     其他的通知值,value属性就可以使用这个方法的名称代替切入点表达式了
  *
  * */


 @Pointcut(value = "execution(* *..SomeServiceImpl.doThird(..))")
    public void myPointcut(){}

}

第四章 Spring集成mybatis框架

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

使用技术: ioc

为什么使用ioc ?

​ 因为ioc能创建对象,可以把mybatis框架中的对象交给spring统一创建,开发人员从spring中获取对象. 开发人员就不用同时面对两个或者多个框架了,就面对一个spring就行了.

mybatis使用步骤,创建对象

  1. 定义dao接口,StudentDao

  2. 定义mapper文件, StudentDao.xml

  3. 定义主配置文件 mubatis.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对象.

我们会使用性能相对更好的独立的连接池来代替mybatis默认自己带的,把连接此类也交给spring创建对象.

主配置文件信息:

  1. 数据库信息

     <environment id="development">
                <transactionManager type="JDBC"/>
                <!--配置数据源: 创建Connection对象-->
                <dataSource type="POOLED">
                    <!--driver:驱动的内容-->
                    <property name="driver" value="com.mysql.jdbc.Driver"/>
                    <!--连接数据库的url: 不能直接使用 & 符号,要将其转换为 html 实体即 &amp; 代表的就是 & 符号 -->
                    <property name="url" value="jdbc:mysql://localhost:3306/mysql_DBName?
                    useSSL=true&amp;useUnicode=true&amp;characterEncoding=utf-8"/>
                    <!--用户名-->
                    <property name="username" value="root"/>
                    <!--用户密码-->
                    <property name="password" value="root"/>
                </dataSource>
            </environment>
    
  2. mapper文件的位置

      <mappers>
    <!--
        使用mapper的resource属性指定mapper文件的路径.
        这个路径是从target/classes路径开始的
            使用注意:
                resource = "mapper文件的路径,使用 / 分割路径"
                一个mapper resource 指定一个mapper文件
    -->
            <mapper resource="com\nguyenxb\dao\StudentDao.xml"/>
        </mappers>
    

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

  1. 独立的连接类的对象, 使用阿里的druid连接池
  2. SqlSessionFactory对象
  3. 创建出dao对象

即使用xml的bean标签创建上面三个对象.

druid连接池官网

spring和mybatis的集成
步骤:
  1. 新建maven项目
  2. 加入maven依赖
    • spring依赖
    • mybatis依赖
    • mysql驱动
    • spring的事务依赖
    • mybatis和spring集成的依赖: mybatis官方提供的,用来在spring项目中创建mybatis的SqlSessionFactory,dao对象的.
  3. 创建实体类
  4. 创建dao接口和mapper文件
  5. 创建mybatis主配置文件
  6. 创建Service接口和实现类,属性是dao.
  7. 创建spring的配置文件:声明mybatis的对象交给spring创建
    1. 数据源
    2. SqlSessionFactory
    3. Dao对象
    4. 声明自定义的service

所有依赖:

 <dependencies>
<!--    单元测试-->
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.11</version>
      <scope>test</scope>
    </dependency>
<!--    spring核心ioc-->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>5.2.5.RELEASE</version>
    </dependency>
<!--    spring事务要用到的-->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-tx</artifactId>
      <version>5.2.5.RELEASE</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-jdbc</artifactId>
      <version>5.2.5.RELEASE</version>
    </dependency>
<!--    mybatis依赖-->
    <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis</artifactId>
      <version>3.5.1</version>
    </dependency>
<!--    mybatis和spring集成的依赖-->
    <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis-spring</artifactId>
      <version>1.3.1</version>
    </dependency>
<!--    mysql驱动-->
    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>5.1.9</version>
    </dependency>
<!--    阿里公司的数据库连接池-->
    <dependency>
      <groupId>com.alibaba</groupId>
      <artifactId>druid</artifactId>
      <version>1.1.12</version>
    </dependency>
  </dependencies>

<!--  插件-->
  <build>
<!--    目的是把src/main/java 目录中的xml文件包含到输出classes目录中-->
    <resources>
      <resource>
        <directory>src/main/java</directory><!--所在的目录-->
        <includes><!--包括目录下的.properties,.xml 文件都会扫描到-->
          <include>**/*.properties</include>
          <include>**/*.xml</include>
        </includes>
        <filtering>false</filtering>
      </resource>
    </resources>
<!--    指定jdk版本的-->
    <plugins>
      <plugin>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>3.1</version>
        <configuration>
          <source>1.8</source>
          <target>1.8</target>
        </configuration>
      </plugin>
    </plugins>
  </build>

spring的配置文件:applicationContext.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<!--    把数据库的配置信息,写在一个独立的文件,编译修改数据库的配置内容
        spring知道jdbc.properties文件的位置
-->
    <context:property-placeholder location="jdbc.properties"/>
<!--    声明数据源DataSource,作用是连接数据库的
        使用属性配置文件中的数据
-->
    <bean id="myDataSources" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
        <property name="maxActive" value="${jdbc.maxActive}"/>
    </bean>

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

<!--    创建dao对象,使用sqlSession的getMapper(Student.class)
        MapperScannerConfigurer: 在北部调用getMapper()歙观察每个dao接口的代理对象
-->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<!--        指定SqlSessionFactory对象的id-->
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
<!--        指定包名,包名是dao接口所在的包名.
        mapperScannerConfigurer会扫描这个包中的所有接口,把每个接口都执行异常getMapper()方法
            得到每个接口的dao对象,创建好的dao对象放入到spring的容器中.
            dao对象的默认名称是接口名的首字母小写
-->
        <property name="basePackage" value="com.nguyenxb.dao"/>
    </bean>

    <bean id="studentService" class="com.nguyenxb.service.impl.StudentServiceImpl">
        <property name="studentDao" ref="studentDao"/>
    </bean>

</beans>

mybatis主配置文件:mybatis.xml

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

    <settings>
        <!--配置输出日志 -->
        <setting name="logImpl" value="STDOUT_LOGGING"/>
    </settings>

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

    <!--指定其他mapper文件的位置:
            目的: 使用这里的连接数据库信息来执行其他文件的sql语句-->
    <!--Enter the path to your mapper file -->
    <mappers>
<!--        mapper文件所在的包名-->
        <package name="com.nguyenxb.dao"/>
    </mappers>
</configuration>

属性文件: jdbc.properties

jdbc.url=jdbc:mysql://localhost:3306/ssm? useSSL=true&amp;useUnicode=true&amp;characterEncoding=utf-8
jdbc.username=root
jdbc.password=root
jdbc.maxActive=20

Dao包下的文件

操作学生表的接口:StudentDao.java
package com.nguyenxb.dao;

import com.nguyenxb.entity.Student;

import java.util.List;

public interface StudentDao {
    int insertStudent(Student student);
    List<Student> selectStudents();
}
mapper文件:StudentDao.xml
<?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.nguyenxb.dao.StudentDao">
    <!--Enter your sql statement with (select | insert | update |delete) tag
        The id of the tag is suggested to use the name of the method that called it-->
    <select id="selectStudents" resultType="com.nguyenxb.entity.Student">
        select id,name,email,age from student order by id desc
    </select>

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

entity包下的文件

Student.java

package com.nguyenxb.entity;

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

    @Override
    public String toString() {
        return "Student{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", email='" + email + '\'' +
                ", 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;
    }
}

service包下的文件

学生业务接口:StudentService.java
package com.nguyenxb.service;

import com.nguyenxb.entity.Student;

import java.util.List;

public interface StudentService {
    int addStudent(Student student);

    List<Student> queryStudents();
}
学生业务实现类: impl.StudentServiceImpl.java
package com.nguyenxb.service.impl;

import com.nguyenxb.dao.StudentDao;
import com.nguyenxb.entity.Student;
import com.nguyenxb.service.StudentService;

import java.util.List;

public class StudentServiceImpl implements StudentService {

    // 引用类型
    private StudentDao studentDao;

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

    public StudentServiceImpl(StudentDao studentDao) {
        this.studentDao = studentDao;
    }

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

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

测试代码:

package com.nguyenxb;

import com.nguyenxb.dao.StudentDao;
import com.nguyenxb.entity.Student;
import com.nguyenxb.service.StudentService;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import java.util.List;

public class MyTest {
    @Test
    public void test01(){
        String config = "applicationContext.xml";
        ApplicationContext ac = new ClassPathXmlApplicationContext(config);
        String[] names = ac.getBeanDefinitionNames();
        for (String name:names){
            System.out.println("容器中对象名称:"+name+"---|---" + ac.getBean(name).getClass());
        }

    }
    @Test
    public void testInsertStudent(){
        String config = "applicationContext.xml";
        ApplicationContext ac = new ClassPathXmlApplicationContext(config);
        StudentDao studentDao = (StudentDao) ac.getBean("studentDao");
        Student student = new Student(1009,"张伟","zhangwei@qq.com",20);
        int row = studentDao.insertStudent(student);
        System.out.println(row);
    }
    @Test
    public void testServiceInsert(){
        String config = "applicationContext.xml";
        ApplicationContext ac = new ClassPathXmlApplicationContext(config);
        StudentService studentService = (StudentService) ac.getBean("studentService");
        Student student = new Student(1010,"李双","zhangwei@qq.com",20);
        int row = studentService.addStudent(student);
//        spring和mybatis整合在一起使用,事务是自动提交的,无需执行sqlSession.commit()
        System.out.println(row);
    }

    @Test
    public void testSelectStudents(){
        String config = "applicationContext.xml";
        ApplicationContext ac = new ClassPathXmlApplicationContext(config);
        StudentService studentService = (StudentService) ac.getBean("studentService");
        List<Student> students = studentService.queryStudents();
        for (Student student: students){
            System.out.println(student);
        }
    }
}

第五章 spring的事务处理

什么是事务?

​ 事务是 一组sql语句的集合, 集合中有多条sql语句,可能是insert, update, select, delete, 我们希望这些多个sql语句都能执行成功, 否则就都失败. 这些sql语句的执行都是一致的,作为一个整体执行.

在什么时候要使用事务?

​ 当我的操作涉及到多个表,或者多个sql语句的insert,update,delete.需要这些语句都是成功才能完成我的功能,或者都失败,保证操作都是符合要求的.比如用于转账.

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

​ 答: 应该放在service类的业务方法上,因为我们的业务方法会调用多个dao方法,执行多个sql语句.

通常使用jdbc访问数据库,还有使用mybaits访问数据库怎么处理事务?

  1. jdbc访问数据库,处理事务

    Connection conn;
    // 提交事务
    conn.commit();
    // 回滚事务
    conn.rollback();
    
  2. mybati访问数据库,处理事务

    SqlSession.commit();
    // 回滚事务
    SqlSession.rollback();
    

前一个问题中事务的处理方式,有什么不足?

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

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

  3. 需要掌握处理事务的多种方法.

    总结: 就是多种数据库的访问技术,有不同的事务处理的机制, 对象, 方法.

怎么解决不足?

开发人员
spring的事务处理模型
mybatis数据库访问技术的事务处理
hibernate访问数据库处理事务
其他的数据库访问技术

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

​ spring的事务处理模型: 抽象了事务处理的各个方面,定义了事务的处理步骤.

spring使用的是声明式事务,把事务相关的资源和内容都提供给spring, spring就能处理事务提交,回滚了.

​ 即使用spring的事务处理机制,可以完成多个不同的访问数据库的事务处理, 比如可以完成mybatis的访问数据库事务处理,也能完成hibernate访问数据的事务处理.等等.

处理事务,需要做什么, 怎么做?

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

1) 事务内部提交,回滚事务.

​ 使用的事务管理器对象,代替你完成commit, roolback. 事务管理器是一个接口和它的众多实现类.

接口:

​ PlatformTransactionManager, 定义了事务重要方法,commit,rollback.

实现类:

​ spring把每一种数据库访问技术对应的事务处理类都创建好了,

mybatis访问数据库—-spring创建好的是DataSourceTransactionManager

hibernate访问数据库—–spring创建的是HibernateTransactionManager

怎么使用:

​ 你需要告诉spring , 你用的是哪种数据库访问技术.

怎么告诉spring你使用的是哪种数据库访问技术呢?

​ 声明数据库访问技术对应的事务管理器的实现类, 在spring的配置文件中使用<bean>声明就可以了.

​ 例如, 你要使用mybatis访问数据库, 你应该在配置文件中声明:

<bean id="xxx" class="...DataSourceTransactionManager">
2)你的业务方法需要什么样的事务,说明需要事务的类型.
1)事务的隔离级别: 有4个值.

​ DEFAULF: 采用DB默认的事务隔离级别. MySql 的默认的隔离级别为 REPEATABLE_READ, Oracle默认为READ_COMMITTED.

​ 1, READ_UNCOMMITTED:读未提交,未解决任何并发问题.

​ 2, READ_COMMITTED: 读已提交. 解决脏读, 存在不可重复读与幻读.

​ 3, REPEATABLE_READ: 可重复读. 解决脏读, 不可重复读, 存在幻读.

​ 4, SERIALIZABLE: 串行化. 不存在并发问题.

(2) 事务的超时时间:

​ 表示一个方法最长的执行时间, 如果方法执行时超过了时间, 事务就回滚. 单位是秒, 整数值, 默认是 -1.

(3) 事务的传播行为 :

​ 控制业务方法是不是有事务, 是什么样的传播行为.7个传播行为, 表示你的业务方法调用时, 事务在方法之间如何使用的.

a. PROPAGATION_REQUIRED

​ 指定的方法必须在事务内执行. 若当前存在事务,就加入到当前事务中;若当前没有事 务,则创建一个新事务. 这种传播行为是最常见的选择,也是 Spring 默认的事务传播行为.

​ **例如:**该传播行为加在 doOther()方法上. 若 doSome()方法在调用 doOther()方法时就是在事 务内运行的,则 doOther()方法的执行也加入到该事务内执行. 若 doSome()方法在调用 doOther()方法时没有在事务内执行,则 doOther()方法会创建一个事务,并在其中执行,

在这里插入图片描述

​ 用实际生活中的来说就是, 比如张三和李四在打王者荣耀, 两个人都是使用的4G网络, 但是张三的流量比李四的流量多. 然后李四就问张三: “能不能开个热点给我 ?” 若张三说: “可以开热点给你”. 那么张三和李四就使用的是同一个网络,即一起使用张三的流量.

若张三说: “我拒绝开热点给你”, 那么张三和李四就使用各自的流量玩.

b. PROPAGATION_SUPPORTS

​ 指定的方法支持当前事务,但若当前没有事务,也可以以非事务方式执行.

在这里插入图片描述

c. PROPAGATION_REQUIRES_NEW

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

在这里插入图片描述

d. PROPAGATION_MANDATORY

​ 使用当前的事务,如果当前没有事务,就抛出异常。

e. PROPAGATION_NESTED

​ 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与PROPAGATION_REQUIRED类似的操作。

f. PROPAGATION_NEVER

​ 以非事务方式执行,如果当前存在事务,则抛出异常。

g. PROPAGATION_NOT_SUPPORTED

​ 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。

3)事务管理器接口定义了默认事务超时时限

​ 常量 TIMEOUT_DEFAULT 定义了事务底层默认的超时时限,sql 语句的执行时长。 注意,事务的超时时限起作用的条件比较多,且超时的时间计算点较复杂。所以,该 值一般就使用默认值即可。

4)spring提交事务,回滚事务的时机.
  • 当你的业务方法, 执行成功, 没有异常抛出. 即在方法执行完毕后,spring提交事务.使用的是事务管理器 commit.

  • 当你的业务方法抛出运行时异常,spring执行回滚, 调用事务管理器rollback.

    运行时异常 的定义 : RuntimeException 和他的子类都是运行时异常.

  • 当业务方法抛出非运行时异常, 主要是受查异常时, 提交事务.

    受查异常: 在你写代码中, 必须处理的异常, 例如 IOException, SQLException

总结spring的事务

  1. 管理事务的是 事务管理器和它的实现类

  2. spring的事务是一个统一模型

    • 指定要使用的事务管理器实现类, 使用 <bean>
    • 指定哪些类, 哪些方法需要加入事务的功能
    • 指定方法需要的隔离级别, 传播行为, 超时

    你需要告诉spring, 你的项目中类信息, 方法的名称, 方法的事务传播行为.

spring框架中提供的事务处理方案
A. 适合中小项目使用的,注解方案.

​ spring框架自己用aop实现给业务方法增加事务的功能, 使用@Transactional注解增加事务.@Transactional注解是spring框架的注解, 放在public方法的上面, 表示当前方法具有事务.可以给注解的属性赋值, 表示具体的隔离级别, 传播行为, 异常信息等.

@Transactional 的所有可选属性如下所示:

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

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

​ 若@Transaction 注解在类上,则表示该类上所有的方法均将在执行时织入事务。

使用@Transactional的步骤:

1.需要声明事务管理器对象

<bean id="xx" class="DataSourceTransactionManager">
  1. 开启事务注解驱动, 告诉spring框架, 我要使用注解的方式管理事务.

spring使用aop机制, 创建@Transactional所在的类代理对象, 给方法加入事务的功能.

spring给业务方法加入事务:

​ 在你的业务方法执行之前,先开启事务, 在业务放啊之后提交或回滚事务, 使用aop的环绕通知.

@Around("你要增加的事务功能的业务方法名称")
Object myAround(){
    // 开启事务,spring 给你开启
    try{
        buy(1001,10);
        spring的事务管理.commit();
    }catch(Exception e){
        spring的事务管理.rollback();
    }
}

3.在你的方法上面加入@Trancational注解

例子:

spring的配置文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context
       https://www.springframework.org/schema/context/spring-context.xsd
       http://www.springframework.org/schema/tx
       http://www.springframework.org/schema/tx/spring-tx.xsd">
<!--    把数据库的配置信息,写在一个独立的文件,编译修改数据库的配置内容
        spring知道jdbc.properties文件的位置
-->
    <context:property-placeholder location="jdbc.properties"/>
<!--    声明数据源DataSource,作用是连接数据库的
        使用属性配置文件中的数据
-->
    <bean id="myDataSources" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
        <property name="maxActive" value="${jdbc.maxActive}"/>
    </bean>

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

<!--    创建dao对象,使用sqlSession的getMapper(Student.class)
        MapperScannerConfigurer: 在北部调用getMapper()歙观察每个dao接口的代理对象
-->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<!--        指定SqlSessionFactory对象的id-->
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
<!--        指定包名,包名是dao接口所在的包名.
        mapperScannerConfigurer会扫描这个包中的所有接口,把每个接口都执行异常getMapper()方法
            得到每个接口的dao对象,创建好的dao对象放入到spring的容器中.
            dao对象的默认名称是接口名的首字母小写
-->
        <property name="basePackage" value="com.nguyenxb.dao"/>
    </bean>

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

<!--    使用spring的事务处理-->
<!--    声明事务管理器-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!--        连接的数据库, 指定的数据库源-->
        <property name="dataSource" ref="myDataSources"/>
    </bean>
    <!--        开启事务注解驱动, 告诉spring使用注解管理事务, 创建代理对象
             导入的应该是这个地址: http://www.springframework.org/schema/tx
             transaction-manager 表示的是事务管理器的id
    -->
    <tx:annotation-driven transaction-manager="transactionManager"/>

</beans>

业务方法:

public class BuyGoodsServiceImpl implements BuyGoodsService {
    private SaleDao saleDao;
    private GoodsDao goodsDao;
    /*
    *  声明事务
    *
    *   propagation: 设置传播行为
    *   readOnly : 只读
    *   rollbackFor: 如果发生指定的异常, 一定回滚.
    *    处理逻辑:
    *     1. spring框架会先检查方法抛出的异常是不是在rollbackFor的
    *       属性值中,如果异常在rollbackFor列表中,不管是什么类型的异常,一定回滚.
    *      2.如果抛出的异常不在rollbackFor列表中, spring会判断异常是不是运行时异常,
    *       如果是,则一定回滚.
    *
    * */
   /* @Transactional(
            propagation = Propagation.REQUIRED,
            isolation = Isolation.DEFAULT,
            readOnly = false,
            rollbackFor = {
                    NullPointerException.class,
                    NotEnoughException.class
            }
    )*/
    /* 使用的是事务控制的默认值, 默认的传播行为是Required, 默认的
    隔离级别Default,默认抛出运行时异常的时候,就回滚事务.

    */
    @Transactional
    @Override
    public void buy(Integer goodsId, Integer nums) {
        System.out.println("======buy 方法的开始===");
        // 记录销售信息, 向sale表添加记录
        Sale sale = new Sale();
        sale.setGid(goodsId);
        sale.setNums(nums);
        saleDao.insertSale(sale);

        // 更新库存
        Goods goods = goodsDao.selectGoods(goodsId);
        if (goods==null){
            // 商品不存在
            throw new NullPointerException("编号是:"+goodsId+", 商品不存在");
        }else if ( goods.getAmount() < nums){
            throw new NotEnoughException("编号是:"+goodsId+",商品库存不足");
        }

        // 修改库存
        Goods buyGoods = new Goods();
        buyGoods.setId(goodsId);
        buyGoods.setAmount(nums);
        goodsDao.updateGoods(buyGoods);
        System.out.println("=========buy方法完成===========");
    }

    public void setSaleDao(SaleDao saleDao) {
        this.saleDao = saleDao;
    }

    public void setGoodsDao(GoodsDao goodsDao) {
        this.goodsDao = goodsDao;
    }
}
B. 适合大型项目,有很多类,方法.

​ 需要大量的配置事务, 使用aspectj框架功能, 在spring配置文件中声明类, 方法需要的事务. 这种方式业务方法和事务配置完全分离.

​ 实现步骤: 都是在xml配置文件中实现.

(1) 要使用的是ascpectj框架, 需要加入依赖.

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

(2) 声明事务管理器对象

<bean id="xx" class="DataSourceTransactionManager">

(3) 声明方法需要的事务类型(配置方法的事务属性 [隔离级别, 传播行为, 超时] )

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

<!--    2. 声明业务方法的事务属性,(隔离级别,传播行为,超时时间)
        id: 自定义名称, 表示 <tx:advice> 和 </tx:advice> 之间的配置内容的
        transaction-manager: 事务管理器对象的id
-->
    <tx:advice id="myAdvice" transaction-manager="transactionManager">
<!--        tx:attributes  配置事务属性 -->
        <tx:attributes>
<!--            tx:method : 给具体的方法配置事务属性 , method可以有多个, 分布给不同的方法设置事务属性,
            name: 方法名称, 1)完成的方法名称, 不带有包和类.
                            2)方法可以使用通配符, * 表示任意字符
            propagation: 传播行为, 枚举值
            isolation: 隔离级别
            rollback-for : 你指定的异常类名,全限定类名. 发生异常一定回滚.
-->
            <tx:method name="buy" propagation="REQUIRED" isolation="DEFAULT"
                       rollback-for="java.lang.NullPointerException,com.nguyenxb.excep.NotEnoughException"/>
<!--            使用通配符,指定很多的方法-->
<!--            如设置添加方法, 若所有的添加方法命名都是 add开头的-->
            <tx:method name="add*" propagation="REQUIRES_NEW"/>
<!--            如设置修改方法, 若所有的修改方法命名都是 modify开头的-->
            <tx:method name="modify*" />
<!--            如设置删除方法, 若所有的删除方法命名都是 remove开头的-->
            <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: 切入点表达式,指定哪些类要使用事务.

            如要求几个包下有事务:
                    com.sadads.service
                    com.crm.service
                    com.nguyenxb.service
-->
<!--        <aop:pointcut id="servicePt" expression="execution(* *..service..*.*(..))"/>-->
            <aop:pointcut id="servicePt" expression="execution(* *..serive..*(..))"/>
<!--配置增强器 : 管理advice和pointcut
        advice-ref : 通知, 上面tx: advice是那里的配置
        pointcut-ref : 切入点表达式.

-->
        <aop:advisor advice-ref="myAdvice" pointcut-ref="servicePt"/>

    </aop:config>

例子:

要导入的依赖:

    <!--    单元测试-->
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.11</version>
      <scope>test</scope>
    </dependency>
    <!--    spring核心ioc-->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>5.2.5.RELEASE</version>
    </dependency>
    <!--    spring事务要用到的-->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-tx</artifactId>
      <version>5.2.5.RELEASE</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-jdbc</artifactId>
      <version>5.2.5.RELEASE</version>
    </dependency>
    <!--    mybatis依赖-->
    <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis</artifactId>
      <version>3.5.1</version>
    </dependency>
    <!--    mybatis和spring集成的依�?-->
    <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis-spring</artifactId>
      <version>1.3.1</version>
    </dependency>
    <!--    mysql驱动-->
    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>5.1.9</version>
    </dependency>
    <!--    阿里公司的数据库连接�?-->
    <dependency>
      <groupId>com.alibaba</groupId>
      <artifactId>druid</artifactId>
      <version>1.1.12</version>
    </dependency>
<!--    aspectj-->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-aspects</artifactId>
      <version>5.2.5.RELEASE</version>
    </dependency>

业务类:

package com.nguyenxb.service.impl;

import com.nguyenxb.dao.GoodsDao;
import com.nguyenxb.dao.SaleDao;
import com.nguyenxb.entity.Goods;
import com.nguyenxb.entity.Sale;
import com.nguyenxb.excep.NotEnoughException;
import com.nguyenxb.service.BuyGoodsService;


public class BuyGoodsServiceImpl implements BuyGoodsService {
    private SaleDao saleDao;
    private GoodsDao goodsDao;
    @Override
    public void buy(Integer goodsId, Integer nums) {
        System.out.println("======buy 方法的开始===");
        // 记录销售信息, 向sale表添加记录
        Sale sale = new Sale();
        sale.setGid(goodsId);
        sale.setNums(nums);
        saleDao.insertSale(sale);

        // 更新库存
        Goods goods = goodsDao.selectGoods(goodsId);
        if (goods==null){
            // 商品不存在
            throw new NullPointerException("编号是:"+goodsId+", 商品不存在");
        }else if ( goods.getAmount() < nums){
            throw new NotEnoughException("编号是:"+goodsId+",商品库存不足");
        }

        // 修改库存
        Goods buyGoods = new Goods();
        buyGoods.setId(goodsId);
        buyGoods.setAmount(nums);
        goodsDao.updateGoods(buyGoods);
        System.out.println("=========buy方法完成===========");
    }

    public void setSaleDao(SaleDao saleDao) {
        this.saleDao = saleDao;
    }

    public void setGoodsDao(GoodsDao goodsDao) {
        this.goodsDao = goodsDao;
    }
}

spring配置文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx"
       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/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
<!--    把数据库的配置信息,写在一个独立的文件,编译修改数据库的配置内容
        spring知道jdbc.properties文件的位置
-->
    <context:property-placeholder location="jdbc.properties"/>
<!--    声明数据源DataSource,作用是连接数据库的
        使用属性配置文件中的数据
-->
    <bean id="myDataSources" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
        <property name="maxActive" value="${jdbc.maxActive}"/>
    </bean>

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

<!--    创建dao对象,使用sqlSession的getMapper(Student.class)
        MapperScannerConfigurer: 在北部调用getMapper()歙观察每个dao接口的代理对象
-->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<!--        指定SqlSessionFactory对象的id-->
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
<!--        指定包名,包名是dao接口所在的包名.
        mapperScannerConfigurer会扫描这个包中的所有接口,把每个接口都执行异常getMapper()方法
            得到每个接口的dao对象,创建好的dao对象放入到spring的容器中.
            dao对象的默认名称是接口名的首字母小写
-->
        <property name="basePackage" value="com.nguyenxb.dao"/>
    </bean>

    <bean id="buyGoodsService" class="com.nguyenxb.service.impl.BuyGoodsServiceImpl">
        <property name="saleDao" ref="saleDao"/>
        <property name="goodsDao" ref="goodsDao"/>
    </bean>

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

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

<!--    配置aop-->
    <aop:config>
<!--        配置切入点表达式: 指定哪些包中的类,需要使用事务
            id : 切入点表达式的名称, 唯一值
            Expression: 切入点表达式,指定哪些类要使用事务.

            如要求几个包下有事务:
                    com.sadads.service
                    com.crm.service
                    com.nguyenxb.service
-->
<!--        <aop:pointcut id="servicePt" expression="execution(* *..service..*.*(..))"/>-->
            <aop:pointcut id="servicePt" expression="execution(* *..serive..*(..))"/>
<!--配置增强器 : 管理advice和pointcut
        advice-ref : 通知, 上面tx: advice是那里的配置
        pointcut-ref : 切入点表达式.

-->
        <aop:advisor advice-ref="myAdvice" pointcut-ref="servicePt"/>

    </aop:config>

</beans>

第六章 web项目中怎么使用容器对象

在web项目中使用spring完成学生注册的功能.

​ 实现步骤:

  • 创建maven,web项目

  • 加入依赖

    <!--    单元测试-->
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.11</version>
      <scope>test</scope>
    </dependency>
    <!--    spring核心ioc-->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>5.2.5.RELEASE</version>
    </dependency>
    <!--    spring事务要用到的 -->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-tx</artifactId>
      <version>5.2.5.RELEASE</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-jdbc</artifactId>
      <version>5.2.5.RELEASE</version>
    </dependency>
    <!--    mybatis依赖 -->
    <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis</artifactId>
      <version>3.5.1</version>
    </dependency>
    <!--    mybatis和spring集成的依赖 -->
    <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis-spring</artifactId>
      <version>1.3.1</version>
    </dependency>
    <!--    mysql驱动 -->
    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>5.1.9</version>
    </dependency>
    <!--    阿里公司的数据库连接池 -->
    <dependency>
      <groupId>com.alibaba</groupId>
      <artifactId>druid</artifactId>
      <version>1.1.12</version>
    </dependency>
    <!-- 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>
    
  • 创建实体类

  • 创建jsp发起请求,有参数id,name,email,age.

  • 创建servlet, 接受请求参数, 调用service,调用dao,完成注册.

  • 创建结果页面,作为显示结果页面.

需求:

web项目中容器对象只需要创建异常, 把容器对象放入全局作用域ServletContext中.

怎么实现?

使用监听器,当全局作用域对象被创建时 ,创建容器,存入servletContext.

监听器的作用:

​ (1) 创建容器对象, 执行ApplicationContext ac = new ClassPathXmlApplicationContext(“applicationContext.xml”);

​ (2) 把容器对象放入到servletContext, servletContext.setAttribute(key,ac);

监听器可以自己创建,也可以使用框架写好的ContextLoaderListener.

要使用框架写好的监听器, 需要加入依赖

<!--    为了使用监听器对象,需要加入依赖 -->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-web</artifactId>
      <version>5.2.5.RELEASE</version>
    </dependency>

然后在web.xml中注册监听器

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

        可以修改默认的文件位置, 使用 context-param 重新指定配置文件的路径
-->
    <context-param>
<!--    contextConfigLocation: 表示配置文件的路径-->
        <param-name>contextConfigLocation</param-name>
<!--        自定义配置文件的路径, 注意: 此时我的spring配置文件放在自定义的resources目录下-->
        <param-value>classpath:applicationContext.xml</param-value>
    </context-param>
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

该监听器的基本内部实现

 public interface WebApplicationContext extends ApplicationContext {
     // 获取spring容器的名称
    String ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE = WebApplicationContext.class.getName() + ".ROOT";
 }

public class ContextLoader {
    // 定义web容器对象
    private WebApplicationContext context;
	// 将web容器对象存入 servletContext(全局作用域)中.
	public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
    servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
	}
}

三种获取spring容器对象的方法

public class RegisterServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        req.setCharacterEncoding("utf-8");
        resp.setCharacterEncoding("utf-8");
        resp.setContentType("text/html;utf-8");
        
        String stuID = req.getParameter("id");
        String stuName = req.getParameter("name");
        String stuEmail = req.getParameter("email");
        String stuAge = req.getParameter("age");

//        // 方式一 : 创建spring的容器对象,每请求一次就创建一次容器对象,效率很低
        /*String config = "applicationContext.xml";
        ApplicationContext ac = new ClassPathXmlApplicationContext(config);
        System.out.println("容器的对象信息:" + ac);*/

        // 方式二 自己写代码获取容器对象 : 
       /* WebApplicationContext ac = null;
        // 获取servletContext中的容器对象, 它已经是创建好的,可以直接使用,
        String key = WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE;
        Object attribute = getServletContext().getAttribute(key);
        // 当 attribute 不为空的时候, 就说明已经存在容器对象
        if (attribute != null){
            ac = (WebApplicationContext)attribute;
        }*/

        // 方式三 : 通过框架中的方法获取容器对象.其实现原理类似于方式二
        ServletContext sc = getServletContext();
        WebApplicationContext ac = WebApplicationContextUtils.getRequiredWebApplicationContext(sc);


        //获取service
        StudentService studentService = (StudentService) ac.getBean("studentService");
        Student student = new Student();
        student.setId(Integer.parseInt(stuID));
        student.setName(stuName);
        student.setEmail(stuEmail);
        student.setAge(Integer.parseInt(stuAge));

        studentService.addStudent(student);
        // 给一个注册成功页面
        req.getRequestDispatcher("/result.jsp").forward(req,resp);

    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doGet(req, resp);
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值