Spring
spring核心文档:
https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#spring-core
概述与IOC推导
1.概述
- Spring : 春天 —>给软件行业带来了春天
- 2002年,Rod Jahnson首次推出了Spring框架雏形interface21框架。
- 2004年3月24日,Spring框架以interface21框架为基础,经过重新设计,发布了1.0正式版。
spring框架优点:1、Spring是一个开源免费的框架 , 容器;2、Spring是一个轻量级的框架 , 非侵入式的;3、控制反转IoC , 面向切面Aop;4、对事物的支持 , 对框架的支持。
一句话概括:Spring是一个轻量级的控制反转(IoC)和面向切面(AOP)的容器(框架)。
Spring 框架是一个分层架构,由 7 个定义良好的模块组成。Spring 模块构建在核心容器之上,核心容器定义了创建、配置和管理 bean 的方式 .
组成 Spring 框架的每个模块(或组件)都可以单独存在,或者与其他一个或多个模块联合实现。每个模块的功能如下:
- Spring Core:核心容器提供 Spring 框架的基本功能。核心容器的主要组件是
BeanFactory,它是工厂模式的实现。BeanFactory 使用控制反转(IOC)模式将应用程序的配置和依赖性规范与实际的应用程序代码分开。 - Spring Context:Spring 上下文是一个配置文件,向 Spring 框架提供上下文信息。Spring 上下文包括企业服务,例如JNDI、EJB、电子邮件、国际化、校验和调度功能。
- Spring AOP:通过配置管理特性,Spring AOP 模块直接将面向切面的编程功能 , 集成到了 Spring框架中。所以,可以很容易地使 Spring 框架管理任何支持 AOP的对象。Spring AOP 模块为基于 Spring的应用程序中的对象提供了事务管理服务。通过使用 Spring AOP,不用依赖组件,就可以将声明性事务管理集成到应用程序中。
- Spring DAO:JDBC DAO抽象层提供了有意义的异常层次结构,可用该结构来管理异常处理和不同数据库供应商抛出的错误消息。异常层次结构简化了错误处理,并且极大地降低了需要编写的异常代码数量(例如打开和关闭连接)。Spring
DAO 的面向 JDBC 的异常遵从通用的 DAO 异常层次结构。 - Spring ORM:Spring 框架插入了若干个 ORM 框架,从而提供了 ORM 的对象关系工具,其中包括JDO、Hibernate 和 iBatis SQL Map。所有这些都遵从 Spring 的通用事务和 DAO 异常层次结构。
- Spring Web:Web 上下文模块建立在应用程序上下文模块之上,为基于 Web 的应用程序提供了上下文。所以,Spring框架支持与 Jakarta Struts 的集成。Web 模块还简化了处理多部分请求以及将请求参数绑定到域对象的工作。
- Spring MVC:MVC 框架是一个全功能的构建 Web 应用程序的 MVC 实现。通过策略接口,MVC框架变成为高度可配置的,MVC 容纳了大量视图技术,其中包括 JSP、Velocity、Tiles、iText 和 POI。
2.IOC推导
我们先用我们原来的方式写一段代码 .
1、先写一个UserDao接口
public interface UserDao {
public void getUser();
}
2、再去写Dao的实现类
public class UserDaoImpl implements UserDao {
@Override
public void getUser() {
System.out.println("获取用户数据");
}
}
3、然后去写UserService的接口
public interface UserService {
public void getUser();
}
4、最后写Service的实现类
public class UserServiceImpl implements UserService {
private UserDao userDao = new UserDaoImpl();
@Override
public void getUser() {
userDao.getUser();
}
}
5、测试一下
@Test
public void test(){
UserService service = new UserServiceImpl();
service.getUser();
}
这是我们原来的方式 , 开始大家也都是这么去写的对吧 . 那我们现在修改一下 .
把Userdao的实现类增加一个 .
public class UserDaoMySqlImpl implements UserDao {
@Override
public void getUser() {
System.out.println("MySql获取用户数据");
}
}
紧接着我们要去使用MySql的话 , 我们就需要去service实现类里面修改对应的实现new对象
public class UserServiceImpl implements UserService {
private UserDao userDao = new UserDaoMySqlImpl();
@Override
public void getUser() {
userDao.getUser();
}
}
在假设, 我们再增加一个Userdao的实现类 .
public class UserDaoOracleImpl implements UserDao {
@Override
public void getUser() {
System.out.println("Oracle获取用户数据");
}
}
那么我们要使用Oracle , 又需要去service实现类里面修改对应的实现new对象。假设我们的这种需求非常大 , 这种方式就根本不适用, 甚至反人类对吧 , 每次变动 , 都需要修改大量原代码 . 这种设计的耦合性太高了, 牵一发而动全身 。
那我们如何去解决呢 ?
我们可以在需要用到他的地方 , 不去实现它 , 而是留出一个接口 , 利用set , 我们去代码里修改下 。
public class UserServiceImpl implements UserService {
private UserDao userDao;
// 利用set实现,程序不去创建对象,有用户去通过调用setUserDao选择需要哪个实现
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
@Override
public void getUser() {
userDao.getUser();
}
}
现在去我们的测试类里 , 进行测试 ;
@Test
public void test(){
UserServiceImpl service = new UserServiceImpl();
service.setUserDao( new UserDaoMySqlImpl() );
service.getUser();
//那我们现在又想用Oracle去实现呢
service.setUserDao( new UserDaoOracleImpl() );
service.getUser();
}
大家发现了区别没有 ? 以前所有东西都是由程序去进行控制创建 , 而现在是由我们自行控制创建对象 , 把主动权交给了调用者 . 程序不用去管怎么创建、怎么实现了,它只负责提供一个接口。
这种思想 , 从本质上解决了问题 , 我们程序员不再去管理对象的创建了 , 更多的去关注业务的实现 . 耦合性大大降低 . 这也就是IOC的原型 !
IOC本质
控制反转IoC(Inversion of Control),是一种设计思想,DI(依赖注入)是实现IoC的一种方法,也有人认为DI只是IoC的另一种说法。没有IoC的程序中 , 我们使用面向对象编程 , 对象的创建与对象间的依赖关系完全硬编码在程序中,对象的创建由程序自己控制,控制反转后将对象的创建转移给第三方,个人认为所谓控制反转就是:获得依赖对象的方式反转了。
IoC是Spring框架的核心内容,使用多种方式完美的实现了IoC,可以使用XML配置,也可以使用注解,新版本的Spring也可以零配置实现IoC。
Spring容器在初始化时先读取配置文件,根据配置文件或元数据创建与组织对象存入容器中,程序使用时再从Ioc容器中取出需要的对象。
采用XML方式配置Bean的时候,Bean的定义信息是和实现分离的,而采用注解的方式可以把两者合为一体,Bean的定义信息直接以注解的形式定义在实现类中,从而达到了零配置的目的。
控制反转是一种通过描述(XML或注解)并通过第三方去生产或获取特定对象的方式。在Spring中实现控制反转的是IoC容器,其实现方法是依赖注入(Dependency Injection,DI)。
一.入门
1.导入spring-webmvc.jar包(它会自动导入spring依赖的jar包,包括spring-core、spring-aop等)
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.3.9</version>
</dependency>
2.编写一个Hello实体类
public class Hello {
private String name;
public Hello() {
System.out.println("无参构造执行了");
}
public Hello(String name) {
this.name = name;
System.out.println("有参构造执行了");
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Hello{" +
"name='" + name + '\'' +
'}';
}
}
3.编写spring的xml配置文件 , 这里我们命名为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 id="hello" class="com.liqingfeng.pojo.Hello">
<!-- 属性 这里实际调用类的set方法>
<property name="name" value="liqingfeng"/>
</bean>
</beans>
4.写测试类,测试得出结果
@Test
public void test(){
//创建spring容器,将applicationContext.xml的所有对象都new出来了
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
//要用啥对象就从对应bean中取出(用id取)
Hello hello = (Hello) context.getBean("hello");
System.out.println(hello);
}
思考:
- Hello 对象是谁创建的 ? hello 对象是由Spring创建的
- Hello 对象的属性是怎么设置的 ? hello 对象的属性是由Spring容器设置的
- 这个过程就叫控制反转 : 控制 : 谁来控制对象的创建 , 传统应用程序的对象是由程序本身控制创建的 , 使用Spring后 ,对象是由Spring来创建的 反转 : 程序本身不创建对象 , 而变成被动的接收对象 .
- 依赖注入 : 就是利用set方法来进行注入的.
- IOC是一种编程思想,由主动的编程变成被动的接收
修改IOC推导中的案例:(给每个实现类利用spring创建对象托管着,然后需要利用哪个实现类就在userServiceImpl的bean中ref引用即可,所以只改xml配置文件就可以,之前是在测试类中手动修改)
<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 id="userDaoImpl" class="com.liqingfeng.dao.UserDaoImpl"/>
<bean id="userMySqlImpl" class="com.liqingfeng.dao.UserMySqlImpl"/>
<bean id="userOracleImpl" class="com.liqingfeng.dao.UserOracleImpl"/>
<bean id="userServiceImpl" class="com.liqingfeng.service.UserServiceImpl">
<property name="userDao" ref="userOracleImpl"/>
</bean>
</beans>
测试:
@Test
public void test(){
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
UserServiceImpl userServiceImpl = context.getBean("userServiceImpl", UserServiceImpl.class);
userServiceImpl.getUser();
}
二.IoC
1.IoC创建对象的方式
对象的创建是在创建spring容器(applicationContext.xml)的时候,就将applicationContext.xml的所有对象都new出来了
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
方式1:通过无参构成创建
上面的Hello类中有无参和有参构造方法
public class Hello {
private String name;
public Hello() {
System.out.println("无参构造执行了");
}
public Hello(String name) {
this.name = name;
System.out.println("有参构造执行了");
}
applicationContext.xml配置文件中默认按照无参构造创建对象
<bean id="hello" class="com.liqingfeng.pojo.Hello">
<property name="name" value="liqingfeng"/>
</bean>
测试结果:
方式2:通过有参构成创建:
applicationContext.xml配置文件中添加有参构造的配置constructor-arg,但是有参构造有三种方法,我们选择第二种比较明了的
<!-- 第一种根据index参数下标设置 -->
<bean id="userT" class="com.kuang.pojo.UserT">
<!-- index指构造方法 , 下标从0开始 -->
<constructor-arg index="0" value="kuangshen2"/>
</bean>
<!-- 第二种根据参数名字设置 -->
<bean id="userT" class="com.kuang.pojo.UserT">
<!-- name指参数名 -->
<constructor-arg name="name" value="kuangshen2"/>
</bean>
<!-- 第三种根据参数类型设置 -->
<bean id="userT" class="com.kuang.pojo.UserT">
<constructor-arg type="java.lang.String" value="kuangshen2"/>
</bean>
在applicationContext.xml配置文件中设置根据有参构造来创建对象
<bean id="hello" class="com.liqingfeng.pojo.Hello">
<constructor-arg name="name" value="liqingfeng"/>
</bean>
测试结果:
2.Spring配置(了解)
别名配置
alias 设置别名 , 为bean设置别名 , 可以设置多个别名
<!--设置别名:在获取Bean的时候可以使用别名获取-->
<alias name="userold" alias="userNew"/>
Bean配置
<!--bean就是java对象,由Spring创建和管理-->
<!--
id 是bean的标识符,要唯一,如果没有配置id,name就是默认标识符
如果配置id,又配置了name,那么name是别名
name可以设置多个别名,可以用逗号,分号,空格隔开
如果不配置id和name,可以根据applicationContext.getBean(.class)获取对象;
class是bean的全限定名=包名+类名
-->
<bean id="hello" name="hello2 h2,h3;h4" class="com.kuang.pojo.Hello">
<property name="name" value="Spring"/>
</bean>
import配置
可以将多个spring的applicationContext.xml配置文件信息合成一个xml文件,和jsp中的资源导入有类似。
<import resource="{path}/beans.xml"/>
三.依赖注入(Dependency Injection,DI)
- 依赖 : 指Bean对象的创建依赖于容器
- 注入 : 指Bean对象所依赖的资源 , 由容器来设置和装配
依赖注入通俗点说,就是在创建对象的时候,需要初始化(初始化也有很多方式)
比如前面我们学过set方式注入、构造器方式注入
1.set注入(重点)
环境搭建:
public class Student {
private int age;
private Address address;
private String[] books;
private List<String> hobbys;
private Set<String> games;
private Map<String,String> card;
private String wife;
private Properties info;
public void setAge(int age) {
this.age = age;
}
public void setAddress(Address address) {
this.address = address;
}
public void setBooks(String[] books) {
this.books = books;
}
public void setHobbys(List<String> hobbys) {
this.hobbys = hobbys;
}
public void setGames(Set<String> games) {
this.games = games;
}
public void setCard(Map<String, String> card) {
this.card = card;
}
public void setWife(String wife) {
this.wife = wife;
}
public void setInfo(Properties info) {
this.info = info;
}
@Override
public String toString() {
return "Student{" +
"age=" + age +
", address=" + address +
", books=" + Arrays.toString(books) +
", hobbys=" + hobbys +
", games=" + games +
", card=" + card +
", wife='" + wife + '\'' +
", info=" + info +
'}';
}
}
public class Address {
private String address;
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
@Override
public String toString() {
return "Address{" +
"address='" + address + '\'' +
'}';
}
}
常量注入
既然属于set注入的一种,常量注入会找到对应属性的set方法,value是根据set方法的参数类型必须为普通类型(包括String类型),所以value要求类的成员变量为普通类型(包括String类型)才能使用value赋值注入。
<bean id="student" class="com.liqingfeng.pojo.Student">
<property name="age" value="20"/>
</bean>
Bean注入
Bean注入同样会找到对应属性的set方法,ref是根据set方法的参数类型必须为引用类型(即java类),所以ref要求类的成员变量为引用类型才能使用ref赋值注入。
<bean id="address" class="com.liqingfeng.pojo.Address">
<property name="address" value="贵阳"/>
</bean>
<bean id="student" class="com.liqingfeng.pojo.Student">
<property name="address" ref="address"/>
</bean>
数组注入
<bean id="student" class="com.liqingfeng.pojo.Student">
<property name="books">
<array>
<value>红楼梦</value>
<value>西游记</value>
<value>三国演义</value>
<value>水浒传</value>
</array>
</property>
</bean>
List注入
<bean id="student" class="com.liqingfeng.pojo.Student">
<property name="hobbys">
<list>
<value>红楼梦</value>
<value>西游记</value>
<value>三国演义</value>
<value>水浒传</value>
</list>
</property>
</bean>
set注入
<bean id="student" class="com.liqingfeng.pojo.Student">
<property name="games">
<set>
<value>红楼梦</value>
<value>西游记</value>
<value>三国演义</value>
<value>水浒传</value>
</set>
</property>
</bean>
Map注入
<bean id="student" class="com.liqingfeng.pojo.Student">
<property name="card">
<map>
<entry key="唐三" value="111111"/>
<entry key="果宝特攻" value="222222"/>
</map>
</property>
</bean>
插话:上面可以写key-ref 和 value-ref,但需要map<object, object>
null注入
<bean id="student" class="com.liqingfeng.pojo.Student">
<property name="wife"><null/></property>
</bean>
Properties注入
<bean id="student" class="com.liqingfeng.pojo.Student">
<property name="info">
<props>
<prop key="username">root</prop>
<prop key="password">123456</prop>
</props>
</property>
</bean>
2.p命名空间注入和c命名空间注入
环境搭建:
public class User {
private String name;
private int age;
public void setName(String name) {
this.name = name;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
p命名空间注入 : 需要在xml文件头部中加入约束文件
xmlns:p="http://www.springframework.org/schema/p"
在spring配置文件中使用p命名空间注入(其实就是简化了之前的set注入操作)
<!--P(属性: properties)命名空间 , 属性依然要设置set方法-->
<bean id="user" class="com.liqingfeng.pojo.User" p:name="唐三" p:age="20"/>
测试结果:
c 命名空间注入 : 需要在xml文件头部中加入约束文件
xmlns:c="http://www.springframework.org/schema/c"
在spring配置文件中使用c命名空间注入(其实就是简化了之前的构造器注入操作)
<!--C(构造: Constructor)命名空间 , 属性依然要设置set方法-->
<bean id="user" class="com.liqingfeng.pojo.User" c:name="唐三" c:age="19"/>
发现问题:爆红了,刚才我们没有写有参构造!
public User(String name, int age) {
this.name = name;
this.age = age;
}
测试结果:
四.Bean的作用域
在Spring中,那些组成应用程序的主体及由Spring IoC容器所管理的对象,被称之为bean。简单地讲,bean就是由IoC容器初始化、装配及管理的对象 .
bean的几种作用域中,request、session、application、websocket作用域仅在基于web的应用中使用(不必关心你所采用的是什么web应用框架),只能用在基于web的Spring ApplicationContext环境。
1.Singleton(单例模式)
- Singleton:单例模式,就是在创建容器时就同时自动创建了一个bean的对象,不管你是否使用,他都存在了,每次获取到的对象都是同一个对象。
- 当一个bean的作用域为Singleton,那么Spring IoC容器中只会存在一个共享的bean实例,并且所有对bean的请求,只要id与该bean定义相匹配,则只会返回bean的同一实例。
Spring中的默认作用域是Singleton,要在XML中将bean显示定义成singleton作用域,可以这样配置:
<bean id="hello" class="com.liqingfeng.pojo.Hello" scope="singleton"/>
测试:(结果为true,说明两次获取的对象是同一个)
@Test
public void test(){
//创建spring容器,将applicationContext.xml的所有对象都new出来了
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
//要用啥对象就从对应bean中取出(用id取)
Hello student1 = context.getBean("hello", Hello.class);
Hello student2 = context.getBean("hello", Hello.class);
System.out.println(student1 == student2); //true
}
2.Prototype(原型模式)
- Prototype是原型模式,它在我们创建容器的时候并没有实例化,而是当我们获取bean的时候才会去创建一个对象,而且我们每次获取到的对象都不是同一个对象。
- 当一个bean的作用域为Prototype,表示一个bean定义对应多个对象实例。Prototype作用域的bean会导致在每次对该bean请求(将其注入到另一个bean中,或者以程序的方式调用容器的getBean()方法)时都会创建一个新的bean实例。
根据经验,对有状态的bean应该使用prototype作用域,而对无状态的bean则应该使用singleton作用域。在XML中将bean定义成prototype,可以这样配置:
<bean id="hello" class="com.liqingfeng.pojo.Hello" scope="prototype"/>
或者
<bean id="hello" class="com.liqingfeng.pojo.Hello" singleton="false"/>
测试:(测试代码还是上面的,可看出不仅无参构造执行两次(因为每次getBean()就创建一个对象,单例模式是在创建容器的时候创建的),而且两次获取的对象不是同一个)
3.Request
- 当一个bean的作用域为Request,表示在一次HTTP请求中,一个bean定义对应一个实例,即每个HTTP请求都会有各自的bean实例,它们依据某个bean定义创建而成。该作用域仅在基于web的Spring ApplicationContext情形下有效。
- 针对每次HTTP请求,Spring容器会根据loginAction bean的定义创建一个全新的LoginAction bean实例,且该loginAction bean实例仅在当前HTTP request内有效,因此可以根据需要放心的更改所建实例的内部状态,而其他请求中根据loginAction bean定义创建的实例,将不会看到这些特定于某个请求的状态变化。当处理请求结束,request作用域的bean实例将被销毁。
在XML中将bean定义成request作用域:
<bean id="hello" class="com.liqingfeng.pojo.Hello" scope="request"/>
4.Session
- 当一个bean的作用域为Session,表示在一个HTTP Session中,一个bean定义对应一个实例。该作用域仅在基于web的Spring ApplicationContext情形下有效。
- 针对某个HTTP Session,Spring容器会根据userPreferences bean定义创建一个全新的userPreferences bean实例,且该userPreferences bean仅在当前HTTP Session内有效。与request作用域一样,可以根据需要放心的更改所创建实例的内部状态,而别的HTTP Session中根据userPreferences创建的实例,将不会看到这些特定于某个HTTP Session的状态变化。当HTTP Session最终被废弃的时候,在该HTTP Session作用域内的bean也会被废弃掉。
在XML中将bean定义成session作用域:
<bean id="hello" class="com.liqingfeng.pojo.Hello" scope="session"/>
五.自动装配
自动装配其实本质就是简化set依赖注入中的ref赋值
Spring中bean有三种装配机制,分别是:1.在xml中显式配置;2.在java中显式配置;3.隐式的bean发现机制和自动装配。
Spring的自动装配需要从两个角度来实现,或者说是两个操作:
- 组件扫描(component scanning):spring会自动发现应用上下文中所创建的bean;
- 自动装配(autowiring):spring自动满足bean之间的依赖,也就是我们说的IoC/DI;
组件扫描和自动装配组合发挥巨大威力,使得显示的配置降低到最少。
测试环境搭建:
public class Cat {
public void shout() {
System.out.println("miao~");
}
}
public class Dog {
public void shout() {
System.out.println("wang~");
}
}
public class Master {
private Cat cat;
private Dog dog;
private String str;
public Cat getCat() {
return cat;
}
public void setCat(Cat cat) {
this.cat = cat;
}
public Dog getDog() {
return dog;
}
public void setDog(Dog dog) {
this.dog = dog;
}
public String getStr() {
return str;
}
public void setStr(String str) {
this.str = str;
}
@Override
public String toString() {
return "Master{" +
"cat=" + cat +
", dog=" + dog +
", str='" + str + '\'' +
'}';
}
}
<bean id="dog" class="com.liqingfeng.pojo.Dog"/>
<bean id="cat" class="com.liqingfeng.pojo.Cat"/>
<bean id="master" class="com.liqingfeng.pojo.Master">
<property name="cat" ref="cat"/>
<property name="dog" ref="dog"/>
<property name="str" value="qinjiang"/>
</bean>
@Test
public void test(){
//创建spring容器,将applicationContext.xml的所有对象都new出来了
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
//要用啥对象就从对应bean中取出(用id取)
Master master = context.getBean("master", Master.class);
master.getDog().shout();
master.getCat().shout();
}
}
1.byName
byName:按名称自动装配
将上面的master bean中有ref的使用byName进行自动装配注入值
<bean id="master" class="com.liqingfeng.pojo.Master" autowire="byName">
<property name="str" value="qinjiang"/>
</bean>
总结:
当一个bean节点带有 autowire byName的属性时。
- 获取属性名(将查找其类中所有的set方法名,例如setCat,获得将set去掉并且首字母小写的字符串,即cat)。
- 去spring容器中(即xml配置文件)寻找是否有此属性名cat对应的id也为cat的bean对象。
- 如果有,就取出注入;如果没有,就报空指针异常。
2.byType
byType:按类型自动装配
使用autowire byType首先需要保证:同一类型的对象,在spring容器中唯一。如果不唯一,会报不唯一的异常。
NoUniqueBeanDefinitionException
测试:
1、将user的bean配置修改一下 : autowire=“byType”
<bean id="user" class="com.liqingfeng.pojo.User" autowire="byType">
<property name="str" value="qinjiang"/>
</bean>
2、测试,正常输出
3、再注册一个cat 的bean对象!
<bean id="cat1" class="com.liqingfeng.pojo.Cat"/>
4、测试,报错:NoUniqueBeanDefinitionException
5、删掉cat2,将cat的bean名称改掉!测试!因为是按类型装配,所以并不会报异常,也不影响最后的结果。甚至将id属性去掉,也不影响结果。
总结:
当一个bean节点带有 autowire byType的属性时。
- 获取参数类型(将查找其类中所有的set方法,并获取参数的类型。例如setCat(Cat cat),参数类型为Cat)。
- 去spring容器中(即xml配置文件)寻找是否有此参数类型Cat对应的bean对象也是Cat类型(但是要求该bean对象只能有一个,不然按类型装配就不能对应(因为多个bean对象都是Cat型,我怎么知道匹配哪个))。
- 如果有,就取出注入;如果没有,就报空指针异常。
六.注解开发
在spring4之后,想要使用注解形式,必须得要引入aop的包
在spring配置文件中引入context文件头
xmlns:context="http://www.springframework.org/schema/context"
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd
开启属性注解支持
<context:annotation-config/>
具体如下:
1.自动装配的注解
1.@Autowired
@Autowired:按类型自动装配(byType)
Master类使用@Autowired注解,可以在字段、方法上使用
public class Master {
@Autowired
private Cat cat;
@Autowired
public void setDog(Dog dog) {
this.dog = dog;
}
spring配置文件修改如下:(这里使用了注解,将autowire 删掉)
<bean id="dog" class="com.liqingfeng.pojo.Dog"/>
<bean id="cat" class="com.liqingfeng.pojo.Cat"/>
<bean id="master" class="com.liqingfeng.pojo.Master">
<property name="str" value="qinjiang"/>
</bean>
测试,成功输出结果!
@Autowired(required=false) 说明:
- required=false,该注解作用的对象可以为null;
- required=true,该注解作用的对象不能为null。
2.@Nullable
同@Autowired(required=false) 一样:使用了该注解作用的对象可以为null;
3.@Qualifier
@Autowired是根据byType自动装配的,加上@Qualifier则可以根据byName自动装配,@Qualifier不能单独使用。
在xml中加一个id为cat1的bean对象
<bean id="dog" class="com.liqingfeng.pojo.Dog"/>
<bean id="cat" class="com.liqingfeng.pojo.Cat"/>
<bean id="cat1" class="com.liqingfeng.pojo.Cat"/>
<bean id="master" class="com.liqingfeng.pojo.Master">
<property name="str" value="qinjiang"/>
</bean>
在Master类中指定@Qualifier(“cat1”),表示按类型装配不了,则按id为cat1的bean对象进行装配注入
public class Master {
@Autowired
@Qualifier("cat1")
private Cat cat;
测试,成功输出!
4.@Resource
- @Resource如有指定的name属性,先按该属性进行byName方式查找装配;
- 其次再进行默认的byName方式进行装配;(即寻找该属性名对应的id同名的bean对象)
- 如果以上都不成功,则按byType的方式自动装配。
- 都不成功,则报异常。
在Master类中使用@Resource
public class Master{
//如果允许对象为null,设置required = false,默认为true
@Resource(name = "cat1") //按name寻找对应id同名装配
private Cat cat;
@Resource //按默认byName方式装配
private Dog dog;
private String str;
}
xml配置文件不变
<bean id="dog" class="com.liqingfeng.pojo.Dog"/>
<bean id="cat" class="com.liqingfeng.pojo.Cat"/>
<bean id="cat1" class="com.liqingfeng.pojo.Cat"/>
<bean id="master" class="com.liqingfeng.pojo.Master">
<property name="str" value="qinjiang"/>
</bean>
测试成功
xml配置文件改变,将id为cat、cat1的bean对象取消,新建一个id为cat2的bean对象
<bean id="dog" class="com.liqingfeng.pojo.Dog"/>
<bean id="cat2" class="com.liqingfeng.pojo.Cat"/>
<bean id="master" class="com.liqingfeng.pojo.Master">
<property name="str" value="qinjiang"/>
</bean>
测试也成功(说明@Resource(name = “cat1”) 找不到该同名id,默认的byName也不行,只能按照byType自动装配了)
@Autowired与@Resource异同:
小结
- @Autowired与@Resource都可以用来自动装配bean。都可以写在字段上,或写在setter方法上
- @Autowired默认按类型装配(属于spring规范),默认情况下必须要求依赖对象必须存在,如果要允许null值,可以设置它的required属性为false,如:@Autowired(required=false),如果我们想使用byName装配可以结合@Qualifier注解进行使用
- @Resource(属于J2EE的),默认按照byName进行装配,名称可以通过name属性进行指定。如果没有指定name属性,当注解写在字段上时,默认取字段名进行按照名称查找,如果注解写在setter方法上默认取属性名进行装配。当找不到与名称匹配的bean时才按照byType进行装配。但是需要注意的是,如果name属性一旦指定,就只会按照名称进行装配
- 它们的作用相同都是用注解方式注入对象,但执行顺序不同。@Autowired先byType,@Resource先byName
2.bean的注解
我们之前都是使用 bean 的标签进行bean注入,但是实际开发中,我们一般都会使用注解!
1.@Component
@Component:作用于类,被注解的类就是一个bean对象
在xml中不用写<bean …>标签来创建类的对象了,只用写如下配置:(下面的配置也可以作用于自动装配的注解,
<!--扫描pojo包下的所有使用了@Component注解的类-->
<context:component-scan base-package="com.liqingfeng.pojo"/>
在Hello类中使用@Component注解,其bean的id为hello
@Component("hello")
public class Hello {
private String name;
测试OK
2.@Component的三个衍生注解
为了更好的进行分层,Spring可以使用其它三个注解,功能一样,目前使用哪一个功能都一样。
- @Controller:用于web控制层的注解
- @Service:用于service层的注解
- @Repository:用于dao层的注解
3.@value
@value :作用于字段或set方法上,被注解的该属性被注入赋值(相对于简化了之前
<property name= ... value=...>
标签
作用在字段上:
@Component("hello")
public class Hello {
@Value("唐三")
private String name;
或则作用在ser方法上:
@Value("唐三")
public void setName(String name) {
this.name = name;
}
4.@Scope
@Scope作用在类上,表示该bean对象作用域
@Component("hello")
@Scope("prototype")
public class Hello {
@Value("唐三")
private String name;
测试OK,结果都一样
我们这些注解,就是替代了在配置文件当中配置步骤而已!更加的方便快捷!
xml与注解共同开发才是最佳实践,xml管理Bean,注解完成属性注入
3.在java类上使用xml配置注解
在java类上使用xml配置注解,相当于将之前的xml配置挪到了一个java类上,该类等同于xml配置文件
1.@Configuration
@Configuration注解作用在类上,被该注解作用的类即为一个spring配置类(等同于xml配置文件)
2.@Bean
@Bean注解作用在方法上,且该方法有点讲究。作用的方法返回值类型即为要创建的bean对象。该bean对象的id即为方法名,创建的bean对象类型为返回值类型。如果不想使用方法名作为id名,则 @Bean(“pig1”)即可将id名改为pig1。
3.@Import
@Import注解作用在类上,注解括号里的类会被IOC容器作为一个组件bean。
4.@ImportResource
@ImportResource注解作用在类上,和之前xml配置文件中的<import…/>标签一样,作用是导入其他spring配置文件资源,合并为一个spring配置容器。
5.@ComponentScan
@ComponentScan注解作用在类上,@ComponentScan("com.liqingfeng.pojo")
就等价于之前在xml中进行包扫描注解功能<context:component-scan base-package="com.liqingfeng.pojo"/>
@ComponentScan(“com.liqingfeng.pojo”)
下面进行测试:
环境搭建
public class Pig {
@Value("唐三")
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Pig{" +
"name='" + name + '\'' +
'}';
}
}
spring配置类Config1 和 Config2
@Configuration
@Import(Config2.class) //导入合并其他配置类,类似于配置文件中的 inculde 标签
public class Config1 {
@Bean
public Pig pig(){
return new Pig();
}
}
@Configuration
public class Config2 {
}
测试:(之前一直使用new ClassPathXmlApplicationContext( )来创建spring配置,现在使用基于类上的xml配置注解,所以要改为new AnnotationConfigApplicationContext( ))
@Test
public void test1(){
ApplicationContext context = new AnnotationConfigApplicationContext(Config1.class);
Pig pig = context.getBean("pig", Pig.class);
System.out.println(pig);
}
测试结果OK
关于这种Java类的xml配置注解方式,我们在之后的SpringBoot 和 SpringCloud中还会大量看到。
七.AOP
1.概念
- AOP(Aspect Oriented Programming)意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
- AOP的基础是代理模式,这里参见23中设计模式里的代理模式。(代理模式可以在不改变原有类的基础上,利用代理类增强功能)
下图表示,在原有业务基础上还需要加验证参数、前置日志、后置日志。怎么办?这就要使用代理来增强功能
SpringAOP默认为AOP代理使用标准的JDK动态代理。这使得任何接口(或一组接口)都能被代理。
以下名词需要了解下:
- 横切关注点:跨越应用程序多个模块的方法或功能。即是,与我们业务逻辑无关的,但是我们需要关注的部分,就是横切关注点。如日志 , 安全 ,缓存 , 事务等等
- 切面(ASPECT):横切关注点被模块化的特殊对象。即,它是一个类。
- 通知(Advice):切面必须要完成的工作。即,它是类中的一个方法。
- 目标(Target):被通知对象。
- 代理(Proxy):向目标对象应用通知之后创建的对象。
- 切入点(PointCut):切面通知执行的 “地点”的定义。
- 连接点(JointPoint):与切入点匹配的执行点。
SpringAOP中,通过Advice定义横切逻辑,Spring中支持5种类型的Advice:
表示该通知在连接点方法的什么地方执行,比如before(前置通知)表示在执行连接点方法的前面执行通知
即 Aop 在不改变原有代码的情况下, 去增加新的功能。
2.AOP在spring中的应用
使用AOP织入,需要导入aspectjweaver包,
aspectjweaver:支持切入点表达式
aspectjrt:支持aop相关注解
aspectjweaver包含aspectjrt,所以我们只需要引入aspectjweaver依赖包就可以了
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.3</version>
</dependency>
1.第一种方法:通过 Spring API 实现
首先编写我们的业务接口
public interface UserService {
public void add();
public void delete();
public void update();
public void search();
}
实现类(真实对象)
public class UserServiceImpl implements UserService{
@Override
public void add() {
System.out.println("增加用户");
}
@Override
public void delete() {
System.out.println("删除用户");
}
@Override
public void update() {
System.out.println("更新用户");
}
@Override
public void search() {
System.out.println("查询用户");
}
}
然后去写我们的增强类 , 我们编写两个 , 一个前置增强 一个后置增强
public class Log implements MethodBeforeAdvice {
//method : 要执行的目标对象的方法
//objects : 被调用的方法的参数
//Object : 目标对象
@Override
public void before(Method method, Object[] objects, Object o) throws Throwable {
System.out.println( o.getClass().getName() + "的" + method.getName() + "方法被执行了");
}
}
public class AfterLog implements AfterReturningAdvice {
//returnValue 返回值
//method被调用的方法
//args 被调用的方法的对象的参数
//target 被调用的目标对象
@Override
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
System.out.println("执行了" + target.getClass().getName()
+"的"+method.getName()+"方法,"
+"返回值:"+returnValue);
}
}
最后去spring的文件中注册 , 并实现aop切入实现 , 注意导入约束 (下面pointcut-ref表示要切入点,将方法切入到真实对象的哪里)
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!--注册bean-->
<bean id="userService" class="com.liqingfeng.service.UserServiceImpl"/>
<!--第一种方式实现AOP-->
<!--注册bean-->
<bean id="beforeLog" class="com.liqingfeng.log.BeforeLog"/>
<bean id="afterLog" class="com.liqingfeng.log.AfterLog"/>
<aop:config>
<!--切入点 expression:表达式匹配要执行的方法,下面是UserServiceImpl下的所有方法都匹配-->
<aop:pointcut id="pointcut" expression="execution(* com.liqingfeng.service.UserServiceImpl.*(..))"/>
<!--执行环绕; advice-ref执行方法 . pointcut-ref切入点-->
<!--表示将需要增加的功能(这里是通知,也即方法)插入到哪个切入点执行,而这里的切入点即真实对象里的点-->
<aop:advisor advice-ref="beforeLog" pointcut-ref="pointcut"/>
<aop:advisor advice-ref="afterLog" pointcut-ref="pointcut"/>
</aop:config>
</beans>
测试(获取的代理对象就是同真实对象一样实现接口,且在真实对象基础上增加了功能的)
public class SpringTest {
@Test
public void test(){
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
UserService proxy= (UserService) context.getBean("userService");
//这里获取的是代理对象,不能写为UserServiceImpl userService = context.getBean("userService", UserServiceImpl.class)获取真实对象
//否则报错 Bean named 'userService' is expected to be of type 'com.liqingfeng.service.UserServiceImpl' but was actually of type 'com.sun.proxy.$Proxy7'
proxy.search(); //代理对象执行方法,实则执行真实对象方法,且由于AOP的使用,在真实对象原有功能的基础上还增加了两个Log功能方法
}
}
Aop的重要性 : 很重要 . 一定要理解其中的思路 , 主要是思想的理解这一块
Spring的Aop就是将公共的业务 (日志 , 安全等) 和领域业务结合起来 , 当执行领域业务时 , 将会把公共业务加进来 . 实现公共业务的重复利用 . 领域业务更纯粹 , 程序猿专注领域业务 , 其本质还是动态代理
2.第二种方法:自定义类来实现AOP(推荐使用)
目标业务类(真实对象)不变依旧是userServiceImpl
第一步 : 写我们自己的一个切入类(即切面),里面写通知(即需要增强真实对象方法的方法)
public class DiyPointcut {
public void before(){
System.out.println("---------方法执行前---------");
}
public void after(){
System.out.println("---------方法执行后---------");
}
}
去spring的xml文件中配置
<!--第二种方式自定义实现-->
<!--注册bean-->
<bean id="diy" class="com.kuang.config.DiyPointcut"/>
<!--aop的配置-->
<aop:config>
<!--第二种方式:使用AOP的标签实现-->
<aop:aspect ref="diy">
<aop:pointcut id="diyPonitcut" expression="execution(* com.kuang.service.UserServiceImpl.*(..))"/>
<aop:before method="before" pointcut-ref="diyPonitcut"/>
<aop:after method="after" pointcut-ref="diyPonitcut"/>
</aop:aspect>
</aop:config>
测试代码和第一种方法一样,测试OK
3.第三种方法:使用注解实现
第一步:编写一个注解实现的增强功能类
@Aspect:作用类上,表示该类为一个切面
@Before、@After、@Around等:作用于方法上,表示该方法为切面的通知,字符串里的值表示该通知在哪个切入点执行
@Aspect
public class AnnotationPointcut {
@Before("execution(* com.liqingfeng.service.UserServiceImpl.*(..))")
public void before(){
System.out.println("---------方法执行前---------");
}
@After("execution(* com.liqingfeng.service.UserServiceImpl.*(..))")
public void after(){
System.out.println("---------方法执行后---------");
}
@Around("execution(* com.liqingfeng.service.UserServiceImpl.*(..))")
public void around(ProceedingJoinPoint jp) throws Throwable {
System.out.println("环绕前");
System.out.println("签名:"+jp.getSignature());
//执行目标方法proceed
Object proceed = jp.proceed();
System.out.println("环绕后");
System.out.println(proceed);
}
}
第二步:在Spring配置文件中,注册bean,并增加支持注解的配置
<!--第三种方式:注解实现-->
<bean id="annotationPointcut" class="com.kuang.config.AnnotationPointcut"/>
<aop:aspectj-autoproxy/>
测试代码和第一种方法一样,测试OK
<aop:aspectj-autoproxy/>
说明
- 通过aop命名空间的
<aop:aspectj-autoproxy/>
声明自动为spring容器中那些配置@aspectJ切面的bean创建代理,织入切面。当然,spring在内部依旧采用AnnotationAwareAspectJAutoProxyCreator进行自动代理的创建工作,但具体实现的细节已经被<aop:aspectj-autoproxy/>
隐藏起来了 <aop:aspectj-autoproxy/>
有一个proxy-target-class属性,默认为false,表示使用jdk动态代理织入实现功能增强,当配为<aop:aspectj-autoproxy poxy-target-class="true"/>
时,表示使用CGLib动态代理技术织入实现功能增强。不过即使proxy-target-class
设置为false,如果目标类没有声明接口,则spring将自动使用CGLib动态代理。
八.整合MyBatis
1.导入相关jar包
junit
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
</dependency>
mybatis
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.2</version>
</dependency>
mysql-connector-java
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
spring相关
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.3.9</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.3.9</version>
</dependency>
aspectJ AOP 织入包
<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.3</version>
</dependency>
mybatis-spring整合包 【重点】
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.0.6</version>
</dependency>
配置Maven静态资源过滤问题!
<build>
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>false</filtering>
</resource>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>false</filtering>
</resource>
</resources>
</build>
2.mybatis-spring
http://mybatis.org/spring/zh/index.html
1.什么是 MyBatis-Spring?
MyBatis-Spring 会帮助你将 MyBatis 代码无缝地整合到 Spring 中。
2.知识基础
在开始使用 MyBatis-Spring 之前,你需要先熟悉 Spring 和 MyBatis 这两个框架和有关它们的术语。这很重要
MyBatis-Spring 需要以下版本:
要和 Spring 一起使用 MyBatis,需要在 Spring 应用上下文中定义至少两样东西:一个 SqlSessionFactory 和至少一个数据映射器类。
在 MyBatis-Spring 中,可使用SqlSessionFactoryBean来创建 SqlSessionFactory。要配置这个工厂 bean,只需要把下面代码放在 Spring 的 XML 配置文件中:
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
</bean>
注意:SqlSessionFactory需要一个 DataSource(数据源)。这可以是任意的 DataSource,只需要和配置其它 Spring 数据库连接一样配置它就可以了。在基础的 MyBatis 用法中,是通过 SqlSessionFactoryBuilder 来创建 SqlSessionFactory 的。而在 MyBatis-Spring 中,则使用 SqlSessionFactoryBean 来创建。
在 MyBatis 中,你可以使用 SqlSessionFactory 来创建 SqlSession。一旦你获得一个 session 之后,你可以使用它来执行映射了的语句,提交或回滚连接,最后,当不再需要它的时候,你可以关闭 session。
SqlSessionFactory有一个唯一的必要属性:用于 JDBC 的 DataSource。这可以是任意的 DataSource 对象,它的配置方法和其它 Spring 数据库连接是一样的。
一个常用的属性是 configLocation,它用来指定 MyBatis 的 XML 配置文件路径。它在需要修改 MyBatis 的基础配置非常有用。通常,基础配置指的是 < settings> 或 < typeAliases>元素。(这样在spring配置文件中使用MyBatis xml配置文件(这里面只写一些设置或则别名))
需要注意的是,这个xml配置文件并不需要是一个完整的 MyBatis 配置。确切地说,任何环境配置(),数据源()和 MyBatis 的事务管理()都会被忽略。SqlSessionFactoryBean 会创建它自有的 MyBatis 环境配置(Environment),并按要求设置自定义环境的值。
在 MyBatis 中,你可以使用 SqlSessionFactory 来创建 SqlSession。 一旦你获得一个 session 之后,你可以使用它来执行映射了的语句,提交或回滚连接,最后,当不再需要它的时候,你可以关闭 session。 使用 MyBatis-Spring 之后,你不再需要直接使用 SqlSessionFactory了,因为你的 bean 可以被注入一个线程安全的 SqlSession,它能基于 Spring 的事务配置来自动提交、回滚、关闭 session。
SqlSessionTemplate 是 MyBatis-Spring 的核心。作为 SqlSession 的一个实现,这意味着可以使用它无缝代替你代码中已经在使用的 SqlSession。 SqlSessionTemplate 是线程安全的,可以被多个 DAO 或映射器所共享使用。
可以使用 SqlSessionFactory 作为SqlSessionTemplate的构造方法参数来创建 SqlSessionTemplate 对象。
<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
<constructor-arg index="0" ref="sqlSessionFactory" />
</bean>
现在,这个 bean 就可以直接注入到你的 DAO bean 中了。你需要在你的 bean 中添加一个 SqlSession 属性,比如下面这样:
public class UserDaoImpl implements UserDao {
private SqlSession sqlSession;
public void setSqlSession(SqlSession sqlSession) {
this.sqlSession = sqlSession;
}
public User getUser(String userId) {
return sqlSession.getMapper(UserMapper.class).getUser();
}
}
按下面这样,注入 SqlSessionTemplate:
<bean id="userDao" class="org.mybatis.spring.sample.dao.UserDaoImpl">
<property name="sqlSession" ref="sqlSession" />
</bean>
3.实现方式一
1、在Spring配置文件beans.xml,配置数据源替换mybaits的数据源
<!--配置数据源:数据源有非常多,可以使用第三方的,也可使使用Spring的-->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=true&useUnicode=true&characterEncoding=utf8"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</bean>
2、配置SqlSessionFactory,关联MyBatis
<!--配置SqlSessionFactory-->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<!--关联Mybatis(相当于在这里设置mybatis核心配置文件
configLocation:是导入mybatis核心配置文件
mapperLocations:设置映射mapper.xml配置文件
typeAliasesPackage:设置类的别名
-->
<property name="configLocation" value="mybatis-config.xml"/>
<!-- <property name="mapperLocations" value="com/liqingfeng/mapper/*.xml"/>-->
<!-- <property name="typeAliasesPackage" value="com.liqingfeng.pojo"/>-->
</bean>
这个是mybatis的xml核心配置文件,可以看到由于environments中的数据源在spring配置了,所以这里可以删掉。上面的configLocation是扫描该xml文件将其所有配置添加到SqlSessionFactoryBean中,如果把别名和映射也放在SqlSessionFactoryBean中去设置,那mybatis的xml核心配置文件就可以不需要了,这样不厚道。所以一般我们会在mybatis的xml核心配置文件中设置别名和映射,其他的设置(比如开启日志等)就在SqlSessionFactoryBean中设置行了
<?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>
<!--类的别名-->
<typeAliases>
<package name="com.liqingfeng.pojo"/>
</typeAliases>
<mappers>
<mapper class="com.liqingfeng.mapper.UserMapper"/>
</mappers>
</configuration>
3、注册sqlSessionTemplate,关联sqlSessionFactory;
<!--注册sqlSessionTemplate , 关联sqlSessionFactory-->
<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
<!--利用构造器注入-->
<constructor-arg name="sqlSessionFactory" ref="sqlSessionFactory"/>
</bean>
4、增加UserMapper接口的实现类,私有化sqlSessionTemplate
public class UserMapperImpl implements UserMapper{
//sqlSession不用我们自己创建了,Spring来管理
private SqlSessionTemplate sqlSession;
public void setSqlSession(SqlSessionTemplate sqlSession) {
this.sqlSession = sqlSession;
}
public List<Users> getUsers() {
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
return mapper.getUsers();
}
}
5、注册bean实现
<bean id="userMapperImpl" class="com.liqingfeng.mapper.UserMapperImpl">
<property name="sqlSession" ref="sqlSession"/>
</bean>
6、测试OK
@Test
public void test(){
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
UserMapperImpl userMapperImpl = context.getBean("userMapperImpl", UserMapperImpl.class);
List<Users> users = userMapperImpl.getUsers();
for (Users user : users) {
System.out.println(user);
}
}
4.实现方式二
mybatis-spring1.2.3版以上的才有这个 .
官方文档截图 :
dao继承Support类 , 直接利用 getSqlSession() 获得 , 然后直接注入SqlSessionFactory . 比起方式一, 不需要管理SqlSessionTemplate , 而且对事务的支持更加友好 . 可跟踪源码查看
1、将我们上面写的UserMapperImpl修改一下
public class UserMapperImpl extends SqlSessionDaoSupport implements UserMapper {
public List<User> selectUser() {
UserMapper mapper = getSqlSession().getMapper(UserMapper.class);
return mapper.selectUser();
}
}
2、修改bean的配置。sqlSessionFactory,sqlSessionTemplate是SqlSessionDaoSupport 类中的属性,如果不注入值,就测试不了。但sqlSessionFactory和sqlSessionTemplate只用注入一个就行,因为sqlSession由sqlSessionFactory创建出来,我们最终只要能拿到sqlSession就行。(建议使用sqlSessionFactory注入,因为这样就可以不用去bean id为sqlSession的sqlSessionTemplate对象了,减少资源浪费)
<bean id="userMapperImpl" class="com.liqingfeng.mapper.UserMapperImpl">
<property name="sqlSessionFactory" ref="sqlSessionFactory"/>
<!-- <property name="sqlSessionTemplate" ref="sqlSession"/>-->
</bean>
3、测试(和方式一代码一样)OK
总结 : 整合到spring以后可以完全不要mybatis的配置文件,除了这些方式可以实现整合之外,我们还可以使用注解来实现,这个等我们后面学习SpringBoot的时候还会测试整合!
九.声明式事务
1.回顾事务
事务在项目开发过程非常重要,涉及到数据的一致性的问题,不容马虎!事务管理是企业级应用程序开发中必备技术,用来确保数据的完整性和一致性。事务就是把一系列的动作当成一个独立的工作单元,这些动作要么全部完成,要么全部不起作用。
事务ACID原则:
- 原子性(atomicity):事务是原子性操作,由一系列动作组成,事务的原子性确保动作要么全部完成,要么完全不起作用
- 一致性(consistency):一旦所有事务动作完成,事务就要被提交。数据和资源处于一种满足业务规则的一致性状态中
- 隔离性(isolation):可能多个事务会同时处理相同的数据,因此每个事务都应该与其他事务隔离开来,防止数据损坏
- 持久性(durability):事务一旦完成,无论系统发生什么错误,结果都不会受到影响。通常情况下,事务的结果被写到持久化存储器中
2.spring的事务管理
在整合mybatis中,我们已经学会了如何在spring配置文件设置mybatis。但是如果进行增删改操作,在整合mybatis前,在mybatis中我们知道要sqlsession.comit()才能提交事务通过。在spring整合mybatis后,我们进行增删改查都是自动提交,spring托管的sqlsession上不允许手动提交(即默认没有事务)。所以中途遇到异常,前面执行的就提交成功了,后面的没成功。
Spring在不同的事务管理API之上定义了一个抽象层,使得开发人员不必了解底层的事务管理API就可以使用Spring的事务管理机制。Spring支持编程式事务管理和声明式的事务管理。
- 编程式事务管理: 将事务管理代码嵌到业务方法中来控制事务的提交和回滚 缺点:必须在每个事务操作业务逻辑中包含额外的事务管理代码
- 声明式事务管理: 一般情况下比编程式事务好用。 将事务管理代码从业务方法中分离出来,以声明的方式来实现事务管理。将事务管理作为横切关注点,通过aop方法模块化。Spring中通过Spring AOP框架支持声明式事务管理。
现在需要怎么实现手动提交实现事务呢?
3.声明式事务实现
以前我们都需要自己手动管理事务,十分麻烦!但是Spring给我们提供了事务管理,我们只需要配置即可。
1.使用Spring管理事务,注意在spring的xml配置文件头部插入的约束导入
xmlns:tx="http://www.springframework.org/schema/tx"
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">
2.配置事务管理器:
- 无论使用Spring的哪种事务管理策略(编程式或声明式),事务管理器都是必须的。
- 其实就是 Spring的核心事务管理对象,管理封装了一组独立于技术的方法。
使用spring的JDBC事务包的事务管理器DataSourceTransactionManager
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
3.配置事务的通知
通知:即切面的通知(AOP思想),切面是类,通知是方法
在spring的xml配置文件,配置事务的通知
一般只用配置<tx:method name="*" propagation="REQUIRED"/>
,表示所有方法都有事务REQUIRED特性
<!--配置事务通知-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<!--配置哪些方法使用什么样的事务,配置事务的传播特性-->
<tx:method name="add" propagation="REQUIRED"/>
<tx:method name="delete" propagation="REQUIRED"/>
<tx:method name="update" propagation="REQUIRED"/>
<tx:method name="search*" propagation="REQUIRED"/>
<tx:method name="get" read-only="true"/>
<tx:method name="*" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
spring事务传播特性:事务传播行为就是多个事务方法相互调用时,事务如何在这些方法间传播。spring支持7种事务传播行为
- propagation_requierd:如果当前没有事务,就新建一个事务,如果已存在一个事务中,加入到这个事务中,这是最常见的选择。
- propagation_supports:支持当前事务,如果没有当前事务,就以非事务方法执行。
- propagation_mandatory:使用当前事务,如果没有当前事务,就抛出异常。
- propagation_required_new:新建事务,如果当前存在事务,把当前事务挂起。
- propagation_not_supported:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
- propagation_never:以非事务方式执行操作,如果当前事务存在则抛出异常。
- propagation_nested:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与propagation_required类似的操作
Spring 默认的事务传播行为是 PROPAGATION_REQUIRED,它适合于绝大多数的情况。
假设 ServiveX#methodX() 都工作在事务环境下(即都被 Spring 事务增强了),假设程序中存在如下的调用链:Service1#method1()->Service2#method2()->Service3#method3(),那么这 3 个服务类的 3 个方法通过 Spring 的事务传播机制都工作在同一个事务中。
就好比,我们刚才的几个方法存在调用,所以会被放在一组事务当中!
4.配置AOP
在spring的xml配置文件头部插入的约束导入
xmlns:aop="http://www.springframework.org/schema/aop"
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
配置aop织入事务(将配置好的txAdvice事务,织入到txPointcut中去生效)
下面代码表示,com.liqingfeng.mapper包下的所有类的所有方法(任何参数)都将被事务管理
<aop:config>
<aop:pointcut id="txPointcut" expression="execution(* com.liqingfeng.mapper.*.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut"/>
</aop:config>
进行测试,(因为UserMapperImpl被AOP织入了事务,相对于UserMapperImpl里的方法被代理对象加强了,代理对象在UserMapperImpl里的方法基础上加了事务,所以我们获取的是proxy 代理对象(也就是context.getBean(“userMapperImpl”)返回的类型为接口类型才行,不能是UserMapperImpl类型,不然要报错)
@Test
@Test
public void test(){
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
UserMapper proxy = (UserMapper)context.getBean("userMapperImpl");
List<Users> users = proxy.getUsers();
for (Users user : users) {
System.out.println(user);
}
}