框架 - Spring框架

框架 - Spring框架

第1章:Spring 概述

1.1 概述

Spring: 出现在2002年左右,兴起于2003年的一个轻量级的 Java 开发框架,它是为了解决企业应用开发的复杂性而创建的。
	可以减轻对项目模块之间的管理、类和类之间的管理, 帮助开发人员创建对象、管理对象之间的关系。
	2017出现新的流行框架Spring Boot,核心思想与Spring相同。
	
Spring 的核心是控制反转(IoC)和面向切面编程(AOP)。能实现模块之间、类之间的解耦合。

	Spring 根据代码的功能特点,使用 Ioc 降低业务对象之间耦合度。IoC 使得主业务在相互调用过程中,
	不用再自己维护关系了,即不用再自己创建要使用的对象了。而是由 Spring容器统一管理,自动“注入”, 注入即赋值。 
	而 AOP 使得系统级服务得到了最大复用,且不用再由程序员手工将系统级服务“混杂”到主业务逻辑中了,
	而是由 Spring 容器统一完成“织入”。

Spring 是可以在 Java SE/EE 中使用的轻量级开源框架。

Maven是管理项目的外部依赖,而Spring是管理项目的内部依赖。
依赖:classA中使用classB的属性或者方法, 叫做classA依赖classB

Spring的资源和文档可从官网获取 https://spring.io/

1.2 优点

(1)轻量:
	Spring的所需要的jar包都非常小,一般1M以下,几百kb。核心功能所需要的jar包总共3M左右。
	Spring框架运行占有资源少,运行效率高,不依赖其他jar。

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

(3)AOP 编程的支持
	通过 Spring 提供的 AOP 功能,方便进行面向切面的编程,许多不容易用传统 OOP 实现的功能可以通过 AOP 轻松应付。
	在 Spring 中,开发人员可以从繁杂的事务管理代码中解脱出来,通过声明式方式灵活地进行事务的管理,提高开发效率和质量。

(4)方便集成各种优秀框架
	Spring 不排斥各种优秀的开源框架,相反 Spring 可以降低各种框架的使用难度,
	Spring 提供了对各种优秀框架(如 Struts、Hibernate、MyBatis)等的直接支持。简化框架的使用。
	Spring 像插线板一样,其他框架是插头,可以容易的组合到一起。
	需要使用哪个框架,就把这个插头放入插线板。不需要可以轻易的移除。

1.3 Spring 体系结构

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

第2章:IoC 控制反转

2.1 概述

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

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

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

正转:由开发人员在代码中,使用new 构造方法创建对象, 开发人员主动管理对象。
	public static void main(String args[]){
	       Student student = new Student(); // 在代码中,创建对象 ————正转。
	}

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

使用 IoC 目的 :减少对代码的改动, 也能实现不同的功能。 实现解耦合。 

java中创建对象的方式:
	1. 构造方法 , new Student()
	2. 反射
	3. 序列化
	4. 克隆
	5. 动态代理
	6. IoC :容器创建对象
	
IoC 的体现: servlet	

	1)创建类继承HttpServelt 

	2)在web.xml 注册servlet,使用	<servlet-name> myservlet </servlet-name>
					<servelt-class>com.yuming.controller.MyServlet1</servlet-class>
	 
	3)没有创建 Servlet对象。 没有 MyServlet myservlet = new MyServlet()
	 
	4)Servlet 是Tomcat服务器它为你创建的。 Tomcat也称为容器
	   Tomcat作为容器:里面存放的有Servlet对象、Listener、Filter对象

IoC的技术实现: 
	DI(Dependency Injection): 依赖注入 。
		只需要在程序中提供要使用的对象名称就可以, 
		至于对象如何在容器中创建、赋值、查找,都由容器内部实现。

	Spring  框架使用依赖注入(DI)技术实现 IoC。 spring底层创建对象,使用的是反射机制。

2.2 Spring 的第一个程序

项目【ch01-spring-first】:使用 IOC, 由spring创建对象

实现步骤:
	1)创建Maven项目
	2)pom文件加入spring的依赖(5.2.5版本)
	3)创建接口和实现类
		和没有使用框架一样, 就是普通的类
	4)创建Spring的配置文件
		声明类的信息,这些类由spring创建和管理
	5)测试spring创建的对象

1、创建Maven项目		

	新建一个maven版普通java项目,选择模板:maven-archetype-quickstart
	删除src下默认创建的App和TestApp类文件;
	创建缺少的resources目录并标识。右键resources ———— Mark Directory as —————— Resources Root
	pom文件中:删掉无用的一些标签:name和url标签、build标签;
	pom文件中:将jdk版本1.7要修改为1.8;

2、pom文件加入spring的依赖(5.2.5版本)

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

3、创建接口和实现类

	public interface SomeService {
	      void doSome();
	}

	public class SomeServiceImpl implements SomeService{
	    @Override
	    public void doSome() {
		System.out.println("====业务方法doSome()被调用了===");
	    }
	}

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

	在 src/main/resources/目录,右键 - new - XML Configuration File - Spring Config
	创建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"
	       xsi:schemaLocation="http://www.springframework.org/schema/beans
	       http://www.springframework.org/schema/beans/spring-beans.xsd">

	    <bean id="someService" class="com.yuming.service.SomeServiceImpl"/>

	</beans>
	-----------------------------------------------------------------------

	<bean />:用于定义一个实例对象。一个实例对应一个 bean 元素。
	id: 该属性是 Bean 实例的唯一标识,程序通过 id 属性访问 Bean,
	     Bean 与 Bean 间的依赖关系也是通过 id 属性关联的。
	class:指定该 Bean 所属的类,注意这里只能是类,不能是接口。

5、测试spring创建的对象
	-----------------------------------------------------------------------
		//自己手动在程序中创建对象的方式
	@Test      
	public void test01() {
	    SomeService service = new SomeServiceImpl();
	    service.doSome();  
	}
	-----------------------------------------------------------------------
   /** 使用Spring创建对象:
 	* spring默认创建对象的时间:在创建spring的容器时,会创建配置文件中的所有的对象。
	* spring创建对象:默认调用的是无参数构造方法
   */			
	@Test  
	public void test02() {
	    //1.指定spring配置文件的位置和名称
	    String config = "applicationContext.xml";

	    //2.创建spring容器对象
	    /*  ApplicationContext就是表示Spring容器
	        ClassPathXmlApplicationContext:表示从类路径(target/classes)中加载spring的配置文件
	        创建Spring容器,会读取配置文件中的所有bean标签,并创建对应的对象(无参构造方法)。 */
	    ApplicationContext ac = new ClassPathXmlApplicationContext(config);

	    //3、从容器中获取某个对象。通过配置文件中的<bean>的id值
	    SomeService service = (SomeService)ac.getBean("someService");

	    //4、调用对象的方法
	    service.doSome(); 
	}
	-----------------------------------------------------------------------

【拓展】可以获取spring容器中 java 对象的信息

	@Test
	public void test03(){
		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);
		}
	}

【拓展】可以使用 spring 创建 非自定义类对象
	spring 配置文件加入 java.util.Date 定义:
		<bean id="myDate" class="java.util.Date" />

	MyTest 测试类中:
		调用 getBean(“myDate”); 获取日期类对象。

2.3 基于XML的DI

本节项目【ch02-xml-di】

前面讲到:	
	Spring 框架使用 DI 技术实现 IoC。  DI(Dependency Injection): 依赖注入,表示创建对象、给属性赋值。
	只需要在程序中提供要使用的对象名称就可以,至于对象如何在容器中创建、赋值、查找,都由容器内部实现。

DI依赖注入的实现有两种:
	1)基于 XML 的DI实现:在spring的配置文件中,使用标签和属性给对象的属性进行赋值。
	2)基于 注解 的DI实现:使用spring中的注解,完成属性赋值

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

2.3.1 set注入

  • spring调用类的 set方法,在set方法可以实现属性的赋值。使用较多。
  • (1)简单类型的set注入
    简单类型: spring中规定 java的基本数据类型String都是 简单类型。
    注入:就是赋值的意思
java类:
	public class Student {
	    private String name;  //简单类型
	    private int age;      //简单类型

	    //set方法、toString方法....
	}

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"
	       xsi:schemaLocation="http://www.springframework.org/schema/beans
	       http://www.springframework.org/schema/beans/spring-beans.xsd">

	    <bean id="myStudent" class="com.yuming.bao1.Student">
			<property name="name" value="张三"/>   <!--setName("张三")-->
			<property name="age" value="20"/>      <!--setAge(20)-->
	    </bean>

	</beans>

测试类中的测试方法:
	@Test
	public void test01() {
	    String config = "bao1/applicationContext.xml";
	    ApplicationContext ac = new ClassPathXmlApplicationContext(config);

	    Student student = (Student) ac.getBean("myStudent");
	    System.out.println(student);     
	    
	    //Student{name='张三', age=20}
	}
  • (2)引用类型的set注入
    当指定 bean 的某属性值为另一 bean 的实例时,通过 ref 指定它们间的引用关系。ref的值必须为某 bean 的 id 值。
java类:
	public class School {
	    private  String name;
	    private  String address;

	    //set方法、toString方法...
	}

	public class Student {
	    private String name;   //简单类型
	    private int age;       //简单类型
	    private School school;       //引用类型

	    //set方法、toString方法...
	}

配置文件(部分):
	......
	<bean id="mySchool" class="com.yuming.bao2.School">    <!--声明school对象、给其属性赋值(set注入)-->
	    <property name="name" value="市一中"/>
	    <property name="address" value="上海"/>
	</bean>

	<!--写法1:ref作为属性-->
	<bean id="myStudent" class="com.yuming.bao2.Student">
	    <property name="name" value="张三111"/>
	    <property name="age" value="20"/>
	    <!--引用类型:-->
	    <property name="school" ref="mySchool"/>   <!--调用setSchool(mySchool)-->
	</bean>

	<!--写法2:ref作为子标签-->
	<bean id="myStudent2" class="com.yuming.bao2.Student">
	    <property name="name" value="张三222"/>
	    <property name="age" value="20"/>
	    <!--引用类型:-->
	    <property name="school">
	        <ref bean="mySchool"></ref>   <!--调用setSchool(mySchool)-->
	    </property>
	</bean>
	......

