Spring
第一章:Spring 概念
Spring框架概述
Spring是轻量级的开源的JavaEE框架
解决企业开发的难度,减轻对项目模块之间的管理,类和类之间的管理,交给Spring来处理,帮助开发人员创建对象,管理对象之间的关系
spring的目的就是解耦合,让关系变得松散。一个模块的变化对另一个模块影响最小!!
依赖:类A中使用类B的属性或方法,叫做A依赖于B。
框架就是半成品,需要结合框架,加上我们自己的代码,来完成开发。
框架提供了很多工具,现有的工具和功能,组件
我们在框架的基础之上进行开发,自己写的项目的功能,可利用框架中已有的功能
要用框架,那么需要进行jar包的导入,依赖的添加。而Spring框架需要我们添加的依赖很少,所以是轻量级的,同理,Mybatis也是轻量级的,因为添加的依赖很少,配置简单。
spring框架运行占用的资源少,运行效率高,不依赖其他jar,Spring核心功能的所需jar总共在3M左右
框架要完成一个功能,需要一定的步骤支持的。
Spring的目的是解决企业应用开发的复杂性。
Spring有很多组成部分,但是有两个核心组成部分
IOC
控制反转,之前原始方式创建对象,需要new一个类,把对象创建,或者还有反射的new Instance,反射通过构造器创建。现在通过IOC,把创建对象的过程交给Spring进行管理
AOP
面向切面(面向方面),比如在程序中想加功能或者扩展功能,在不修改原代码的情况下,进行功能的增强。不改原代码
Spring框架的特点
轻量级
方便解耦,简化开发,针对接口编程
AOP编程支持,不改变原代码的方式,进行功能的增强
方便程序的测试
方便集成各种优秀框架
Spring的核心技术,ioc、aop实现模块之间、类之间的解耦合,所以aop才能支持在不改变原代码的方式进行功能增强。
SpringFramework内部模块
- 数据访问模块
- web应用模块
- AOP模块
- 集成功能模块
- IOC核心容易模块
- 测试模块
第二章:IoC控制反转
概述
IoC---Inversion of Control,控制反转,是一个概念,一个思想
指将传统上由程序代码直接操控的对象调用权交给代码之外的容器(这个容器在Java代码中需要被创建出来),通过外部容器来实现对象的装配和管理。
控制反转就是对对象控制权的转移,从程序代码本身反转到了外部容器。
通过容器实现对象的创建,属性赋值,依赖的管理(对象之间的关系管理)
IOC描述的:把对象的创建、属性赋值、管理工作都交给代码之外的容器实现。
spring容器中的对象默认都是单例的,在容器中叫这个名称的对象只有一个。
IoC是一种概念,一种思想,其实现方式多种多样,当前比较流行的实现方式是依赖注入
依赖:
classA中含有classB的实例,在classA中调用classB的方法完成功能,即classA对classB有依赖。
控制反转
控制:创建对象,对象的属性赋值,对象之间的关系管理
反转:由代码实现控制,交给外部容器来实现控制
正转:由开发人员,在代码中,使用new构造方法创建对象,开发人员主动管理对象。
容器:是一个服务器软件,一个框架---spring
IOC的实现:
依赖注入:DI,Dependency Injection,这些工作由容器自行完成
依赖注入是指程序运行过程中,若需要调用另一个对象协助时,无需在代码中创建被调用者,而是依赖于外部容器,由外部容器创建后传递给程序。
Spring的依赖注入对调用者和被调用者几乎没有任何要求,完全支持对象之间依赖关系的管理
为什么要使用ioc
目的是减少代码的改动,也能实现不同的功能,解耦合,让对象的管理更松散。
Java中创建对象有哪些方式
- new的常规方式
- new的变形:XxxBuilder、XxxFactory的静态方法
- 反射,调用newInstance()
- 反射,调用无参的或带参的构造器
- 克隆,实现Cloneable接口,实现clone()方法
- 使用反序列化
- 动态代理
- IoC依赖注入,由容器创建对象,然后传递给程序
在此基础上,再加一种方式:
IoC:外部容器创建对象!
除了IoC,其他创建对象的方式都要在我们的程序中写代码来创建对象,但是IoC不需要,由外部容器创建对象,并且IoC是一个思想,有很大的功能,他不仅仅是创建对象,还可以管理对象之间的关系(管理依赖),属性赋值
ioc的体现:
servlet:
创建类继承HttpServlet
在web.xml注册servlet
但是没有创建过Servlet对象,没有
MyServlet myServlet = new MyServlet();
Servlet类的对象是Tomcat服务器创建的
所以Tomcat也称为容器,里面存放的有Servlet对象,监听器对象,过滤器对象,这是Tomcat的三大组件,Servlet是核心组件
Tomcat作为容器,完成了对象的创建,我们只需要在web.xml文件中修改就可以了,不用在代码里创建对象,这就是IOC的体现。
IOC是在程序的代码之外完成对象的创建的!!!
IoC的技术实现
DI,即依赖注入,是IoC的技术实现
我们只需要在提供要使用的对象的名称就可以了,至于对象如何在容器中创建,给属性赋值,查找都由代码外部、容器内部实现。
Spring是使用了DI实现了IoC的功能,Spring底层创建对象,使用的是反射机制(根本)
什么样的对象放入到容器中创建?
dao类,service类,controller类,工具类(可知这些都是功能性类,在框架中担任一定作用的类)
spring容器中的对象默认都是单例的,在容器中叫这个名称的对象只有一个。
一个<bean>标签对应于一个对象。
不放入到spring容器中的对象
实体类的对象,因为实体类对象的属性数据是来自于数据库的,在数据库访问过程中或者说查询过程中便可以创建对象,比如由mybatis框架完成,对应于jdbc的这个操作
但是mybatis并不需要写以上这几行代码,只是对应于jdbc里是这样操作,所以我们不需要把实体类对象放到spring容器中,在查询过程中便会由mybatis来帮我们创建实体对象
servlet、listener、filter等,这些对象交给tomcat来创建
所以面试的时候说到IOC,不只是出现在spring框架中,这是一种思想,可以说tomcat服务器也用到。而tomcat本身又是一个容器。
对象怎么放入spring容器中创建,并且给对象的属性赋值,即完成依赖注入操作。
- xml配置文件方式
- set注入
- 简单类型
- 引用类型
- set注入
- 注解方式
- xml配置文件方式
ioc能够实现业务对象之间的解耦合,例如service和dao对象之间的解耦合
Spring通过IoC创建对象的实现步骤
总体步骤
创建maven项目
加入maven 依赖
spring的依赖,版本5.2.5
junit依赖,单元测试
创建类(可以有接口和实现类)(Spring中,实体类的对象的创建不交给容器(没有必要),而是mybatis创建,因为一个实体类是对应于数据库中一个表,一个实体类对象是对应于数据库中表的一行!!)
交给spring容器创建的对象是service类、dao类、controller类、工具类的对象!!
和没有使用框架一样,就是普通的类
创建spring需要使用的配置文件,把第三步创建的类的信息交给spring
声明类的信息,这些类的对象由spring创建和管理,使用<bean>
测试spring创建的对象。
注意:
spring中,类不是不需要写,类是需要写的,只是类的创建交给spring容器(底层使用反射机制)
容器对象的创建是在读取配置文件的时候,读取完,那么容器对象创建好了,并且配置文件中声明的对象也创建好了
mybatis中,Dao接口的实现类不需要写,也就是不仅对象直接交给mybatis创建,连类也不需要写(底层是动态代理)。mybatis通过动态代理创建接口的实现类的对象。
在spring中,类得自己写(不要和之前mybatis的dao接口的实现类搞混淆,那个不需要写)!!并且要把类的信息交给容器(可以理解为配置文件。)把类的信息交给配置文件。在配置文件中声明类的信息。
创建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">
<!--
告诉spring创建对象
声明bean,就是告诉spring要创建某个类的对象
id:对象的自定义名称,唯一值,spring通过这个名称来找到对象
class:类的全限定名称(不能是接口,因为spring是反射机制创建对象,必须使用类)
写了下面这行之后,spring就会在内部完成对象的创建,底层使用反射机制,实现对象的依赖注入
相当于spring就完成SomeService someService = new SomeServiceImpl();
对象创建完了,需要保存起来,spring把创建好的对象放入到map中,spring框架有一个map存放对象
springMap.put(id的值,对象)
即 springMap.put("someService", new SomeServiceImpl())
-->
<bean id="someService" class="org.example.impl.SomeServiceImpl"/>
</beans>
<!--
spring的配置文件
1. beans:是根标签,spring把Java对象称为bean
2.spring-beans.xsd 是约束文件,和mybatis中的 .dtd是一样的,用来控制和限制在这个文件中可以出现的标签和属性
-->
一个bean标签声明一个对象
并且要注意依赖注入的底层原理使用的是反射
创建的对象要保存起来,保存在Map对象里。
通过IoC创建对象在Java代码中的步骤
public void testDoSome() { //1. 指定spring配置文件的名称 String config = "beans.xml"; //2 创建表示spring容器的对象,ApplicationContext, // ApplicationContext就表示spring容器对象,通过容器对象,就能获取对象了 // ClassPathXmlApplicationContext 表示从类路径中加载spring的配置文件 // 类路径是target/classes/之后的路径,而项目目录resources下的配置文件本来就在类路径的根路 径下。 // 执行完下面这行代码,就把对象创建好了 // 读取完配置文件的同时,对象就已经创建好了 ApplicationContext applicationContext = new ClassPathXmlApplicationContext(config); //3. 从容器中获取某个对象,要调用applicationContext对象的方法 // 这一行代码只是拿到对象 SomeService someService = (SomeService) applicationContext.getBean("someService"); //4. 使用IoC创建好的对象, someService.doSome(); }
spring默认创建对象的时间是:在创建spring的容器ApplicationContext时,会创建配置文件中所有的对象,也就是说,对象在读取配置文件完成的时候就创建好了。(构造方法也会在此时调用,无参构造方法还是有参构造方法,看bean标签里是怎么配置的,是配置的<property>还是<construtor-arg>)
spring能创建一个非自定义类的对象。
spring创建对象,默认调用的是无参的构造方法。
基于XML的依赖注入(DI)
概述
在spring的配置文件中,给Java对象的属性赋值
依赖注入(DI)表示
- 创建对象
- 给对象的属性赋值
依赖注入(DI)的实现语法有两种
第一种方式:在spring的配置文件中,使用标签和属性完成,叫做基于XML的依赖注入实现
set注入(设值注入):spring容器调用类的set方法,在set方法可以实现属性的赋值
使用很广泛,80%左右都是使用的set注入
- 简单类型的set注入
- 引用类型的set注入
构造注入,spring调用类的有参数构造方法(spring容器默认调的是无参构造方法),创建对象,在构造方法中完成赋值
第二种方式:使用spring的注解来完成对象的属性赋值,这叫基于注解的DI实现
set注入
1. 简单类型的set注入
<!-- 声明student对象 注入:就是赋值的意思 简单类型;spring中规定Java的基本数据类型和String都是简单类型(和mybatis一样) di:给属性赋值 1. set注入 ==》 spring调用类的set方法,你可以在set方法中完成属性赋值 1) 简单类型的set注入 <bean id="xx" class="yyy"> <property name="属性名字" value="此属性的值"> ==》一个property只能给一个属性赋值 </bean> --> <bean id="myStudent" class="org.example.spring01.Student"> <property name="name" value="李四" /> <property name="age" value="20" /> </bean>
像这种方式注入,都是spring容器通过调用类的set方法,实现属性的赋值的。
要注意,spring容器,我们之前创建出来,创建好容器之后,类的对象就已经创建好了,因为创建spring容器的时候,会读取配置文件,配置文件读取完成,就创建好了对象。
ApplicationContext就表示spring容器,通过容器对象,就能获取对象了
在配置文件中通过set注入,要求类必须有属性的setter方法,没有的话,会报错。
spring通过set注入,调用类的set方法,是在构造方法之后执行的,spring容器创建对象,默认调用类的无参构造方法。
先调用无参构造器创建对象,再调用set方法。
set注入是说spring调用类的set方法,只要在配置文件里写了对应的property,那么就去类里调用setXxx方法,只要有这个方法, 程序就会顺利执行,不会报错,即使这个类只是有这样一个set方法,根本没有属性。
set注入,spring只看set方法,别的都不用管。
创建Junit测试方法
- public方法
- 没有返回值
- 方法名称自定义,建议名称是test+要测试的方法名称
- 方法没有参数
2. 引用类型的set注入
需要用到bean的id,显然还要对这个引用类型,在xml文件中创建一个bean,并且也要给这个引用类型的属性通过property标签赋上值
对于spring来说,Java的八种基本数据类型和String类型被认为是简单类型。
3.set注入的注意事项
用set注入,那么spring就是通过调用类的set方法给对象赋值,那么类里面必须要写set方法,不然会报错
spring只是执行set方法,set方法体内的内容是由我们自己决定的,包括赋不赋值,都是我们自己决定,因为spring容器只是负责调用set方法。
set方法是在构造方法之后执行的。
spring容器找的是属性的值所对应的set方法,而不是找的类里面的属性,即使类里面根本没有写email这个属性,写了setEmail方法那么也是能够顺利执行的,spring容器只是根据property标签的内容找到类里面对应的set方法执行。
set方法写了就不会报错,没有就会报错,spring容器不会去看是否有对应的属性。
不管类里面属性是什么类型的变量,在xml文件中定义property的时候,name属性和value属性的值都必须加上引号,这个是xml文件的规则。
构造注入
构造注入是指在创建类的实例化对象的同时,完成对象的属性的赋值,通过调用类的有参数构造方法,完成对象的实例化。即使用构造器设置依赖关系。
构造注入:spring调用类的有参构造方法,在创建对象的同时,在构造方法中给属性赋值
不过要注意spring默认创建对象,用的是无参构造器。
IoC底层创建对象是通过反射机制来创建对象,默认使用类的无参构造器。
构造注入使用<constructor-arg>
<constructor-arg>标签:一个<constructor-arg>表示构造方法的一个参数
<constructor-arg>标签属性:
name:表示构造方法的形参名
index:表示构造方法的参数的位置,参数从左往右是0,1,2的顺序
index也可以省略,如果省略,那么在xml文件里,就要按构造函数参数写。
value:构造方法的形参类型是简单类型的,使用value
ref:构造方法的形参是引用类型的,使用ref
示例:
<bean id="myStudent" class="org.example.spring01.Student"> <constructor-arg name="name" value="李四" /> <constructor-arg name="age" value="20" /> </bean>
在spring配置文件中写bean标签创建对象,不用关心顺序。
构造方法什么时候被spring调用呢?
创建spring容器的时候,因为创建spring容器ApplicationContext的时候,便会读取xml配置文件,读取完毕,便会默认调用类的无参构造方法创建对象,文件读取完毕,对象也就创建好了,所以对象的创建是在创建spring容器的同时进行的,而类的构造方法的调用,是在创建对象的时候调用的
所以创建spring容器,创建类的对象,类的构造方法的调用是同时进行的。
解耦合是什么意思呢?
比如说
UserDao userDao = new UserDaoImpl();
这样就是没有通过spring容器来创建对象,赋值符号的左边和右边是紧密联系在一起的,左边是变量名(对象引用),右边是实例化的对象
如果在某一刻,想将userDao这个对象引用指向另一个实例化对象,那么就需要在代码中来修改,这就是紧密联系的,很不方便。
如果采用spring容器的方式来创建实例化对象,代码会这么写:
private UserDao userDao; public void setUserDao(UserDao userDao) { this.userDao = userDao; }
通过set注入的方式来实现依赖注入,并且是引用类型注入,那么只需要在配置文件中修改ref属性的值。
引用类型属性自动注入
spring框架根据某些规则可以自动给引用类型赋值(只能对引用类型有效。简单类型不可以)
byName---引用类型变量属性名和bean标签的id属性比
按名称注入,Java类中引用类型变量的属性名(变量名)和spring容器中(配置文件)<bean>的id名称一样,且数据类型是一致的,这样的容器中的bean,spring能够赋值给引用类型变量。
byType---引用类型变量的类型和bean标签的class属性比
按类型注入,Java类中引用类型变量的数据类型和spring容器中(配置文件)<bean>的class属性是同源关系的, 这样的bean能够赋值给引用类型变量。
同源就是一类的意思
- Java类中引用类型变量的数据类型和class的值是一样的。
- Java类中引用类型变量的数据类型和class的值是父子类关系的。
- Java类中引用类型变量的数据类型和class的值是接口和实现类关系的。
注意:
采用引用数据类型变量自动注入,使用byType,在xml配置文件中声明bean只能有一个符合条件的,要保证匹配上的实例化对象只有1个。
这就能匹配上两种,就有问题。
byName检查的是bean标签的id
byType检查的是bean标签的class
多个配置文件
多个配置文件优势
- 每个文件的大小比一个文件要小很多,效率高
- 避免多人修改配置文件带来的冲突
- 如果项目中有多个模块(一个模块代表相关的功能在一起),那么一个模块一个配置文件
spring-total.xml是主配置文件,主配置文件,一般是不定义对象的,即不定义bean标签,主配置文件是包含其他配置文件的。
语法:
<import resource="其他配置文件的路径">
关键字:
classpath:""
表示类路径(class文件所在的目录即target/classes),在spring文件中要指定其他文件的位置,那么就要只用classpath,告诉spring去哪里加载其他文件。这种叫包含关系的配置文件
在使用通配符这种方式时,主配置文件名称不能包含在通配符的范围内。
并且如果要使用通配符这种方案,那么配置文件必须放在一个目录下。
基于注解的依赖注入(DI)
概述
通过注解来完成Java对象创建和属性赋值
创建的对象仍然是单例的,放在spring容器中。底层是放在spring框架的map中,ConcurrentHashMap
做项目以注解实现依赖注入的方式为主。
注解点进去,注解本身是一个类文件,@interface代表注解
注意:对象的创建仍然发生在spring容器ApplicationContext创建的同时,配置文件读取完,对象也创建好。
String config = "applicationContext.xml"; //这一步执行完之后,对象就创建好了,对象仍然是随着容器的创建而创建,因为容器的创建,会读取配置文件, // 配置文件里又指定了组件扫描器,指定应该去哪个包下面读取注解 // 仍然是下面这行代码执行完成,容器就创建完成,对象就通过spring默认调用无参构造器的方式创建完成。 ApplicationContext applicationContext = new ClassPathXmlApplicationContext(config);
使用注解的步骤
加入maven依赖spring-context,
在加入spring-context的同时,会间接加入spring-aop依赖
要想使用注解,必须有spring-aop依赖
创建类,在类中加入spring的注解(多个不同功能的注解)
在实际开发中,实体类的对象可能不会交给spring来创建,而是mybatis在查询数据库的过程中创建,因为一个实体类对应于一个数据库,一个实体类的对象对应于数据表中的一行数据。
在实际开发中,交给spring容器创建的类是dao类、service类、controller类、工具类等。
这些类都要写,只是对象交给spring容器创建并给属性赋值,使用的DI,底层是反射机制。
而mybatis中dao接口的实现类不用写,而且实现类的对象的创建交给mybatis,底层使用的是动态代理。
@Component(value = "myStudent")
在spring的配置文件中,加入一个组件扫描器的标签,说明注解在你的项目中的位置。
<!-- 声明组件扫描器,组件就是Java对象 base-package:指定注解在项目中的包名 component-scan工作方式:spring会扫描遍历base-package指定的包,扫描包中和子包中的所有类, 找到类中的注解,按照注解的功能创建对象,或给属性赋值。 --> <context:component-scan base-package="org.example.domain"/>
注意:
使用注解创建对象,创建spring容器ApplicationContext
仍然要写配置文件,只是不在配置文件里写bean标签,写的是组件扫描器,扫描注解!!
使用注解创建对象,并使用注解给对象的属性赋值,即依赖注入,不会调用类的set方法!!!
类的set方法不写都可以。
使用注解的方式,通过spring容器创建对象,仍然是调用类的无参构造方法创建的。
之前通过xml配置文件的方式注入,其中set注入,是调用类的无参构造方法创建对象(因为给属性赋值是通过set方法),而构造注入,则是调用类的有参数构造方法创建对象(因为给属性赋值,直接通过有参构造方法就可以赋值了)。
重要注解
@Componet
创建对象的,等同于<bean>标签的功能
属性:value ,value的值就是对象的名称,也就是<bean>标签的id属性的值
value的值是唯一的,创建的对象在整个spring容器中就一个。(通过spring容器创建对象,创建的对象都是单例的。)
位置:在类的上面,这个对象创建完之后是放在容器中的。
@Component(value = "myStudent")等同于<bean id="myStudent" class="org.example.domain.Student"/> // value 也可以省略 // @Component("myStudent")
也可以不指定对象名称,由spring提供默认名称,类名首字母改为小写,作为对象名。
spring容器ApplicationContext的getBean方法,进入方法内部,底层是从一个Map中取得对象,也就是说Spring造好容器后,会把对象也造好,并把对象放入一个Map中,这个Map的实例化对象是采用的concurrentHashMap(性能好,又线程安全)来实现。(分段锁segment是ReentrantLock的子类。)
@Repository
用在持久层的,放在dao接口的实现类上面,表示创建dao对象,dao对象是访问数据库的
@Service
用在业务层的,放在service接口的实现类上面,创建service对象,service对象是做业务处理的,可以有事务等功能的。
@Controller
用在控制器上的,放在Controller类的上面,创建Controller对象,Controller对象能够接收用户提交的参数,调用service对象的方法进行业务逻辑的处理,返回请求的处理结果。----这就是servlet组件的功能
@Repository @Service @Controller和@Component的用法一样,都能创建对象,但是这三个注解还有额外的功能,能够给对应的类赋予对应的角色,这三个注解能够给项目的类和对象分层。
@Repository对应于持久层 @Service对应于业务层 @Controller对应于控制层,对不同的类有额外的角色声明。
那什么时候用@Component呢?当这个类不是三层任何一层时,或者说不确定它是哪一层时,就用@Component
@Value
这个注解是给简单类型的属性赋值的。
@Component(value = "myStudent") public class Student { /** * @Value 简单类型的属性赋值 * 属性:value 是String类型的,表示简单类型的属性值 * 位置:1.在属性定义的上面,无需set方法,推荐使用,用的是反射,不需要set方法就能赋值了。 * private的属性,也能通过反射赋值,设置setAccessible能够访问到private声明的属性. * 之前用set注入,底层也是通过反射,IOC通过DI实现,底层是反射,set注入,就是通过反射调用 * 类的set方法,但并不是说反射就一定要调用set方法,这里通过注解写在属性上面给属性赋值,同 * 样底层是通过反射机制,但是就没有调用set。 * 2. 在set方法的上面,就是通过反射调用set方法,给属性赋值 */ @Value(value = "富富") private String name; @Value(value = "18") private int age;
@Value注解也可以放在set方法上,此时就会调用类的set方法来给属性赋值,底层仍然是反射机制。并不是说@Value注解就不能放在set方法上了。但是直接写在属性的上面,是最常用的。
@Autowired----spring框架提供的注解。
这个注解给引用类型的属性赋值。
前提是扫描器扫描的包下面能够找到同源关系的类
/** * @Autowired: spring框架提供的注解,给引用类型的对象引用(变量)赋值, * 使用的是自动注入原理。支持byName,byType。Autowired默认使用的是byType自动注入 * 位置:1.写在属性定义的上面,无需set方法,推荐使用,用的是反射,不需要set方法就能赋值了。 * 2.写在set方法的上面,就是通过反射调用set方法赋值 */ @Autowired private School school;
/** * @Autowired: spring框架提供的注解,给引用类型的对象引用(变量)赋值, * 使用的是自动注入原理。支持byName,byType。Autowired默认使用的是byType自动注入 * 位置:1.写在属性定义的上面,无需set方法,推荐使用,用的是反射,不需要set方法就能赋值了。 * 2. 写在set方法的上面,就是通过反射调用set方法赋值 * * 如果要使用byName方式,需要做的是: * 1. 在属性上面加入@Autowired * 2. 在属性上面加入@Qualifier(value="bean的id") 表示使用指定名称的bean完成赋值 */ @Autowired @Qualifier(value = "mySchool") private School school;
@Autowired注解有一个属性:
required:是一个boolean类型的,默认true,表示引用类型赋值失败,程序报错,并终止执行。
若为false,引用类型如果赋值失败,程序正常执行,引用类型变量的值是null
@Resource-----JDK提供的注解。
是jdk的注解,完成的功能和@Autowired一样,而@Autowired是spring的注解
也是支持byName和byType
只不过默认是byName,而@Autowired默认是byType
@Resource先使用byName自动注入, 如果byName给引用类型变量赋值失败,再使用byType
/** * 引用类型 * @Resource:来自jdk中的注解,spring框架提供了对这个注解的功能支持,可以使用它给引用类型赋值 * 使用的也是自动注入原理,支持byName,byType,默认使用byName * 位置:1.在属性定义的上面,无需set方法,推荐使用 * 2.在set方法上面 * 两种方法底层都是反射机制。 */
使用注解就是用更少的代码,做更多的工作,更方便,可读性好,使用注解是一种趋势。
指定多个包的三种方式
IoC总结
这是理论、思想、概念:指导开发人员在容器中,代码之外管理对象,给对象属性赋值,管理依赖
ioc技术实现使用的DI(依赖注入):开发人员在项目中只需要提供对象的名称,对象的创建、查找、赋值都交给容器内部实现
spring使用di的技术,底层是使用的反射机制
为什么使用反射机制?
通过容器调用类的set方法,调用类的无参构造方法,构造注入调用类的有参构造方法,这就是反射
di给属性赋值
- set注入
- 简单类型 - name 、value
- 引用类型 - name、 ref
- 构造注入
- <constructor-arg>:name
- <constructor-arg>:index
- set注入
第三章:AOP面向切面编程
动态代理
动态代理能创建对象,目标对象的代理对象只是由代理生成工具(不是真实定义的类)在程序运行时由JVM根据反射等机制动态生成的。代理对象与目标对象的代理关系在程序运行时才确定。
jdk的动态代理要求目标对象必须实现接口,如果没有实现接口,用的CGLIB动态代理(是第三方开源工具库)
动态代理可以实现在不修改原有的代码的前提下,额外的增加功能
动态代理:
可以在程序的执行过程中,创建代理对象,目标对象是被代理对象
通过代理对象执行方法,给目标类的方法增加额外的功能(功能增强)
jdk动态代理实现步骤:
- 创建目标类,SomeServiceImpl目标类(目标类的对象就是被代理对象),给它的doSome,doOther增加输出时间、事务
- 创建InvocationHandler接口的实现类MyInvocationHandler,在这个类实现给目标方法增加功能
- 使用jdk中类Proxy,创建代理对象,通过代理对象执行方法,显然Proxy要用到InvocationHandler接口的实现类里面。
- 创建目标对象
- 创建InvocationHandler接口的实现类的对象
- 创建代理对象---通过
Proxy.newProxyInstance
- 通过代理对象,执行方法。那么这一步的执行方法,会调用MyInvocationHandler的invoke方法
代码实现:
public class MyInvocationHandler implements InvocationHandler { // 目标对象 private Object target; public MyInvocationHandler(Object target) { this.target = target; } // 通过代理对象执行被代理对象的方法时,会调用执行这个invoke() @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("执行MyInvocationHandler的invoke()"); Object res = null; ServiceTools.doLog(); //执行目标类(被代理对象)的方法,通过method.invoke来实现。 // SomeServiceImpl.doSome()/SomeServiceImpl.doOther() // 这个method是什么方法,就看代理对象调用的被代理类的哪个方法!!! res = method.invoke(this.target, args); ServiceTools.doTransaction(); // 目标方法的执行结果 return res; } }
public static void main(String[] args) { // 使用jdk的proxy创建代理对象 // 创建目标对象---被代理对象 SomeService someService = new SomeServiceImpl(); // 创建InvocationHandler对象 InvocationHandler invocationHandler = new MyInvocationHandler(someService); // 使用Proxy创建代理对象 SomeService proxyInstance = (SomeService) Proxy.newProxyInstance(someService.getClass().getClassLoader(), someService.getClass().getInterfaces(), invocationHandler); // 通过proxyInstance这个代理对象执行被代理对象的方法,会调用MyInvocationHandler中的invoke() //代理对象调用的被代理类的doSome()方法,那么MyInvocationHandler中的invoke方法参数method就是doSome() proxyInstance.doSome(); }
使用了动态代理,doSome()、doOther()这两个方法的方法体里就只需要专注业务逻辑
通过动态代理控制哪些方法去加额外的功能,哪些功能不加。
AOP底层,就是采用动态代理实现的,可以采用以下两种实现方式:
jdk动态代理,使用jdk中的Proxy,调用newProxyInstance()方法创建代理对象,通过编写InvocationHandler的实现类来实现,重写invoke方法
jdk动态代理要求目标类即被代理类必须实现接口
cglib动态代理:第三方的工具库,创建代理对象,原理是继承,通过继承目标类,创建子类。
子类就是代理对象,要求目标类(目标类就是被代理类,或者说是被代理对象)不能是final的,方法也不能是final的
因为需要重写方法,需要通过继承目标类来创建子类,目标类不能是final的是为了继承,方法不能是final的是为了可以重写。(static或final或private修饰的方法都是不可以重写的。)
动态代理的作用:(AOP的作用)
- 在目标类原代码不改变的情况下,增加目标类的方法的非业务代码的功能
- 减少重复代码
- 专注业务逻辑的开发。
- 解耦合---使业务逻辑各部分之间的耦合度降低,让业务功能和日志、事物等非业务功能分离
AOP:面向切面编程,基于动态代理的,可以使用jdk、cglib两种代理方式
AOP就是动态代理的规范化,把动态代理的实现步骤,方式都定义好了,让开发人员用一种统一的方式,使用动态代理。
ioc的原理是依赖注入,底层是spring通过反射的方式实现。
IOC是使对象之间解耦合,AOP是使业务方法内部的各部分之间解耦合,将业务逻辑和非业务逻辑解耦合。
AOP编程术语
AOP概念:
面向切面编程,可通过运行期动态代理实现程序功能的统一维护的一种技术,利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
切面---Aspect
切面泛指交叉业务逻辑,上例中的事务处理,日志处理就可以理解为切面,常用的切面就是通知,实际是对主业务逻辑的一种增强。
切面就是我们增加的功能或者事务代码,就叫做切面。
一般是非业务的功能,常见的有日志、事务、统计信息、参数检查、权限验证
切面的特点:一般都是非业务方法, 可以独立使用。所以单独拿出去,通过动态代理实现,实现业务代码与非业务代码的解耦合!!
aop的底层实现就是动态代理
ioc的底层实现是反射
连接点--JoinPoint:
连接点指可以被切面织入的具体方法,**通常业务接口中的方法均为连接点。**就是某个类中的业务方法。比如上例中的doSome()和doOther(),连接点就是业务方法
连接点一般指一个方法
切入点----PointCut:
切入点指声明的一个或多个连接点的集合,通过切入点指定一组业务方法
被标记为final的方法是不能作为连接点和切入点的,因为最终的是不能被修改的,不能被增强的。
目标对象:我们要给哪个类的方法增加功能,这个类就是目标对象。
通知--Advice
表示切面功能执行的时间,在业务方法前还是后
动态代理很灵活,aop就是把动态代理规范化,让动态代理更好写。
AOP底层就是采用动态代理实现的,采用了两种动态代理:
- jdk的动态代理
- CGLIB的动态代理
AOP是面向切面编程,可通过运行期动态代理实现程序功能的统一维护的一种技术。
AOP是spring框架的一个重要内容,利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率
IOC也可以减低耦合度,降低的是类与类之间或者说对象与对象之间的耦合度,通过依赖注入的方式来实现,依赖注入就是生成对象并且给对象的属性赋值,把对象的创建和给对象的属性赋值这一些操作交给容器来管理,来实现对象与对象,类与对象之间的解耦合,比如类A里用到了类B,需要我们声明类B的实例化对象并且赋值,调用方法, 这就是依赖。依赖注入有set注入、构造注入、还有引用类型自动注入,set注入有简单类型注入、引用类型注入,还可以通过注解实现依赖注入,基于XML配置文件实现依赖注入的话,80%都是set注入,而通过注解实现依赖注入,底层是通过反射的机制来给对象的属性赋值,即使是private声明的属性,也能够通过反射访问到,没有用到set方法,把类里面的set方法注释掉也可以。(set注入和构造注入也是用到反射机制,也就是说IOC底层就是反射机制,set注入、构造注入、基于注解的注入都是反射机制)
总结:AOP--使业务逻辑各部分之间的耦合度降低
ioc----能够实现业务对象之间的解耦合,例如service和dao对象之间的解耦合
怎么理解面向切面编程?
- 需要在分析项目功能时,找出切面
- 合理地安排切面的执行时间,在目标方法前还是目标方法后
- 合理地安排切面执行的位置,在哪个类,哪个方法里去增加功能(切面)
说一个切面有三个关键的要素
- 切面的功能代码,一般是非业务功能代码
- 切面的执行位置--pointCut---一组业务方法的集合,指明在哪些业务方法里要加切面,要做功能的增强。
- 切面的执行时间---advice,在业务方法前还是业务方法后
AOP的实现
AOP的实现
AOP是一个规范,是动态代理的一个规范化,一个标准,就像IOC是一种思想,是一种控制反转的思想,目的就是为了将我们需要手动创建对象并且赋值的这些操作交给外部容器去处理,spring就基于这种思想有了实现,可以通过依赖注入的方式实现。IOC这种思想的实现就是依赖注入,底层是反射机制。
AOP这个规范化和标准也需要落地实现
aop的技术实现框架:
spring:spring在内部实现了aop规范,能做aop的工作
spring主要在事务处理时使用aop
我们项目开发种很少使用spring的aop实现,因为spring的aop比较笨重
aspectJ:一个开源的专门做aop的框架,用得最广泛的,我们主要使用这个框架实现aop
spring框架中集成了aspectJ框架,通过spring就能使用aspectj的功能
总结
ioc的实现
- xml依赖注入
- set注入
- 简单类型注入
- 引用类型注入
- 构造注入
- 引用类型自动注入
- set注入
- 注解依赖注入
aop的实现
- spring框架内部实现的aop
- aspectJ,spring中集成了aspectJ框架,可以直接用
- xml依赖注入
aspectJ实现aop的两种方式
- 使用xml的配置文件---spring事务管理的时候,使用这种方式
- 使用注解,我们在项目中要做aop功能,一般都使用注解,和ioc一样,aspectJ有5个注解
学习aspectJ框架的使用:
切面的执行时间,这个执行时间在规范中叫做Advice(通知)
在aspectj框架中使用注解表示的
- @Before
- @AfterReturning
- @Around
- @AfterThrowing
- @After
表示切面执行的位置,切入表达式
切入点表达式要匹配的对象就是目标方法(被代理对象的方法,可以说是业务方法,被代理对象也可以说是目标对象),所以execution表达式中明显就是方法的签名,注意,表达式中黑色文字表示可省略部分,各部分间用空格隔开。
什么时候需要用AOP
- 当要给系统中存在的类修改功能,但是原有类的功能不完善,原代码改不了的情况下,就是用aop增加功能
- 当要给项目中的多个类,增加一个相同的功能,使用aop
- 给业务方法增加事务,日志输出
aspectj实现aop的步骤
AspectJ基于注解的AOP实现
新建maven项目
加入依赖
- spring依赖
- aspectJ依赖
创建目标类:接口和它的实现类(实现类就是目标类)
接口中定义抽象方法,目标类定义方法的具体实现,目标类中的方法就作为目标方法也就是业务方法,就是连接点,多个连接点的集合就是切入点。
@Service(value = "someService") public class SomeServiceImpl implements SomeService { @Override public void doSome(String name, int age) { System.out.println("=======目标方法doSome()======" + "name:" + name + "\tage:" + age); } }
创建切面类:普通类。
在类的上面加入@Aspect
在类中定义方法,方法就是切面要执行的功能代码(业务功能之外的功能增强代码)
在方法的上面加入aspectj中的通知注解,例如@Before
有需要指定切入点表达式execution()
/** * 表示当前类是切面类,用来给我们的业务方法增加功能的类, * 在这个类中有切面的功能代码,这些功能代码,是非业务的增强功能代码 * 位置:在类定义的上面 */ @Aspect @Component(value = "myAspect") public class MyAspect { /** * 定义方法,方法是实现切面功能的 * 方法的定义要求: * 1.公共方法public * 2.方法没有返回值 * 3.方法名称自定义 * 4.方法可以有参数,也可以没有参数 * 如果有参数,参数不是自定义的,有几个参数类型可以使用 * 5. 需要在方法上面加注解表示切面执行时间 */ /** * @Before * 属性:value:是切入点表达式,表示切面功能的执行的位置 * 位置:在方法的上面 * 特点:1.在目标方法之前先执行的 * 2. 不会改变目标方法的执行结果 * 3. 不会影响目标方法的执行。 */ @Before(value = "execution(public void org.example.impl.SomeServiceImpl.doSome(String,int))") public void myBefore() { //就是切面要执行的非业务的增强功能代码 System.out.println("前置通知,切面功能:在目标方法之前输出时间:" + new Date()); } }
创建spring的配置文件:声明对象,把对象交给容器统一管理
声明对象可以使用注解或者xml配置文件<bean>
声明目标对象
- 可通过注解依赖注入
- 可通过xml配置方式依赖注入
声明切面类对象
- 可通过注解依赖注入
- 可通过xml配置方式依赖注入
声明aspectj框架中的自动代理生成器标签
自动代理生成器:用来完成代理对象的自动创建功能的。这样就不用写一堆代码来创建代理对象了
<!-- 把对象交给spring容器,由spring容器ApplicationContext统一创建,管理对象--> <context:component-scan base-package="org.example"/> <!-- 声明自动代理生成器:使用aspectj框架内部的功能,创建目标对象(被代理对象)的代理对象 创建代理对象是在内存中实现的,修改目标对象中的内存中的结构,创建为代理对象 所以代理对象就是被修改后的目标对象。 <aop:aspectj-autoproxy /> 会把spring容器中的所有目标对象(根据切面类的切面方法上的注解的参数的execution指定的切入点表达式,会知道哪些类是目标类,那么将这些目标类在内存中进行结构的修改,得到代理类。所以目标类就是代理类),一次性都生成代理对象 怎么知道目标对象呢?根据切面类的切入点表达式。如果切入点表达式写错了,那么找不到目标类,便不会将目标类修改结构为代理类 怎么知道切面类呢?根据@Aspect注解 --> <aop:aspectj-autoproxy />
注意:创建代理对象是在内存中实现的,修改目标对象中的内存中的结构,创建为代理对象,并加上新增功能
所以目标对象就是被修改后的代理对象。看起来是目标对象,其实是代理对象
代码如下:
@Test public void test01() { String config = "applicationContext.xml"; ApplicationContext applicationContext = new ClassPathXmlApplicationContext(config); // 从容器中获取目标对象 // 获取的这个目标对象(被代理对象)其实是被修改后的代理对象(看起来是目标对象,其实是代理对象) // 因为<aop:aspectj-autoproxy /> 会把spring容器中的所有目标对象,一次性都生成代理对象 // 而不使用aspectj框架的时候,是通过创建代理对象,代理对象,调用被代理对象的目标方法来做 //// 使用Proxy创建代理对象 // SomeService proxyInstance = (SomeService) Proxy.newProxyInstance(someService.getClass().getClassLoader(), // someService.getClass().getInterfaces(), // invocationHandler); // // 通过proxyInstance这个代理对象执行方法, 会调用handler中的invoke() // proxyInstance.doSome(); // 下面这个someService就是代理对象!!!!!!!!!!!是修改目标对象结构而来 SomeService someService = (SomeService) applicationContext.getBean("someService"); // 通过动态代理的对象执行方法,实现目标方法执行时,增强了功能 someService.doSome("lisi", 20); }
aspectj底层目前用的是jdk的动态代理。
aspectj框架的几种注解和参数
参数JoinPoint
注解:
- @Before
- @AfterReturning
- @Around
- @AfterThrowing
- @After
以上这几个注解都可以用JoinPoint这个参数
@Before
/** * 表示当前类是切面类,用来给我们的业务方法增加功能的类, * 在这个类中有切面的功能代码,这些功能代码,是非业务的增强功能代码 * 位置:在类定义的上面 */ @Aspect @Component(value = "myAspect") public class MyAspect { /** * 定义通知方法,方法是实现切面功能的 * 方法的定义要求: * 1.公共方法public * 2.方法没有返回值 * 3.方法名称自定义 * 4.方法可以有参数,也可以没有参数 * 如果有参数,参数不是自定义的,有几个参数类型可以使用 * 5. 需要在方法上面加注解表示切面执行时间 */ /** * @Before * 属性:value:是切入点表达式,表示切面功能的执行的位置 * 位置:在方法的上面 * 特点:1.在目标方法之前先执行的 * 2. 不会改变目标方法的执行结果 * 3. 不会影响目标方法的执行。 */ @Before(value = "execution(public void org.example.impl.SomeServiceImpl.doSome(String,int))") public void myBefore() { //就是切面要执行的非业务的增强功能代码 System.out.println("前置通知,切面功能:在目标方法之前输出时间:" + new Date()); } }
@AfterReturning
- 如果返回值类型是基本数据类型和String(Spring中把Java中的基本数据类型和String认为是简单类型,字符串是不可变的),在后置通知方法中,即使把拿到的目标方法即业务方法的返回值改了,对最后通过业务方法拿到的返回值也是没有影响的。
- 如果返回值类型是引用数据类型,那么即使值已经被返回了,但是如果后面修改了返回值这个对象引用中(堆空间中实例化对象)的内容,结果就会发生改变
@Around
/** * 环绕通知方法的定义格式 * 1.public * 2.必须有一个返回值,推荐使用Object * 3.方法名称自定义 * 4. 方法有参数,固定的参数ProceedingJoinPoint,是一个接口,继承于JoinPoint(接口之间可以实现多继承) */ /** * @Around 环绕通知 * 属性:value 切入表达式execution * 位置:在方法定义的上面 * 特点: * 1.是功能最强的通知 * 2.在目标方法的前和后都能增强功能,所以叫环绕通知 * 3.控制目标方法是否能被调用执行 * 4.修改原来的目标方法执行结果,影响最后的调用结果 * 环绕通知等同于jdk动态代理的InvocationHandler接口 * * 参数:proceedingJoinPoint等同于InvocationHandler接口的实现类的方法invoke的参数method * 作用:执行目标方法 * * 返回值:就是目标方法的执行结果,这个执行结果可以被修改 * 如果是@AfterReturning,对于基本数据类型和String来说,结果不能修改,但是对于引用类型来说,最终结果会被修改 */ @Around(value = "execution(* *..SomeServiceImpl.doFirst(..))") public Object myAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { // 实现环绕通知 Object result; System.out.println("环绕通知;在目标方法之前,输出时间:" + new Date()); // 1. 目标方法调用 result = proceedingJoinPoint.proceed(); System.out.println("环绕通知;在目标方法之后,提交事务"); return result; }
环绕通知经常做的是事务
- 在目标方法之前开启事务
- 执行目标方法
- 在目标方法之后提交事务
AfterThrowing:异常通知----代码中出现了异常,便会执行这个增强功能的代码
@After
一般做资源清除工作的。代码总是会被执行,相当于try-catch-finally,finally代码块里面的。
如果目标方法执行有异常,最终通知方法仍然会被执行,即使目标方法有异常,相当于finally
@Pointcut定义切入点
这不是通知注解,是辅助注解
此时mypt()就是切入点表达式的别名
注意:@PointCut是定义在自定义方法上,而不是通知增强方法上,要明确@PointCut不是通知注解,而是一个辅助注解!!
目标类没有接口,使用cglib动态代理,spring框架会自动应用cglib
有接口既可以使用cglib代理,也可以使用jdk的动态代理
但是没有接口只能使用cglib动态代理(spring会自动应用,代码写法都一样)
第四章:Spring集成MyBatis
概述
把mybatis框架和spring框架集成在一起,像一个框架一样使用,用的技术是IoC
为什么ioc能把mybatis和spring集成起来呢?
因为ioc能创建对象,可以把mybatis框架中的对象交给spring容器统一创建、管理。
开发人员从spring中获取对象,开发人员就不用同时面对两个或多个框架了,就面对一个spring
mybatis使用步骤
定义dao接口,StudentDao**(不定义实现类)**
定义mapper文件,StudentDao.xml
定义mybatis的主配置文件 mybatis.xml
创建dao的代理对象,因为实现类通过mybatis动态代理来创建,并且调用SqlSession访问数据库的方法
StudentDao dao = SqlSession.getMapper(StudentDao.class)
所以我们需要SqlSession,那么就需要SqlSessionFactory对象的openSession()方法。
我们会使用独立的连接池类替换mybatis默认的,把连接池交给spring创建,用阿里的druid
通过以上说明,我们需要让spring创建以下对象:
- 独立的连接池类的对象,使用阿里的druid连接池
- SqlSessionFactory对象
- 创建dao接口的实现类的对象。
我们需要学习以上这三个对象的创建语法,一般不需要让spring容器创建对象的是实体类。
目前使用xml的bean标签,因为以上三个类没有原代码,用不了注解,以后可以用注解
dao接口的实现类都没有原代码。为什么?因为mybatis框架是通过动态代理来自动帮我们创建dao接口的实现类的对象,我们在使用mybatis的时候,并没有写dao接口的实现类,对象是自动创建。现在我们是通过spring来集成mybatis
创建规则是固定的。
spring和mybatis集成步骤
新建maven项目
添加maven依赖
- spring依赖
- mybatis依赖
- mysql驱动connector
- spring的事务的依赖
- mybatis和spring集成的依赖:mybatis官方提供的,用来在spring项目中创建mybatis的SqlSessionFactory、dao接口的实现类的对象的
<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.4</version> </dependency> <!-- mybatis和spring集成的依赖,mybatis提供的 --> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>1.3.1</version> </dependency> <!--阿里公司提供的德鲁伊连接池依赖--> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.1.12</version> </dependency> <!--mysql驱动--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.16</version> <scope>runtime</scope> </dependency>
创建实体类bean
创建dao接口(不用创建的dao接口的实现类)和SQL 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> <setting name="logImpl" value="STDOUT_LOGGING"/> </settings> <!-- 设置别名 --> <typeAliases> <!-- name:实体类所在的包名 --> <package name="org.example.domain"/> </typeAliases> <mappers> <!-- name:包名,这个包中的所有文件一次都能加载。 --> <package name="org.example.dao"/> </mappers> </configuration>
发现
<environments>
标签没有了,就是把连接池对象交给spring容器来创建,连接信息写在spring容器配置文件中。创建一个Service接口和实现类,属性是dao(通过service调dao是开发中的主要流程)
创建spring的配置文件:声明mybatis的对象交给spring创建。
- 数据源-Druid连接池,数据源就是Connection
- SqlSessionFactory
- dao接口的实现类的对象(mybatis之前就是通过动态代理自动创建,不需要我们手动创建)
- 声明自定义的service
创建测试类,获取Service对象,调用dao,完成对数据库的访问(access)
注意:ioc可以是基于xml的依赖注入,也可以是基于注解的,那么基于xml的依赖注入,在给创建的对象(创建的是对象,类还是需要自己写的)的属性赋值这一步,需要调用类的set方法(set注入),所以set方法一定要写,如果通过注解给创建的对象的属性赋值,就不需要用set方法,set方法也可以不写。
不管是基于注解还是基于XML配置文件,IOC底层都是反射机制。
本来类是需要我们自己写的,只是把类的对象的创建交给spring容器来创建,并且给属性赋值,但是mybatis框架中,是靠动态代理来进行dao接口的实现类的创建,实现类根本就不需要我们自己写,所以我们拿不到dao接口的实现类的源码,因为根本就不写,mybatis是靠动态代理来创建,它会自动创建dao接口的实现类的对象(通过SqlSession的getMapper()方法),并且调用SqlSession的访问数据库的方法。
我们没有dao接口的实现类的原代码,所以用不了注解,所以用基于xml的依赖注入,而基于xml的依赖注入,80%都是用的set注入,20%用的是构造注入。我们在这里用set注入没有问题
spring的配置文件
创建连接池对象
<!-- 声明数据源,作用:连接数据库,用druid来代替之前mybatis的environment部分 --> <bean id="myDataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close" > <!-- set注入给DruidDataSource提供连接数据库的信息,依赖注入分为创建对象和给对象赋值两部分,这里的set注入指给对象赋值 --> <!-- 如果是通过注解的方式实现依赖注入即创建对象和给对象赋值,那么不需要在类里面写set方法,底层是通过反射的机制给属性赋值(即使是set注入,底层也是通过反射的机制)--> <property name="url" value="jdbc:mysql://localhost:3306/mybatistest?serverTimezone=Asia/Shanghai" /> <property name="username" value="root" /> <property name="password" value="fufu" /> <property name="maxActive" value="20" /> </bean>
像上面的name属性的值都不是随便写的,都是有类里面写了对应的set方法(这是set注入,必须类里面有set方法,才不会报错!!),因为这是通过set来实现注入。
依赖注入又分为基于xml的和基于注解的,其中基于xml的分为set注入和构造注入。这里用的是set注入。
没有用基于注解的原因:没有原代码,没有类的代码,没有创建dao接口的实现类,因为mybatis不需要我们这么做!没有原代码,那么就不能写注解即不能在类的上方加注解的方式来创建对象,就通过配置bean的方式来实现set注入
set注入一定在类里面有对应的set方法,不然spring会报错
创建SqlSessionFactory对象,为了得到SqlSession对象,通过SqlSession的getMapper()方法便可以得到dao接口的实现类的对象
<!-- 声明的是mybatis中提供的SqlSessionFactoryBean类,这个类的内部是创建SqlSessionFactory的 --> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <!-- set注入,把数据库连接池对象,赋值给dataSource属性,这里是set注入的引用类型变量注入 --> <property name="dataSource" ref="myDataSource"/> <!--mybatis主配置文件的位置,根据主配置文件才能创建出SqlSession,而mybatis主配置文件指定了sql映射文件即mapper文件的位置,根据mapper文件,才能创建出对应的dao接口的实现类的对象 --> <!--configLocation是spring提供的,用来读取配置文件的,赋值的前面一定要加上classpath:来表示文件的位置,classpath表示类路径下 --> <property name="configLocation" value="classpath:mybatis.xml"/> </bean> <!-- 以上两个property加一块,可以代表原来的主配置文件信息,只用mybatis的时候,通过主配置文件创建SqlSessionFactory -->
创建dao接口的实现类的对象
<!-- 创建dao接口的实现类的对象,使用SqlSession的getMapper()--> <!--MapperScannerConfigurer:在内部调用getMapper(),生成每个dao接口的代理对象--> <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <!-- 指定SqlSessionFactory对象的id--> <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" /> <!-- 指定包名,dao接口所在的包名 MapperScannerConfigurer会扫描这个包中的所有接口,对于每个接口都会执行一次SqlSession.getMapper()方法,得到每个dao接口的实现类的对象 创建好的dao实现类对象是放在spring容器中的,实际底层是放在concurrentHashMap中的 --> <property name="basePackage" value="org.example.dao" /> </bean>
注意:
spring和mybatis整合在一起使用,事务是自动提交的,无需执行SqlSession的commit()
可以用配置文件的属性,将连接信息配置在配置文件中
<context:property-placeholder location="classpath:jdbc.properties"/> <!-- 使用属性配置文件中的数据 格式:${} --> <bean id="dataSource" 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>
第五章:事务管理
概述
事务是指一组sql语句的集合,集合中有多条sql语句,可能是insert、update、select、delete,我们希望这些多个sql语句都能成功,或者都失败,这些sql语句的执行是一致的,作为一个整体执行(原子性、一致性)
一致性是指数据库经过一个事务提交之后,从一个一致性状态到另一个一致性状态,保持这种一致性。
什么时候想到用到事务?
当我们的操作涉及到多个表,或者多个sql语句的insert、update、delete,这些语句需要批量执行。需要保证这些语句都是成功才能完成我们的功能,或者都失败,保证数据库状态的一致性。
在Java代码中,写程序来控制事务,事务应该放在哪里呢?
我们是在业务方法中,就是业务层,即Service接口的实现类里写了方法去调用dao层的访问数据库的方法,那么每调用一个dao层的方法,就是执行一条sql语句,那么我们可能在service层调用多个dao层的方法,即执行多条sql语句,这个时候就需要事务了,所以事务是写在service层,即service接口的实现类里
在开发中,事务放在service层(service)的业务方法里,因为业务方法可能会涉及到多个dao的调用,或者说调用多个dao层的方法,即执行多个sql语句
怎么处理事务
- jdbc访问数据库,处理事务,Connection conn; conn.commit(); conn.rollback();
- mybatis访问数据库,sqlSession.commit();sqlSession.rollback();
- spring和mybatis整合在一起使用,事务是自动提交的,无需执行SqlSession的commit(),事务的回滚也交给spring处理
第四点说的处理事务的方式,有什么不足?
- 不同的数据库访问技术,处理事务的对象方法不同。jdbc用的是Connection对象,mybatis用的是SqlSession,需要了解不同数据库访问技术使用事务的原理
- 掌握多种数据库中事务的处理逻辑,什么时候提交,什么时候回滚
- 处理事务的多种方法
总结:多种数据库的访问技术或者说数据库访问框架有不同的事务处理机制、对象、方法
怎么解决?
统一机制,通过spring解决
spring提供了一种处理事务的统一模型,做成了一个统一的步骤或者说方式,来完成多种不同数据库访问技术的事务处理
使用spring的事务处理机制,可以完成hibernate、mybatis访问数据库的事务处理
spring,将不同数据库处理事务的方式统一了起来,开发人员只需要面对spring处理事务的方式。通过接口和实现类的方式来做,开发人员相当于只需要面对接口,这是面对接口编程,不同类处理事务的方式封装在这个统一接口的不同实现类里面。
spring的事务处理模型:
抽象了不同数据库访问技术的事务处理各个方面,定义了事务的处理步骤,这种框架的思想就类似于JDBC,JDBC类似于一个访问数据库的接口规范,我们面向jdbc编程,将访问不同数据库的步骤统一了起来,各种不同数据库的具体访问方法相当于基于jdbc这个规范的实现,而我们不需要考虑,只需要面向JDBC!!
不同的数据库访问技术的事务处理都交给spring来管理,开发人员只需要访问spring就可以了
这种叫声明式事务:把事务的相关的资源和内容都提供给spring,spring就能处理事务的提交和回滚了,几乎不用代码
spring的事务处理实现
spring的事务处理实现
spring处理事务的模型,使用的步骤都是固定的,把事务使用的信息提供给spring
spring内部提交、回滚事务,使用的事务管理器对象,代替我们完成commit(),rollback()
事务管理器是一个接口和这个接口的众多实现类,事务管理器是PlatformTransactionManager接口对象
这个接口定义了事务重要方法:commit() rollback(),接口对应于不同数据库访问技术有不同的实现类,这些实现类有这两个重要方法的重写
实现类:spring把每一种数据库访问技术对应的事务处理类都创建好了
总结:接口是统一的,针对于不同的数据库访问技术有不同的实现类,spring都创建好了
- mybatis访问数据库---spring创建好的是DataSourceTransactionManager
- hibernate访问数据库---spring创建好的是HibernateTransactionManager
需要告诉spring我们用的哪种数据库访问技术,怎么告诉spring?
声明数据库访问技术对应的事务管理器接口的实现类,在spring配置文件中使用<bean>
例如,我们要用mybatis访问数据库,那么在xml配置文件中
我们的业务方法需要什么样的事务类型,说明需要的事务类型
说明方法需要的事务:
- 事务的隔离级别:有4个值
事务的超时时间:
表示一个方法的最长执行时间, 如果方法执行时超过了时间,事务就回滚
单位是秒,整数值,默认是-1,一般做项目不设置这个值
事务的传播行为
控制业务方法是不是有事务的,是什么样的事务的
有7个传播行为,表示我们的业务方法调用时,事务在方法之间是如何使用的。
事务传播行为:
所谓事务传播行为是指,处于不同事务中的方法在相互调用时,执行期间事务的维护情况,如A事务中的方法doSome()调用B事务中的方法doOther(),在调用执行期间事务的维护情况,就称为事务传播行为。
事务的7个传播行为:
PROPAGATION_REQUIRED
指定的方法必须在事务内执行,若当前存在事务,就加入到当前事务中,若当前没有事务,则创建一个新事务,这种事务传播行为是最常见的选择,也是Spring默认的事务传播行为
说明加了PROPAGATION_REQUIRED,一定有事务,因为没有的话,会新建事务
PROPAGATION_REQUIRES_NEW
如果PROPAGATION_REQUIRES_NEW加在一个方法上,那么这个方法总是新建一个事务,若当前存在事务,就将当前事务挂起,直到新事务执行完毕。不用别人提供的事务,就用自己新建的。
有没有事务都新建。
PROPAGATION_SUPPORTS
如果PROPAGATION_SUPPORTS加在一个方法上,说明该方法支持事务,但是如果当前没有事务,也没有影响,也可以以非事务方式执行,查询操作(读操作)就是有事务没有事务都可以
PROPAGATION_MANDATORY
PROPAGATION_NESTED
PROPAGATION_NEVER
PROPAGATION_NOT_SUPPORTED
spring提交事务、回滚事务
当业务方法执行成功,没有异常抛出,当方法执行完毕,spring在方法执行后,提交事务,自动提交,自动调用事务管理器的commit(),不需要写代码
当业务方法抛出运行时异常或ERROR,spring执行回滚,自动调用事务管理器的rollback()方法
运行时异常的定义:RuntimeException和他的子类都是运行时异常,例如NullPointException,NumberFormatException
当业务方法抛出非运行时异常,主要是受查异常时,默认提交事务。
非运行时异常:就是指的我们写代码时,必须处理的异常,例如IOException、SQLException
而运行时异常一般是不处理的
总结spring的事务
管理事务的是spring的事务管理器接口和他的实现类(实现类是针对不同数据库的事务处理机制,那么有不同的实现类)
spring的事务是一个统一模型
- 指定要使用的事务管理器实现类,使用<bean>
- 指定哪些类,哪些方法需要加入事务的功能
- 指定方法需要的隔离级别、传播行为、超时时间
注意:
在service层,接口和实现类都是要创建的
在dao层,只需要创建接口,不需要创建实现类,dao层的接口的实现类的对象通过mybatis动态代理创建(SqlSession.getMapper()),并调用SqlSession访问数据库的方法
但是不管是service层还是dao层,类的对象,都是不用手动实例化的,都交给spring容器来做,依赖注入,这是IOC思想
service层会创建实现类而dao层不需要创建实现类,但是他们的共同点是都没有创建对象!对象的创建交给spring来做
spring框架中提供的事务处理方案
1. 中小型项目 @Transactional---spring自己实现的aop
spring框架自己用aop实现给业务方法增加事务的功能,使用@Transactional注解增加事务
@Transactional注解是spring框架自己的注解,放在public方法的上面,表示当前这个方法具有事务,可以给注解的属性赋值,表示具体的隔离级别,传播行为,异常信息等。
@Transactional的所有可选属性,写在注解后的括号里面()
使用@Transactional的步骤
需要声明事务管理器接口的对象(对象的创建仍然是交给spring来处理,用spring这个框架,对象的创建都交给他,除了一些实体类)
开启事务注解驱动,告诉spring,要使用注解的方式管理事务,spring就会使用aop机制,创建@Transactional所在的类的代理对象,给方法加入事务的功能(aop的底层是采用动态代理实现)
spring给业务方法加入事务:
在业务方法执行之前,先开启事务,在业务方法之后,提交或回滚事务,使用的是aop的环绕通知!
<!--1. 使用spring的事务处理,声明事务管理器接口的实现类对象 --> <bean id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="myDataSource"/> </bean> <!-- 2. 开启事务注解驱动,告诉spring使用注解来管理事务 --> <!-- dataSourceTransactionManager: 事务管理器接口的实现类的对象的id --> <tx:annotation-driven transaction-manager="dataSourceTransactionManager"/>
在公共方法或类上面加入@Transactional注解,
一般写在需要事务的方法上,方法是公共方法,这个方法是业务方法,因为加事务一般是加在业务方法上,有时也说目标方法,业务方法才采用aop新增功能,原来的业务方法里面就专心处理业务逻辑。业务方法是service接口的实现类内部的方法。service接口的实现类的对象,是通过aop采用动态代理的方式创建,在原有的目标对象上面,结构做了修改,最后创建出来的是代理对象,看上去是目标对象,但实际上是内部结构修改了的代理对象。这是aop动态代理。所以aop的实现机制是动态代理,而ioc是依赖注解,底层是反射。动态代理创建的是代理对象。如果不采用aop的方式,自己实现,那么代理对象和被代理对象是分开的,采用aop就不是。(aop是动态代理的规范)
@Transactional( propagation = Propagation.REQUIRED, isolation = Isolation.DEFAULT, readOnly = false, rollbackFor = { NullPointerException.class, NotEnoughException.class } ) @Override public void buy(int gid, int nums) { System.out.println("buy方法开始"); // 1. 记录销售的信息,向sale表添加记录 Sale sale = new Sale(); sale.setGid(gid); sale.setNums(nums); saleDao.insertSale(sale); //2. 更新库存 // 现有商品 Good good = goodsDao.selectGood(gid); if (good == null) { throw new NullPointerException(gid + " 商品不存在"); } else if (good.getAmount() < nums) { throw new NotEnoughException(gid + " 商品库存不足"); } // 要买的商品 Good buyGood = new Good(); buyGood.setId(gid); buyGood.setAmount(nums); goodsDao.updateGoods(buyGood); System.out.println("buy方法完成"); }
rollbackFor:表示发生指定的异常一定回滚
处理逻辑是:
spring框架会首先去检查方法抛出的异常是不是在rollbackFor属性值中,如果异常在rollbackFor列表中,不管是什么类型的异常,一定回滚,不管是非运行时异常还是运行时异常,都回滚,因为这是rollbackFor指定的!
如果抛出的异常不在rollbackFor列表中,spring会判断异常是不是运行时异常,如果是运行时异常,那么一定回滚,如果是非运行时异常,那么默认提交
前两步都是在配置文件中实现的
注意:
这是spring框架自己用aop实现的给业务方法增加事务功能的方式,aop是基于动态代理的,ioc是基于依赖注入,底层是基于反射的。spring采用了aop给业务方法增加事务功能,那么就会用到动态代理
而动态代理,使用<aop:aspectj-autoproxy /> 会把spring容器中的所有目标对象(怎么知道哪些对象是目标对象呢,根据切入点表达式找到目标方法,目标方法所在类的对象便是目标对象),一次性都生成代理对象
使用aop,动态代理:创建代理对象是在内存中实现的,修改目标对象中的内存中的结构,创建为代理对象,并加上新增功能,所以目标对象就是被修改后的代理对象。看起来是目标对象,其实是代理对象。(和手动创建InvocationHandler,创建proxy那种方式不同,那种方式代理对象和被代理对象就是分开的。)
面向切面编程有一个很重要的点就是,为什么知道给哪个方法加切面也就是新增功能呢?因为通知方法那里会写execution来指定切入表达式,指明的就是哪个方法需要做功能的新增。
2. 大型项目---AspectJ实现AOP,基于配置管理事务
采用aspectJ实现aop,只不过改为配置文件的方式,把原先注解的代码移到配置文件中
需要大量的配置事务,使用aspectj框架功能,在spring配置文件中,声明类,方法需要的事务。
这种方式业务方法和事务配置完全分离
spring管理事务有两种方式
- @Transactional---spring自己实现aop
- aspectj实现aop---我们自己实现aop,之前就说过,aop是面向切面编程,那么可以在不改动原有代码的情况下增加新的事务功能,所以实现事务当然要使用aop机制
不管是哪种方式,都要实现aop,aop的底层是动态代理
不管是哪种方式,都要声明事务管理器接口的实现类对象,这是spring处理事务的根本---将不同数据库访问技术的事务处理都统一起来---通过事务管理器接口这种规范,针对于不同的数据库访问技术有不同的实现类
实现步骤--都是在xml配置文件中实现:
要使用的是aspectj框架,需要加入依赖
声明事务管理器对象---不管采用哪种方式实现事务的管理,只要通过spring来做,必须使用事务管理器,这是spring实现事务管理的机制,不管是通过@Transaction(spring自己实现aop)还是通过aspectj实现aop(我们自己实现aop)
<!-- 声明式事务,和业务代码完全分离--> <!-- 1.声明事务管理器对象 --> <bean id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="myDataSource"/> </bean>
声明方法需要的事务类型(配置方法的事务的属性---包含事务隔离级别,传播行为,超时时间)
<!-- 2.声明业务方法需要的事务属性-配置传播行为、超时、隔离级别 --> <!-- id: 自定义名称,表示<tx:advice></tx:advice>之间的配置内容 transaction-manager:事务管理器对象的id --> <tx:advice id="myAdvice" transaction-manager="dataSourceTransactionManager"> <!-- 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,org.example.exception.NotEnoughException"/> </tx:attributes> </tx:advice>
经过上一步,配置好了哪些方法,使用事务,并且配置好了事务属性。但是并不知道这些方法是在哪些类里面,所以还需要配置类。
配置aop,指定哪些类要创建代理---使用<aop:aspectj-autoproxy /> 会把spring容器中的所有目标对象,一次性都生成代理对象
为什么要使用代理对象?
因为我们就是为了不改变原代码,而可以添加新的功能比如事务和日志。
如果不使用代理对象去调用原service实现类的方法,那么就是用原对象就是被代理对象自身去掉,虽然没有新增代码,但是也不可能新增功能,因为我们的目的就是不在业务方法里添加新的代码,而实现新增功能
所以必须使用代理对象去调目标对象的方法,就新增了功能,又没有改目标类的代码(目标类是service实现类),自己写的话,那么会调用InvocationHandler里的invoke方法,aspectj实现aop来做的话,获得的目标对象就是代理对象,目标对象内部结构修改之后,就是代理对象!!
<!--3. 配置aop--> <aop:config> <!-- 配置切入点表达式:指定哪些包中哪些类,要使用事务 id:切入点表达式的名称,唯一值 expression:切入点表达式,指定哪些类要使用事务,aspectj会创建代理对象,代理对象是改造目标对象而来,通过这种方式,获得的目标对象,其实就是代理对象。 是目标对象改造而来 service是处理业务逻辑的,所有的service的包里面的类的定义的方法应该具有事务 --> <aop:pointcut id="servicePt" expression="execution(* *.service..*.*(..))"/> <!-- 配置增强器:关联advice和pointcut advice-ref:通知,上面tx:advice那里的配置 pointcut-ref:切入点表达式的id --> <aop:advisor advice-ref="myAdvice" pointcut-ref="servicePt"/> </aop:config>
第六章:在web项目中使用容器对象
web项目是在tomcat服务器运行的,tomcat一启动,项目就是一直运行的。
每一次调用controller层的servlet类的doPost方法或者doGet方法,都会创建spring的容器对象applicationContext,如果客户端在一秒钟之内发了很多次请求,比如100次,那么一秒钟之内就要调用100次servlet类的doPost方法,就要新创建100次spring的容器对象,这显然是不合理的。
spring容器对象只需要创建一次,是单例的!!!
需求:在web项目中,容器对象只需要创建一次,把spring容器对象放入到全局作用域ServletContext中,这样这个容器就可以在多个Servlet类中使用这个容器对象
方法:使用监听器,当全局作用域ServletContext对象被创建时,创建容器,存入ServletContext
监听器作用:
创建spring容器对象applicationContext,执行
把容器对象放入到ServletContext全局作用域,
ServletContext.setAttribute(key, ctx);
监听器可以自己创建,也可以使用框架中提供好的ContextLoaderListener
一个web工程,只有一个ServletContext对象实例,不管调用几次getServletContext都是返回同一个ServletContext对象实例,说明是单例模式
ServletContext对象是一个域对象
域对象,是可以像Map一样存取数据的对象,叫域对象
ServletContext对象是在web工程启动的时候创建,工程停止的时候销毁
总结:使用监听器+ServletContext,实现spring容器的单例创建
为了使用监听器对象,需要加入如下依赖:
总结步骤:
- 配置监听器,配置了就创建好了容器对象,创建好的容器对象放在ServletContext里
- 通过调用工具类的方法,取得容器对象