SpringIOC控制反转(Inverse of Control)
- IOC的概念:控制反转就是对对象控制权的转移,从程序代码本身反转到了外部容器。通过容器实现对象的创建,属性赋值,依赖的管理。
- IOC的实现方式:IOC的实现方式多种多样,当前比较流行的实现方式是依赖 注入(DI)。
- DI(dependency injection)的概念:指程序运行过程中,若需要调用另一个对象协助时,无须在代码中创建被调用者,而是依赖于外部容器,由外部容器创建后传递给程序
Spring装载Bean
1.默认装载方式
代码通过getBean()方式从容器获取指定的 Bean实例,容器首先会调用Bean类的无参构造器,创建空值的实例对象。(Bean的定义:Spring容器是一个超级大工厂,负责创建、管理所有的Java对象,这些 Java 对象被称为 Bean。)
2.容器中Bean的作用域(默认singleton)
当通过 Spring 容器创建一个 Bean 实例时,不仅可以完成 Bean 的实例化,还可以通过 scope 属性,为 Bean 指定特定的作用域。Spring 支持 5 种作用域。
● singleton:单态模式。即在整个 Spring 容器中,
使用 singleton 定义的 Bean 将是单例的, 只有一个实例。默认为单态的。
当spring核心配置文件被加载时,实例化配置的Bean实例。
对象创建:当应用加载,创建容器时对象就被创建。
● prototype:原型模式。即每次使用 getBean 方法获取的同一个的实例都是一个 新的实例。
在代码中使用该Bean实例时才进行装配
对象创建:当使用对象时,创建新的对象实例
● request:对于每次 HTTP 请求,都将会产生一个不同的 Bean 实例。
● session:对于每个不同的 HTTP session,都将产生一个不同的 Bean 实例。
3.Bean生命周期配置
- init-method:指定类中的初始化方法名称,对象创建之后调用
- destroy-method:指定类中的销毁方法名称
Bean实例化的三种方式
1.无参构造方法实例化
package com.nrvcer;
public interface UserDao {
public abstract void save();
}
package com.nrvcer.impl;
import com.nrvcer.UserDao;
public class UserDaoImpl implements UserDao {
public void save() {
System.out.println("save success!");
}
}
// applicationContext.xml如下
<bean id="userDao" class="com.nrvcer.impl.UserDaoImpl"></bean>
2.工厂静态方法实例化
package com.nrvcer.factory;
import com.nrvcer.UserDao;
import com.nrvcer.impl.UserDaoImpl;
public class StaticFactory {
public static UserDao getUserDao() {
return new UserDaoImpl();
}
}
// applicationContext.xml如下
<!--静态工厂可以直接调用方法,无需创建工厂类对象 -->
<bean id="userDao" class="com.nrvcer.factory.StaticFactory" factory-method="getUserDao"></bean>
3.工厂实例方法实例化
package com.nrvcer.factory;
import com.nrvcer.UserDao;
import com.nrvcer.impl.UserDaoImpl;
public class DynamicFactory {
public UserDao getUserDao() {
return new UserDaoImpl();
}
}
// applicationContext.xml如下
<!-- 动态工厂需要先有工厂类对象-->
<bean id="factory" class="com.nrvcer.factory.DynamicFactory"></bean>
<bean id="userDao" factory-bean="factory" factory-method="getUserDao"></bean>
Spring中基于XML的注入
1.xml注入的概念
- bean实例在调用无参构造器创建了空值对象后,就要对 bean对象的属性进行初始化。初始化是由容器自动完成的,称为注入。
- 根据注入方式的不同,常用的有两类:设值注入(set注入)、构造注入。
- 注入数据的三种数据类型:普通数据类型、引用数据类型、集合数据类型。
2.设值注入
设值注入是指,通过 setter 方法传入被调用者的实例,这样就不用通过getBean方法从容器中获取被调用者的实例。要求被调用者所在类需要有setter方法。
- 简单数据类型示例
<!-- Student类中必须有setter方法-- >
<!-- id属性值表示Bean实例在spring容器中的唯一标识-->
<!-- class属性值表示类的全限定名-->
<bean id="student" class="impl.Student">
<property name="name" value="张三"></property>
<property name="age" value="23"></property>
</bean>
- 引用数据类型示例,需要使用bean标签的ref属性。ref的使用场景:指定 bean 的某属性值为另一 bean 的实例时,通过 ref 指定它们间的引用关系。ref 的值必须为某 bean 的 id 值。
<!-- Student类中的school属性是一个引用数据类型-->
<bean id="school" class="nrv.School">
<property name="addr" value="浙江大学"></property>
</bean>
<bean id="stu" class="nrv.Student">
<property name="name" value="李四"></property>
<property name="age" value="24"></property>
<property name="school" ref="school"></property>
</bean>
3.构造注入
标签中用于指定参数的属性有:index和name
<!-- 要求需要有有参构造函数-->
<!-- 1. name:指定参数名称。-->
<bean id="school" class="nrv.School">
<constructor-arg name="addr" value="南昌大学"/>
</bean>
<bean id="stu" class="nrv.Student">
<constructor-arg name="name" value="王五"></constructor-arg>
<constructor-arg name="age" value="34"></constructor-arg>
<constructor-arg name="school" ref="school"></constructor-arg>
</bean>
<!-- index 指明该参数对应着构造器的第几个参数,从 0 开始-->
<bean id="school" class="nrv.School">
<constructor-arg index="0" value="南昌大学"/>
</bean>
<bean id="stu" class="nrv.Student">
<constructor-arg index="0" value="王五"></constructor-arg>
<constructor-arg index="1" value="34"></constructor-arg>
<constructor-arg index="2" ref="school"></constructor-arg>
</bean>
4.具有集合性质的属性注入
- MyCollections类中的属性如下:
private String[] strs;
private List<Student> students;
private Set<String> mySet;
private Map<String,Integer> myMap;
private Properties myPro;
- applicationContext.xml(spring配置文件名任意,推荐使用applicationContext)文件如下:
<bean id="myStudent1" class="impl.Student">
<property name="name" value="张三"></property>
<property name="age" value="23"></property>
</bean>
<bean id="myStudent2" class="impl.Student">
<property name="name" value="李四"></property>
<property name="age" value="25"></property>
</bean>
<bean id="myCollections" class="impl.MyCollections">
<!-- 为数组注入值-->
<property name="strs">
<array>
<value>Hello</value>
<value>World</value>
</array>
</property>
<!--为list注入值-->
<property name="students">
<list>
<ref bean="myStudent1"></ref>
<ref bean="myStudent2"></ref>
</list>
</property>
<!-- 为Set注入值-->
<property name="mySet">
<set>
<value>大叔</value>
<value>萝莉</value>
</set>
</property>
<!-- 为Map注入值-->
<property name="myMap">
<map>
<entry key="height" value="175"></entry>
<entry key="weight" value="138"></entry>
</map>
</property>
<!--为Propertied注入值 -->
<property name="myPro">
<props>
<prop key="user">NrvCer</prop>
<prop key="password">123456</prop>
</props>
</property>
<!-- 为复合集合属性注入-->
<property name="list">
<list>
<map>
<entry key="1" value="a"></entry>
<entry key="2" value="b"></entry>
</map>
</list>
</property>
</bean>
5.对于引用类型属性的自动注入(可以取代ref)
对于引用类型属性的注入,也可不在配置文件中显示的注入。可以通过为标签设置autowire属性,为引用类型属性进行隐式自动注入(默认是不自动注入引用类型属性)。autowire的属性值有两种:byName,byType.
根据自动注入判断标准的不同,可以分为两种:
1. byName:根据名称自动注入
2. byType:根据类型自动注入
- byName方式自动注入的要求:配置文件中被调用者 bean 的 id 值与代码中调用者bean类的属性名相同。byName方式自动注入举例:
<!-- idschool即为Student的一个属性名-->
<bean id="school" class="nrv.School">
<property name="addr" value="浙江大学"></property>
</bean>
<bean id="stu" class="nrv.Student" autowire="byName">
<property name="name" value="李四"></property>
<property name="age" value="24"></property>
</bean>
- byType方式自动注入举例
<bean id="mySchool" class="nrv.School">
<property name="addr" value="浙江大学"></property>
</bean>
<bean id="stu" class="nrv.Student" autowire="byType">
<property name="name" value="李四"></property>
<property name="age" value="24"></property>
</bean>
为应用指定多个配置文件
在实际应用里,随着应用规模的增加,系统中Bean数量也大量增加,导致配置文件变得非常庞大、臃肿。为了避免这种情况的产生,提高配置文件的可读性与可维护性,可以将Spring 配置文件分解成多个配置文件。
1.平等关系的配置文件
// 各个配置文件间为并列关系,不分主次
String[] resource = {"xxx1.xml","xxx2.xml"};
// ClassPathXmlApplicationContext实现类表示从类的根路径下即resources目录下加载配置文件
ApplicationContext ac = new ClassPathXmlApplicationContext(resource);
2.包含关系的配置文件
<!-- spring主配置文件中通过import标签进行加载其他子文件
使用主配置文件对容器进行初始化即可。
-->
<beans>
<!-- <import resource="classpath:impl/serviceImpl.xml"></import>-->
<import resource="classpath:impl/*.xml"/>
</beans>
Spring中基于注解的依赖注入(掌握)
对于 DI 使用注解,将不再需要在Spring配置文件中声明 bean 实例。
1.spring中的原始注解
1.实现DI的步骤
- 在pom.xml文件中添加依赖
<!-- 如果在pom.xml中已经添加了spring-context,那此依赖自动包含AOP,无需再次添加。-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>4.3.16.RELEASE</version>
</dependency>
- 需要更换配置文件头,加入 spring-context.xsd 约束
<!-- 如果使用IDEA进行编辑,这个文件头在声明组件
扫描器时会自动更换-->
- 在类上使用注解
- Spring主配置文件中配置组件扫描器,用于在指定的基本包中或者其子包中扫描注解
<!-- 位于beans标签下,将会扫描service包下的注解-->
<context:component-scan base-package="service"></context:component-scan>
<!-- 指定不同的包路径的两种方式:-->
<!-- 1.方式1:使用多个context:component-scan指定不同的包路径 -->
<!-- 2.方式2:多个base-package 的值之间使用逗号或分号分隔符-->
<!--方式3:base-package的值指定到父报名-->
2.定义 Bean(用于创建对象)的注解@Component
在类上使用注解@Component。@Component注解的value属性用于指定创建出来的Bean的id,如果省略该属性,则Bean的id为类名的首字母小写。 Spring 还提供了 3 个功能基本和@Component 等效的注解:
● @Repository 用于对 DAO 实现类进行注解
● @Service 用于对 Service 实现类进行注解
● @Controller 用于对 Controller 实现类进行注解
- 简单例子
// value可以省略,该属性用于指定Bean的id
@Component(value="student")
public class Student {
3.简单类型的注入,使用@Value注解
在属性上使用注解@Value,该注解的 value 属性用于指定要注入的值。使用该注解完成属性注入时,类中无需 setter。当然,若属性有 setter,则也可将其加到 setter 上。
@Value("张三")
private String name;
@Value(value="23")
private String age;
4.byType自动注入使用@Autowired(按照数据类型从spring容器中进行匹配的)
在引用属性上使用注解@Autowired,该注解默认使用按类型自动装配Bean的方式。 使用该注解完成属性注入时,类中无需 setter。当然,若属性有setter,则也可将其加到 setter 上。
// Student类中
@Value("张三")
private String name;
@Value(value="23")
private String age;
@Autowired
private School school;
5.byName 自动注入使用@Autowired 与@Qualifier(按照id值从容器中进行匹配)
需要在引用属性上联合使用注解@Autowired 与@Qualifier。@Qualifier 的 value 属性用于指定要匹配的 Bean 的 id 值。同样类中无需 setter,也可加到 setter 上。
举例:
@Autowired
@Qualifier(value="school")
private School school;
@Autowired 还有一个属性 required,默认值为 true,表示当匹配失败后,会终止程序运行。若将其值设置为 false,则匹配失败,将被忽略,未匹配的属性值为 null。
6.JDK 注解@Resource 自动注入
Spring提供了对 jdk中@Resource注解的支持。@Resource 注解既可以按名称匹配Bean,也可以按类型匹配 Bean.默认是按名称注入
1、byType 注入引用类型属性
@Resource 注解若不带任何参数,采用默认按名称的方式注入,按名称不能注入 bean,则会按照类型进行 Bean 的匹配注入。
// 先按照名称注入,如果名称注入不行,则按照类型
@Resource
private School school;
2、byName 注入引用类型属性
@Resource 注解指定其 name 属性,则 name 的值即为按照名称进行匹配的 Bean 的 id。
// 指定按照名称注入,我就需要Bean id为school的对象
// 相当于@Autowired + @Qualifier(value="school")
@Resource(name = "school")
private School school;
7.@Scope
@Scope注解标注Bean的作用范围
8.@PostConstruct
@PostConstruct注解使用在方法上标注该方法是Bean的初始化方法
9.@PreDestroy
@PreDestroy注解使用在方法上标注该方法是Bean的销毁方法
2.spring中的新注解
使用spring原始注解不能够全部替代xml配置文件,比如说还能够使用注解替代的配置如下:
- 非自定义的Bean的配置:
<bean></bean>
- 加载properties文件的配置:context:property-placeholder
- 组件扫描器的配置:context:component-scan
- 引入其他文件:
1.@Configuration
该注解用于指定当前类是一个spring配置类,当创建容器时会从该类上加载注解
2.@ComponentScan
该注解用于指定spring在初始化容器时要扫描的包,作用和在springxml配置文件中配置组件扫描器context:component-scan base-package="com.nrvcer"/>
一样
3.@Bean
该注解使用在方法上,标注将该方法的返回值(一般为对象)存储到spring容器中
4.@PropertySource
该注解用于加载.properties文件中的配置
5.@Import
该注解用于导入其他配置类
AOP(aspect oriented programming)的三要素
面向切面编程,就是将交叉业务逻辑封装成切面,利用 AOP 容器的功能将切面织入到主业务逻辑中。所谓交叉业务逻辑是指,通用的、与主业务逻辑无关的代码,如安全检查、事务、日志、缓存等。
- 切面(aspect):表示给业务方法增加的功能(泛指交叉业务逻辑)。常见的切面有日志、事务、统计信息、权限验证等
- 一个切面有三个关键的要素:
- 切面的功能代码,表示切面要干什么
- 切面的执行位置,使用Pointcut表示切面执行的位置
- 切面的执行时间,使用Advice表示执行时间
- 一个切面有三个关键的要素:
- 切入点(pointcut):多个连接点的集合,多个业务接口中的方法。表示切面功能执行的位置。标记为final的方法是不能作为连接点与切入点的(不能被增强)
- 通知(advice,也叫做增强):表示切面执行的时间,目标方法前,目标方法后等。通知定义了增强代码切入到目标代码的时间点。这三个为AOP的三要素。
- 连接点(joinpoint):连接点指的是可以被切面织入的具体方法,通常业务接口中的方法均为连接点。
- 目标对象(target):指的是要被增强的对象,即包含主业务逻辑的类的对象
AspectJ
1.概述
Spring 又将 AspectJ的对于AOP的实现也引入到了自己的框架中。在 Spring 中使用 AOP开发时,一般使用AspectJ 的实现方式。
2.AspectJ的切入点表达式:表示切面执行的位置
切入点表达式用于指定切入点的。 切入点表达式共有四个部分:
// []表示可省略
// 参数中只需要参数的类型,参数名不需要
execution([访问权限] 方法返回值类型 [包名类名]方法声明(参数) [异常类型])
在切入点表达式中可以使用以下符号:
*:0至多个任意字符
..: 用在方法参数中,表示任意多个参数;用在包名后,表示当前包及其子包
+:用在类名后,表示当前类及其子类;用在接口名后,表示当前接口及其实现类
举例:
- 指定切入点为:任意公共方法
execution(public * *(..))
- 指定切入点为:任何一个以set开始的方法,任意方法返回值,任意方法参数
execution(* set*(..))
- 指定切入点为:定义在service包里的任意类的任意方法
execution(* com.xyz.service.*.*(..))
- 指定切入点为定义在service包或者子包中的任意类的任意方法
execution(* com.xyz.service..*.*(..))
- 指定切入点为:指定所有包下的service子包下的所有类(接口)中所有方法
execute(* *..service.*.*(..))
3.AspectJ 的开发环境
// 需要在pom.xml文件中引入AspectJ的依赖
<!--aspectj的依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>4.3.16.RELEASE</version>
</dependency>
4.AspectJ通知注解:表示切面的执行时间
AspectJ 对于 AOP的实现有注解和XML配置文件两种方式,常用是注解方式。
- 通知注解:在切面类中修饰方法的注解,这些注解体现了通知类型。使用了通知注解修饰的方法称之为通知方法。 一共有五种通知类型,就对应了五种通知注解(23456)
1. 前置通知:@Before
2. 后置通知:@AfterReturning
3. 环绕通知:@Around
4. 异常通知:@AfterThrowing
5. 最终通知:@After
- @Before前置通知-方法有JoinPoint参数.所有的通知方法都可以包含该参数,并且该参数必须是第一个位置。
// 所有通知方法都可以包含JoinPoint参数
// JoinPoint参数表示要加入切面功能的业务方法
@Before(value="execution(* *..*.*.*(..))")
public void twoAspect(JoinPoint joinPoint) {
// JoinPoint参数能够获取到方法的定义,方法的参数等信息
// 连接点方法的定义:void com.nrvcer.service.SomeService.dosome(String,Integer)
System.out.println("连接点方法的定义:" + joinPoint.getSignature());
System.out.println("连接点方法的名称:" + joinPoint.getSignature().getName());
// 连接点方法的参数个数:2
System.out.println("连接点方法的参数个数:" + joinPoint.getArgs().length);
// 方法参数的信息
Object[] args = joinPoint.getArgs();
for (Object arg : args) {
System.out.println(arg);
}
System.out.println("前置通知,在目标方法之前执行输出日志");
}
- @AfterReturning 后置通知-注解有 returning 属性,用于指定接收目标方法返回值的变量名。被该注解修饰的方法除了包含JointPoint参数,还能包含用于接收返回值的变量
@AfterReturning(value ="execution(String *..*.*.*(..))"
,returning = "result")
public void threeAspect(Object result) {
// 修改目标方法的执行结果
// result是目标方法的返回值
if (result != null) {
String str = (String)result;
result = str.toUpperCase(Locale.ROOT);
}
System.out.println("后置通知,在目标方法之后执行的功能增强,例如执行事务处理," + result);
}
- @Around 环绕通知-增强方法中有 ProceedingJoinPoint参数。在目标方法执行之前之后执行(增强功能)。被注解为环绕增强的方法要有返回值,Object 类型。并且方法可以包含一个 ProceedingJoinPoint 类型的参数。接口ProceedingJoinPoint其有一个proceed()方法,用于执行目标方法。若目标方法有返回值,则该方法的返回值就是目标方法的返回值。最后,环绕增强方法将其返回值返回。该增强方法实际是拦截了目标方法的执行。环绕通知经常用于做事务,在目标方法之前开启事务,执行目标方法,在目标方法之后提交事务。
@Around(value = "execution(String *..*.*.*First(..))")
public Object fourAspect(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
Object obj = null;
// 增强功能
System.out.println("环绕通知:在目标方法之前执行的,例如输出日志");
// 执行目标方法的调用。等同于method.invoke(target, args)
// 可以控制目标方法是否执行
obj = proceedingJoinPoint.proceed();
// 增强功能
System.out.println("环绕通知:在目标方法之后执行的,例如处理事务");
// 返回目标方法的执行结果
return obj;
}
- @AfterThrowing 异常通知-注解中有 throwing 属性
- @After最终通知
- @Pointcut 定义切入点(辅助)
- @Aspect注解:该注解放在类上,表示当前类是切面类
5.设置AspectJ 实现 AOP的方式
在Spring配置文件中,通过aop:aspectj-autoproxy/的proxy-target-class属性设置选择通过JDK动态代理还是cglib动态代理实现AOP。
<!--声明自动代理生成器:使用aspectj把spring容器中目标类对象生成代理
proxy-target-class="true"表示使用cglib动态代理
目标类有接口,默认使用jdk动态代理。
目标类没有接口,默认时候cglib动态代理
目标类有接口,也可以使用cglib动态代理,需要设置proxy-target-class="true"
-->
<aop:aspectj-autoproxy proxy-target-class="true" />
<!-- <aop:aspectj-autoproxy/> -->
6.示例:给已经存在的业务方法增加额外的功能
- 添加依赖
- 创建目标类:接口和实现类
- 创建切面类:就是一个普通类
- 在类的上面加上@Aspect注解
- 类中定义方法,方法就是切面要执行的功能代码,在方法的上面加入AspectJ中的通知注解,比如说@Before,通知注解的value属性值指定切入点表达式execution()
- 创建spring配置文件:声明对象,将对象交给容器统一管理。声明对象可以使用注解方式或者XML中的Bean标签形式。
- 声明目标类对象
- 声明切面类对象
- 声明AspectJ中的自动代理生成器
- 创建测试类
Spring集成mybatis
1.概述
Spring集成myBatis,其本质工作就是:将使用mybatis框架时用到的一些需要自己创建的对象,交由Spring统一管理。 主要有一下对象:
- 数据源dataSource。就是保存数据库连接信息的对象。在实际业务开发中,我们放弃使用Mybatis自带的数据库连接池,而采用阿里的Druid,更加高效;
- 生成sqlSession对象的sqlSessionFactory;
- Dao接口的实现类对象。
2.Spring集成myBatis创建项目的流程
- 在pom.xml文件中添加依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>spring_mybatis</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<!-- junit依赖-->
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.1</version>
<scope>test</scope>
</dependency>
<!-- spring依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.10</version>
</dependency>
<!-- 数据库连接池Druid-->
<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.26</version>
</dependency>
<!-- mybatis整合spring的依赖-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.3.1</version>
</dependency>
<!-- mybatis的依赖-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.7</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.3.11</version>
</dependency>
</dependencies>
<build>
<resources>
<!-- 资源插件-->
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.xml</include>
<include>**/*.properties</include>
</includes>
<filtering>false</filtering>
</resource>
<!--原有资源路径-->
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*.xml</include>
<include>**/*.properties</include>
</includes>
<filtering>false</filtering>
</resource>
</resources>
</build>
</project>
- 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>
<!-- mapper映射文件的注册:有两种方式-->
<!--1. 使用package标签-->
<!--2. 使用mapper标签-->
<mappers>
<!--加载dao包下的所有mapper文件-->
<package name="dao"/>
</mappers>
</configuration>
- spring的配置文件applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<!--读取配置文件
location:指定属性配置文件的路径
"classpath:":关键字表示类路径,也就是class文件所在的目录
-->
<context:property-placeholder location="classpath:jdbc.properties" />
<!--配置数据源DataSource-->
<bean id="myDataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
<!--读取属性配置文件jdbc.properties的key的值,使用 ${key}-->
<!--数据库的uri-->
<property name="url" value="${jdbc.url}"/> <!--setUrl()-->
<!--数据库的用户名-->
<property name="username" value="${jdbc.username}"/> <!--setUsername()-->
<!--访问密码-->
<property name="password" value="${jdbc.password}" /><!--setPassoword()-->
</bean>
<!--
DruidDataSource myDataSource = new DruidDataSource();
myDataSource.setUrl();
myDataSource.setUsername();
myDataSource.setPassword();
myDataSource.init();
-->
<!--声明SqlSessionFactoryBean,创建SqlSessionFactory对象-->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<!--数据源-->
<property name="dataSource" ref="myDataSource" />
<!--指定mybatis的主配置文件-->
<property name="configLocation" value="classpath:mybatis.xml" />
</bean>
<!--声明MyBatis的扫描器,创建Dao接口的实现类对象-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<!--指定SqlSessionFactory对象,能获取SqlSession-->
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />
<!--指定Dao接口的包名,框架会把这个包中的所有接口一次创建出Dao对象-->
<property name="basePackage" value="dao" />
</bean>
<!--
从spring中获取SqlSessionFacotory,因为spring是一个容器(Map)
SqlSessionFactory factory = map.get("sqlSessionFactory");
SqlSession session = factory.openSession();
遍历dao包下的所有接口
for(接口:dao)
{
Dao对象 = session.getMapper(接口)
//把创建好的对象放入到spring容器中
spring的Map.put( 接口名的首字母小写, Dao对象 )
}
-->
<!--声明Service-->
<bean id="someService" class="service.impl.SomeServiceImpl">
<constructor-arg ref="studentDao"/>
</bean>
</beans>
- jdbc.properties
jdbc.url=jdbc:mysql://localhost:3306/text?serverTimezone=UTC
jdbc.username=root
jdbc.password=123456
- test
ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
SomeService service = (SomeService) ac.getBean("someService");
List<Student> students = service.queryStudents();
for (Student s : students) {
System.out.println(s);
}
Spring的事务
多种数据库的访问技术,有不同的事务处理机制、对象、方法。spring提供一种处理事务的统一模型,能使用统一步骤,方式完成多种数据库访问技术的事务处理。 在Spring中通常可以通过以下两种方式来实现对事务的管理:
- 使用Spring的事务注解管理事务
- 使用AspectJ的AOP配置管理事务
1.Spring事务管理API
- 事务管理器接口:事务管理器是PlatformTransactionManager接口实现类对象。其主要用于完成事务的提交、回滚,及获取事务的状态信息。
- 常用的两个实现类:DataSourceTransactionManager:使用 JDBC 或MyBatis进行数据库操作时使用。
HibernateTransactionManager:使用 Hibernate 进行持久化数据时使用。 - spring的回滚方式:Spring 事务的默认回滚方式是:发生运行时异常和 error 时回滚,发生受查(编译)异常时提交,没有异常抛出则在方法执行后提交事务。对于受查时异常,程序员可以手动设置其回滚方式
- 常用的两个实现类:DataSourceTransactionManager:使用 JDBC 或MyBatis进行数据库操作时使用。
<!--告诉spring使用的是哪种数据库的访问技术,比如说MyBatis-->
<!--或者Hibernate访问数据库-->
<!--声明事务管理器-->
- 事务定义接口:事务定义接口TransactionDefinition 中定义了事务描述相关的三类常量:事务隔离级别、
事务传播行为、事务默认超时时限,及对它们的操作。
五个事务隔离级别常量
1. ISOLATION_DEFAULT:采用 DB 默认的事务隔离级别。MySql 的默认为
REPEATABLE_READ; Oracle默认为 READ_COMMITTED。
2. READ_UNCOMMITTED:读未提交。未解决任何并发问题。
3. EAD_COMMITTED:读已提交。解决脏读,存在不可重复读与幻读。
4. EPEATABLE_READ:可重复读。解决脏读、不可重复读,存在幻读
5. ERIALIZABLE:串行化。不存在并发问题。
七个事务传播行为常量:控制业务方法是不是有事务,是什么样的事务
1. PROPAGATION_REQUIRED
指定的方法必须在事务内执行。若当前存在事务,就加入到当前事务中;
若当前没有事务,则创建一个新事务。Spring 默认的事务传播行为
2. PROPAGATION_REQUIRES_NEW
总是新建一个事务,若当前存在事务,就将当前事务挂起,
直到新事务执行完毕
3. PROPAGATION_SUPPORTS
指定的方法支持当前事务,但若当前没有事务,
也可以以非事务方式执行。
4. PROPAGATION_MANDATORY
5. PROPAGATION_NESTED
6. PROPAGATION_NEVER
7. PROPAGATION_NOT_SUPPORTED
事务的超时时间
常量 TIMEOUT_DEFAULT定义了事务底层默认的超时时限,
sql 语句的执行时长。
表示一个方法最长的执行时间,如果方法执行时超过了时间,事务就回滚
单位是秒,整数值,默认是-1(表示不设置超时时间)
2.总结spring的事务
- 管理事务的是事务管理器和它的实现类
- spring的事务是一个统一模型
- 指定要使用的事务管理器实现类,使用
- 指定哪些类、哪些方法需要加入事务的功能
- 指定方法需要的隔离级别,传播行为,超时时间
- 指定要使用的事务管理器实现类,使用
3.使用Spring的事务注解管理事务
通过@Transactional 注解方式,可将事务织入到相应 public 方法中,实现事务管理。
1.@Transactional的所有可选属性如下:
- @Transactional注解的位置
1. 在方法上:只能用于public方法上,对于其他加上了该注解的非公有方法,
不会将指定事务织入到该方法中
2. 在类上:表示该类上所有的方法均将在执行时织入事务
- 实现注解的事务步骤:
1. 声明事务管理器
<bean class="org.springframework.jdbc.datasource.DataSourceTransactionManager" id="transactionManager">
<property name="dataSource" ref="druidDataSource"/>
</bean>
2. 开启注解驱动:其中transaction-manager属性的值为事务管理器bean的id
<tx:annotation-driven transaction-manager="transactionManager"/>
3. 业务层public方法加入@Transactional注解
比如:@Transactional(
propagation = Propagation.REQUIRED,
isolation = Isolation.DEFAULT,
readOnly = false,
rollbackFor = {
NullPointerException.class,NotEnoughException.class
}
)
或者:
// 表示使用的是事务控制的默认值,默认的传播行为是REQUIRED,默认的隔离级别是DEFAULT
// 默认抛出运行时异常,回滚事务
// @Transactional
4.使用AspectJ的AOP配置管理事务(推荐使用)
使用AspectJ框架功能,在spring配置文件中声明类、方法需要的事务。这种方式业务方法和事务配置完全分离。
- 使用AspectJ框架,需要在POM文件中加入AspectJ依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
- 声明事务管理器对象
<bean id="druidDataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
<!-- 1.声明事务管理器对象-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="druidDataSource"/>
</bean>
- 声明方法需要的事务类型(配置方法的事务属性,隔离级别、传播行为、超时)
<!-- 2.声明业务方法的事务属性(隔离级别,传播行为,超时时间)
transaction-manager:表示事务管理器对象的ID
-->
<td:advice id= "myadvice" transaction-manager="transactionManager">
<!--tx:attributes:配置事务的属性 -->
<tx:attributes>
<!-- tx:method:给具体的方法配置事务属性,method可以有多个,分别给不同的方法设置事务属性
name:方法名称
1. 完整的方法名称,不带有包和类
2. 方法可以使用通配符,*表示任意字符
propagation:传播行为
isolation:隔离级别
rollback-for:指定的异常类名,全限定类名。表示发生异常一定回滚
-->
<tx:method name="buy*"
propagation="REQUIRED"
isolation="DEFAULT"
rollback-for="java.lang.NullPointerException,com.nrvcer.exces.NotEnoughException"/>
</tx:attributes>
</td:advice>
- 配置AOP:指定哪些类需要创建代理
<!--配置AOP -->
<aop:config>
<!-- 配置切入点表达式:指定哪些包中的类需要使用事务
id:切入点表达式的名称
expression:切入点表达式,指定哪些类需要使用事务
-->
<!-- 所有service包及其子包下的所有方法-->
<aop:pointcut id="servicePt" expression="execution(* *..service..*.*(..))"/>
<!--配置增强器:关联advice和pointcut -->
<aop:advisor advice-ref="myadvice" pointcut-ref="servicePt"/>
</aop:config>
Spring与Web
在web项目中使用Spring框架,首先要在web层中获取到spring容器,在web层中获取到了Spring容器,才能从容器中获取到service对象。
1.创建一个Spring容器对象
- 解决不同Servlet或者同一个Servlet中重复创建ApplicationContext对象(即spring容器),造成内存浪费的问题
- 解决:将spring容器的创建时机放在ServletContext初始化时,然后将容器对象放入全局作用域ServletContext中(spring容器具有全局性)。
- 创建一个容器对象的解决方案:使用Spring的监听器ContextLoaderListener。若要在 ServletContext 初 始 化 时 创 建 Spring 容 器 , 就 需 要 使 用 监 听 器 接 口ServletContextListener 对 ServletContext 进行监听
监听器的作用:
1. 创建容器对象,类似于执行ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
2. 将容器对象放入到ServletContext中,即servletContext.setAttribute(key, ac);
步骤:
1. 为了使用spring的那个监听器,需要在pom.xml中加入依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
2. 在web.xml中注册监听器ContextLoaderListener
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
3. 使用context-param标签在web.xml中指定Spring配置文件的位置
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml</param-value>
</context-param>
2.Spring容器对象的获取
- 方式1:直接从ServletContext中获取,调用ServletContext的getAttribute方法
WebApplicationContext app = null;
// 获取spring容器对象
ServletContext servletContext = this.getServletContext();
String key = WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE;
Object attribute= servletContext.getAttribute(key);
if (attribute != null) {
app = (WebApplicationContext) attribute;
}
- 方式2:通过WebApplicationContextUtils工具类获取
ServletContext servletContext = this.getServletContext();
WebApplicationContext context = WebApplicationContextUtils.getRequiredWebApplicationContext(servletContext);
创建一个ServletContextListener(ContextLoaderListener是其实现类)。在ServletContext初始化的时候创建ApplicationContext对象(容器对象),并将它保存在ServletContext中。
这样,在每个servlet中,只要调用当前servlet的ServletContext对象的getAttribute方法就可以获取这个webapp中共享的一个ApplicationContext对象。spring-web框架已经帮我们创建好了这样一个监听器。我们只需要在web.xml注册这个监听器就可以使用了。