测试方法:
	@Test  
	public void test02() {
	    String config = "bao2/applicationContext.xml";
	    ApplicationContext ac = new ClassPathXmlApplicationContext(config);

	    //从容器中获取 已经创建好并且初始化属性值了的对象
	    Student student = (Student) ac.getBean("myStudent");
	    System.out.println(student);
	    
	    //Student{name='张三111', age=20, school=School{name='市一中', address='上海'}}
	}

2.3.2 构造注入

  • spring调用类的 有参数构造方法,创建对象的同时完成属性的赋值。
    • < constructor-arg />标签中用于指定参数的属性有:
      ➢ name:指定参数名称。
      ➢ index:指明该参数对应着构造器的第几个参数,从 0 开始。不过,该属性不要也行,但要注意,
          若参数类型相同,或之间有包含关系,则需要保证赋值顺序要与构造器中的参数顺序一致。
java类:
	public class School {
	    private  String name;
	    private  String address;

	    //set方法、toString方法...
	}

	public class Student {
	    private String name;   //简单类型
	    private int age;       //简单类型
	    private School school;       //引用类型,指向上面那个类

	    //set方法、toString方法、无参构造、【有参构造方法】....
	}

配置文件(部分):
	......

	<!--声明school对象-->
	<bean id="mySchool" class="com.yuming.bao3.School">
	    <property name="name" value="市一中"/>
	    <property name="address" value="上海"/>
	</bean>

	<!--声明Student对象-->   <!--写法一:使用name属性-->
	<bean id="myStudent" class="com.yuming.bao3.Student">
	    <constructor-arg name="name" value="张三"/>
	    <constructor-arg name="age"  value="20"/>
	    <constructor-arg name="school" ref="mySchool"/>
	</bean>

	 <!--声明Student对象-->   <!--写法二:使用index属性-->
	<bean id="myStudent2" class="com.yuming.bao3.Student">
	    <constructor-arg index="0" value="张三222"/>
	    <constructor-arg index="1" value="22"/>
	    <constructor-arg index="2" ref="mySchool"/>
	</bean>

	<!--还可以省略index-->
	<bean id="myStudent3" class="com.yuming.bao3.Student">
	    <constructor-arg  value="张三333"/>
	    <constructor-arg  value="33"/>
	    <constructor-arg  ref="mySchool"/>
	</bean>

	......

测试方法:
	@Test
	public void test01() {
	    String config = "bao3/applicationContext.xml";
	    ApplicationContext ac = new ClassPathXmlApplicationContext(config);

	    Student student = (Student) ac.getBean("myStudent");
	    System.out.println(student);

	    //Student{name='张三', age=20, school=School{name='市一中', address='上海'}}
	}

2.3.3 引用类型属性的自动注入

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

    • byName:按名称自动注入
    • byType:按类型自动注入
  • (1)byName 方式自动注入
    当配置文件中 被调用者bean的id值与代码中 调用者bean类的属性名相同时,且数据类型是一致的
    可使用byName 方式,让容器自动将被调用者 bean 注入给调用者 bean,就能够赋值给引用类型。
    容器是通过调用者的 bean类的属性名 与配置文件的被调用者 bean的id 进行比较而实现自动注入的。

<!--byName自动注入,添加属性autowire="byName"-->
<bean id="myStudent" class="com.yuming.bao4.Student" autowire="byName">
    <property name="name" value="张三111"/>
    <property name="age" value="20"/>
	<!--引用类型:-->
	<!--<property name="school" ref="mySchool"/>-->
</bean>

<!--声明School对象
    这里的bean的id的值和上面的引用类型属性名(school)一样才行  -->
<bean id="school" class="com.yuming.bao4.School">
    <property name="name" value="北京大学"/>
    <property name="address" value="北京"/>
</bean>
  • (2)byType 方式自动注入
    使用 byType 方式自动注入,要求:
    配置文件中 被调用者 bean的 class属性指定的类,要与代码中 调用者 bean类的某引用类型属性是类型同源的
    同源,即要么相同,要么有 is-a 关系(子类,或是实现类)
    但这样的同源的被调用 bean 只能有一个。多于一个,容器就不知该匹配哪一个了
	<!--byType-->
	<bean id="myStudent" class="com.yuming.bao5.Student" autowire="byType">
		<property name="name" value="张三222"/>
		<property name="age" value="22"/>
		<!--引用类型:-->
		<!--<property name="school" ref="mySchool"/>-->
	</bean>

	<!--第一种:声明School对象-->
	<!--
	 <bean id="mySchool" class="com.yuming.bao5.School">
		<property name="name" value="北京大学22"/>
		<property name="address" value="北京22"/>
	 </bean>
	-->

	<!--第二种:声明School的子类对象-->
	<bean id="xiaoxue" class="com.yuming.bao5.PrimarySchool">
		<property name="name" value="希望小学"/>
		<property name="address" value="深圳"/>
	</bean>
注意:在xml配置文件使用byType,符合条件的对象只能有一个,多余一个是报错的。

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

  • 在实际应用里,随着应用规模的增加,系统中 Bean数量也大量增加,导致配置文件变得非常庞大、臃肿。
    为了避免这种情况的产生,提高配置文件的可读性与可维护性,可以将Spring 配置文件分解成多个配置文件。

  • 包含关系的配置文件:
    多个配置文件中有一个总文件,总配置文件将各其它子文件通过引入。
    在 Java代码中只需要使用总配置文件对容器进行初始化即可。

  • 多个配置文件的优势:
    1)每个文件的大小比一个文件要小很多。效率高
    2)避免多人竞争带来的冲突。

  • 多文件的分配方式:
    1)按模块,一个模块一个配置文件(考试管理模块一个配置文件,成绩管理模块一个配置文件。。。)
    2)按类的功能(数据库相关的配置放一个配置文件, 做事务的功能放一个配置文件, 做service功能的放一个配置文件等)

  • 举例:
    在src/main/resources/bao6下面,创建3个配置文件,其中1个是主配置文件,引入另外2个配置文件

【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">

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

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

	    <!--
	       在包含关系的配置文件中,可以使用通配符(*:表示任意字符)
	       注意:	主的配置文件名称不能包含在通配符的范围内
			(比如这里,主配置文件命名不能叫做spring-total.xml)
	       		否则将出现循环递归包含
	    -->
	    <import resource="classpath:bao6/spring-*.xml" />

	</beans>

----------------------------------------------------------------------------------------------------------------
【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">

	    <!-- Student模块的配置文件: Student 模块所有bean的声明   -->

	    <!--byType-->
	    <bean id="myStudent" class="com.yuming.bao6.Student" autowire="byType">
			<property name="name" value="赵六"/>
			<property name="age" value="26"/>
			<!--引用类型:-->
			<!--<property name="school" ref="mySchool"/>-->
	    </bean>
	    
	</beans>

---------------------------------------------------------------------------------
【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模块的配置文件:School模块所有bean的声明 -->

	    <!--声明School对象-->
	    <bean id="mySchool" class="com.yuming.bao6.School">
			<property name="name" value="6666大学"/>
			<property name="address" value="山东"/>
	    </bean>
	    
	</beans>

2.4 基于注解的DI

本节项目【ch04-anno-di】

使用spring中的注解,完成java对象创建、属性赋值。

对于 DI 使用注解,将不再需要在 Spring 配置文件中声明 bean 实例。
Spring 中使用注解,需要在原有 Spring 运行环境基础上再做一些改变。
需要在 Spring 配置文件中配置【组件扫描器】,用于在指定的基本包中扫描注解。

主要的注解:
	 1)@Component、@Respotory、@Service、@Controller
	 2)@Value
	 3)@Autowired、@Qualifier
	 4)@Resource

2.4.0 使用注解的步骤

  • 1)创建Maven项目,在pom文件中加入spring-context依赖
    在你加入spring-context的同时, 它会自动间接加入spring-aop的依赖。
    使用注解必须使用spring-aop依赖
<dependency> 
  	<groupId>org.springframework</groupId>
  	<artifactId>spring-context</artifactId>
  	<version>5.2.5.RELEASE</version>
</dependency>
  • 2)在类中加入spring的注解(有多个不同功能的注解)
@Component(value = "myStudent")
public class Student {
    private String name;  
    private int age;      

    //set方法、toString方法...
}
  • 3)在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">

    <!--声明组件扫描器(component-scan),用于在指定的包中扫描注解。

        component-scan工作原理:
        spring会扫描遍历base-package指定的包,
        找到包和子包中的所有类中的注解,按照注解的功能创建对象,或给属性赋值。

        加入了component-scan标签后,配置文件的变化:
        1.最上面加入一个新的约束文件spring-context.xsd
        2.这个新的约束文件的命名空间的名称:context              -->

    <!--<context:component-scan base-package="com.yuming.bao1"/>-->

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

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

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

</beans>
  • 4)测试 注解创建的对象
@Test
public void test01(){
    String config = "applicationContext.xml";
    ApplicationContext ac =new ClassPathXmlApplicationContext(config);

    Student student = (Student) ac.getBean("myStudent");
    System.out.println(student);

    //输出了 Student{name='null', age=0}------>说明确实是通过注解创建了Student对象
}

