-
Spring是一个使用java语言编写的轻量级开源框架,解决了项目中的一些通用问题,例如依赖注入、切面织入等,同时还提供了项目中各种技术的一站式的解决、整合方案
-
Spring框架中的所有组件模块都依赖于Spring提供的俩个基础功能:是控制反转( IOC)和面向切面(AOP)
-
注意:Spring其他项目(Spring Boot、Spring Data、Spring Cloud、Spring Security、Spring Session、Spring Integration 等等)都会依赖于核心项目 Spring Framework 的中的组件模块
-
我们可以根据自己的需求,来选择引入并使用Spring Framework中不同模块的功能,并不需要一次引入所以模块对应的jar包
1、Spring简介
-
类交给Spring容器管理的方式:使用注解、配置xml、java代码
-
xml配置的好处:配置集中在同一个文件中
-
注解配置的好处:虽然写得少了,但是不好维护,配置分散
1.0、Spring公司的顶级项目
-
Spring Framework
-
Spring Boot
-
Spring Data
-
Spring Cloud
1.1、Spring优势
-
容器性质,方便解耦,简化开发(核心) Spring提供的 IOC 容器,可以将对象间的依赖关系交由Spring容器进行控制,避免硬编码所造成的过度程序耦合。
-
AOP编程的支持(核心) 通过Spring的 AOP 功能,方便进行面向切面的编程,许多不容易用传统OOP实现的功能可以通过AOP轻松应付。
-
声明式事务的支持 通过声明式方式灵活的进行事务的管理,提高开发效率和质量
-
集成各种优秀框架 Spring不仅不排除其他各种优秀的框架,同时对这些框架(Mybatis、Quartz等)提供了很好的支持
-
降低JavaEE API的使用难度 Spring对JavaEE API 进行了封装,使得这些API降低了使用难度
1.2、Spring-Framework模块
-
Spring-framework 框架是一个分层架构,它由大约20个模块组成
1.2.1、Core Container,核心容器
-
Core Container包含有Core、Beans、Context和Expression Language模块
-
Core和Beans模块:框架的基础部分,提供反转控制(IOC)和依赖注入(DI)特性。
-
Context模块:构建于Core和Beans模块基础之上,提供了对国际化、事件传播、资源加载和对Context的透明创建的支持。ApplicationContext接口是Context模块的关键。
-
Expression Language模块:Expression Language模块提供了一个强大的表达式语言用于在运行时查询和操纵对象
1.2.2、Data Access/Integration,数据访问/集成部分
-
Data Access/Integration层包含有JDBC、ORM、OXM、JMS和Transaction模块
-
JDBC模块:该模块提供了一个JDBC抽象层,它可以消除冗长的JDBC编码和解析数据库厂商特有的错误代码。
-
ORM模块:该模块为流行的对象-关系映射API——JPA、JDO、Hibernate、Mybatis等提供了一个交互层。利用ORM封装包,可以混合使用所有Spring提供的特性进行O/R映射。如前边提到的简单声明性事物管理。
-
OXM模块:该模块提供了一个对Object/XML映射实现的抽象层,Object/XML映射实现包括JAXB、Castor、XMLBeans、JiBX和XStream。
-
JMS模块:JMS(Java Messaging Service)模块主要包含了一些制造和消费消息的特性。
-
Transaction模块:该模块支持编程和声明性的事物管理,这些事物类必须实现特定的接口,并且对所有 的POJO都适用。
1.2.3、Web
-
Web层包含了Web、Servlet、WebSocket、Portlet模块
-
Web模块:该模块提供了基础的面向web的集成特性。例如多文件上传、使用servlet listeners初始化IoC容器以及一个面向web的应用上下文。它还包含Spring远程支持中web相关部分。
-
Servlet模块:该模块包含Spring的model-view-controller(MVC)实现。Spring的MVC框架使得模型范围内的代码和web forms之间能够清楚的分离开来,并与Spring框架的其他特性集成在一起。
-
WebSocket模块:该模块提供了对WebSocket的支持。
-
Portlet模块:提供了用于portlet环境的MVC的实现,Portlet是和Servlet类似的一种web技术。
1.2.4、AOP和Instrumentation
-
AOP模块:提供了一个符合AOP联盟标准的面向切面编程的实现,它让你可以定义方法拦截器和切点,从而将逻辑代码分开,降低它们之间的耦合性。
-
Aspects模块:提供了对AspectJ的集成支持,Spring框架的AOP功能是使用AspectJ框架来实现的。
-
Instrumentation模块提供了class instrumentation支持和classloader实现,使得可以在特定的应用服务器上使用。
1.2.5、Test
-
Test模块:支持使用JUnit和TestNG对Spring组件进行测试
1.2.5.1、SpringTest(Spring测试)
-
先导入spring-test的jar包,它可以集成JUnit4或JUnit5
-
实体类上有@Data和@Compnent,
-
配置类上有@Configuration,@ComponentScan("com.briup.bean")
-
测试类上有
-
JUnit4:@RunWith(SpringJUnit4ClassRunner.class)、@ContextConfiguration(classes=TestConfig.class)
-
JUnit5:@ExtendWith(SpringExtension.class) 、@ContextConfiguration(classes=TestConfig.class)
-
JUnit5中这两个可以变成一个@SpringJUnitConfig(classes = TestConfig.class)
-
-
-
测试类中使用
@Autowired private Student student; @org.junit.Test 或 @org.junit.jupiter.api.Test //前一个是4,后一个是5 public void test() { System.out.println(student); }
1.2.5.1.1、测试注解简介
-
@RunWith(SpringJUnit4ClassRunner.class) : 指明使用什么类来创建容器,也可以写成 @RunWith(SpringRunner.class)
-
@ContextConfiguration(classes=TestConfig.class) :
-
location 导入具体的配置文件,如果有的话,例如,@ContextConfiguration(locations = "classpath:applicationContext.xml")
-
classes 导入具体的配置类,如果有的话
-
-
@ExtendWith(SpringExtension.class) 是配置JUnit Jupiter使用Spring支持扩展测试
-
@SpringJUnitConfig(classes = TestConfig.class),集合ExtendWith和ContextConfiguration 这两个注解
# 2、Spring IOC
-
三层架构虽然可以在一定程度上解耦,但是层与层之间耦合度较高,不符合OCP原则(有新增,无修改),改web就得改service,改service就得改dao,会使得创建和维护变得复杂
-
IOC容器:更好的解耦,在启动项目的过程中,提前做好以下工作:
-
创建一个容器(例如一个Map集合即可)
-
把项目中需要的对象提前创建好,并且存入该容器中
-
每一个模块需要用到对象,提前就给该模块提供好
-
-
IOC (Inverse of Control) 控制反转,将对象的创建以及对象依赖关系反转给Spring 容器,程序本身不进行维护。程序员只需要使用,不要创建对象,也不用管理对象
-
IOC底层原理:xml解析、工厂模式、反射
-
Spring对Bean的管理:
-
Bean管理操作
-
基于xml配置文件方式实现
-
基于注解方式实现
-
-
Spring创建对象
-
Spring注入属性
-
-
Spring提供IOC容器两种实现方式:
-
BeanFactory:IOC 容器基本实现,是 Spring 内部的使用接口,不提供开发人员进行使用
-
加载配置文件时候不会创建对象,获取对象(使用)才去创建对象
-
-
ApplicationContext:BeanFactory 接口的子接口,提供更多更强大的功能,一般由开发人员进行使用
-
加载配置文件时候就会把在配置文件对象进行创建
-
-
Spring 如何解决循环依赖的?
什么是循环依赖?
比如 A 依赖 B,B 依赖 A,这就是循环依赖。
class A { public B b; } class B { public A a; }
为什么循环依赖在 Spring 中需要解决?
循环依赖如果不在 Spring 中是无所谓的,但是因为 Spring 需要把其注册成为 bean,那么循环依赖就会导致这种情况:要创建 A bean -> A bean 中依赖了 B,则触发 B bean 创建 -> 但 B bean 又依赖了 A bean,可是 A bean 就在创建中。
如何解决单例模式下的循环依赖?
通过 三级缓存 解决循环依赖问题。即将 A 的原始对象放入三级缓存,那么当 A 需要 B,触发 B bean 的创建的时候,B 就可以得到 A 的原始对象,从而使得 B 完成创建。
Spring 单例对象的初始化主要分为三步:createBeanInstance实例化、populateBean填充属性、initializeBean初始化。
那么如果 A 的某个 field 或者 setter 依赖了 B 的实例对象,同时 B 的某个 field 或者 setter 依赖了 A 的实例对象这种循环依赖的情况。
-
A 首先完成了初始化的第一步(CreateBeanINstance实例化),并且将自己提前曝光到 singletonFactories 中。
-
此时进行初始化的第二步,发现自己依赖对象 B,此时就尝试去 get(B),发现 B 还没有被 create,所以走 create流程,
-
然后 B 在初始化第一步的时候发现自己依赖了对象 A,于是尝试 get(A),一级级尝试获取。先尝试一级缓存 singletonObjects(肯定没有,因为 A 还没初始化完全),然后尝试二级缓存 earlySingletonObjects (也没有),最后尝试三级缓存 singletonFactories,由于 A 通过 ObjectFactory 将自己提前曝光了,所以 B 能够通过 ObjectFactory.getObject 拿到 A对象(虽然 A 还没有初始化完全,但是总比没有好呀),B 拿到 A对象后顺利完成了初始化阶段1、2、3,完全初始化之后将自己放入到一级缓存 singletonObjects 中。
-
然后此时返回 A 的创建过程中,A 此时就能拿到 B 的对象顺利完成自己的初始化阶段2、3,最终 A 也完成了初始化,进去了一级缓存 singletonObjects 中。并且由于 B 之前拿到了 A对象的引用,所以 B 现在拥有了完全的 A对象。
2.0、IOC容器继承关系
-
容器的顶层接口:BeanFactory
2.0.1、BeanFactory 和 ApplicationContext 的区别
-
BeanFactory是Spring容器的顶层接口,ApplicationContext是其子接口
-
ApplicationContext接口,它由BeanFactory接口派生而来,包含BeanFactory的所有功能
-
BeanFactory 创建容器时不会创建对象,当从容器里面获取对象时才会创建
-
ApplicationContext 创建容器时就会创建对象
2.0.2、ApplicationContext 接口的实现类
-
ClassPathXmlApplication:从类的根路径下加载配置文件
-
FileSystemXmlApplication:从磁盘路径加载配置文件,配置文件可以在磁盘任何位置
-
AnnotationConfigApplicationContext:用注解配置容器对象时,需要使用此类来创建spring容器
2.0.3、获取bean的三种方法
2.0.3.1、通过id或name获取对象
ApplicationContext container = new ClassPathXmlApplicationContext("applicationContext.xml"); Husband hunsband = (Husband) container.getBean("husband");
2.0.3.2、无id或name时通过Class类型获取对象
ApplicationContext container = new ClassPathXmlApplicationContext("applicationContext.xml"); Husband hunsband = container.getBean(Husband.class);
2.0.3.3、通过工厂方法获取对象
2.0.3.3.1、静态工厂
//创建静态工厂类 public class HusbandStaticFactory { public static Husband getInstance() { return new Husband(); } } //修改配置文件 <!--class 工厂类的全限定类名、factory-method 工厂类里面的静态方法--> <bean name="husband" class="com.briup.bean.HusbandStaticFactory" factory-method="getInstance"></bean> //测试 // 1.解析配置文件,创建容器 ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml"); // 2.根据配置的Id值,从容器中获取对象,也可以使用Class类型 Husband husband = (Husband) ac.getBean("husband");
2.0.3.3.2、普通工厂
//普通工厂类 public class HusbandFactory { public Husband getInstance() { Husband h = new Husband(); h.setId(1); h.setName("tom"); h.setSalary(2000); return h; } } //修改配置文件 <!--配置工厂,让Spring容器产生工厂实例--> <bean name="factory" class="com.briup.bean.HusbandFactory"></bean> <!--factory-bean 工厂实例在Spring容器中的唯一标识、factory-method 工厂实例中的方法--> <bean name="husband" factory-bean="factory" factory-method="getInstance"></bean> //测试 同上
2.1、Java项目使用
2.1.1、引入jar
2.1.2、配置
2.1.2.1、配置applicationContext.xml
-
在src下面,创建配置文件,名字为: 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 https://www.springframework.org/schema/beans/spring-beans.xsd"> <bean name="husband" class="com.briup.bean.Husband"></bean> </beans>
2.1.2.1.1、bean(IOC容器管理的对象)
-
id:一个bean的唯一标识,命名格式必须符合XML ID属性的命名规范
-
name:可以用特殊字符,并且一个bean可以用多个名称,例如 name="bean1,bean2,bean3"
-
如果没有id,则name的第一个名称默认是id
-
如果一个 bean 标签未指定 id、name 属性,则 spring容器会给其一个默认的id,值为其类全名
-
如果有多个bean标签未指定 id、name 属性,但是class相同,则spring容器会按照其出现的次序,分别给其指定 id 值为 “类全名#1”, “类全名#2”
<bean name="husband" class="com.briup.bean.Husband"></bean>
2.1.2.2、配置log4j.properties
log4j.rootLogger=DEBUG, stdout log4j.appender.stdout=org.apache.log4j.ConsoleAppender log4j.appender.stdout.layout=org.apache.log4j.PatternLayout log4j.appender.stdout.layout.ConversionPattern=%d [%-5p] %c - %m%n
2.1.3、测试
// 1.解析配置文件,创建容器 ApplicationContext container = new ClassPathXmlApplicationContext("applicationContext.xml"); // 2.根据配置的Id值,从容器中获取对象 Husband hunsband = (Husband) container.getBean("husband"); System.out.println(hunsband);
2.2、Maven项目使用
2.3、bean
2.3.1、<bean> 标签的作用
-
用于配置让spring容器来创建对象
-
默认情况下它调用的是类中的无参构造函数(反射)
2.3.2、<bean> 标签的属性
-
id 给在容器中的对象提供一个唯一标识,用来获取对象
-
name 功能类似id,命名要求没有id严格
-
class 指定类的全限定类名。用于反射创建对象。默认情况下调用无参构造函数
-
scope 指定对象范围
-
sigleton 默认值,单例
-
prototype 多例
-
request WEB项目中,Spring容器创建一个Bean对象,会将该对象存入request
-
session WEB项目中,Spring容器创建一个Bean对象,会将该对象存入session
-
golbal session 只有应用在基于portlet的Web应用程序中才有意义,它映射到portlet的global范围的session
-
-
init-method : 指定类的初始化方法的名称
-
destory-method : 指定类中销毁方法的名称
-
lazy-init :指定使用进行延迟加载
2.3.3、单例和多例的区别
-
scope="singleton" 一个应用只有一个对象的实例(默认值)
-
创建容器时,对象就被创建了
-
只要容器不销毁,对象一直存在
-
当容器销毁时,对象就被销毁了
-
-
scope="prototype" 每次访问获取对象,都会重新创建对象实例。
-
当从容器中获取对象时,创建新的对象实例
-
只要对象在使用中,就一直存在
-
当对象长时间不用时,被java的垃圾回收器回收
-
# 3、Spring DI
-
DI(Denpendency Injection),依赖注入,SpringIOC容器中的核心操作,可以将对象需要的属性值自动注入
-
使用 <bean> 标签,可以将一个类配置到Spring的IOC容器中,将其进行对象的管理工作,包括对象的创建、出初始化、销毁等。
-
如果一个对象的初始化需要依赖一些属性值或者其他对象,那么Spring中的DI就可以帮我们来完成对象初始化时候属性的依赖注入工作
-
DI在IOC的基础上
3.1、setter方法注入
3.1.1、实体类
@Data @AllArgsConstructor @NoArgsConstructor public class Husband { private int id; private String name; private double salary; private Date date; }
3.1.2、配置
<!--property 给属性采用set方法赋值, name:属性名,调用该属性的set方法、value:属性值,通过set方法赋值、 ref:属性是其他bean类型采用ref(字符串除外)--> <bean name="hunsband" class="com.briup.bean.Hunsband"> <property name="id" value="1"></property> <property name="name" value="tom"></property> <property name="salary" value="2000"></property> <property name="date" ref="now"></property> </bean> <bean name="now" class="java.util.Date"></bean>
3.1.3、测试
ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml"); Husband husband = (Husband) ac.getBean("husband"); System.out.println(husband);
3.2、构造器注入
3.2.1、实体类相同、配置文件不同、测试相同
-
可以使用name进行指定属性,也可以用index指定属性
<!--constructor-arg 让属性赋值采用构造函数赋值, index: 指定参数在构造函数中的索引位置、type: 指定参数在构造函数中的数据类型 name: 指定参数在构造函数中的名字、value: 具体的参数值 ref: 如果需要的数据为其他bean类型,使用ref--> <bean id="husband" class="com.briup.bean.Husband"> <constructor-arg name="id" value="2"></constructor-arg> <constructor-arg name="name" value="mary"></constructor-arg> <constructor-arg name="salary" value="3000"></constructor-arg> <constructor-arg name="date" ref="now"></constructor-arg> </bean> <bean id="now" class="java.util.Date"></bean>
3.3、名称空间注入
3.3.1、实体类相同、配置文件不同、
-
xml文件的beans里面添加p名称空间的声明,这里p指的的是property
xmlns:p="http://www.springframework.org/schema/p" //配置 <bean name="husband" class="com.briup.bean.Husband" p:id="3" p:name="lucy" p:salary="4000" p:date-ref="now"></bean> <bean name="now" class="java.util.Date"></bean>
3.4、集合属性注入(非注入方式)
3.4.1、实体类
@Data public class Entity { private String[] myStrs; private List<String> myList; private Set<String> mySet; private Map<String, String> myMap; private Properties myProps; }
3.4.2、配置
<bean name="entity" class="com.briup.ioc.Entity"> <!-- 注入数组 --> <property name="myStrs"> <array> <value>AAA</value> <value>BBB</value> <value>CCC</value> </array> </property> <!-- 注入List --> <property name="myList"> <list> <value>DDD</value> <value>EEE</value> <value>FFF</value> </list> </property> <!-- 注入Set --> <property name="mySet"> <set> <value>GGG</value> <value>KKK</value> <value>LLL</value> </set> </property> <!-- 注入myMap --> <property name="myMap"> <map> <!-- 俩种形式都可以注入 --> <entry key="name" value="tom"></entry> <entry key="age"> <value>20</value> </entry> </map> </property> <!-- 注入Properties --> <property name="myProps"> <props> <prop key="name">mary</prop> <prop key="age">30</prop> </props> </property> </bean>
3.4.3、测试一样
ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml"); Entity entity = (Entity) ac.getBean("entity");
# 4、Spring注解
4.1、简单使用
4.1.1、pojo实体类
@Data @Component //代表该类需要让Spring容器去创建对象 public class Teacher { // @Value 用来给属性注入数据 @Value("1") private int id; @Value("tom") private String name; @Value("20") private int age; }
4.1.2、配置xml文件
//加上context名称空间 xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <!-- 配置要扫描的注解包 --> <context:component-scan base-package="com.briup.pojo"></context:component-scan>
4.1.3、引入aop的jar包
4.1.4、测试
ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml"); Teacher teacher = (Teacher) ac.getBean("teacher"); System.out.println(teacher);
4.2、注解(重要)
-
用 AnnotationConfigApplicationContext 使用
4.2.1、常用注解
-
@Component
-
作用:让spring容器来管理对象,相当于在xml中配置一个bean。
-
属性:value: 指定bean的名字,如果不指定,默认为当前类名且首字母小写
-
可以是这样@Component("address"),指定让容器加载的时候该对象的名字
-
-
@Controller , @Service , @Repository
-
作用:功能与 @Component 作用一样,只不过其语义更加明确
-
@Controller 一般作用于 表现层
-
@Service 一般作用于 业务层
-
@Repository 一般作用于 持久层
-
-
属性:value : 指定bean的名字,如果不指定,默认为当前类名,首字母小写
-
-
@Value
-
作用:用于给属性注入基本数据,此注解中还支持SpEL
-
属性:value :具体的属性值
-
-
@Autowired
-
作用:自动按照类型注入,使用注解注入属性时,set方法可以省略,该注解只能注入bean类型,当有多个相同类型时,将bean的名字作为要注入对象的属性名,也可以注入成功
-
注意:期望唯一匹配,若同类型的bean较多,且没有指定容器中的名字,与对象名相同的加载,否则报错
-
-
@Qualifier
-
作用:在 @Autowired 注入的基础之上,再按照Bean的名字注入。在给属性注入数据时不能独立使用,必须和 @Autowired 一起使用
-
属性:value:指定bean的名字
-
@Qualifier("名字")写在Autowaired下面
-
-
@Resource
-
作用:直接按照bean的id注入,只能注入bean类型
-
属性:name: bean的名字
-
用的少,name=""的方式,用的少
-
-
@Scope
-
作用:指定bean的作用范围,默认单例
-
属性:value: 指定范围值,取值为:singleton , prototype , request , session , globalsession
-
-
@PostConstruct
-
作用:指定初始化方法
-
-
@PreDestroy
-
作用:指定销毁方法
-
Spring 中 bean 的作用域
使用 @Scope 注解去指定 bean 的作用范围的时候,可以发现 value 有五个取值范围:singleton(默认就是单例)、prototype、request、session、globalsession。
正常的 Spring框架程序中,bean 的作用域是这两个:
-
singleton 单例:即在整个 Spring容器里面只会存在一个 bean实例,
-
prototype 原型:即每次从 Spring容器去获取指定 bean 的时候,都会返回一个新的实例对象, 并且在基于 Spring框架下的 Web应用里面,增加了会话这个维度来控制 bean,所以还有三个作用域:
-
request:即每一次 http请求都会创建一个新的 bean,
-
session:即同一个 session 共享同一个 bean实例,不同 session 的 bean实例不同,
-
globalsession:即全局 session 都共享同一个 bean实例,
4.2.2、重要注解(完成配置类)
4.2.2.1、@Configuration
-
用于指定当前类是一个Spring配置类,当创建容器时会从该类加载注解
-
可使用AnnotationConfigApplicationContext(配置类.class) 加载配置
-
相对于xml配置
4.2.2.2、@ComponentScan(需要配合@Component)
-
用于指定Spring在初始化容器时要扫描的包,要扫描的类
-
与@Configuration配合使用,放在一起,用作配置@ComponentScan("com.briup.bean")
4.2.2.3、@Bean(与@ComponentScan作用相同,二选一)
-
该注解只能写在方法上,表明使用此方法创建一个对象,并且放入Spring容器,方法名就是唯一标识。也放在Configuration配置类里面
@Bean("teacher") public Teacher teacher() { return new Teacher(1,"tom"); }
4.2.2.4、@PropertySource
-
用于加载 .properties 文件中的值,加载后可以使用 @Value("${key}") 的形式来获取
-
@PropertySource("my-value.properties")//指定读取的资源文件位置
@Configuration @PropertySource("my-value.properties")//指定要读取的资源文件位置 public class SpringConfig { @Value("${teacher.id}") private int id; @Value("${teacher.name}") private String name; @Bean("t") public Teacher teacher() { Teacher t = new Teacher(); t.setId(id);t.setName(name); return t; } }
4.2.2.5、@Import
-
用于导入其他配置类
-
被引入配置类中,可以不用再写 @Configuration 注解。当然,写上也没问题,即被 Import 的类,就不需要写@Configuration 注解了,但是 Import 的类要写上
-
一般项目中写了很多配置类的时候,可以再写一个总的配置类,这个配置类上使用 @Import 去引入其他的配置,将来容器只要读取这个一个总的配置类即可
@Import(value = {SpringConfig.class, XXX.class})
4.3、只使用注解,去掉xml文件
-
注解也会比xml快些,因为xml需要通过网络进行dtd验证
-
主要的就是:ApplicationContext、AnnotationConfigApplicationContext、ClassPathXmlApplicationContext
-
全部使用注解的方式完成 IOC 和 DI 配置和操作,但是需要使用专门的容器类型:AnnotationConfigApplicationContext
-
只需要两步:①@Data@Component 实体类 ②测试
ApplicationContext container = new AnnotationConfigApplicationContext("com.briup.bean"); User user = (User) container.getBean("user"); System.out.println(user);
4.4、Spring的三种配置方式
-
xml配置、annotation 注解配置、javaconfig Configuration配置
-
xml配置,可以把配置信息都写在xml中,方便阅读、维护和修改。
-
annotation配置,可以使用注解来代替xml中的标签,在java代码中配置,但是这种方式,会让配置信息分散在各个java类中,虽然简化了xml的繁琐配置,但是不方便阅读、维护和修改。
-
javaconfig配置,主要是使用了 @Configuration 注解,来代替原来的xml配置,可以将配置信息较为集中的写在一个java类中,并且还提供了相关的配套的其他注解类,解决了使用普通注解配置的一些问题。同时将来配合条件注解可以完成一些高级的配置功能。例如springboot中使用的自动配置等
# 5、代理模式
-
代理模式是一种常见的设计模式,它提供间接对目标对象访问的方式,通过代理对象访问目标对象,从而在在目标对象现有的功能能上,增加额外的功能补充,实现扩展目标对象的功能
-
即目标的任务变得简单了,只需要完成其主要功能即可,其他杂事交给代理对象来做。代理对象调用 sing 方法,在sing方法里面调用目标的sing方法,然后在目标的sing方法前后做杂事操作。
-
代理的前提:让目标对象和代理对象具有相同的方法。
-
所以实现同一个接口,就可以有同一个方法,所以final修饰的方法不可以做代理
5.1、代理分析
5.1.1、代理模式中的3个角色
-
主题类角色:可以是接口,也可以是抽象类,需要让目标对象和代理对象去实现或者继承,目的是为了让目标对象和代理对象具有相同的方法
-
目标类角色:它的对象就是目标对象,核心业务逻辑的具体执行者
-
代理类角色:它的对象就是代理对象,内部含有对真实目标对象的引用,负责对目标对象方法的调用,并且在调用前后进行预处理
5.2、静态代理
-
静态代理是代理模式的实现方式之一,在程序运行前,手动创建代理类,从而实现对目标类中的方法进行增强。目标类与代理类一一对应,
-
在运行之前写好的代理类,这就是静态代理
5.3、动态代理
-
静态代理的例子中,目标类与代理类是一一对应,并且是提前写好的,如果多个目标类需要被代理,那么就需要在程序运行之前建立多个代理类为之代理
-
动态代理则是在程序运行期间,采用字节码技术,动态生成的一个代理对象,从而实现目标对象中方法的增强。比如MyBatis的getMapper
-
相比静态代理,动态代理可以很方便地对目标类的相关方法进行统一增强处理
-
动态代理的两种方式:JDK动态代理、CGLIB动态代理
5.3.1、JDK动态代理
-
JDK动态代理主要是借助 java.lang.reflect.Proxy 生成代理对象
-
动态生成 $Proxy数字 这个对象
-
使用JDK动态代理,有一个前提条件,就是目标对象必须实现了一个或多个接口,并且传给Proxy的对象必须是父类引用的子类对象,那么产生的代理对象就是用来代理这个接口中的方法。
-
但是如果目标对象没有实现任何接口,那么就不能使用JDK动态代理,这时候可以使用CGLIB的动态代理
-
Proxy类中的 newProxyInstance 方法,可以在运行启动期间,动态生成指定接口的代理类对象
//loader,目标类的类加载器、interfaces,目标类所实现的接口,可以是多个 //InvocationHandler,接口的实现类对象 public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)throws IllegalArgumentException{ }
-
java.lang.reflect.InvocationHandler 接口中只有一个抽象方法
//proxy,将来动态生成的代理类对象、method,将来需要代理的目标对象中的方法 //args,将来调用目标对象方法时所传入的参数列表 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
5.3.1.1、代码实现
//创建目标类对象 Singer target = new Target(); //第一个参数,目标类的类加载 ClassLoader loader = Target.class.getClassLoader(); //第二个参数,目标类所实现的接口 Class<?>[] interfaces = Target.class.getInterfaces(); //第三个参数,InvocationHandler接口的实现类对象 InvocationHandler h = new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("安排时间");System.out.println("联系场地"); System.out.println("安全保障");System.out.println("合同签订"); System.out.println("费用结算");System.out.println("------------"); //反射调用目标对象中需要代理的方式,并传入参数 Object result = method.invoke(target, args); return result; } }; Singer proxy = (Singer) Proxy.newProxyInstance(loader, interfaces, h); //调用代理对象的sing方法、proxy对象将代理target对象中的sing、在完成真正的sing功能之前,会完成一些额外的功能 proxy.sing();
5.3.1.2、万能动态代理工厂
-
T代表接口类型
-
T target 而不使用 Class<?> c,是因为InvocationHandler中使用方法的时候,需要指定对象
public class MyPrxoyFactory { public static <T> T getProxy(T target){ Class<?> c = target.getClass(); ClassLoader classLoader = c.getClassLoader(); Class<?>[] interfaces = c.getInterfaces(); InvocationHandler h = new MyInvocationHandler(target); @SuppressWarnings("unchecked") T proxy = (T) Proxy.newProxyInstance(classLoader, interfaces, h); return proxy; } private static class MyInvocationHandler implements InvocationHandler{ private Object target; public MyInvocationHandler(Object target) { this.target = target; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //String className = target.getClass().getName(); //String methodName = method.getName(); //System.out.println(className+" 类型对象中的 "+methodName+" 方法被调用了"); Object result = method.invoke(target, args); return result; } } }
5.3.2、CGLIB动态代理
-
如果目标对象没有实现任何接口,那么就不能使用JDK动态代理,这时候可以使用CGLIB的动态代理
-
CGLIB通过ASM动态操作指令,生成了被代理类的子类,如果目标类是final修饰的,则不能够被代理。
-
CGLIB生成的代理对象,其实是目标对象的子类型对象,并重写了目标类中所有的非private、非final的方法,从而完成这些方法的代理工作
-
可以给没实现任何接口的目标对象,产生动态代理对象了
5.3.2.1、引入jar包
-
其实不需要引入jar包,因为Spring5自带
5.3.2.2、创建目标类
//service对象 public class StudentService { public void saveOrUpdate() { System.out.println("操作成功"); } }
5.3.2.3、测试
public static void main(String[] args) { //CGLIB中产生代理对象的核心类型 Enhancer enhancer = new Enhancer(); // 设置代理类的父类对象 enhancer.setSuperclass(StudentService.class); enhancer.setCallback(new MethodInterceptor() { //@param proxy 代理对象实例、@param method 目标类中的方法对象 //@param args 方法参数、@param methodProxy 方法代理对象 @Override public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { System.out.println("开启事务"); //调用代理对象父类型的中的指定方法 //代理对象的父类型也就是目标对象的类型 //因为CGLIB生成代理对象的方式就是给目标对象动态生成一个子类对象对象 //这个子类型对象就是代理对象 Object result = methodProxy.invokeSuper(proxy, args); System.out.println("提交事务"); return result; } }); // 产生代理对象 StudentService studentService = (StudentService) enhancer.create(); studentService.saveOrUpdate(); }
# 6、Spring AOP
-
AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术
-
AOP是OOP的延续,是函数式编程的一种衍生范型
-
利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。即将程序中的重复代码抽取出来,在程序运行过程中,利用动态代理技术,在不修改源码的基础上,动态增强现有的功能
-
AOP的特点:减少代码重复、提高开发效率、提高可维护性
-
抽出重复代码,使得代码简洁,可重复利用,容易维护修改。再把重复代码动态放进去利用,当需要修改时统一修改
-
Spring会根据目标是否实现了接口,自动选择使用JDK动态代理或CGLIB动态代理
-
bean的生命周期中,有一个后置处理器:BeanPostProcessor,bean的生命周期中,最后一定会通过这个处理器,所以可以 implements这个后置处理器,重写postProcessAfterInitialization(Object bean,String beanName)这个方法,也可以对bean进行代理
6.1、术语
-
切面/切面类(aspect):将来要被织入到方法执行 前/后/异常 的时候去执行的代码片段
-
连接点(joinpoint):Spring中的连接点是目标对象里面需要被代理的方法,默认情况下是目标对象中所有 非final 修饰的方法
-
如果不是在SpringAOP中,joinPoint可能还会是属性
-
-
切入点(pointCut):一组连接点的集合,就是一个切入点。因为连接点就是方法(spring中是这样),所有一个切入点也是一组方法的集合
-
通知/拦截器(advice):控制 切面/切面类 将来要在目标对象中方法的什么位置执行,例如方法的前面或者后面或者抛异常的时候
-
织入(wave):将切面类织入到指定方法中去执行的动作
-
目标对象(target):需要被代理的对象,一般是代理目标对象的一个或多个指定的方法
-
代理对象(proxy):代理目标对象,在完成核心功能的前提下,添加额外的代码去执行
6.2、xml文件配置使用AOP
6.2.1、引入jar
6.2.2、目标接口、目标接口实现类
public interface ITeacherService { void saveOrUpdate(); void delete(); void deleteBatch(); } public class TeacherServiceImpl implements ITeacherService{ //实现接口中的方法 }
6.2.3、切面类(Aspect)
-
切面类名字任意,切面类中的方法名也任意
public class MyAspect { //将该代码片段织入到目标对象中方法的执行之前 public void beforAdvice() { System.out.println("前置通知"); } //将该代码片段织入到目标对象中方法的执行之后(被代理的方法必须正常返回,抛出异常不织入) public void afterReturn() { System.out.println("后置通知"); } //将该代码片段织入到目标对象中方法的执行抛异常的时候 public void throwable() { System.out.println("异常通知"); } //将该代码片段织入到目标对象中方法的执行之后(被代理的方法正常返回或者抛出异常都会织入该代码片段) public void after() { System.out.println("最终通知"); } //环绕通知,将该代码片段织入到目标对象中方法的执行之前和和之后 public void arround(ProceedingJoinPoint joinPoint) throws Throwable { System.out.println("环绕通知:前"); // 获取执行方法的参数 Object[] args = joinPoint.getArgs(); joinPoint.proceed(args); System.out.println("环绕通知: 后"); } }
6.2.4、配置xml文件
-
配置目标类
-
配置切面类
-
配置AOP标签
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:aop="http://www.springframework.org/schema/aop" 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 http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <!-- 配置目标类到SpringIOC容器中--> <bean name="teacherService" class="com.briup.service.impl.TeacherServiceImpl"></bean> <!-- 配置切面类到SpringIOC容器中--> <bean name="myAspect" class="com.briup.aspect.MyAspect"></bean> <!-- 配置aop标签 --> <aop:config proxy-target-class=false> <!-- 配置切入点,一组连接点的集合(Spring中就是一组方法的集合) --> <!-- id: 切入点的唯一标识 --><!-- expression:切入点表达式 --> <!-- 注意,切入点myPointcut1和myPointcut2分别代表俩组不同的连接点(方法) --> <aop:pointcut id="myPointcut1" expression="execution(*com.briup.service..*.*(..))"/> <aop:pointcut id="myPointcut2" expression="execution(*com.briup.service..*.delete*(..))"/> <!-- 配置切面的通知器,即通知切面的每个方法的织入的切入点(连接点的集合) --> <aop:aspect id="aspect" ref="myAspect"> <!-- 前置通知,会将切面类中的代码片段(beforAdvice方法)织入到myPointcut1中 --> <aop:before method="beforAdvice" pointcut-ref="myPointcut1"></aop:before> <!-- 最终通知,会将切面类中的代码片段(after方法)织入到myPointcut1中 --> <aop:after method="after" pointcut-ref="myPointcut1"/> <!-- 后置通知,会将切面类中的代码片段(afterReturn方法)织入到myPointcut1中 --> <aop:after-returning method="afterReturn" pointcut-ref="myPointcut1"/> <!-- 环绕通知,会将切面类中的代码片段(around方法)织入到myPointcut1中 --> <aop:around method="around" pointcut-ref="myPointcut1"/> <!-- 异常通知,会将切面类中的代码片段(throwable方法)织入到myPointcut2中 --> <aop:after-throwing method="throwable" pointcut-ref="myPointcut2"/> </aop:aspect> </aop:config> </beans>
6.2.4.1、标签简介
6.2.4.1.1、aop:config
-
aop配置标签,所有的aop配置代码都写入该标签里面
-
这个标签中有 proxy-target-class 这个属性,默认是false,意思是是否使用CGLIB动态代理。可以主动指定为true
6.2.4.1.2、aop:pointcut
-
配置切入点,需要使用execution表示来定义切入点
-
官方文档中,提供了很多参考的样例,不知道如何配置时可去查
6.2.4.1.3、aop:aspect
-
配置切面,所有通知都写入该里面
-
属性:
-
id: 切面唯一标识
-
ref: 容器中切面对象的唯一标识
-
6.2.4.1.4、advice
-
aop:before 前置通知,在切入点执行之前执行
-
aop:after-returning 后置通知,在切入点执行之后执行,发生异常不执行
-
aop:after-throwing 异常通知,在切入点发生异常时执行
-
aop:after 最终通知,在切入点执行之后执行,发生异常也执行
-
aop:around 环绕通知,在切入点执行之前,执行一部分,执行后执行一部分
-
以上所有通知都有如下属性:
-
method: 指定通知中方法的名称。
-
pointct: 定义切入点表达式(临时定义)
-
pointcut-ref: 指定切入点表达式的引用(引用上面定义好的)
-
6.2.5、测试
import com.briup.service.ITeacherService; @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations = "classpath:applicationContext.xml") public class AOPXMLTest { @Autowired private ITeacherService teacherService; @Test public void test() { System.out.println(teacherService); System.out.println(teacherService.getClass()); } @Test public void test_saveOrUpdate() { teacherService.saveOrUpdate(); } @Test public void test_delete() { teacherService.delete(); } @Test public void test_deleteBatch() { teacherService.deleteBatch(); } }
6.3、注解配置使用AOP
6.3.1、目标接口和目标接口实现类不变,在实现类上加@Service
@Service public class TeacherServiceImpl implements ITeacherService {}
6.3.2、切面类上加@Component、@Aspect,在方法上加特定注解
@Component @Aspect public class MyAspect { //注意,方法名字就是这个切入点的名字 @Pointcut("execution(* com.briup.service..*.*(..))") public void pointcut1() {} //注意,方法名字就是这个切入点的名字 @Pointcut("execution(* com.briup.service..*.delete*(..))") public void pointcut2() {} @Before("pointcut1()") public void beforAdvice() { System.out.println("前置通知"); } @AfterReturning("pointcut1()") public void afterReturn() { System.out.println("后置通知"); } @AfterThrowing("pointcut2()") public void throwable() { System.out.println("异常通知"); } @After("pointcut1()") public void after() { System.out.println("最终通知"); } @Around("pointcut1()") public void around(ProceedingJoinPoint joinPoint) throws Throwable { System.out.println("环绕通知:前"); // 获取执行方法的参数 Object[] args = joinPoint.getArgs(); joinPoint.proceed(args); System.out.println("环绕通知: 后"); } }
6.3.3、使用配置类,代替xml文件
-
ComponentScan可以直接扫包,也可以 ({"" , ""}),扫具体的类
-
@EnableAspectJAutoProxy用来指定当前spring配置中使用了AspectJ框架的注解,AspectJ框架是专门实现AOP功能的一个框架,Spring中AOP功能也是依赖于AspectJ来实现的
@Configuration @EnableAspectJAutoProxy @ComponentScan("com.briup") public class AOPConfig {}
6.3.4、测试
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes = AOPConfig.class) public class AOPAnnotationTest { @Autowired private ITeacherService teacherService; @Test public void test() { System.out.println(teacherService); System.out.println(teacherService.getClass()); } @Test public void test_saveOrUpdate() { teacherService.saveOrUpdate(); } @Test public void test_delete() { teacherService.delete(); } @Test public void test_deleteBatch() { teacherService.deleteBatch(); } }
6.4、后置处理器
public class MyBeanPostProcessor implements BeanPostProcessor{ private LogUtil logUtil; public void setLogUtil(LogUtil logUtil) { this.logUtil = logUtil; } @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { if("categoryService".equals(beanName)) { // System.out.println(bean); // System.out.println(beanName); return Proxy.newProxyInstance( bean.getClass().getClassLoader(), bean.getClass().getInterfaces(), new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("hello"); Object result = method.invoke(bean, args); System.out.println("world"); return result; } }); } return bean; } } //xml <bean id = "myPostProcessor" class="com.briup.aop.demo.life.MyBeanPostProcessor"></bean> public class LogUtil { public void before() { System.out.println("执行前"); } public void after() { System.out.println("执行后"); } } public class MyBeanPostProcessor implements BeanPostProcessor{ private LogUtil logUtil; public void setLogUtil(LogUtil logUtil) { this.logUtil = logUtil; } @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { if(beanName.startsWith("user")) { // System.out.println(bean); // System.out.println(beanName); return Proxy.newProxyInstance( bean.getClass().getClassLoader(), bean.getClass().getInterfaces(), new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // System.out.println("hello"); logUtil.before(); Object result = method.invoke(bean, args); logUtil.after(); // System.out.println("world"); return result; } }); } return bean; } } //更新xml <bean id = "logUtil" class="com.briup.aop.demo.aspect.LogUtil"/> <bean id = "myPostProcessor" class="com.briup.aop.demo.life.MyBeanPostProcessor"> <property name="logUtil" ref="logUtil"/> </bean>
# 7、Spring JDBC
-
Spring框架中提供的一个类 JdbcTemplate ,是对原始Jdbc-API的简单封装。除了这个模板类,Spring框架为提供了很多的操作模板类:
-
操作关系型数据库: JdbcTemplate , HibernateTemplate
-
操作redis数据库的: RedisTemplate
-
操作消息队列的: JmsTemplate
-
7.1、简单使用
7.1.1、引入jar
-
spring-tx 是管理事务相关的jar包
7.1.2、建表、建POJO、建dao层接口、建dao层实现类
-
@Repository 一般作用于 持久层,即dao层的实现类
-
自动注入JdbcTemplate,使用jdbctemplate 可以用来与数据库交互
-
update对应增删改,query、queryForObject对应查
@Repository public class TeacherDaoImpl implements ITeacherDao{ // 注入 jdcbTemplate 对象 @Autowired private JdbcTemplate jdbcTemplate; @Override public void insert(Teacher t) { String sql = "insert into teacher values(?,?,?,?)"; Object[] args = {t.getId(),t.getName(),t.getAge(),t.getSalary()}; jdbcTemplate.update(sql,args); } @Override public void update(Teacher t) { String sql = "update teacher set name = ?,age = ?,salary = ? where id = ?"; Object[] args = {t.getName(),t.getAge(),t.getSalary(),t.getId()}; jdbcTemplate.update(sql,args); } @Override public void deleteById(int id) { String sql = "delete from teacher where id = ?"; Object[] args = {id}; jdbcTemplate.update(sql,args); } @Override public Teacher selectById(int id) { String sql = "select * from teacher where id = ?"; Object[] args = {id}; Teacher teacher = jdbcTemplate.queryForObject(sql,args, new RowMapper<Teacher>() { @Override public Teacher mapRow(ResultSet rs, int rowNum) throws SQLException { Teacher t = new Teacher(); t.setId(rs.getInt("id")); t.setName(rs.getString("name")); t.setAge(rs.getInt("age")); t.setSalary(rs.getDouble("salary")); return t; } }); return teacher; } @Override public List<Teacher> selectAll() { String sql = "select * from teacher"; List<Teacher> list = jdbcTemplate.query(sql,new RowMapper<Teacher>() { @Override public Teacher mapRow(ResultSet rs, int index) throws SQLException { Teacher teacher = new Teacher(); teacher.setId(rs.getInt("id")); teacher.setName(rs.getString("name")); teacher.setAge(rs.getInt("age")); teacher.setSalary(rs.getDouble("salary")); return teacher; } }); return list; } @Override public int selectCount() { String sql = "select count(*) from teacher"; return jdbcTemplate.queryForObject(sql, Integer.class); } }
7.1.3、JdbcTemplate的配置类,配置数据源
@Configuration @ComponentScan("com.briup.dao") public class JdbcConfig { //创建数据库连接池,并且交给Spring容器管理 @Bean public DataSource dataSource() { DruidDataSource dataSource = new DruidDataSource(); dataSource.setDriverClassName("oracle.jdbc.OracleDriver"); dataSource.setUrl("jdbc:oracle:thin:@127.0.0.1:1521:XE 记得改成ORCL"); dataSource.setUsername("briup"); dataSource.setPassword("briup"); dataSource.setInitialSize(5); dataSource.setMaxActive(10); return dataSource; } //创建JdbcTemplate对象交给Spring容器管理 //注意,需要将配置好的数据源注入到JdbcTemplate中 @Bean public JdbcTemplate jdbcTemplate() { JdbcTemplate template = new JdbcTemplate(); template.setDataSource(dataSource()); return template; } }
7.1.4、测试
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes = JdbcConfig.class) public class JdbcTest { @Autowired private ITeacherDao teacherDao; @Test public void test_insert() { Teacher t = new Teacher(1,"tom1",21,2000); teacherDao.insert(t); } @Test public void test_selectById() { Teacher t = teacherDao.selectById(1); System.out.println(t); } //各种测试 }
7.2、事务操作
-
在项目的三层架构设计中,事务管理工作需要放在service进行来处理,用来保证当前正在完成的业务功能要么同时成功,要么同时失败
-
Spring中操作事务的主要方式:编程式事务、声明式事务
-
编程式事务:使用编程的方式,自己去实现事务管理工作,例如事务的开启、提交、回滚操作,需要开发人员自己调用commit()或者rollback()等方法来实现。
-
编程式事务需要我们自己在逻辑代码中手动书写事务控制逻辑,所以编程式事务是具有侵入性的。
-
我们之前的代码中,对事务进行提交或者回滚的操作,就属于编程式事务。需要自己写,我们不用
-
-
声明式事务:只需要声明或者配置一个事务就可以了,不需要我们手动去编写事务管理代码
-
这种方式属于非侵入性的,可以使用AOP思想实现事务管理,能够提高代码的复用性,提高开发效率
-
例如,在Spring中,使用AOP来实现事务操作
-
将service层事务的开启、提交、回滚等代码抽出来,封装成切面类
-
使用环绕通知,将事务管理的代码织入到service层需要事务支持的方法中
-
获取service 实现类的代理对象,调用方法时会动态加入事务管理的代码
-
-
7.2.1、Spring事务管理接口
-
Spring并不直接管理事务,而是提供了多种事务管理器,他们将事务管理的职责,委托给Hibernate或者JTA或者JDBC等持久化平台框架的事务来实现。
-
Spring事务管理器的接口是org.springframework.transaction.PlatformTransactionManager , 通过这个接口,Spring为各个平台如JDBC、Hibernate、JPA、JTA等都提供了对应的事务管理器,但是具体的实现就是各个平台自己的事情了
-
就比如使用JDBC,就需要配置对应的事务管理器,并注入相应数据源
<bean name="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource" /> </bean>
7.2.1.1、PlatformTransactionManager的三个方法
-
Spring的事务管理接口:PlatformTransactionManager,提供了三个方法:
public interface PlatformTransactionManager{ // 由TransactionDefinition得到TransactionStatus对象 TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException; // 提交 Void commit(TransactionStatus status) throws TransactionException; // 回滚 Void rollback(TransactionStatus status) throws TransactionException; }
7.2.2、TransactionDefinition的五个事务属性(重要)
-
org.springframework.transaction.TransactionDefinition 接口中,定义了事务的一些基本的属性,事务的属性主要包括五个方面:
-
传播行为、隔离级别、是否只读、事务超时、回滚规则
-
7.2.2.1、事务传播行为
-
事务传播行为(propagation behavior),当事务方法被另一个事务方法调用时,必须指定事务应该如何传播
public interface TransactionDefinition { int PROPAGATION_REQUIRED = 0; int PROPAGATION_SUPPORTS = 1; int PROPAGATION_MANDATORY = 2; int PROPAGATION_REQUIRES_NEW = 3; int PROPAGATION_NOT_SUPPORTED = 4; int PROPAGATION_NEVER = 5; int PROPAGATION_NESTED = 6; int getPropagationBehavior(); }
-
Spring定义了7种传播行为:
传播行为 | 简介 |
---|---|
PROPAGATION_REQUIRED | 表示当前方法必须在运行在事务中,如果当前事务存在,方法将会在该事务中运行,否则会启动一个新事务 |
PROPAGATION_SUPPORTS | 表示当前方法不需要事务上下文,但是如果存在当前事务的话,该方法就会在这个事务中运行 |
PROPAGATION_MANDATORY | 表示当前方法必须在运行在事务中,如果当前事务不存在,则会抛出一个异常 |
PROPAGATION_REQUIRES_NEW | 表示当前方法必须运行在它自己的事务中,一个新的事务将会被启动,如果存在当前事务,在该方法执行期间,当前事务会被挂起。 |
PROPAGATION_NOT_SUPPORTED | 表示当前方法不应该运行在事务中,如果存在当前事务,在该方法运行期间,当前事务将被挂起 |
PROPAGATION_NEVER | 表示当前方法不应该运行在事务中,如果当前正有一个事务正在运行,则会抛出异常 |
PROPAGATION_NESTED | 表示如果当前已经存在一个事务,那么该方法将会在嵌套的事务中运行。嵌套的事务可以独立于当前事务进行单独的提交或 <br>回滚。如果当前事务不存在,那么其行为与 <br>PROPAGATION_REQUIRED相同。注意,各个厂商对这种传播行为的支持有所不同,实际情况中需要参考具体的说明文档 |
7.2.2.2、事务隔离级别
-
相对于JDBC的隔离级别,Spring除了读未提交、读已提交、可重复读和串行化之外,还为我们提供了一个默认的隔离级别
public interface TransactionDefinition { int ISOLATION_DEFAULT = -1; int ISOLATION_READ_UNCOMMITTED = Connection.TRANSACTION_READ_UNCOMMITTED; int ISOLATION_READ_COMMITTED = Connection.TRANSACTION_READ_COMMITTED; int ISOLATION_REPEATABLE_READ = Connection.TRANSACTION_REPEATABLE_READ; int ISOLATION_SERIALIZABLE = Connection.TRANSACTION_SERIALIZABLE; int getIsolationLevel(); }
-
ISOLATION_DEFAULT 默认级别,归属下面级别中的某一种
-
是 DataSourceTransactionManager 的默认隔离级别,该隔离级别的意思就是使用数据库默认的隔离级别:
-
mysql是REPEATABLE_READ
-
oracle是READ_COMMITTED
-
-
-
ISOLATION_READ_UNCOMMITTED 读未提交
-
ISOLATION_READ_COMMITTED 读已提交
-
ISOLATION_REPEATABLE_READ 可重复读
-
ISOLATION_SERIALIZABLE 串行化
7.2.2.3、事务是否只读
-
如果事务中只包含对数据库的读操作,那么数据库可以利用事务的只读特性来进行一些特定的优化。
-
通过将事务设置为只读,你就可以给数据库一个机会,让它采用更为合适的优化措施。
-
只读事务并不是一个强制选项,它只是一个“暗示”,提示数据库驱动程序和数据库系统,这个事务并不包含更改数据的操作,那么JDBC驱动程序和数据库就有可能根据这种情况对该事务进行一些特定的优化,比方说不安排相应的数据库锁,以减轻事务对数据库的压力,毕竟事务也是要消耗数据库的资源的
public interface TransactionDefinition { boolean isReadOnly(); }
7.2.2.4、事务超时属性
-
为了使应用程序很好地运行,事务不能运行太长的时间。因为事务可能涉及对后端数据库的锁定,所以长时间的事务会不必要的占用数据库资源。
-
事务超时就是事务的一个定时器,在特定时间内事务如果没有执行完毕,那么就会自动回滚,而不是一直等待其结束
public interface TransactionDefinition { int TIMEOUT_DEFAULT = -1; int getTimeout(); }
7.2.2.5、事务回滚规则
-
默认配置下,事务只会对Error与RuntimeException及其子类这些UnChecked异常,做出回滚。非运行时异常这些Checked异常不会发生回滚,如果一般Exception想要回滚那么必须进行配置
7.2.3、XML方式使用事务
-
记住,事务是针对service层的
7.2.3.1、引入jar
7.2.3.2、建dao层接口、建其实现类
-
与JDBC那一块的dao层相同
-
但要给JdbcTemplate提供getter、setter
7.2.3.3、建service层接口、建其实现类
-
注意要给实现类中的dao层对象提供getter、setter
public interface ITeacherService { void save(Teacher t); } public class TeacherServiceImpl implements ITeacherService{ private ITeacherDao teacherDao; @Override public void save(Teacher t) { teacherDao.insert(t); //throw new RuntimeException("异常测试"); } public ITeacherDao getTeacherDao() { return teacherDao; } public void setTeacherDao(ITeacherDao teacherDao) { this.teacherDao = teacherDao; } }
7.2.3.4、XML文件配置
-
要在xml头部声明中,引入tx命名空间
-
配置property标签,都是通过name,然后使用setter方法进行注入的
<?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" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <!-- 配置数据库连接池 --> <bean name="dataSource" class="com.alibaba.druid.pool.DruidDataSource"> <!-- 注入数据库连接属性 --> <property name="driverClassName" value="oracle.jdbc.OracleDriver"></property> <property name="url" value="jdbc:oracle:thin:@127.0.0.1:1521:ORCL"></property> <property name="username" value="briup"></property> <property name="password" value="briup"></property> </bean> <!-- 配置jdbcTemplate --> <bean name="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <!-- 注入数据源 --> <property name="dataSource" ref="dataSource"></property> </bean> <!-- 配置Dao --> <bean name="teacherDao" class="com.briup.dao.impl.TeacherDaoImpl"> <!-- 注入jdbcTemplate --> <property name="jdbcTemplate" ref="jdbcTemplate"></property> </bean> <!-- 配置Service --> <bean name="teacherService" class="com.briup.service.impl.TeacherServiceImpl"> <!-- 注入dao层实现类 --> <property name="teacherDao" ref="teacherDao"></property> </bean> <!-- 配置JDBC的事务管理器 --> <bean name="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <!-- 注入数据源 --> <property name="dataSource" ref="dataSource"></property> </bean> <!-- 配置事务拦截器 --> <tx:advice id="txAdvice" transaction-manager="transactionManager"> <!-- 配置事务属性 --> <tx:attributes> <!--name : 指定方法名称 *代表任意赐福、read-only 是否是只读事务 isolation:指定事务的隔离级别。默认值是使用数据库的默认隔离级别。 propagation:指定事务的传播行为、timeout:指定超时时间。默认值为:-1。永不超时 rollback-for:用于指定一个异常,当执行产生该异常时,事务回滚。 no-rollback-for:用于指定一个异常,当产生该异常时,事务不回滚。--> <tx:method name="find*" read-only="true" propagation="SUPPORTS"/> <tx:method name="*" propagation="REQUIRED" rollback-for="Exception"/> </tx:attributes> </tx:advice> <!-- 配置 aop --> <aop:config> <!-- 定义切入点(一组方法的集合,这些方法需要织入事务控制代码) --> <aop:pointcut expression="execution(* com.briup.service..*.*(..))" id="txPointCut"/> <!-- 增强器,定义advisor,可以用来组合已有的advice和pointcut --> <!-- 指定 事务拦截器 将 事务管理器的代码(切面类) 织入 到值的切入点内(一组方法的集合) --> <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointCut"/> </aop:config> </beans>
7.2.3.5、测试
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations = "classpath:applicationContext.xml") public class TxXMLTest { @Autowired private ITeacherService teacherService; @Test public void test_insert() { Teacher t = new Teacher(2,"tom1",21,2000); teacherService.save(t); } }
7.2.4、注解方式使用事务
7.2.4.1、给dao层实现加注解@Repository
-
getter、setter写不写都行,可不写
@Repository public class TeacherDaoImpl implements ITeacherDao{ @Autowired private JdbcTemplate jdbcTemplate; //方法实现 }
7.2.4.2、给service层实现加注解@Transactional
-
@Transactional可以写在类上面,也可以写在方法上面,作用优先级为:方法 > 类
-
getter、setter写不写都行,可不写
@Transactional(propagation = Propagation.REQUIRED,rollbackFor = Exception.class) @Service public class TeacherServiceImpl implements ITeacherService{ @Autowired private ITeacherDao teacherDao; @Override public void save(Teacher t) { teacherDao.insert(t); //throw new RuntimeException("异常测试"); } }
7.2.4.3、配置类
-
@EnableTransactionManagement表示开启事务注解配置的支持
-
在xml中,也能通知spring,我们使用了注解的方式进行事务配置。例如,tx:annotation-driven/
@Configuration @ComponentScan("com.briup") //指定扫描com.briup包及其子包下面类中的注解 @EnableTransactionManagement public class JdbcConfig { //创建数据库连接池,并且交给Spring容器管理 @Bean public DataSource dataSource() { DruidDataSource dataSource = new DruidDataSource(); dataSource.setDriverClassName("oracle.jdbc.OracleDriver"); dataSource.setUrl("jdbc:oracle:thin:@127.0.0.1:1521:XE"); dataSource.setUsername("briup"); dataSource.setPassword("briup"); dataSource.setInitialSize(5); dataSource.setMaxActive(10); return dataSource; } //创建JdbcTemplate对象交给Spring容器管理 //注意,需要将配置好的数据源注入到JdbcTemplate中 @Bean public JdbcTemplate jdbcTemplate() { JdbcTemplate template = new JdbcTemplate(); template.setDataSource(dataSource()); return template; } //配置JDBC的事务管理到spring容器中 //注意,如果没有配置事务管理器的话,会报错,如下: //No qualifying bean of type 'org.springframework.transaction.PlatformTransactionManager' available @Bean public PlatformTransactionManager transactionManager() { DataSourceTransactionManager transactionManager = new DataSourceTransactionManager(); transactionManager.setDataSource(dataSource()); return transactionManager; } }
7.2.4.4、测试
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes = {JdbcConfig.class}) public class TxAnnotationTest { @Autowired private ITeacherService teacherService; @Test public void test_insert() { Teacher t = new Teacher(1,"tom1",21,2000); teacherService.save(t); } }
# 8、Spring整合MyBatis
-
想把框架A 结合 Spring来使用:
-
需要把框架A中的核心对象,交给Spring的IOC容器来管理
-
同时把负责 框架A和Spring结合 的的jar引入进来
-
除非这个框架A本来就是Spring中的模块或项目
-
-
Spring也可以和Mybaits进行整合,主要分俩种情况,xml和注解
-
Spring整合Mybaits的核心点有俩个:
-
要把dao层对象配置给Spring容器
-
把Mybaits的SqlSessionFactory交给Spring容器进行管理。即核心对象交给 Spring的IOC容器 管理
-
让Spring去扫描Mybatis的映射接口所在的包,以便为其自动生成动态代理对象,并放到Spring的IOC容器里面
-
8.1、XML方式整合MyBatis
8.1.1、引入jar
8.1.2、建表、建实体类、建dao层和其mapper.xml
public interface ITeacherDao { void insertTeacher(Teacher teacher); Teacher selectTeacherById(int id); } <?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.briup.dao.ITeacherDao"> <insert id="insertTeacher" parameterType="Teacher"> <selectKey keyProperty="id" resultType="int" order="BEFORE"> select my_seq.nextVal from dual </selectKey> insert into teacher(id,name,age,salary) values(#{id},#{name},#{age},#{salary}) </insert> <select id="selectTeacherById" parameterType="int" resultType="Teacher"> select id,name,age,salary from teacher where id=#{id} </select> </mapper>
8.1.3、建service及其实现类
-
注意,需要getter和setter
public interface ITeacherService { void save(Teacher t); } public class TeacherServiceImpl implements ITeacherService{ private ITeacherDao teacherDao; public void save(Teacher t) { teacherDao.insertTeacher(t); // throw new RuntimeException("异常测试"); } public ITeacherDao getTeacherDao() { return teacherDao; } public void setTeacherDao(ITeacherDao teacherDao) { this.teacherDao = teacherDao; } }
8.1.4、log4j日志配置log4j.properties
log4j.rootLogger=DEBUG, stdout log4j.appender.stdout=org.apache.log4j.ConsoleAppender log4j.appender.stdout.layout=org.apache.log4j.PatternLayout log4j.appender.stdout.layout.ConversionPattern=%d [%-5p] %c - %m%n #show sql log4j.logger.java.sql.ResultSet=INFO log4j.logger.org.apache=INFO log4j.logger.java.sql.Connection=DEBUG log4j.logger.java.sql.Statement=DEBUG log4j.logger.java.sql.PreparedStatement=DEBUG # set springframework log-level log4j.logger.org.springframework=WARN # set mybatis log-level log4j.logger.org.mybatis=WARN
8.1.4、资源文件dataSource.properties
jdbc.driverClassName=oracle.jdbc.OracleDriver jdbc.url=jdbc:oracle:thin:@127.0.0.1:1521:ORCL jdbc.username=briup jdbc.password=briup
8.1.5、Spring配置文件spring-dao.xml
-
在整合mybaits的时候,这里主要是将sqlSessionFactory配置到spring的容器中
-
配置MapperScannerConfigurer主要是为了让其扫描映射接口,方便为其生成动态代理对象
<?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" xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <!-- 读取properties资源文件内容 --> <context:property-placeholder location="classpath:dataSource.properties" /> <!-- 配置数据库连接池 --> <bean name="dataSource" class="com.alibaba.druid.pool.DruidDataSource"> <!-- 注入数据库连接属性 --> <property name="driverClassName" value="${jdbc.driverClassName}"></property> <property name="url" value="${jdbc.url}"></property> <property name="username" value="${jdbc.username}"></property> <property name="password" value="${jdbc.password}"></property> </bean> <!-- 配置sqlSessionFactory --> <bean name="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <!-- 注入数据源 --> <property name="dataSource" ref="dataSource"></property> <!-- 配置别名 --> <property name="typeAliasesPackage" value="com.briup.bean"></property> <!-- 配置sql映射文件路径,支持通配符 --> <property name="mapperLocations" value="classpath:com/briup/mapper/*Mapper.xml"></property> </bean> <!-- 配置映射接口所在位置 --> <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <property name="basePackage" value="com.briup.dao"></property> </bean> </beans>
-
在这种配置下,mybatis是不需要配置文件mybatis-config.xml的,该文件中所有配置都写在了spring的配置中,当然也可以和以前一样,配置mybaits-congfig.xml文件,然后再让spring去读取这个文件,比如下面这些,注意,我们这里只是说可以这样,但并没有使用
<!-- 配置sqlSessionFactory --> <bean name="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <!-- 注入数据源 --> <property name="dataSource" ref="dataSource"></property> <!-- 配置读取外部的mybatis-config.xml文件 --> <property name="configLocation" value="mybatis-config.xml"></property> </bean>
8.1.6、Spring配置文件spring-service.xml
-
这里配置service层对象,以及配置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:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <!-- 配置Service --> <bean name="teacherService" class="com.briup.service.impl.TeacherServiceImpl"> <!-- 注入dao层实现类 --> <property name="teacherDao" ref="ITeacherDao"></property> </bean> <!-- 配置事务管理器 --> <bean name="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <!-- 注入数据源 --> <property name="dataSource" ref="dataSource"></property> </bean> <!-- 配置事务拦截器 --> <tx:advice id="txAdvice" transaction-manager="transactionManager"> <!-- 配置事务属性 --> <tx:attributes> <tx:method name="find*" read-only="true" propagation="SUPPORTS"/> <tx:method name="*" propagation="REQUIRED" rollback-for="Exception"/> </tx:attributes> </tx:advice> <!-- 配置 aop --> <aop:config> <!-- 定义切入点(一组方法的集合,这些方法需要织入事务控制代码) --> <aop:pointcut expression="execution(* com.briup.service..*.*(..))" id="txPointcut"/> <!-- 定义advisor,可以用来组合已有的advice和pointcut --> <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut"/> </aop:config> </beans>
8.1.7、Spring配置文件applicationContext.xml
-
这里导入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:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <import resource="spring-dao.xml"/> <import resource="spring-service.xml"/> </beans>
8.1.8、测试
@RunWith(SpringJUnit4ClassRunner.class) //指定要读取的配置文件所在位置 @ContextConfiguration(locations = "classpath:applicationContext.xml") public class MybatisXMLTest { @Autowired private ITeacherService teacherService; @Test public void test_insert() { //id以配置为使用序列自动生成 Teacher t = new Teacher(); t.setName("tom"); t.setAge(20); t.setSalary(2000); teacherService.save(t); } }
8.2、注解方式整合MyBatis
8.2.1、修改service实现类,添加注解
@Transactional(propagation = Propagation.REQUIRED,rollbackFor = Exception.class) @Service public class TeacherServiceImpl implements ITeacherService{ @Autowired private ITeacherDao teacherDao; public void save(Teacher t) { teacherDao.insertTeacher(t); // throw new RuntimeException("异常测试"); } }
8.2.2、新增配置类,SpringConfig.java
@Configuration @ComponentScan("com.briup.service")//指定需要扫描的包及其子包 @EnableTransactionManagement//开启事务配置的注解支持 public class SpringConfig {}
8.2.3、新增配置类,MybatisConfig.java
-
此配置类的作用是代替了之前的spring-dao.xml文件
-
这里主要完成以下几个工作,以代替之前的xml配置:
-
指定要扫描的映射接口所在包
-
配置数据源
-
配置事务管理器
-
配置SqlSessionFactoryBean(核心)
-
@Configuration @PropertySource("classpath:dataSource.properties")//读取资源文件 @MapperScan("com.briup.dao")//指定扫描映射接口的位置 public class MybatisConfig { @Value("${jdbc.driverClassName}") private String driverClassName; @Value("${jdbc.url}") private String url; @Value("${jdbc.username}") private String username; @Value("${jdbc.password}") private String password; //配置数据源dataSource @Bean public DataSource dataSource() { DruidDataSource dataSource = new DruidDataSource(); dataSource.setDriverClassName("oracle.jdbc.OracleDriver"); dataSource.setUrl("jdbc:oracle:thin:@127.0.0.1:1521:ORCL"); dataSource.setUsername("briup"); dataSource.setPassword("briup"); dataSource.setInitialSize(5); dataSource.setMaxActive(10); return dataSource; } //配置SqlSessionFactoryBean,用来产生SqlSessionFactory,从而获取SqlSession @Bean public SqlSessionFactoryBean sqlSessionFactoryBean() throws IOException{ //资源解析器,用来读取指定位置的xml映射文件 ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver(); //创建SqlSessionFactoryBean对象 SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean(); //注入数据源 sqlSessionFactoryBean.setDataSource(dataSource()); //配置别名 sqlSessionFactoryBean.setTypeAliasesPackage("com.briup.bean"); //配置sql映射文件路径,支持通配符 //注意,这里调用的是getResources()方法,而不是getResource()方法 sqlSessionFactoryBean.setMapperLocations(resourcePatternResolver .getResources("classpath:com/briup/mapper/*Mapper.xml")); return sqlSessionFactoryBean; } /** * 配置JDBC的事务管理器(Mybaits通用) */ @Bean public PlatformTransactionManager transactionManager() { DataSourceTransactionManager transactionManager = new DataSourceTransactionManager(); transactionManager.setDataSource(dataSource()); return transactionManager; } }
8.2.4、测试
@RunWith(SpringJUnit4ClassRunner.class) //指定要读取的配置类 @ContextConfiguration(classes = {SpringConfig.class,MybatisConfig.class}) public class MybatisAnnotationTest { @Autowired private ITeacherService teacherService; @Test public void test_insert() { //id以配置为使用序列自动生成 Teacher t = new Teacher(); t.setName("tom"); t.setAge(20); t.setSalary(2000); teacherService.save(t); } }
9、Spring的Web环境(Servlet)(了解)
-
Spring在web环境(Servlet)中的配置,主要分俩种情况,xml和注解
-
Spring框架在web环境(Servlet)中的工作核心是:
-
在web.xml中配置监听器,启动服务器的过程中读取Spring的配置文件或者配置类,创建Spring容器
-
在web环境中,Spring容器的类型是 WebApplicationContext ,它是 ApplicationContext 的子接口
-
-
以前Servlet是由tomcat的web-container管理的,现在新来了个Spring管家,一起管理会乱掉,所以是comcat管理,web.xml启动Spring容器
-
tomcat加载启动web项目 -> web项目读取web.xml,然后通过web.xml创建Spring容器 -> 然后通过Spring容器拿对象
-
web-servlet:
-
让Spring管理servlet
-
把service层对象注入到servlet中
-
-
service:
-
把service层对象配置给Spring容器
-
把dao层对象注入到service层对象中
-
管理事务
-
9.1、XML文件配置Web
9.1.1、引入jar
9.1.2、不变的东西
实体类、dao层及其mapper.xml、service层和实现类、dataSource.properties、log4j.properties、spring-dao.xml、spring-service.xml、appllcationContext.xml 都于之前的XML配置一样
9.1.3、web.xml(重要)
-
服务器启动期间,读取Spring配置文件,创建Spring容器对象
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://xmlns.jcp.org/xml/ns/javaee" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" version="3.1"> <!--配置spring文件的加载路径--> <context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:applicationContext.xml</param-value> </context-param> <!-- 配置监听器,读取配置文件,创建spring容器 --> <listener> <listener- class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> </web-app>
9.1.4、Servlet
-
这里的重点是init方法中,获取了Spring容器对象
-
注意,servlet在web容器中,并不归spring容器管理,所以需要单独从spring容器中拿到Service对象进行赋值
@WebServlet(value="/test",loadOnStartup = 1) public class TestServlet extends HttpServlet{ private static final long serialVersionUID = 1L; private ITeacherService teacherService; @Override public void init() throws ServletException { //获取spring容器,进行初始化 WebApplicationContext ac = WebApplicationContextUtils .getWebApplicationContext(this.getServletContext()); teacherService = ac.getBean(ITeacherService.class); } @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { Teacher t = new Teacher(); t.setName("tom"); t.setAge(20); t.setSalary(5000); teacherService.save(t); resp.setContentType("text/plain;charset=utf-8"); PrintWriter out = resp.getWriter(); out.println("操作成功!"); } }
-
配置到这里就可以让tomcat部署项目,然后启动了
9.2、注解配置Web
-
这里只描述修改或新增的文件/配置,没有改动的不再进行描述
9.2.1、修改service实现类,添加配置
@Transactional(propagation = Propagation.REQUIRED,rollbackFor = Exception.class) @Service public class TeacherServiceImpl implements ITeacherService{ @Autowired private ITeacherDao teacherDao; public void save(Teacher t) { teacherDao.insertTeacher(t); // throw new RuntimeException("异常测试"); } }
9.2.2、新增三个配置类
9.2.2.1、SpringConfig.java
@Configuration @ComponentScan("com.briup.service") @EnableTransactionManagement public class SpringConfig {}
9.2.2.2、MybatisConfig.java
@PropertySource("classpath:dataSource.properties") @MapperScan("com.briup.dao")//指定扫描映射接口的位置 public class MybatisConfig { @Value("${jdbc.driverClassName}") private String driverClassName; @Value("${jdbc.url}") private String url; @Value("${jdbc.username}") private String username; @Value("${jdbc.password}") private String password; //配置数据源dataSource @Bean public DataSource dataSource() { DruidDataSource dataSource = new DruidDataSource(); dataSource.setDriverClassName("oracle.jdbc.OracleDriver"); dataSource.setUrl("jdbc:oracle:thin:@127.0.0.1:1521:XE"); dataSource.setUsername("briup"); dataSource.setPassword("briup"); dataSource.setInitialSize(5); dataSource.setMaxActive(10); return dataSource; } //配置SqlSessionFactoryBean,用来产生SqlSessionFactory,从而获取SqlSession @Bean public SqlSessionFactoryBean sqlSessionFactoryBean() throws IOException{ //资源解析器,用来读取指定位置的xml映射文件 ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver(); //创建SqlSessionFactoryBean对象 SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean(); //注入数据源 sqlSessionFactoryBean.setDataSource(dataSource()); //配置别名 sqlSessionFactoryBean.setTypeAliasesPackage("com.briup.bean"); //配置sql映射文件路径,支持通配符 sqlSessionFactoryBean.setMapperLocations(resourcePatternResolver.getResources("c lasspath:com/briup/mapper/*Mapper.xml")); return sqlSessionFactoryBean; } //配置JDBC的事务管理器(Mybaits通用) @Bean public PlatformTransactionManager transactionManager() { DataSourceTransactionManager transactionManager = new DataSourceTransactionManager(); transactionManager.setDataSource(dataSource()); return transactionManager; } }
9.2.2.3、AppConfig.java
-
这里导入了另外俩个配置类,将来spring只要读取这一个配置类就行了
@Configuration @Import({SpringConfig.class,MybatisConfig.class}) public class AppConfig {}
-
删除spring配置文件:
-
spring-dao.xml 、 spring-service.xml、applicationContext.xml
-
-
因为已经分别使用SpringConfig、MybatisConfig、AppConfig三个配置类代替了
9.2.3、修改Servlet
-
这里使用AutowireCapableBeanFactory,来配置容器外的对象(servlet),使用spring容器的依赖注入,这样这里就可以让servlet使用spring的依赖注入了 @Autowired
@WebServlet(value="/test",loadOnStartup = 1) public class TestServlet extends HttpServlet{ private static final long serialVersionUID = 1L; @Autowired private ITeacherService teacherService; @Override public void init() throws ServletException { //获取spring容器,进行初始化 WebApplicationContext ac = WebApplicationContextUtils.getWebApplicationContext(this.getServletContext()); //通过Spring容器获取AutowireCapableBeanFactory //AutowireCapableBeanFactory可以让容器外的bean使用依赖注入 AutowireCapableBeanFactory autowireCapableBeanFactory = ac.getAutowireCapableBeanFactory(); autowireCapableBeanFactory.autowireBean(this); } @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { Teacher t = new Teacher(); t.setName("tom"); t.setAge(20); t.setSalary(5000); teacherService.save(t); resp.setContentType("text/plain;charset=utf-8"); PrintWriter out = resp.getWriter(); out.println("操作成功!"); } }
9.2.4、修改web.xml
-
这里的头部声明中,加入了context命名空间和对应的schemaLocation
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://xmlns.jcp.org/xml/ns/javaee" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" version="3.1"> <!--配置Spring专门读取注解和配置类的容器类型--> <context-param> <param-name>contextClass</param-name> <param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContext</param-value> </context-param> <!--配置自定义的spring配置类(全限定名) --> <context-param> <param-name>contextConfigLocation</param-name> <param-value>com.briup.config.AppConfig</param-value> </context-param> <!-- 配置监听器,启动服务器期间读取配置 --> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> </web-app>
-
使用tomcat启动