2.4.1 定义Bean 的注解 @Component

	@Component: 创建对象的, 等同于<bean>的功能
		属性:value ,就是对象的名称,也就是该bean的id值,
			value的值是唯一的,创建的对象在整个spring容器中就一个
		位置:	在类的上面, 表示创建此类的对象。

			@Component(value = "myStudent")
			public class Student {
			    private String name;  
			    private int age;      

			    //set方法、toString方法...
			}
	有3种写法:
		第一种: @Component(value = "myStudent")	标准写法
		第二种: @Component("myStudent")		等同于上面那个				
		第三种: @Component				等同于 @Component(value = "student")	
		
		javaSE中学到过:如果一个注解只有一个属性的话,并且属性的名字是value,那么在使用的时候,该属性名可以省略。
		如果 @Component 不指定 value属性,那么默认创建的对象名是:该类名的首字母小写。例如Student类的对象就是student

	@Component(value = "myStudent") 等同于 <bean id="myStudent" class="com.yuming.bao1.Student" />

	另外,Spring 还提供了 3 个创建对象的注解:

		1) @Repository(用在持久层类上面):	放在dao的实现类上面,
							表示创建dao对象,dao对象是能访问数据库的。

		2) @Service   (用在业务层类上面):放在service的实现类上面,
						   创建service对象,service对象是做业务处理,可以有事务等功能的。

		3) @Controller(用在控制器上面):放在Controller 实现类的上面,
						 创建控制器对象的,能够接受用户提交的参数,显示请求的处理结果。
	
		以上三个注解的使用语法和 @Component一样的。 都能创建对象,但是这三个注解还有额外的功能。
		这三个注解是给项目的对象分层的,是对 @Component 注解的细化,标注不同层的对象。
		即持久层对象,业务层对象,控制层对象。

2.4.2 简单类型属性注入 @Value

@Value: 简单类型的属性赋值(赋初始值)
	属性: value 是String类型的,表示简单类型的属性值
	位置:
		第一种:在属性定义的上面,无需set方法,推荐使用。
		第二种:在set方法的上面	
--------------------------------------------------------------------------------
@Component("myStudent2")
public class Student {

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

    private int age;


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

    // @Value(value = "25")
    //同样,因为该注解只有一个属性,并且属性的名字是value,那么该属性是可以省略的
    @Value("25")
    public void setAge(int age) {
		this.age = age;
    }

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

2.4.3 byType 自动注入 @Autowired

@Autowired:  spring框架提供的注解,实现引用类型的赋值。
	     spring中通过注解给引用类型赋值,使用的是自动注入原理 ,支持byName, byType

@Autowired 默认使用的是byType 按类型自动注入

位置:	1)在属性定义的上面,无需set方法, 推荐使用
	2)在set方法的上面
	
--------------------------------------------------------
Student类:
	@Component("myStudent")
	public class Student {

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

	    @Value("25")
	    private int age;
	    
	    //byType 按类型自动注入
	    @Autowired
	    private School school;   //引用类型

	    //set方法、toString方法...
	}

School类:
	@Component("mySchool")
	public class School {
	    @Value("武汉大学")
	    private  String name;

	    @Value("湖北武汉")
	    private  String address;

	    //set方法、toString方法...
	}

【002-byType自动注入 @Autowired.png】

2.4.4 byName 自动注入 @Autowired 与 @Qualifier

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

	-------------------------------------------------------------------------
	//byName 按名称自动注入
	@Autowired
	@Qualifier(value = "mySchool")  //与School类的 @Component注解value值相同
	private School school;   //引用类型
	-------------------------------------------------------------------------

@Autowired 还有一个required属性,默认值为 true,表示当匹配失败后,会终止程序运行。
若将其值设置为 false,则当匹配失败时,会被忽略,未匹配的属性值为 null。
	------------------------------------------
	//byName自动注入
	@Autowired(required = false)
	@Qualifier(value = "mySchool")
	------------------------------------------
注:引用类型的 @Autowired 的required属性值推荐使用true,有利于我们把程序中的问题尽早解决

在这里插入图片描述

2.4.5 JDK 注解 @Resource 自动注入

@Resource: 来自jdk中的注解,spring框架提供了对这个注解的功能支持,可以使用它给引用类型赋值
	   使用的也是自动注入原理,支持byName, byType .默认是byName

位置: 1.在属性定义的上面,无需set方法,推荐使用。
       2.在set方法的上面

默认是byName: 先使用byName自动注入,如果byName赋值失败,再使用byType

如果想只使用byName方式,需要增加一个name属性 ,name的值是bean的id(名称)

-----------------------------------------------------------------------------------------
	// @Resource			//先byName,如果失败则byType
	@Resource(name = "mySchool")	//只使用byName, 与School类的 @Component注解value值相同
	private School school;   //引用类型

2.5 注解与 XML 的对比

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

    注解缺点:以硬编码的方式写入到 Java 代码中,修改是需要重新编译代码的。

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

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

  • 【总结】注解 和 xml 比较用哪个?
    不易发生变动 配置——————>>优先注解
    易变更、第三方、环境相关 配置——————>>优先配置文件

2.6 拓展:使用.properties属性配置文件

使用属性配置文件中的属性值,来进行注解:

第一步:在src\main\resources下面创建属性配置文件:test.properties
	myname=zhaoliu
	myage=26

第二步:在Spring的配置文件中 加载属性配置文件:
	<context:property-placeholder location="test.properties"/>
	
第三步:在类中使用属性配置文件中的内容:${属性配置文件中的属性名},来获取对应的属性值

public class Student {

    // @Value("赵六")
    @Value("${myname}")  //使用test.properties中myname的属性值:myname=zhaoliu
    private String name;

    // @Value("26")
    @Value("${myage}") //使用test.properties中myage的属性值:myage=26
    private int age;

    //set方法、toString方法...
}

第3章:AOP面向切面编程

3.1 动态代理

  • 回顾动态代理

    jdk动态代理:
       使用jdk中的Proxy,Method,InvocaitonHanderl创建代理对象。
       不用自己创建代理类的.java 源文件。
       jdk动态代理要求 —————— 目标类必须实现接口

    cglib动态代理:
       第三方的工具库,创建代理对象,原理是继承。
       通过继承目标类,而子类是增强过的,这个子类对象就是代理对象。
       使用CGLIB生成动态代理要求 —————— 目标类必须能够被继承,即不能是final 的

    动态代理的作用:
       1)在目标类源代码不改变的情况下,增加功能。
       2)减少代码的重复
       3)专注业务逻辑代码
       4)解耦合,让你的业务功能和日志、事务等非业务功能分离。

  • 【实例】使用动态代理,创建代理对象,实现功能增强(不使用 AOP 的开发方式 )

    动态代理:
        可以在程序的执行过程中,创建代理对象。通过代理对象执行方法,
        给目标类的方法增加额外的功能(功能增强),而不需要在目标类中进行修改
    
    jdk动态代理实现步骤:
    1)创建接口,定义目标类要完成的功能:doSome, doOther等业务方法
    
    2) 创建目标类 实现接口。SomeServiceImpl目标类,实现doSome, doOther等业务方法
    
    3) 创建InvocationHandler接口(调用处理器)的实现类,在invoke方法中完成
       代理类的功能:
    	1)调用目标方法
    	2)增强功能(比如输出时间、事务)
    
    4)使用jdk中 Proxy类的静态方法,创建代理对象。 并把返回值转为接口类型。
       再通过代理对象执行方法
    
    项目:【ch05-dynamic-proxy】
    
import java.util.Date;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class MyInvocationHandler  implements InvocationHandler {

    //目标对象
    private Object target; //SomeServiceImpl类

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

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //通过代理对象执行方法时,会调用执行这个invoke()

/*
        System.out.println("方法的执行时间:"+ new Date());//在目标方法之前,输出时间【增强功能】
       
        //调用目标类的方法。通过Method类实现
        Object res = method.invoke(target, args);//SomeServiceImpl 的 doSome()或者doOther()方法
        
        System.out.println("方法执行完毕后,提交事务"); //在目标方法执行之后,提交事务【增强功能】
*/
        //如果只想让SomeServiceImpl 的 doSome()方法执行时会增强功能,doOther()方法还是使用原来的呢?
        String methodName = method.getName();
        Object res = null;

        if("doSome".equals(methodName)){
            //在目标方法之前,输出时间【增强功能】
            System.out.println("方法的执行时间:"+ new Date());
            
            //调用目标类的方法。通过Method类实现
            res = method.invoke(target, args);//SomeServiceImpl 的 doSome()方法
            
            //在目标方法执行之后,提交事务【增强功能】
            System.out.println("方法执行完毕后,提交事务"); 
            
        } else {
            res = method.invoke(target, args);//SomeServiceImpl 的 doOther()方法
        }

        return res;
    }
}
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;

public class MyApp {
    public static void main(String[] args) {
        //调用doSome,doOther方法
/*
        SomeService someService = new SomeServiceImpl();

        someService.doSome();
        System.out.println("------------------------------");
        someService.doOther();
*/
        //0、 创建目标对象
        SomeService target = new SomeServiceImpl();

        //1、获取——目标对象的类加载器
        ClassLoader loader = target.getClass().getClassLoader();
        //2、获取——目标对象实现的接口
        Class<?>[] interfaces = target.getClass().getInterfaces();
        //3、获取——自己写的调用处理器实现类
        InvocationHandler handler = new MyInvocationHandler(target);

        //4、使用Proxy类的静态方法newProxyInstance(),创建代理对象。并把返回值转为接口类型。
        SomeService proxy = (SomeService) Proxy.newProxyInstance(loader, interfaces, handler);

        /*SomeService proxy = (SomeService) Proxy.newProxyInstance(
                target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),
                new MyInvocationHandler(target) );  //省略上面3步,一步到位*/

        //5、通过代理执行 功能增强后的方法
        proxy.doSome();
        System.out.println("------------------------------");
        proxy.doOther();
    }
}

/*
方法的执行时间:Sat Sep 26 17:16:30 CST 2020
业务方法dosome执行了
方法执行完毕后,提交事务
------------------------------
业务方法doOther执行了
*/

3.2 AOP概述

  • AOP(Aspect Orient Programming),面向切面编程。面向切面编程是从动态角度考虑程序运行过程。
    AOP 底层,就是采用动态代理模式实现的。采用了两种代理:JDK 的动态代理,与 CGLIB的动态代理。
    Aop就是动态代理的【规范化】, 把动态代理的实现步骤、方式都定义好了,让开发人员用统一的方式使用动态代理。

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

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

3.3 AOP相关术语

  • (1)切面(Aspect )
    切面泛指交叉业务逻辑。上例中的事务处理、日志处理就可以理解为切面。
    常用的切面是通知(Advice)。实际就是对主业务逻辑的一种增强。

  • (2)连接点(JoinPoint )
    连接点指可以被切面织入的具体方法。通常业务接口中的方法均为连接点。

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

  • (4)目标对象(Target )
    目标对象指将要被增强的对象。即包含主业务逻辑的类的对象。
    上例中的 SomeServiceImpl 的对象若被增强,则该类称为目标类,该类对象称为目标对象。
    当然,不被增强,也就无所谓目标不目标了。

  • (5)通知(Advice )
    通知表示切面的执行时间,Advice 也叫增强。上例中的 MyInvocationHandler 就可以理解为是一种通知。
    换个角度来说,通知定义了增强代码切入到目标代码的时间点,是目标方法执行之前执行,还是之后执行等。
    通知类型不同,切入时间不同。
    切入点定义切入的位置,通知定义切入的时间。

3.4 怎么理解面向切面编程 ?

(1)需要在分析项目功能时,找出切面。使用Aspect,切面干什么。
(2)合理的安排切面执行的时间。使用 Advice 表示时间,在目标方法之前,还是目标方法之后。
(3)合理的安排切面执行的位置。使用 Pointcut 表示切面执行的位置,在哪个类、哪个方法增加增强功能。

3.5 面向切面编程对有什么好处 ?

(1)减少重复;(2)专注业务;
注意:面向切面编程只是面向对象编程的一种补充。

使用 AOP 减少重复代码,专注业务实现
在这里插入图片描述

3.6 AOP 的实现

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

  • AOP的技术实现框架:

    • 1.spring
      spring主要在事务处理时使用AOP。
      spring的AOP比较笨重,项目开发中很少使用spring的AOP实现。

    • 2.aspectJ:
      一个开源、专门做AOP的框架。是Eclipse 的开源项目

      spring框架中集成了aspectj框架,通过spring就能使用aspectj的功能。

      aspectJ框架 实现AOP有 2 种方式:
      (1)使用xml配置文件,一般用于配置全局事务;
      (2)使用注解。一般在项目开发中使用这种方式。 aspectj有5个注解。

3.7 AspectJ 对 AOP 的实现

3.7.1 AspectJ 的通知类型

  • 通知(Advice),表示切面的执行时间,也叫增强。AspectJ 中常用的通知有五种类型:
    (1)前置通知
    (2)后置通知
    (3)环绕通知
    (4)异常通知
    (5)最终通知

  • 在 AspectJ框架中可以使用xml配置文件中的标签表示,也可以使用注解表示:
    (1) @Before
    (2) @AfterReturning
    (3) @Around
    (4) @AfterThrowing
    (5) @After

3.7.2 AspectJ 的切入点表达式

  • 切入点(Pointcut)表示切面执行的位置。是声明了一个或多个连接点的集合,通过切入点指定一组方法

  • 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)用在方法参数中,表示任意多个参数; 2)用在包名后,表示当前包及其子包路径
+1)用在类名后,表示当前类及其子类; 2)用在接口名后,表示当前接口及其实现类
  • 举例:
execution(public * *(..))
指定切入点为:任意公共方法。

execution(* set*(..))
指定切入点为:任何一个以“set”开始的方法。

execution(* com.xyz.service.*.*(..))
指定切入点为:定义在 service 包里的任意类的任意方法。

execution(* com.xyz.service..*.*(..))
指定切入点为:定义在 service 包或者子包里的任意类的任意方法。
注意:“..”出现在包名类名中时,后面必须跟“*”,表示包、子包下的所有类。

execution(* *..service.*.*(..))
指定所有包下的 serivce 子包下所有类(接口)中所有方法为切入点
例如:com.service.implA、 cn.abc.service.implB

3.8 AspectJ 基于注解的 AOP 实现

  AspectJ 对于 AOP 的实现有注解和xml配置文件2种方式,常用是注解方式。

  项目【ch06-aop-aspectj】: 用aspectj框架实现aop。
   目的是给已经存在的一些类和方法,增加额外的功能。 前提是不改变原来的类的代码。

3.8.1 基本步骤

	(1)新建maven项目(quickstart模板), 加入依赖
		junit单元测试依赖、spring依赖、aspectj依赖; 

		<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>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-aspects</artifactId>
			<version>5.2.5.RELEASE</version>
		</dependency>
		</dependencies>


	(2)创建业务接口和其实现类
		希望是给目标类中的方法增加功能,但不改变该类的代码。
		--------------------------------------------------------------------------------
		//接口
		public interface SomeService {
		    void doSome(String name , Integer age);
		}

		//目标类
		public class SomeServiceImpl implements SomeService {

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

	(3)创建切面类
		类中定义了若干普通方法,将作为不同的通知方法,用来增强功能。
		1)在类的上面加入 @Aspect
		2)在类中定义方法, 方法就是切面要执行的功能代码
		3)在方法的上面加入aspectj中的通知注解,例如 @Before,表示前置通知
		   注解中需要指定切入点表达式execution()
		---------------------------------------------------------------------------------------------------------
		@Aspect
		public class MyAspect {

		    @Before(value = "execution(public void com.yuming.service01.SomeServiceImpl.doSome(String ,Integer))")
		    public void myBefore(){
				System.out.println("前置通知, 切面功能:在目标方法之前输出执行时间:"+ new Date());
		    }
		}
		---------------------------------------------------------------------------------------------------------
	
	(4)创建spring的配置文件:声明目标对象与切面类对象、注册AspectJ的自动代理

		1)声明对象,把对象交给容器统一管理。声明对象可以使用注解或者xml配置文件<bean>
		2)注册 AspectJ 框架中的【自动代理生成器】:<aop:aspectj-autoproxy/>
		   自动代理生成器,会自动扫描到 @Aspect注解 定义的切面类,
		   切面类中的方法上面有通知类型和切入点,根据切入点可找到目标类中的目标方法,
		   而通知类型则可以确定具体切入的时间,然后生成“目标类+切面”的代理对象。

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

		    <bean id="someService" class="com.yuming.service01.SomeServiceImpl"/>
		    <bean id="myAspect"    class="com.yuming.service01.MyAspect"/>

		    <aop:aspectj-autoproxy/>

		</beans>
		-----------------------------------------------------------------------------------------------------------------------------------

	(5)创建测试类,从spring容器中使用目标对象的id 获取目标对象(实际上是经过aspectj生成的代理对象)
	     通过代理对象执行方法,实现aop的功能增强。
		-------------------------------------------------------------------------------------------
		@Test
		public void test01() {
		    String config = "applicationContext.xml";
		    ApplicationContext ac = new ClassPathXmlApplicationContext(config);

		    //从容器中获取目标对象(实际上是经过aspectj生成的代理对象)
		    SomeService proxy = (SomeService) ac.getBean("someService");

		    //System.out.println(proxy.getClass().getName());
		    //com.sun.proxy.$Proxy8: 目标类有接口,使用的是jdk的动态代理

		    // 通过代理对象执行方法,实现aop的功能增强。
		    proxy.doSome("lisi", 25);
		}
		/*程序输出结果:
		        前置通知, 切面功能:在目标方法之前输出执行时间:Tue Aug 25 21:06:54 CST 2020
		        ====目标方法doSome()执行了====
		*/
		-------------------------------------------------------------------------------------------

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

	在目标方法执行之前执行。被注解为前置通知的方法,可以指定一个 JoinPoint 类型参数。
	注:不光前置通知的方法,所有的通知方法均可包含一个 JoinPoint 类型参数。

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

	(2) 指定通知方法中的参数 : JoinPoint
		JoinPoint: 表示连接点的方法 ,是要加入切面功能的业务方法
		作用:	可以在通知方法中获取方法执行时的信息, 例如方法名称,方法的实参。
			如果你的切面功能中需要用到方法的信息,就加入JoinPoint.
		要求:这个JoinPoint参数的值是由框架赋予, 必须是在第一个位置的参数

	(3) 前置通知 ———— 方法的定义格式:
		1.公共方法 public
		2.方法没有返回值
		3.方法名称自定义
		4.方法可以有参数,也可以没有参数。
		  如果有参数,参数不是自定义的,有几个参数类型可以使用
		  
	举例:
		--------------------------------------------------------------------------------------------
		@Aspect
		public class MyAspect {

		    @Before(value = "execution(void *..SomeServiceImpl.doSome(String,Integer))")
		    public void myBefore(JoinPoint jp){
			
				//获取方法的完整定义
				System.out.println("方法的签名(定义):"+jp.getSignature());
				System.out.println("方法的名称:"+jp.getSignature().getName());
				System.out.println("方法的参数个数:"+jp.getArgs().length);

				//获取方法的实参
				Object args [] = jp.getArgs();
				for (Object obj: args) {
			    	System.out.println("实际参数:"+obj);
				}	

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

	调用doSome方法: proxy.doSome("lisi", 25),执行的结果:
		--------------------------------------------------------------------------------------------
		方法的签名(定义):void com.yuming.service01.SomeService.doSome(String,Integer)
		方法的名称:doSome
		方法的参数个数:2
		实际参数:lisi
		实际参数:25
		前置通知, 切面功能:在目标方法之前输出执行时间:Wed Aug 26 13:17:50 CST 2020
		====目标方法doSome()执行了====
		--------------------------------------------------------------------------------------------

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

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

	(1) @AfterReturning:后置通知注解
		属性:	1.value 切入点表达式
			2.returning 自定义的变量,表示目标方法的返回值的。
	                     自定义变量名必须和该通知方法的形参名一样!!!
		特点:
			1. 在目标方法之后执行的。
			2. 能够获取到目标方法的返回值,可以根据这个返回值做不同的处理功能
				Object res = doOther();
			3. 可以修改这个返回值
                               								   
	(2) 后置通知的执行                                                            
		Object res = doOther();                                               		 
     	    
		myAfterReturning(res){                                                
			修改res的内容,是否会影响 最后的方法调用结果?                   
		}                                                                     
		
		注意:参数传递的2种方式:                                                  
			1) 值传递, 并不会改变原来方法的返回结果                         
			2) 引用传递(地址传递),会改变原来方法的返回结果     

	(3) 后置通知 —— 方法的定义格式:
		1.公共方法 public
		2.方法没有返回值
		3.方法名称自定义
		4.方法有参数的,  推荐是Object ,参数名自定义

	举例:
		--------------------------------------------------------------------------------------------
		@Aspect
		public class MyAspect {

		    @AfterReturning(value = "execution(* *..SomeServiceImpl.doOther(..))",
				           returning="res")
		    public void myAfterReturning(Object res){
			
				System.out.println("后置通知:在目标方法之后执行的,获取的返回值是:"+res);
			
				// Object res:是目标方法执行后的返回值,可以根据返回值做切面的不同功能处理
				if(res.equals("abcd")){
			   		 //做一些功能
				} else{
			    	//做其它功能
				}

				//修改目标方法的返回值, 是否会影响 最后的方法调用结果?  ————》并不会
				if( res != null){
			    	String st = (String)res;
			    	st = "Hello Aspectj";
				}
		    }


		    @AfterReturning(value = "execution(* *..SomeServiceImpl.doOther2(..))",
			    returning = "res")
		    public void myAfterReturing2(Object res){

				System.out.println("后置通知:在目标方法之后执行的,获取的返回值是:"+res);

				//修改目标方法的返回值,属性值等,是否会影响最后的调用结果呢-------》会
				if (res != null){
			    	Student student= (Student)res;
			    	student.setName("lisi2222");
			    	student.setAge(2222);
				}
		    }
		}
		--------------------------------------------------------------------------------------------

	调用 doOther 和 doOther 2 方法后:
		--------------------------------------------------------------------------------------------
			====目标方法doOther()执行了====
			后置通知:在目标方法之后执行的,获取的返回值是:abcd
			最后的调用结果:abcd
			------------------------------------
			====目标方法doOther 2()执行了====
			后置通知:在目标方法之后执行的,获取的返回值是:Student{name='lisi', age=20}
			最后的调用结果:Student{name='LISI2222', age=2222}
		--------------------------------------------------------------------------------------------

3.8.4 @Around 环绕通知-增强方法有 ProceedingJoinPoint 参数

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

	(1) @Around: 环绕通知注解
		属性:	value 切入点表达式
		特点:
			1.它是功能最强的通知
			2.在目标方法的前和后都能增强功能。
			3.可以控制目标方法是否被调用执行
			4.可以修改原来的目标方法的执行结果、影响最后的调用结果

	(2) 环绕通知 —— 方法的定义格式:
		1.public
		2.【必须有一个返回值,推荐使用Object】
		3.方法名称自定义
		4.方法有参数,固定的参数 ProceedingJoinPoint
	
	(3) 环绕通知,等同于jdk动态代理的 InvocationHandler接口实现类 执行的功能

	(4) 参数:  ProceedingJoinPoint 就等同于 jdk动态代理的 Method
			作用:执行目标方法的

	(5) 返回值: 目标方法的执行结果,可以被修改。

	(6) 环绕通知: 经常做事务,在目标方法之前开启事务,执行目标方法, 在目标方法之后提交事务

	举例:
		-------------------------------------------------------------------------------------------------
		接口方法:
			String doFirst(String name,int age);

		在目标类中实现此方法:
			@Override
			public String doFirst(String name, Integer age) {
			    System.out.println("====目标方法doFirst()执行了====");
			    return "first";
			}

		切面类:
			@Aspect
			public class MyAspect {

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

				//【1.目标方法 之前 添加功能】
				System.out.println("***环绕通知:在目标方法之前,输出时间:"+ new Date());

				//【2.目标方法调用】

				//因为 ProceedingJoinPoint 继承 JoinPoint,所以它也可以获取方法执行时的信息。
				String name = "";
				Object[] args = pjp.getArgs();
				if(args!= null && args.length > 1){
				    Object arg=  args[0];        //获取第一个参数值
				    name =(String)arg;
				}

				Object result = null;
				if ("lisi".equals(name)){  //【控制目标方法是否被调用】
				    result = pjp.proceed(); //执行目标方法doFirst()。等同于jdk动态代理的method.invoke();
				}

				//【3.目标方法 之后 添加功能】
				System.out.println("***环绕通知:在目标方法之后,提交事务");

				//还可以修改目标方法的执行结果, 影响方法最后的调用结果
				if( result != null){
				    result = "Hello AspectJ AOP";
				}

				//返回目标方法的执行结果
				return result;
			    }
			}

		测试调用方法:
			@Test
			public void test01() {
				String config = "applicationContext.xml";
				ApplicationContext ac = new ClassPathXmlApplicationContext(config);

				SomeService proxy = (SomeService) ac.getBean("someService");

				String str = proxy.doFirst("lisi",23); //实际调用的是切面类的myAround()方法
				System.out.println("目标方法的最终执行结果:"+str);
			}

		运行结果:
			***环绕通知:在目标方法之前,输出时间:Wed Aug 26 17:43:45 CST 2020
			====目标方法doFirst()执行了====
			***环绕通知:在目标方法之后,提交事务
			目标方法的最终执行结果:Hello AspectJ AOP
		-------------------------------------------------------------------------------------------------

3.8.5 @AfterThrowing 异常通知-注解中有 throwing 属性

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

	(1) @AfterThrowing: 异常通知注解
		属性:	1. value 切入点表达式
			2. throwinng 自定义的变量,表示目标方法抛出的异常对象。
			   变量名必须和通知方法的参数名一样
		特点:
			1. 在目标方法抛出异常时执行的
			2. 可以做异常的监控程序, 监控目标方法执行时是不是有异常。
			   如果有异常,可以发送邮件,短信进行通知

	(2) 异常通知 —— 方法的定义格式:
		1.public
		2.没有返回值
		3.方法名称自定义
		4.方法可以没有参数,如果有,是一个Exception,如果还有就是JoinPoint参数
    
	(3) 在执行效果上,相当于一个【try...catch语句】。目标方法在try语句块中,而切面方法放在了catch子句中。
		try{
			SomeServiceImpl.doSecond(..)
		}catch(Exception e){
			myAfterThrowing(e);
		}		
	
	举例:
		--------------------------------------------------------------------------------------------
		接口方法:
			void doSecond();

		目标类实现方法:
			@Override
			public void doSecond() {
			    //  10/0,一定会有异常
			    System.out.println("===业务方法doSecond()执行===" + (10/0));
			}

		定义切面:
			@AfterThrowing(value = "execution(* *..SomeServiceImpl.doSecond(..))",
				       throwing = "ex")
			public void myAfterThrowing(Exception ex)  {
			    //把异常发生的时间、位置、原因记录到数据库,日志文件等等
			    //可以在异常发生时,把异常信息通过邮件、短信,发送给开发人员
			    System.out.println("***异常通知:在目标方法发生异常时执行,异常原因:"+ex.getMessage());
			}

		调用doSecond()方法运行后:

			***异常通知:在目标方法发生异常时执行,异常原因:/ by zero

			java.lang.ArithmeticException: / by zero

				at com.yuming.service04.SomeServiceImpl.doSecond(SomeServiceImpl.java:9)
				at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
				at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
				at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
				at java.lang.reflect.Method.invoke(Method.java:498)
				......
		--------------------------------------------------------------------------------------------

3.8.6 @After最终通知

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

	(1) @After: 最终通知注解
		属性: value 切入点表达式
		特点:
			1.总是会执行
			2.在目标方法之后执行的

	(2) 最终通知 —— 方法的定义格式:
		1.public
		2.没有返回值
		3.方法名称自定义
		4.方法没有参数, 如果有就是JoinPoint,
	
	(3) 在执行效果上,相当于将 切面方法 放在了【try...catch...finally...语句】的 finally子句中
		try{
			SomeServiceImpl.doThird(..)
		}catch(Exception e){

		}finally{
			myAfter()
		}

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

3.8.7 定义切入点 @Pointcut

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

		@Aspect
		public class MyAspect {

		    // @After(value = "execution(* *..SomeServiceImpl.doThird(..))")
		    @After(value = "mypc()")
		    public void myAfterThrowing( )  {
				System.out.println("最终通知:在最后总是会被执行的");
		    }

		    // @Before(value = "execution(* *..SomeServiceImpl.doThird(..))")
		    @Before(value = "mypc()")
		    public  void  myBefore(){
				System.out.println("前置通知,在目标方法之前先执行的");
		    }

		    // 定义切入点
		    @Pointcut(value = "execution(* *..SomeServiceImpl.doThird(..))")
		    private void mypc(){
				//无需代码
		    }
		}

3.8.8 使用 cglib 动态代理

	使用CGLIB生成动态代理要求是:目标类必须能够被继承,即不能是final 的。
	无论有没有实现接口,都可以使用cglib的动态代理。

	在Spring配置文件中,通过<aop:aspectj-autoproxy/>的 proxy-target-class属性 
	可以设置选择通过【JDK动态代理】还是【cglib动态代理】实现AOP。
	
	------------------------------------------------------------------------------------
	<!--注册aspectj框架中的【自动代理生成器】:生成“目标类+切面”的代理对象
		
		<aop:aspectj-autoproxy/>表示:
		        目标类有接口,默认使用jdk动态代理。
		        目标类没有接口,默认使用cglib动态代理

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

第4章:Spring 集成 MyBatis

4.1 实现思路

  • 使用Spring的IoC技术,把MyBatis框架中使用的对象交给Spring创建、管理、赋值

  • 为什么IoC能把 MyBatis和 Spring集成在一起,像一个框架一样使用?
       是因为ioc能创建对象。
       可以把MyBatis框架中的对象交给Spring统一创建,开发人员从Spring中获取对象。
       开发人员就不用同时面对两个或多个框架了,只用面对一个Spring

  • 回顾MyBatis使用步骤,和需要的对象:

1)定义实体类,Student ————保存表中的一行数据的
2)定义dao接口,StudentDao  ————定义操作数据库的方法
3)定义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.yuming.dao.StudentDao">
		<select id="selectStudents" resultType="com.yuming.entity.Student">
			select * from student order by id
		</select>
		
	</mapper>
	---------------------------------------------------------------------
4)定义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>

	    <!--1、数据库的连接信息-->
	    <environments default="myFirst">
		<environment id="myFirst">
		    <transactionManager type="JDBC"/>
		    <dataSource type="POOLED">       <!--数据源的类型: POOLED 表示使用数据库的连接池-->
			<property name="driver" value="com.mysql.jdbc.Driver"/>
			<property name="url" value="jdbc:mysql://localhost:3306/springdb"/>
			<property name="username" value="root"/>
			<property name="password" value="123"/>
		    </dataSource>
		</environment>
	    </environments>

	    <!--2、 mapper映射文件的位置信息-->
	    <mappers>
		 	<mapper resource="com/yuming/dao/StudentDao.xml"/>
	    </mappers>

	</configuration>
	-----------------------------------------------------------------------
	注:我们会使用独立的连接池类,替换上面MyBatis默认自己带的, 把连接池类也交给Spring创建。
	    所以后面不需要<environments/>标签了

5)创建并获取dao接口的实现类对象(代理对象),然后进行相关操作
	-----------------------------------------------------------------------
	// 读取主配置文件
	InputStream in = Resources.getResourceAsStream("mybatis.xml");  
	// 创建 SqlSessionFactory 对象
	SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
	// 创建 SqlSession对象
	SqlSession sqlSession = factory.openSession();
	
	 //创建并获取dao接口的实现类对象(代理对象)
	StudentDao dao = sqlSession.getMapper(StudentDao.class);
	
	// 查询数据库中所有student信息
	List<Student> studentList = dao.selectStudents();
	studentList.forEach( student -> System.out.println(student));
  • 通过以上的说明,我们需要让Spring创建以下对象:
       (1)独立的连接池类的对象。替换上面MyBatis默认自己带的,使用阿里的druid连接池,更加高效;
       (2)SqlSessionFactory对象
       (3)dao接口的实现类对象(代理对象)

    需要学习就是上面三个对象的创建语法,这里使用Spring的xml配置文件的bean标签(因为目前知识量的原因,没有使用注解的方式)。

4.2 实现步骤

项目【ch07-spring-mybatis】

1、创建mysql数据库和表student
	数据库名 springdb ; 表名 student 

2、新建maven项目,pom文件加入maven的依赖
	1)spring依赖
	2)spring的事务依赖
	3)mybatis依赖
	4)mysql驱动
	5)阿里公司的数据库连接池
	6)mybatis和spring集成的依赖: 
	   是mybatis官方提供的,用来在spring项目中创建mybatis的SqlSesissonFactory对象、dao对象的
	
	另外,记住要在pom文件中设置扫描xml文件,结果输出到target/classes中

3、创建实体类
4、创建dao接口
5、创建mapper映射文件
6、创建mybatis主配置文件

7、创建Service接口和实现类,实现类的属性是dao接口。
	在实际项目中,我们在对数据库进行相关操作前需要一些其他的业务代码,
	例如逻辑判断、身份认证等,这些放在Service中。

8、【重点】创建 Spring的配置文件, 声明mybatis的对象交给Spring创建:
	1)声明数据源DataSource对象
	2)声明SqlSessionFactoryBean, 用来创建SqlSessionFactory对象
	3) 声明MyBatis的扫描器MapperScannerConfigurer,用来创建Dao接口的实现类对象
	4)声明自定义的service对象。把3)中的Dao对象注入给service的属性

9、创建测试类,从spring容器中获取Service对象,调用Service的方法,完成数据库的操作
  • 【源码】

pom.xml

<?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.yuming</groupId>
  <artifactId>ch07-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>   <!--记住修改jdk版本为1.8-->
    <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.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>

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

    <!--mybatis和spring集成的依赖-->
    <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis-spring</artifactId>
      <version>1.3.1</version>
    </dependency>
  </dependencies>

  <build>
    <!--目的是把src/main/java目录中的xml文件和properties文件包含到输出结果中。输出到target/classes目录中-->
    <resources>
      <resource>
        <directory>src/main/java</directory>
        <includes>
          <include>**/*.properties</include>
          <include>**/*.xml</include>
        </includes>
        <filtering>false</filtering>
      </resource>
    </resources>
  </build>

</project>

实体类

public class Student {
    //定义属性, 推荐是属性名和数据库表的列名一样。
    private Integer id;
    private String name;
    private String email;
    private Integer age;

    //set和get方法,无参和有参构造方法,toString方法
}    

dao接口:

public interface StudentDao {

    //添加
    int insertStudent(Student student);

    //查询
    List<Student> selectStudents();
    
}

mapper映射文件:

<?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.yuming.dao.StudentDao">

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

	<select id="selectStudents" resultType="com.yuming.entity.Student">
		select * from student order by id desc
	</select>

</mapper>

mybatis主配置文件:

<?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>
    	<!--加入日志配置,可以在控制台输出执行的 sql 语句和参数-->
		<setting name="logImpl" value="STDOUT_LOGGING" />
    </settings>

    <!--设置类型别名-->
    <typeAliases>
        <!--name是包名, 这个包中的所有类,类名就是别名。就可以使用Student表示com.yuming.entity.Student了-->
        <package name="com.yuming.entity"/>
    </typeAliases>
    
    <!--由于使用阿里的druid数据库连接池,所以不需要<environments/>标签-->

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

</configuration>

创建Service接口和实现类,实现类的属性是dao接口。
   在实际项目中,我们在对数据库进行相关操作前需要一些其他的业务代码,
   例如逻辑判断、身份认证等,这些放在Service中。

public interface StudentService {

    //添加
    int addStudent(Student student);

    //查询
    List<Student> queryStudents();
}
public class StudentServiceImpl implements StudentService {

    //引用类型
    private StudentDao studentDao ;

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

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

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

Spring的配置文件
【重点】创建 Spring的配置文件, 声明mybatis的对象交给Spring创建:
   1)声明数据源DataSource对象
   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">
	
    <!-- 把数据库的配置信息,写在一个独立的文件,便于修改数据库的配置内容
       让spring知道jdbc.properties文件的位置-->
    <context:property-placeholder location="classpath:jdbc.properties"/>
	
	<!--配置阿里的Druid数据库连接池-->
    <!--声明数据源DataSource对象, 作用是连接数据库,创建数据库连接对象的
        这里的内容代替了之前使用mybatis时,主配置文件mybatis.xml的 <environments/>标签    -->
    <bean id="myDataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
        <!--set注入.用来给DruidDataSource类的对象的属性赋值,提供连接数据库信息 -->
        <!--注:Druid会自动根据url识别驱动类名,所以说无需配置driver驱动-->
        <!--    使用属性配置文件中的数据,语法 ${key}来获取对应的属性值 -->
        <property name="url" value="${jdbc.url}"/>   <!--setUrl()方法-->
        <property name="username" value="${jdbc.username}"/>        <!--setUsername()方法-->
        <property name="password" value="${jdbc.passwd}"/>   <!--setPassword()方法-->
        <property name="maxActive" value="${jdbc.max}"/>  <!--setMaxActive()方法。连接池支持的最大连接数,取值为20同时最多有20个数据库连接-->
    </bean>

    <!--声明的是mybatis中提供的SqlSessionFactoryBean类,这个类内部是创建SqlSessionFactory的。-->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <!--引用类型的set注入.把数据库连接池赋给了dataSource属性-->
        <property name="dataSource" ref="myDataSource"/>
        <!--mybatis主配置文件的位置
          configLocation属性是Resource类型,用来读取配置文件
          它的赋值,使用value,指定文件的路径,使用classpath:表示文件的位置 -->
        <property name="configLocation" value="classpath:mybatis.xml"/>
    </bean>
	
	 <!-- 声明MyBatis的扫描器,用来创建Dao接口的实现类对象(动态代理对象)-->
    <!--创建dao对象,需要使用SqlSession的getMapper(StudentDao.class)方法
       声明MyBatis的扫描器MapperScannerConfigurer: 在内部调用getMapper()生成每个dao接口的代理对象。-->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <!--指定SqlSessionFactory对象,能获取SqlSession-->
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />
        <!--指定基本扫描包,即Dao接口的包名,框架会把这个包中的所有接口一次创建出对应的dao代理对象
            创建好的dao对象放入到spring的容器中的。 dao对象的默认名称是:接口名首字母小写 -->
        <property name="basePackage" value="com.yuming.dao"/>
    </bean>


    <!--声明自定义的service-->
    <bean id="studentService" class="com.yuming.service.StudentServiceImpl">
        <!--引用类型的set注入。把上面创建的dao对象赋值给service对象的属性-->
        <property name="studentDao" ref="studentDao" />
    </bean>
</beans>

属性配置文件:

jdbc.url=jdbc:mysql://localhost:3306/zt
jdbc.username=root
jdbc.passwd=123456
jdbc.max=30	

创建测试类,从spring容器中获取Service对象,调用Service的方法,完成数据库的操作

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));
        }
    }
    @Test
    public void testDaoInsert(){
        String config="applicationContext.xml";
        ApplicationContext ctx = new ClassPathXmlApplicationContext(config);

        //获取spring容器中的dao对象

        StudentDao dao  = (StudentDao) ctx.getBean("studentDao");
        Student student  = new Student(1028,"王五","ww@qq.com",25);

        int nums = dao.insertStudent(student);
        //spring和mybatis整合在一起使用,事务是自动提交的。 无需执行SqlSession.commit();
        System.out.println("nums="+nums);
    }

    @Test
    public void testServiceInsert(){
        String config="applicationContext.xml";
        ApplicationContext ctx = new ClassPathXmlApplicationContext(config);

        //获取spring容器中的Service对象

        StudentService service  = (StudentService) ctx.getBean("studentService");
        Student student  = new Student(1228,"赵六","zl@qq.com",26);

        int nums = service.addStudent(student);
        System.out.println("nums="+nums);
    }
    @Test
    public void testServiceSelect(){
        String config="applicationContext.xml";
        ApplicationContext ctx = new ClassPathXmlApplicationContext(config);

        //获取spring容器中的Service对象
        StudentService service  = (StudentService) ctx.getBean("studentService");

        List<Student> studentList = service.queryStudents();
        studentList.forEach(student -> System.out.println(student));
    }
}

第5章:Spring事务

5.1 Spring 的事务管理

  • 在学习mysql数据库的时候,提出了事务。
    事务是指一组sql语句的集合,集合中有多条sql语句,可能是insert , update ,select ,delete
    我们希望这多个sql语句都能成功,或者都失败, 这些sql语句的执行是一致的,作为一个整体执行。

  • 【典型例子】:转账业务,从A账户向B账户转账,需要执行两条update更新语句,
         这两条语句必须同时成功,或者同时失败,不允许出现一条成功,一条失败。

  • 事务原本是数据库中的概念,在 Dao 层。
    但一般情况下,需要将事务提升到业务层,即 Service 层。
    这样做是为了能够使用事务的特性来管理具体的业务。
    另外,业务方法一般会调用多个dao方法,执行多个sql语句,就需要用到事务。

  • 不同数据库的访问技术,有不同的事务处理的机制、对象、方法。(jdbc、mybatis、hibernate…)
    Spring提供了一种处理事务的统一模型,能使用统一步骤、方式完成多种不同数据库访问技术的事务处理。

  • 在 Spring 中通常可以通过以下两种方式来实现对事务的管理
       (1)使用 Spring 的事务注解 管理事务
       (2)使用 AspectJ 的 AOP 配置管理事务

5.2 事务管理器接口

  • 事务管理器是 PlatformTransactionManager 接口对象
    其主要用于完成事务的提交、回滚,及获取事务的状态信息。

    PlatformTransactionManager 接口有两个常用的实现类:
    ➢ DataSourceTransactionManager:使用 JDBC 或 MyBatis 进行数据库操作时使用。
    ➢ HibernateTransactionManager:使用 Hibernate 进行持久化数据时使用。

5.3 事务定义接口

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

  • (1)定义了五个【事务隔离级别】常量

    这些常量均是以 ISOLATION_开头。即形如 ISOLATION_XXX。

    DEFAULT: 采用 DB 默认的事务隔离级别。
         MySql 的默认为 REPEATABLE_READ; Oracle 默认为 READ_COMMITTED。
    READ_UNCOMMITTED: 读未提交。未解决任何并发问题。
    READ_COMMITTED: 读已提交。解决脏读;存在不可重复读与幻读。
    REPEATABLE_READ: 可重复读。解决脏读、不可重复读;存在幻读。
    SERIALIZABLE: 串行化。不存在并发问题。

  • (2)定义了七个【事务传播行为】常量

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

    PROPAGATION_REQUIRED
    PROPAGATION_REQUIRES_NEW
    PROPAGATION_SUPPORTS    前三个是需要掌握的
    PROPAGATION_MANDATORY
    PROPAGATION_NESTED
    PROPAGATION_NEVER
    PROPAGATION_NOT_SUPPORTED

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

    如该传播行为加在 doOther()方法上:
    若 doSome()方法在调用 doOther()方法时 就是在事务内运行的,则 doOther()方法的执行也加入到该事务内执行。
    若 doSome()方法在调用 doOther()方法时 没有在事务内执行的,则 doOther()方法会创建一个事务,并在其中执行。
    在这里插入图片描述

    2) PROPAGATION_SUPPORTS
    指定的方法支持当前事务,但若当前没有事务,也可以以非事务方式执行。
    在这里插入图片描述
    3)PROPAGATION_REQUIRES_NEW
    总是新建一个事务,若当前存在事务,就将当前事务挂起,直到新事务执行完毕。

在这里插入图片描述

  • (3)定义了默认【事务超时时限】
    超时时限:表示一个方法最长的执行时间,如果方法执行时超过了时间,事务就回滚。
    单位是秒,整数值,默认是 -1.
    常量 TIMEOUT_DEFAULT 定义了事务底层默认的超时时限,sql 语句的执行时长。
    注意,事务的超时时限起作用的条件比较多,且超时的时间计算点较复杂。
    所以,该值一般就使用默认值即可。

5.4 Spring 的默认回滚方式

(1)当业务方法执行成功,没有异常抛出,Spring在方法执行完毕后 提交事务【事务管理器commit】

(2)当业务方法抛出 运行时异常ERROR, Spring执行回滚事务。【事务管理器rollback】
   运行时异常,是 RuntimeException 类及其子类,是在运行时才出现的异常。
   例如NullPointException , NumberFormatException

(3)当业务方法抛出 非运行时异常,主要是受查异常时,Spring默认提交事务。【事务管理器commit】
   不过,对于受查异常,程序员也可以手工设置其回滚方式
   受查异常,也叫编译时异常,在写代码中,必须处理的异常,若不处理,则无法通过编译。
   例如IOException, SQLException

5.5 总结 Spring的事务

(1)管理事务的是:事务管理器和它的实现类
(2)Spring的事务是一个统一模型
   1)指定要使用的事务管理器实现类,使用< bean>
   2)指定哪些类,哪些方法需要加入事务的功能
   3)指定方法需要的隔离级别、传播行为、超时

5.6 程序实例:电商购买商品项目

  • 项目 【ch08-spring-transaction】
       本例要实现购买商品,模拟用户下订单,向订单表添加销售记录,同时从商品表减少库存。

  • 实现步骤:

    (和上面那个spring集成mybatis项目大致相同)

    1、创建mysql数据库和表
        数据库名 springdb ; 表名 商品表goods、销售表sale

    2、新建maven项目,pom文件加入maven的依赖
        1)spring依赖
        2)spring的事务依赖
        3)mybatis依赖
        4)mysql驱动
        5)阿里公司的数据库连接池
        6)mybatis和spring集成的依赖
        另外,记住要在pom文件中设置扫描xml文件,结果输出到target/classes中

    3、创建实体类:Sale, Goods

    4、创建dao接口和mapper映射文件
        SaleDao接口 ,GoodsDao接口
        SaleDao.xml , GoodsDao.xml

    5、创建mybatis主配置文件

    6、创建一个运行时异常: 购买商品时,如果商品库存不够,抛出此异常
        Java中怎么自定义异常呢?
          第一步:编写一个类继承Exception或者RuntimeException.
          第二步:提供两个构造方法,一个无参数的,一个带有String参数的。

7、创建Service接口和实现类,实现类的属性是saleDao, goodsDao。

public class BuyGoodsServiceImpl implements BuyGoodsService {

    private SaleDao saleDao;
    private GoodsDao goodsDao;
	
	//set方法,为了spring配置文件中set注入
    public void setSaleDao(SaleDao saleDao) {
		this.saleDao = saleDao;
    }

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

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

		//【2、更新库存,更新商品表中库存数量】

		//注:数据的验证放到这里,是为了演示,当上面销售表添加数据了,这里抛异常的话,下面的商品表库存就不会修改了。即 数据不一致了
		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方法的结束====商品购买成功====");
    }
}

8、创建 Spring的配置文件, 声明mybatis的对象交给Spring创建:
    1)声明数据源DataSource对象
    2)声明SqlSessionFactoryBean, 用来创建SqlSessionFactory对象
    3) 声明MyBatis的扫描器MapperScannerConfigurer,用来创建Dao接口的实现类对象
    4)声明自定义的service对象。把3)中的Dao对象注入给service的属性

9、创建测试类,从spring容器中获取Service对象,调用Service的方法,完成数据库的操作

public class MyTest {
    @Test
    public void test01(){
		String config = "applicationContext.xml";
		ApplicationContext ac=new ClassPathXmlApplicationContext(config);

		//从容器获取service对象
		BuyGoodsService service = (BuyGoodsService) ac.getBean("buyService");

		//调用方法,购买商品
		//service.buy(1001,10);  //可以正常购买商品的
		//service.buy(1005,10);  //异常,1005商品不存在---销售信息表数据增加了,但是商品表库存没有减少
		service.buy(1001,100000);//异常,1001的商品库存不足100000---销售信息表数据增加了,但是商品表库存没有减少
    }
}
  • 通过测试可以发现,上面的程序是有问题的:
    出现了数据库表中数据不一致的情况,即顾客购买商品没有成功,但销售记录却增加了
    解决方法:通过AOP技术,给service实现类的 buy()方法 添加事务功能。

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

  • 适合中小项目使用的 —————— 注解方案

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

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

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

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

  • 使用 @Transactional的步骤

复制上面“电商购买商品”项目【ch08-spring-transaction】修改为【ch09-spring-transaction-anno】,添加事务功能

(1)pom文件中加入spring-tx依赖

	<!--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>2)Spring配置文件中:声明事务管理器对象

	    <!--1、声明事务管理器对象-->
	    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
			<!--指定连接的数据库,ref为自己配置的数据源对象的id值-->
			<property name="dataSource" ref="myDataSource"/>
	    </bean>3)Spring配置文件中:开启事务注解驱动

	    <!--2、开启事务注解驱动,告诉spring使用注解管理事务,创建代理对象
		   transaction-manager:上面事务管理器对象的id
	       注意,下面输入annotation-driven后,有4个同名的,来源要选择以tx结尾的,tx是表示事务的  
	    -->
	    <tx:annotation-driven transaction-manager="transactionManager"/>

	声明事务注解驱动,就是告诉Spring框架,要使用注解的方式管理事务。
	Spring使用AOP机制,创建 @Transactional所在的类代理对象,给方法加入事务的功能。
	
	Spring给业务方法加入事务:
	在业务方法执行之前,先开启事务,在业务方法之后提交或回滚事务。使用的是AOP的环绕通知
	
	相当于:
		@Around("需要增加事务功能的业务方法名称")
		Object myAround(){
		    Spring开启事务
		    try{
				buy(1001,10);
				Spring的事务管理器.commit();
		    }catch(Exception e){
				Spring的事务管理器.rollback();
		    }
		 }4)service业务层 public 方法上加入事务注解 @Transactional 及相关属性

	   /* @Transactional(
		    propagation = Propagation.REQUIRED,
		    isolation = Isolation.DEFAULT,
		    readOnly = false,
		    rollbackFor = {
			    NullPointerException.class,  NotEnoughException.class
		    }
	    )*/
	    //使用的是事务控制的默认值:
	    @Transactional  //等同于上面那种写法,因为上面那种写法后面的属性值,本身就是事务控制的默认值
	    @Override
	    public void buy(Integer goodsId, Integer nums) {
			//.....多条sql语句的执行
	    }
	--------------------------------------------------------------------------------
	* rollbackFor:表示发生指定的异常一定回滚.
	* 处理逻辑是:
	* 1) spring框架会首先检查方法抛出的异常是不是在rollbackFor的属性值中
	*         如果异常在rollbackFor列表中,不管是什么类型的异常,一定回滚。
	* 2) 如果你的抛出的异常不在rollbackFor列表中,spring会判断异常是不是 运行时异常
	*        如果是就一定回滚。【Spring 的默认回滚方式】


=================================================================================

	通过测试可以发现,添加了事务功能之后,解决了之前数据库表中数据不一致的情况(顾客购买商品没有成功,但销售记录却增加了)
	现在要么购买成功:订单表添加销售记录,商品表减少库存。
	要么购买失败,抛出异常:订单表、商品表数据一致,没有发生变化。

	另外通过测试发现:在测试了几次失败的商品购买之后,虽然数据库表表面上看起来没有发生变化,但是再成功购买一次商品之后,
	会看到销售记录表的 设置为自动递增的主键id值,和上一条记录的id值相差几位,并不是连续的,这就说明:
	    销售记录表确实是添加了一些记录,只不过因为添加了事务功能,在方法出现异常之后就被【回滚】了,
	    这就造成了这几个id值被用过了,再次添加数据之后,就会往后递增id值

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

  • 适合大型项目,有很多的类、方法,需要大量的配置事务,使用AspectJ框架功能
    在Spring的 XML配置文件中声明类、方法需要的事务。
    这种方式使业务方法和事务配置完全分离。在不更改源代码的条件下管理事务

  • 实现步骤
    复制上面“电商购买商品”项目【ch08-spring-transaction】修改为【ch10-spring-transaction-aspectj】,添加事务功能

1)pom文件中加入spring-aspects依赖

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

2)Spring的配置文件中:声明事务管理器对象

<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
	<!--指定连接的数据库,ref为自己配置的数据源对象的id值-->
	<property name="dataSource" ref="myDataSource"/>
</bean>

3)Spring的配置文件中: 配置事务通知(切面)————声明方法的事务属性【隔离级别、传播行为、超时、回滚等】
为事务通知配置相关属性。用于指定要将事务以什么方式织入给哪些方法。

例如,应用到 buy 方法上的事务要求是必须的,且当 buy 方法发生异常后要回滚业务。

<!--2.配置事务通知(切面)——声明业务方法的事务属性(隔离级别,传播行为,超时时间,回滚等)
  id:自定义名称,表示这一个事务通知整体; transaction-manager:事务管理器对象的id            -->
<tx:advice id="myAdvice" 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 , com.yuming.excep.NotEnoughException"/>
    
    <!--使用通配符,指定很多的方法-->
    
    <!--指定添加方法-->
    <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) Spring的配置文件中: 配置AOP(配置切入点表达式、配置增强器)
配置切入点表达式:指定在第三步中事务管理的方法 所在的类和包
配置增强器:关联adivce和pointcut

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

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

第6章:Spring与Web

在 Web 项目中使用 Spring 框架,首先要解决在 web 层(这里指 Servlet)中获取到 Spring容器的问题。
只要在 web 层获取到了 Spring 容器,便可从容器中获取到 Service 对象。

6.1 Web 项目使用 Spring 存在的问题

项目【ch11-spring-web】
	在web项目中使用spring,完成学生注册功能

实现步骤:
	1.创建maven版web项目(maven-archetype-webapp模板)
	2.加入依赖
		1)拷贝【ch7-spring-mybatis】Spring集成MyBatis项目中的依赖;
		2)添加jsp、servlet依赖
	3.拷贝ch7-spring-mybatis的代码和配置文件
	4.创建一个jsp,学生注册页面,发起请求,有参数id、 name、email、age.
	5.创建Servlet,接收请求参数, 调用 Service方法 ,完成注册
	6.创建一个jsp,作为显示结果页面
	7.发布网站(部署项目)

注:因为使用模板生成的web.xml的版本是2.3的,较低,目前很多功能不能实现,比如EL表达式
	所以需要我们把版本提升一下:
	1)方法见下图
	2)或者也可以直接拷贝之前的项目是4.0版本的web.xml拷贝一下。	

提示web.xml版本:
在这里插入图片描述
在这里插入图片描述

创建Servlet,接收请求参数, 调用 Service方法 ,完成注册

public class RegisterServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //接收请求参数
        String strId = request.getParameter("id");
        String strName = request.getParameter("name");
        String strEmail = request.getParameter("email");
        String strAge = request.getParameter("age");

		//=================================================================================================
        //创建spring的容器对象
        String config= "applicationContext.xml";
        ApplicationContext ctx = new ClassPathXmlApplicationContext(config);
        System.out.println("容器对象的信息:"+ctx);

        // 容器对象的信息:org.springframework.context.support.ClassPathXmlApplicationContext@34af5268
        // 容器对象的信息:org.springframework.context.support.ClassPathXmlApplicationContext@3ce1b683
        //......

		//=================================================================================================
        
        //获取service
        StudentService service = (StudentService) ctx.getBean("studentService");
        
        Student student  = new Student();
        student.setId(Integer.valueOf(strId));
        student.setName(strName);
        student.setEmail(strEmail);
        student.setAge(Integer.parseInt(strAge));
        
        service.addStudent(student); //注册学生

        //返回一个结果页面
        request.getRequestDispatcher("/result.jsp").forward(request,response); //学生注册成功

    }
}
  • 结果分析
    通过测试发现,启动tomcat之后,每注册一次学生信息(发送一次请求),都会重新创建一个Spring容器对象,
    而每创建一次Spring容器对象,就会把spring配置文件中所有对象都创建一次!!!
    如果同时有很多人发起请求,那么就会创建非常多重复的对象,浪费内存,且响应时间长,显然是有问题的。
    对于一个应用来说,只需要一个 Spring 容器即可。
    所以,将 Spring 容器的创建语句放在 Servlet 的 doGet()或 doPost()方法中是有问题的。

  • 解决方法
    web项目中Spring容器对象只需要创建一次, 把容器对象放入到 ServletContext【全局作用域】对象中。

6.2 使用 Spring 的监听器 ContextLoaderListener

  • 对于 Web 应用来说,ServletContext 全局作用域对象是唯一的,
    一个 Web 应用,只有一个ServletContext 对象,该对象是在 Web 应用装载时初始化的。
    若将 Spring 容器的创建时机,放在 ServletContext 初始化时,就可以保证Spring容器的创建只会执行一次,
    也就保证了Spring 容器在整个应用中的唯一性

  • 当 Spring 容器创建好后,在整个应用的生命周期过程中,Spring 容器应该是随时可以被访问的。
    即,Spring 容器应具有全局性。 放入 ServletContext 对象的属性,就具有应用的全局性。
    所以,将创建好的 Spring 容器,以属性的形式放入到 ServletContext 的空间中,就保证了 Spring 容器的全局性

  • 若要在 ServletContext 初始化时创建 Spring 容器 ,就需要使用监听器对 ServletContext 进行监听。
    回顾之前学习Servlet中的【监听器】的作用:
       监控【作用域对象-生命周期变化时刻】以及【作用域对象-共享数据变化时刻】

   ServletContext: 全局作用域对象
   HttpSession : 会话作用域对象
   HttpServletRequest: 请求作用域对象

  • 所以我们可以使用监听器,当全局作用域对象被创建时,创建Spring容器,并存入ServletContext中

   1)创建容器对象。执行 ApplicationContext ctx = new ClassPathXmlApplicationContext(“applicationContext.xml”);
   2)把容器对象放入到ServletContext。 ServletContext.setAttribute(key,ctx)

  • 监听器可以自己创建,也可以使用框架中提供好的ContextLoaderListener,这里使用ContextLoaderListener

实现步骤
1、添加Maven依赖

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

2、在web.xml中 注册监听器 ContextLoaderListener

监听器被创建对象后,会寻找/WEB-INF/applicatoinContext.xml,为什么找这个文件?
在监听器的初始方法中,会创建spring的容器对象, 在创建容器对象时,需要读取配置文件,
监听器默认是找/WEB-INF/applicatoinContext.xml。

但,我们一般会将spring配置文件放置于项目的 classpath 下,即 src 下,
所以需要在 web.xml 中对 Spring 配置文件的位置及名称进行指定。
	
	=======================================================================================
	<!--自定义spring配置文件的位置和名称-->
	<context-param>
	    <!-- contextConfigLocation:表示是配置文件的路径的  -->
	    <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>
	=======================================================================================
	
通过观察ContextLoaderListener源码,在其初始方法内部是这样的:
	
	 //创建Spring容器对象
	this.context = this.createWebApplicationContext(servletContext); 
	
	 //将容器对象放到ServletContext中
	servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context); 

	即 key= WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE
	   value= this.context

3、在Sevlet中获取 Spring 容器对象

在 Servlet 中获取容器对象的常用方式有两种:

(1)直接获取容器对象

	从对监听器 ContextLoaderListener 的源码分析可知,容器对象在 ServletContext 的中存放的
	key 为 WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE。
	所以,可以直接通过 ServletContext 的 getAttribute()方法,按照指定的 key 将容器对象获取到。
		=======================================================================================
		WebApplicationContext ctx = null;
		String key = WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE;
		Object attribute = getServletContext().getAttribute(key); //通过key获取value
		if( attribute != null){
		    ctx = (WebApplicationContext)attribute;
		}
		System.out.println("容器对象的信息:"+ctx);
		=======================================================================================

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

(2)通过 WebApplicationContextUtils 工具类

	为了不使用框架给出的难记的key值获取webApplicationContext,这个框架还提供了一个工具类。
	使用工具类获取webApplicationContext:
	
		=======================================================================================
	    ServletContext sc = getServletContext();
		WebApplicationContext ctx = WebApplicationContextUtils.getRequiredWebApplicationContext(sc);
		System.out.println("容器对象的信息:"+ctx);
		=======================================================================================

以上两种方式,无论使用哪种获取容器对象,注册多次学生(发送多次请求),获取到的Spring 容器均为同一个对象!!!

【思维导图】

在这里插入图片描述

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

传送门

上一章:框架 - MyBatis框架
下一章:框架 - SpringMVC框架

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值