1、Spring介绍
1.1、简介
Spring : 春天 --->给软件行业带来了春天
2002年,Rod Jahnson首次推出了Spring框架雏形interface21框架。
2004年3月24日,Spring框架以interface21框架为基础,经过重新设计,发布了1.0正式版。
很难想象Rod Johnson的学历 , 他是悉尼大学的博士,然而他的专业不是计算机,而是音乐学。
Spring理念 : 使现有技术更加实用 . 本身就是一个大杂烩 , 整合现有的框架技术
官网 : Spring | Home
官方下载地址 : JFrog
GitHub : Spring · GitHub
1.2、优点
- Spring是一个开源免费的框架 , 容器 .
- Spring是一个轻量级的框架 , 非侵入式的 .
- 控制反转 IoC , 面向切面 Aop
- 对事务的支持 , 对框架的支持
- .......
一句话概括:
Spring是一个轻量级的 控制反转(IoC) 和 面向切面(AOP) 的容器(框架)
1.3、组成
Spring 框架是一个分层架构,由 7 个定义良好的模块组成。Spring 模块构建在核心容器之上,核心容器定义了创建、配置和管理 bean 的方式 .
组成 Spring 框架的每个模块(或组件)都可以单独存在,或者与其他一个或多个模块联合实现。每个模块的功能如下:
- 核心容器:核心容器提供 Spring 框架的基本功能。核心容器的主要组件是 BeanFactory,它是工厂模式的实现。BeanFactory 使用控制反转(IOC) 模式将应用程序的配置和依赖性规范与实际的应用程序代码分开。
- Spring 上下文: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。
1.4、拓展
Spring Boot与Spring Cloud
- Spring Boot 是 Spring 的一套快速配置脚手架,可以基于Spring Boot 快速开发单个微服务;
- Spring Cloud是基于Spring Boot实现的;
- Spring Boot专注于快速、方便集成的单个微服务个体,Spring Cloud关注全局的服务治理框架;
- Spring Boot使用了约束优于配置的理念,很多集成方案已经帮你选择好了,能不配置就不配置 , Spring Cloud很大的一部分是基于Spring Boot来实现,Spring Boot可以离开Spring Cloud独立使用开发项目,但是Spring Cloud离不开Spring Boot,属于依赖的关系。
- SpringBoot在SpringClound中起到了承上启下的作用,如果你要学习SpringCloud必须要学习SpringBoot。
2、控制反转IOC
新建一个空白的maven项目Demo1
2 .1、分析实现
我们先用我们原来的方式写一段代码 .
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、测试一下(这里每次更换不同接口都需要在service层更改new的接口)
@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实现类里面修改对应的实现
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实现类里面修改对应的实现 . 假设我们的这种需求非常大 , 这种方式就根本不适用了, 甚至反人类对吧 , 每次变动 , 都需要修改大量代码 . 这种设计的耦合性太高了, 牵一发而动全身 .
那我们如何去解决呢 ?
我们可以在需要用到它的地方 , 不去实现它 , 而是留出一个接口 , 利用set , 我们去代码里修改下 .
public class UserServiceImpl implements UserService {
private UserDao userDao;
// 利用set实现
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
@Override
public void getUser() {
userDao.getUser();
}
}
现在去我们的测试类里 , 进行测试 ;(有了set方法就可以在调用的时候由用户选择调用的接口)
@Test
public void test(){
UserServiceImpl service = new UserServiceImpl();
service.setUserDao( new UserDaoMySqlImpl() );
service.getUser();
//那我们现在又想用Oracle去实现呢
service.setUserDao( new UserDaoOracleImpl() );
service.getUser();
}
大家发现了区别没有 ? 可能很多人说没啥区别 . 但是同学们 , 他们已经发生了根本性的变化 , 很多地方都不一样了 . 仔细去思考一下 , 以前所有东西都是由程序去进行控制创建 , 而现在是由我们自行控制创建对象 , 把主动权交给了调用者 . 程序不用去管怎么创建,怎么实现了 . 它只负责提供一个接口 .
这种思想 , 从本质上解决了问题 , 我们程序员不再去管理对象的创建了 , 更多的去关注业务的实现 . 耦合性大大降低 . 这也就是IOC的原型 !
2.2、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)。
温馨提示
明白IOC的思想,是理解Spring的核心技巧
3、HelloSpring
3.1、导入jar包
注 : spring 需要导入commons-logging进行日志记录 . 我们利用maven , 他会自动下载对应的依赖项 .
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.3.7</version>
</dependency>
3.2、编写代码
1、编写一个Hello实体类
public class Hello {
private String name;
public String getName() {
return name;
}
//Spring依靠set方法来注入,这个方法必须要有
public void setName(String name) {
this.name = name;
}
public void show(){
System.out.println("Hello,"+ name );
}
}
2、编写我们的spring文件 , 这里我们命名为beans.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--bean就是java对象 , 由Spring创建和管理-->
<bean id="hello" class="com.kuang.pojo.Hello">
<!--注意: 这里的name并不是属性 , 而是set方法后面的部分 , 首字母小写-->
<property name="name" value="Spring"/>
</bean>
</beans>
3、我们可以去进行测试了 .
public static void main(String[] args) {
//解析beans.xml文件 , 生成管理相应的Bean对象
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
//getBean : 参数即为spring配置文件中bean的id .
Hello hello = context.getBean("hello",Hello.class);
hello.show();
}
3.3、思考
-
Hello 对象是谁创建的 ?
hello 对象是由Spring创建的
-
Hello 对象的属性是怎么设置的 ?
hello 对象的属性是由Spring容器设置的
这个过程就叫控制反转 :
- 控制 : 谁来控制对象的创建 , 传统应用程序的对象是由程序本身控制创建的 , 使用Spring后 , 对象是由Spring来创建的
- 反转 : 程序本身不创建对象 , 而变成被动的接收对象 .
依赖注入 : 就是利用set方法来进行注入的.
IOC是一种编程思想,由主动的编程变成被动的接收
对象由Spring 来创建 , 管理 , 装配 !
可以通过newClassPathXmlApplicationContext去浏览一下底层源码 .
Demo1改造后:
4、IOC创建对象的方式
4.1、无参构造器创建
在执行getBean的时候, 对象已经创建好了 , 通过无参构造
User.java
public class User {
private String name;
//可以不写
//在没有定义有参构造器的情况下,系统会有默认的无参构造器
//如果定义了有参构造器,默认的无参构造器会被覆盖
public User() {
System.out.println("user无参构造方法");
}
public void setName(String name) {
this.name = name;
}
public void show(){
System.out.println("name="+ name );
}
}
beans.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="user" class="com.kuang.pojo.User">
<!--容器将调用无参构造器创建对象-->
<property name="name" value="kuangshen"/>
</bean>
</beans>
测试类
public void test(){
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
//在执行getBean的时候, user已经创建好了 , 通过无参构造
User user = context.getBean("user",User.class);
//调用对象的方法 .
user.show();
}
4.2、有参构造器创建
定义有参构造器后,默认的无参构造器将被覆盖。在配置文件中要配constructor-arg,否则容器因找不到无参构造器而无法创建对象,将抛出错误
UserT . java
public class UserT {
private String name;
//有参构造器
//已经覆盖了无参构造器
public UserT(String name) {
this.name = name;
}
public void setName(String name) {
this.name = name;
}
public void show(){
System.out.println("name="+ name );
}
}
beans.xml 有三种方式编写
<!-- 第一种根据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>
constructor-arg三种配置方法:
- 按下标 index
- 按名称 name 推荐
- 按类型 type 不推荐
测试
public void testT(){
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
UserT user = context.getBean("userT",UserT.class);
user.show();
}
5、Spring配置
5.1、别名
alias 设置别名 , 为bean设置别名 , 可以设置多个别名
<!--设置别名:在获取Bean的时候可以使用别名获取-->
<alias name="userT" alias="userNew"/>
5.2、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>
getBean()调用对象的方式:
- id值
- name值 (可以指定多个别名,用逗号、空格、分号分隔)
- alias别名
- 全限定类名
5.3、导入import
团队的合作通过import来实现
<import resource="{path}/beans.xml"/>
6、依赖注入DI
依赖:bean对象的创建依赖于容器
注入:bean对象的所有属性,由容器注入
6.1、构造器注入
见4中的构造器创建对象。
构造器创建对象就是构造器注入
6.2、Set方法注入 【重点】
要求被注入的属性 , 必须有set方法 , set方法的方法名由set + 属性,首字母大写
如果属性是boolean类型 , 没有set方法 , 是is
6.2.1、常量注入
<bean id="student" class="com.kuang.pojo.Student">
<property name="name" value="小明"/>
</bean>
6.2.2、Bean注入
注意点:这里的值是一个引用,ref
<bean id="student" class="com.kuang.pojo.Student">
<property name="address" ref="addr"/>
</bean>
6.2.3、数组注入
<bean id="student" class="com.kuang.pojo.Student">
<property name="books">
<array>
<value>西游记</value>
<value>红楼梦</value>
<value>水浒传</value>
</array>
</property>
</bean>
6.2.4、List注入
<property name="hobbys">
<list>
<value>听歌</value>
<value>看电影</value>
<value>爬山</value>
</list>
</property>
6.2.5、Map注入
<property name="card">
<map>
<entry key="中国邮政" value="456456456465456"/>
<entry key="建设" value="1456682255511"/>
</map>
</property>
6.2.6、set注入(集合)
<property name="games">
<set>
<value>LOL</value>
<value>BOB</value>
<value>COC</value>
</set>
</property>
6.2.7、Null注入
<property name="wife"><null/></property>
6.2.8、Properties注入
<property name="info">
<props>
<prop key="学号">20190604</prop>
<prop key="性别">男</prop>
<prop key="姓名">小明</prop>
</props>
</property>
6.3、扩展注入P、C
6.3.1、P注入 (P即属性)
P命名空间注入,P是property属性的简称,这种情况不需要有参构造器,容器调用默认的无参构造器创建对象
需要在头文件中加入约束文件
xmlns:p="http://www.springframework.org/schema/p"
<!--P(属性: properties)命名空间 , 属性依然要设置set方法-->
<bean id="user" class="com.kuang.pojo.User" p:name="狂神" p:age="18"/>
6.3.2、C注入 (C即构造器)
C命名空间注入,C是Constructor属性的简称,这种情况需要有参构造器
需要在头文件中加入约束文件
xmlns:c="http://www.springframework.org/schema/c"
<!--c(构造器: Constructor)命名空间 , 属性不需要设置set方法-->
<bean id="user" class="com.kuang.pojo.User" c:name="狂神" c:age="18"/>
6.3.3、P和C的区别
-
相同点
-
都要在头文件中加入约束
-
都可以对属性注入
-
-
不同点
- P 为属性简称,不需要有参构造器,相当于Set方法注入
- C 为构造器简称,必须要有参构造器,相当于构造器注入
6.4、Bean的作用域
<!--默认singleton-->
<bean id="ServiceImpl" class="cn.csdn.service.ServiceImpl">
<bean id="ServiceImpl" class="cn.csdn.service.ServiceImpl" scope="prototype">
<bean id="ServiceImpl" class="cn.csdn.service.ServiceImpl" scope="request">
类别 | 说明 |
---|---|
singleton | 单例模式。使用 singleton 定义的 Bean 在 Spring 容器中只有一个实例,这也是 Bean 默认的作用域 |
prototype | 原型模式,或多例模式。每次通过 Spring 容器获取 prototype 定义的 Bean 时,容器都将创建一个新的 Bean 实例。 |
request | 在一次 HTTP 请求中,容器会返回该 Bean 的同一个实例。而对不同的 HTTP 请求,会返回不同的实例,该作用域仅在当前 HTTP Request 内有效。 |
session | 在一次 HTTP Session 中,容器会返回该 Bean 的同一个实例。而对不同的 HTTP 请求,会返回不同的实例,该作用域仅在当前 HTTP Session 内有效。 |
global session | 在一个全局的 HTTP Session 中,容器会返回该 Bean 的同一个实例。该作用域仅在使用 portlet context 时有效。 |
websocket | websocket 是一种应用层的通信协议,它提供应用层的全双工通信。若一个bean 的作用域为websocket ,则只作用于一次websocket 通信,若连接被释放,则bean 自然也会被销毁。 |
6.4.1、singleton
singleton(单例)是Spring中bean默认的作用域。若一个bean
的作用域是单例的,那么每个IoC
容器只会创建这个bean
的一个实例对象。所有对这个bean
的依赖,以及获取这个bean
的代码,拿到的都是同一个bean
实例。Spring
容器在创建这个bean
后,会将它缓存在容器中(实际上是放在一个ConcurrentHashMap
中)。Spring中的单例bean不是线程安全的,所以只有在我们只关注bean能够提供的功能,而不在意它的状态(属性)时,才应该使用这个作用域。
<!--默认singleton-->
<bean id="ServiceImpl_1" class="cn.csdn.service.ServiceImpl">
<bean id="ServiceImpl_2" class="cn.csdn.service.ServiceImpl">
ServiceImpl_1、ServiceImpl_2 是同一个类的两个不同的对象,并且都是单例的
6.4.2、prototype
prototype
可以理解为多例。若一个bean
的作用域是prototype
,那么Spring
容器并不会缓存创建的bean
,程序中对这个bean
的每一次获取,容器都会重新实例化一个bean
对象。通常,如果我们需要使用bean的状态(属性),且这个状态是会改变的,那么我们就可以将它配置为这个作用域,以解决线程安全的问题。因为对于单例bean
来说,多个线程共享它的可变属性,会存在线程安全问题。
7、Bean的装配(注入)
7.1、场景
Cat.java
public class Cat {
public void shout(){
System.out.println("猫叫:喵喵~~~");
}
}
Dog.java
public class Dog {
public void shout(){
System.out.println("狗叫:汪汪~~~");
}
}
User.java
public class User {
private Cat cat;
private Dog dog;
private String name;
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 getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
beans.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--bean就是java对象 , 由Spring创建和管理-->
<bean id="cat" class="com.ajun.pojo.Cat"/>
<bean id="dog222" class="com.ajun.pojo.Dog"/>
<bean id="user" class="com.ajun.pojo.User">
<property name="name" value="ajun"/>
<property name="cat" ref="cat"/>
<property name="dog" ref="dog"/>
</bean>
</beans>
测试
public class MyTest {
@Test
public void test() {
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
User user = context.getBean("user", User.class);
System.out.println("我是"+user.getName()+",我的宠物");
user.getCat().shout();
user.getDog().shout();
}
}
7.2、xml中手动配置
必须要有set方法
<bean id="cat" class="com.ajun.pojo.Cat"/>
<bean id="dog" class="com.ajun.pojo.Dog"/>
<bean id="user" class="com.ajun.pojo.User">
<property name="cat" ref="cat"/>
<property name="dog" ref="dog"/>
</bean>
7.3、xml中自动装配autowire
必须要有set方法
7.3.1、byName自动装配
<bean id="cat" class="com.ajun.pojo.Cat"/>
<bean id="dog" class="com.ajun.pojo.Dog"/>
<bean id="user" class="com.ajun.pojo.User" autowire="byName"/>
需要装配的bean的名称,必须和set方法后面的名称一致。
bean的id,name,alias别名中有一个和set方法后的名称一致即可
如 id="cat" ,
id="cat111" name="cat cat222 , cat333 ; cat444"
alias name="cat2" alias="cat"
setCat()
7.3.2、byType自动装配
<bean class="com.ajun.pojo.Cat"/>
<bean class="com.ajun.pojo.Dog"/>
<bean id="user" class="com.ajun.pojo.User" autowire="byType"/>
只要bean的类型一致就可以。id、name、alias即使没有也可以。
使用autowire byType首先需要保证:同一类型的对象,在spring容器中唯一。
如果不唯一,会报不唯一的异常。
NoUniqueBeanDefinitionException
7.4、在类中用注解自动装配
jdk1.5开始支持注解,spring2.5开始全面支持注解。
7.4.1、准备工作
-
在beans配置文件中引入context文件头
xmlns:context="http://www.springframework.org/schema/context" http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
-
开启注解支持!
<context:annotation-config/>
7.4.2、@Autowired
-
@Autowired 是属于spring规范
-
@Autowired是按类型自动装配的,不支持名称匹配
-
如果要按名称匹配,需加上@Qualifier
-
可以省略set方法。前提是这个自动装配的属性在IOC容器中存在,且符合名字byName
-
不建议使用字段注入。应在set方法上注入。
-
在spring4之后,想要使用注解形式,必须得要引入aop的包
测试
将User类中的set方法去掉,使用@Autowired注解
public class User {
@Autowired
private Cat cat;
@Autowired
private Dog dog;
private String name;
public Cat getCat() {
return cat;
}
public Dog getDog() {
return dog;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
beans.xml
因为是按类型装配的,所以被装配的bean(如Cat、Dog)可以省去id
<context:annotation-config/>
<bean class="com.ajun.pojo.Cat"/>
<bean class="com.ajun.pojo.Dog"/>
<bean id="user" class="com.ajun.pojo.User"/>
测试通过
注:
- @Autowired(required=false) 说明:false,对象可以为null;true,必须存对象,不能为null。
//如果允许对象为null,设置required = false,默认为true
@Autowired(required = false)
private Cat cat;
- @Autowired是根据类型自动装配的,加上@Qualifier则可以根据byName的方式自动装配
- @Qualifier不能单独使用。
@Autowired
@Qualifier(value = "cat2")
private Cat cat;
@Autowired
@Qualifier(value = "dog2")
private Dog dog;
@Nullable 如果字段标记了这个注解,说明这个字段可以是null
7.4.3、@Resource
- @Resource 是属于J2EE规范
- @Resource先按该属性进行byName方式查找装配;
- 其次再进行默认的byName方式进行装配;
- 如果以上都不成功,则按byType的方式自动装配。
- 都不成功,则报异常。
public class User {
@Resource(name = "cat2")
private Cat cat;
@Resource
private Dog dog;
private String str;
}
7.4.4、@Autowired与@Resource比较
-
相同点
- 都是注解方式注入对象,即装配bean
- 都可以写在字段上,或写在set方法上
- set方法可省略
- 两种方式如果都显式的指定按名称装配,就只会按名称进行装配,装配不成功就直接报错。【@Autowired中@Qualifier(value = "cat2")、@Resource(name="cat2")】
-
不同点
-
@Autowired 属于spring规范
-
@Resource 属于J2EE规范
-
@Autowired 默认按类型byType装配
默认情况下必须要求依赖对象存在,如果要允许null 值,可以设置它的required属性为false,如:@Autowired(required=false) ,如果我们想使用名称装配可以结合@Qualifier注解进行使用
-
@Resource 默认按名称byName装配,如果找不到名称,就按类型byType装配
名称可以通过name属性进行指定。如果没有指定name属性,当注解写在字段上时,默认取字段名进行按照名称查找,如果注解写在setter方法上默认取属性名进行装配。当找不到与名称匹配的bean时才按照类型进行装配。
-
7.4.5、注解开发基本流程
要实现bean的自动配置,需要
- 先配置包的自动扫描
- 在bean类中加@Component注解,(或者衍生注解:@Controller、@Service、@Repository)
7.4.5.1、配置要扫描的包
<!--开启注解支持-->
<context:annotation-config/>
<!--指定注解扫描包-->
<context:component-scan base-package="com.ajun.pojo"/>
7.4.5.2、添加注解@Component
在类上添加@Component之后才能被扫描到。不添加的话就是普通类。
@Component("user")
// 相当于配置文件中 <bean id="user" class="当前注解的类"/>
public class User {
public String name = "秦疆";
}
-
测试
@Test
public void test1(){
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
User user = context.getBean("user", User.class);
System.out.println(user.name);
}
7.4.5.3、用注解注入属性
- 不用set方法。直接在属性名上添加@value("值")。此时可以省略set方法。
@Value("Ajun")
public String name;
- 如果提供了set方法,在set方法上添加@value("值")
public String name;
@Value("Ajun")
public void setName(String name) {
this.name = name;
}
7.4.5.4、Bean的衍生注解
为了更好的进行分层,Spring可以使用其它三个注解,功能一样,目前使用哪一个功能都一样。
- @Controller:web层
- @Service:service层
- @Repository:dao层
写上这些注解,就相当于将这个类交给Spring管理装配了!
7.4.5.5、作用域@scope
- singleton:默认的,Spring会采用单例模式创建这个对象。关闭工厂 ,所有的对象都会销毁。
- prototype:多例模式。关闭工厂 ,所有的对象不会销毁。内部的垃圾回收机制会回收
@Component(value = "user")
@Scope("singleton")
//相当于配置文件中 <bean id="user" class="当前注解的类"/>
public class User {
public String name;
@Value("hello")
public void setName(String name) {
this.name = name;
}
}
7.5、XML与注解的比较
XML与注解比较
- XML:可以适用任何场景 ,结构清晰,维护方便
- 注解:不是自己提供的类使用不了,开发简单方便
xml与注解整合开发 :【推荐最佳实践】
- xml管理Bean
- 注解完成属性注入
- 使用过程中, 可以不用扫描,扫描是为了类上的注解
<!--开启注解支持-->
<context:annotation-config/>
作用:
- 进行注解驱动注册,从而使注解生效
- 用于激活那些已经在spring容器里注册过的bean上面的注解,也就是显示的向Spring注册
- 如果不扫描包,就需要手动配置bean
- 如果不加注解驱动,注解无效,则注入的值为null!
7.6、java中显式配置
JavaConfig 原来是 Spring 的一个子项目,它通过 Java 类的方式提供 Bean 的定义信息,在 Spring4 的版本, JavaConfig 已正式成为 Spring4 的核心功能 。
关于这种Java类的配置方式,在之后的SpringBoot 和 SpringCloud中还会大量看到
测试:
1、编写一个实体类,Dog
@Component
//将这个类标注为Spring的一个组件,放到容器中!
public class Dog {
public String name = "dog";
}
2、新建一个config配置包,编写一个MyConfig配置类
@Configuration
//代表这是一个配置类
public class MyConfig {
@Bean
//通过方法注册一个bean,这里的返回值就是Bean的类型,方法名就是bean的id!
public Dog dog(){
return new Dog();
}
}
3、测试
@Test
public void test2(){
ApplicationContext context = new AnnotationConfigApplicationContext(MyConfig.class);
Dog dog = context.getBean("dog",Dog.class);
System.out.println(dog.name);
}
4、成功输出结果!
导入其他配置如何做呢?
1、我们再编写一个配置类!
@Configuration
//代表这是一个配置类
public class MyConfig2 {
}
2、在之前的配置类中我们来选择导入这个配置类
@Configuration
@Import(MyConfig2.class)
//导入合并其他配置类,类似于配置文件中的 import 标签
public class MyConfig {
@Bean
public Dog dog(){
return new Dog();
}
}
8、代理模式
8.1、静态代理
8.1.1、案例图
8.1.2、角色分析
-
抽象角色(Rent) : 一般使用接口或者抽象类来实现
//租房接口 public interface Rent { public void rent(); }
-
真实角色(Host) : 被代理的角色
//房东 public class Host implements Rent{ @Override public void rent() { System.out.println("房东出租房子"); } }
-
代理角色(Proxy) : 代理真实角色 ; 代理真实角色后 , 一般会做一些附属的操作
//中介 public class Proxy implements Rent { private Host host; public void setHost(Host host) { this.host = host; } @Override public void rent() { seeHorse(); heTong(); fare(); host.rent(); } //看房 private void seeHorse(){ System.out.println("中介带看房"); } //签合同 private void heTong(){ System.out.println("与中介签合同"); } //收费 private void fare(){ System.out.println("收费:房租和中介费"); } }
-
客户(Client) : 使用代理角色来进行一些操作
//客户租房 public class Client { public static void main(String[] args) { Host host = new Host(); //host.rent(); Proxy proxy = new Proxy(); proxy.setHost(host); proxy.rent(); } }
分析:在这个过程中,你直接接触的就是中介,就如同现实生活中的样子,你看不到房东,但是你依旧租到了房东的房子通过代理,这就是所谓的代理模式,程序源自于生活,所以学编程的人,一般能够更加抽象的看待生活中发生的事情。
8.1.3、优点
- 可以使得我们的真实角色更加纯粹 . 不再去关注一些公共的事情 .
- 公共的业务由代理来完成 . 实现了业务的分工 ,
- 公共业务发生扩展时变得更加集中和方便 .
8.1.4、缺点
- 类多了 , 多了代理类 , 工作量变大了 . 开发效率降低
8.1.5、深入理解
增删改查添加日志功能
-
抽象角色 (接口)
UserService.java
//抽象角色:增删改查业务
public interface UserService {
void add();
void delete();
void update();
void query();
}
-
实现类
UserServiceImpl.java
//真实对象,完成增删改查操作的人
public class UserServiceImpl implements UserService {
public void add() {
System.out.println("增加了一个用户");
}
public void delete() {
System.out.println("删除了一个用户");
}
public void update() {
System.out.println("更新了一个用户");
}
public void query() {
System.out.println("查询了一个用户");
}
}
-
代理类
UserServiceProxy.java
//代理角色,在这里面增加日志的实现
public class UserServiceProxy implements UserService {
private UserServiceImpl userService;
public void setUserService(UserServiceImpl userService) {
this.userService = userService;
}
public void add() {
log("add");
userService.add();
}
public void delete() {
log("delete");
userService.delete();
}
public void update() {
log("update");
userService.update();
}
public void query() {
log("query");
userService.query();
}
public void log(String msg){
System.out.println("执行了"+msg+"方法");
}
}
- 测试
public class Client {
public static void main(String[] args) {
//真实业务
UserServiceImpl userService = new UserServiceImpl();
//代理类
UserServiceProxy proxy = new UserServiceProxy();
//使用代理类实现日志功能!
proxy.setUserService(userService);
proxy.add();
}
}
总结
在不改变原有代码的情况下,实现了对原有功能的增强(添加日志功能),这是AOP中最核心的思想
8.2、动态代理
8.2.1、概述
动态代理就是动态生成代理类
- 动态代理的角色和静态代理的一样 .
- 动态代理的代理类是动态生成的 . 静态代理的代理类是我们提前写好的
- 动态代理分为两类 : 一类是基于接口动态代理 , 一类是基于类的动态代理
- 基于接口的动态代理----JDK动态代理
- 基于类的动态代理--cglib
- 现在用的比较多的是 javassist 来生成动态代理 . 百度一下javassist
8.2.2、JDK动态代理剖析
JDK的动态代理需要了解两个类
核心 : InvocationHandler 和 Proxy
1、InvocationHandler 调用处理程序
Object invoke(Object proxy, 方法 method, Object[] args);
//参数
//proxy - 调用该方法的代理实例
//method -所述方法对应于调用代理实例上的接口方法的实例。方法对象的声明类将是该方法声明的接口,它可以是代理类继承该方法的代理接口的超级接口。
//args -包含的方法调用传递代理实例的参数值的对象的阵列,或null如果接口方法没有参数。原始类型的参数包含在适当的原始包装器类的实例中,例如java.lang.Integer或java.lang.Boolean 。
2、Proxy 代理
//生成代理类
public Object getProxy(){
return Proxy.newProxyInstance(
this.getClass().getClassLoader(),
rent.getClass().getInterfaces(),
this
);
}
8.2.3、角色分析
抽象角色 和 真实角色 与之前的静态代理一样!
Rent . java 即抽象角色
//抽象角色:租房
public interface Rent {
public void rent();
}
Host . java 即真实角色
//真实角色: 房东
public class Host implements Rent{
@Override
public void rent() {
System.out.println("房东出租房屋");
}
}
ProxyInvocationHandler. java 即代理角色
public class ProxyInvocationHandler implements InvocationHandler {
//要代理的抽象角色
//接口类型:实现该接口的类都可以代理
private Rent rent;
public void setRent(Rent rent) {
this.rent = rent;
}
//生成代理类,重点是第二个参数,获取要代理的抽象角色!之前都是一个角色,现在可以代理一类角色
public Object getProxy(){
return Proxy.newProxyInstance(
this.getClass().getClassLoader(), //类加载器
rent.getClass().getInterfaces(), //要代理的接口列表
this //InvocationHandler 调用处理程序,就是当前类本身
);
}
// proxy : 代理类
// method : 代理类的调用处理程序的方法对象.
// 处理代理实例上的方法调用并返回结果
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
seeHouse();
//核心:本质利用反射实现!
Object invoke = method.invoke(rent, args);
fare();
return invoke;
}
//看房
public void seeHouse(){
System.out.println("带房客看房");
}
//收中介费
public void fare(){
System.out.println("收中介费");
}
}
Client . java
//租客
public class Client {
public static void main(String[] args) {
Host host = new Host();
ProxyInvocationHandler pih = new ProxyInvocationHandler();
pih.setRent(host);
Rent proxy = (Rent) pih.getProxy();
proxy.rent();
}
}
核心:一个动态代理 , 一般代理某一类业务 , 可以代理多个类(都实现某个接口),代理的是接口!
8.2.4、优点
静态代理有的它都有,静态代理没有的,它也有!
- 可以使得我们的真实角色更加纯粹 . 不再去关注一些公共的事情 .
- 公共的业务由代理来完成 . 实现了业务的分工 ,
- 公共业务发生扩展时变得更加集中和方便 .
- 一个动态代理 , 一般代理某一类业务
- 一个动态代理可以代理多个类,只要实现同一个接口即可,代理的是接口!
8.2.5、缺点
比较抽象,难以理解。
8.2.6、深入理解
修改静态代理中 增删改查添加日志功能
UserService.java 不变
UserServiceImpl.java 不变
ProxyInvocationHandler.java 本方法具有通用性
public class ProxyInvocationHandler implements InvocationHandler {
//要代理的抽象角色
//接口类型:实现该接口的类都可以代理
private Object target;
public void setTarget(Object target) {
this.target = target;
}
//生成代理类,重点是第二个参数,获取要代理的抽象角色!之前都是一个角色,现在可以代理一类角色
public Object getProxy(){
return Proxy.newProxyInstance(
this.getClass().getClassLoader(), //类加载器
target.getClass().getInterfaces(), //要代理的接口列表
this //InvocationHandler 调用处理程序,就是当前类本身
);
}
// proxy : 代理类 method : 代理类的调用处理程序的方法对象.
// 处理代理实例上的方法调用并返回结果
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
log(method.getName());
//核心:本质利用反射实现!
Object res = method.invoke(target,args);
return res;
}
//代理的增强方法
private void log(String arg){
System.out.println("[debug] 执行了"+arg+"方法");
}
}
测试
public static void main(String[] args) {
//真实对象
UserService userService = new UserServiceImpl();
//代理对象的调用处理程序
ProxyInvocationHandler pih = new ProxyInvocationHandler();
pih.setTarget(userService); //设置要代理的对象
UserService proxy = (UserService)pih.getProxy(); //动态生成代理类!
proxy.delete();
}
9、 AOP
9.1、AOP概念
AOP(Aspect Oriented Programming)意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
9.2、AOP在Spring中的作用
- 提供声明式事务
- 允许用户自定义切面。增加日志 , 安全 , 缓存等等 ....
Aop的重要性 : 很重要 . 一定要理解其中的思路 , 主要是思想的理解这一块 .
Spring的Aop就是将公共的业务 (日志 , 安全等) 和领域业务结合起来 , 当执行领域业务时 , 将会把公共业务加进来 . 实现公共业务的重复利用 . 领域业务更纯粹 , 程序猿专注领域业务 , 其本质还是动态代理 .
9.3、名词解释
-
横切关注点:跨越应用程序多个模块的方法或功能。即是,与我们业务逻辑无关的,但是我们需要关注的部分,就是横切关注点。如日志 , 安全 , 缓存 , 事务等等 ....
-
切面(ASPECT):横切关注点 被模块化 的特殊对象。即,它是一个提供增强功能的类。
-
通知(Advice):切面必须要完成的工作。即,它是类中的一个方法,即增强方法。
-
目标(Target):被通知对象。
-
代理(Proxy):向目标对象应用通知之后创建的对象。
-
切入点(PointCut):切面通知 执行的 “地点”的定义。
-
连接点(JointPoint):与切入点匹配的执行点。
9.4、Spring中AOP的实现方式
9.4.1、前提:导入依赖包
在MAVEN的Pom.xml中导入AOP依赖包
基础包:里面包含aop包
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.3.7</version>
</dependency>
新增包:面向切面的
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.6</version>
</dependency>
9.4.2、通过 Spring API 实现
业务接口:UserService.java
public interface UserService {
public void add();
public void delete();
public void update();
public void select();
}
实现类:UserServiceImpl.java
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 select() {
System.out.println("查询记录");
}
}
前置日志增强类:Log.java
public class Log implements MethodBeforeAdvice {
@Override
//method : 要执行的目标对象的方法
//objects : 被调用的方法的参数
//Object : 目标对象
public void before(Method method, Object[] objects, Object o) throws Throwable {
System.out.println(o.getClass().getName()+" 的 "+ method.getName()+" 方法被执行了");
}
}
后置日志增强类:AfterLog.java
public class AfterLog implements AfterReturningAdvice {
@Override
//returnValue 返回值
//method被调用的方法
//args 被调用的方法的对象的参数
//target 被调用的目标对象
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
System.out.println("执行了 "+target.getClass().getName()+" 的 "+method.getName()+" 的方法。返回值为:"+returnValue);
}
}
配置beans.xml
在头文件中添加aop命名空间支持。添加方法见 7.4.1
<?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.ajun.pojo.UserServiceImpl"/>
<bean id="log" class="com.ajun.aop.Log"/>
<bean id="afterLog" class="com.ajun.aop.AfterLog"/>
<!--aop配置-->
<aop:config>
<!--切入点-->
<aop:pointcut id="logPointCut"
expression="execution(* com.ajun.pojo.UserServiceImpl.*(..))"/>
<!--执行增强-->
<aop:advisor advice-ref="log" pointcut-ref="logPointCut"/><!--前置日志-->
<aop:advisor advice-ref="afterLog" pointcut-ref="logPointCut"/><!--后置日志-->
</aop:config>
</beans>
测试
@Test
public void test(){
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
UserService userService = context.getBean("userService", UserService.class);
userService.add();
}
9.4.3、自定义类实现
业务接口:UserService.java 不变
实现类:UserServiceImpl.java 不变
自定义切面类:MyAspect.java
public class MyAspect {
public void before(){
System.out.println("=====方法执行前=====");
}
public void after(){
System.out.println("=====方法执行后=====");
}
}
配置beans.xml
和第一种实现方法的配置不同:引入了 aop:aspect、aop:before、aop:after、method
<!--注册bean-->
<bean id="myAspect" class="com.ajun.aop.MyAspect"/>
<!--第二种方法:自定义增强类-->
<aop:config>
<!--自定义增强类-->
<aop:aspect ref="myAspect">
<!--切入点-->
<aop:pointcut id="myPointCut"
expression="execution(* com.ajun.pojo.UserServiceImpl.*(..))"/>
<!--执行增强-->
<aop:before method="before" pointcut-ref="myPointCut"/>
<aop:after method="after" pointcut-ref="myPointCut"/>
</aop:aspect>
</aop:config>
测试方法不变
9.4.4、使用注解实现
业务接口:UserService.java 不变
实现类:UserServiceImpl.java 不变
自定义注解实现的切面类:AnnotationAspect.java
@Aspect
public class AnnotationAspect {
@Before("execution(* com.ajun.pojo.UserServiceImpl.*(..))")
public void before(){
System.out.println("=====方法执行前=====");
}
@After(value = "execution(* com.ajun.pojo.UserServiceImpl.*(..))")
public void after(){
System.out.println("=====方法执行后=====");
}
@Around("execution(* com.ajun.pojo.UserServiceImpl.*(..))")
public void around(ProceedingJoinPoint pj) throws Throwable {
System.out.println("=====环绕前=====");
System.out.println("签名:"+pj.getSignature());
//执行目标方法proceed
Object proceed = pj.proceed();
System.out.println("=====环绕后=====");
System.out.println(proceed);
}
}
配置beans.xml
<!--第三种方式:注解实现-->
<bean id="annotationPointcut" class="com.ajun.aop.AnnotationAspect"/>
<!--支持AOP注解-->
<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动态代理。
测试方法不变
10、整合MyBatis
10.1、不整合时的写法
步骤:
1、导入相关jar包
- junit测试包
<!--测试包-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
- spring框架包
<!--spring框架包-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.3.7</version>
</dependency>
- spring jdbc包
<!--spring jdbc包-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.3.7</version>
</dependency>
- mysql驱动包
<!--mysql驱动包-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.25</version>
</dependency>
- mybatis包
<!--mybatis包-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.7</version>
</dependency>
- lombok包
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.20</version>
<scope>provided</scope>
</dependency>
2、配置Maven静态资源过滤问题!
<build>
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<!--false:表示不启用过滤器-->
<filtering>false</filtering>
</resource>
</resources>
</build>
使用Maven构建项目的时候,会默认过滤掉静态资源,所以,需要手动来配置
静态资源 : 包含HTMl,图片,CSS,JS,xml等不需要与数据库交互的一类文件
动态资源 : 需要与数据库交互,可以根据需要显示不同的数据,不需要修改页面
如果把配置文件直接放在resources目录下,就不用过滤
3、代码实现
实体类:User.java
@Data
public class User {
private int id;
private String name;
private String pw;
}
Mapper接口及配置
//接口
public interface UserMapper {
public List<User> getAll();
}
<?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.ajun.mapper.UserMapper">
<select id="getAll" resultType="User">
select * from user
</select>
</mapper>
4、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>
<typeAliases>
<package name="com.ajun.pojo"/>
</typeAliases>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.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"/>
</dataSource>
</environment>
</environments>
<mappers>
<package name="com.ajun.mapper"/>
</mappers>
</configuration>
5、测试
@Test
public void test() throws IOException {
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession = sqlSessionFactory.openSession(true);
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<User> userList = mapper.getAll();
for (User user: userList){
System.out.println(user);
}
sqlSession.close();
}
6、运行结果
7、注意事项
com.mysql.jdbc.Driver 和 com.mysql.cj.jdbc.Driver 如何选择?
mysql-connector-java 6 以下用 com.mysql.jdbc.Driver
mysql-connector-java 6及以上用:com.mysql.cj.jdbc.Driver
10.2、MyBatis-Spring介绍
官方网址:mybatis-spring –
什么是 MyBatis-Spring?
MyBatis-Spring 会帮助你将 MyBatis 代码无缝地整合到 Spring 中。它将允许 MyBatis 参与到 Spring 的事务管理之中,创建映射器 mapper 和 SqlSession
并注入到 bean 中,以及将 Mybatis 的异常转换为 Spring 的 DataAccessException
。 最终,可以做到应用代码不依赖于 MyBatis,Spring 或 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>
需要注意的是:所指定的映射器类必须是一个接口,而不是具体的实现类。可以通过注解来指定 SQL 语句,也可以使用 MyBatis 映射器的 XML 配置文件。
在 MyBatis 中,你可以使用 SqlSessionFactory
来创建 SqlSession
。 一旦你获得一个 session 之后,你可以使用它来执行映射了的语句,提交或回滚连接,最后,当不再需要它的时候,你可以关闭 session。 使用 MyBatis-Spring 之后,你不再需要直接使用 SqlSessionFactory
了,因为你的 bean 可以被注入一个线程安全的 SqlSession
,它能基于 Spring 的事务配置来自动提交、回滚、关闭 session。
SqlSessionTemplate
SqlSessionTemplate
是 MyBatis-Spring 的核心。作为 SqlSession
的一个实现,这意味着可以使用它无缝代替你代码中已经在使用的 SqlSession
。 SqlSessionTemplate
是线程安全的,可以被多个 DAO 或映射器所共享使用。
当调用 SQL 方法时(包括由 getMapper()
方法返回的映射器中的方法),SqlSessionTemplate
将会保证使用的 SqlSession
与当前 Spring 的事务相关。 此外,它管理 session 的生命周期,包含必要的关闭、提交或回滚操作。另外,它也负责将 MyBatis 的异常翻译成 Spring 中的 DataAccessExceptions
。
由于模板可以参与到 Spring 的事务管理中,并且由于其是线程安全的,可以供多个映射器类使用,你应该总是用 SqlSessionTemplate
来替换 MyBatis 默认的 DefaultSqlSession
实现。在同一应用程序中的不同类之间混杂使用可能会引起数据一致性的问题。
可以使用 SqlSessionFactory
作为构造方法的参数来创建 SqlSessionTemplate
对象。
<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
<constructor-arg index="0" ref="sqlSessionFactory" />
</bean>
SqlSessionDaoSupport
SqlSessionDaoSupport
是一个抽象的支持类,用来为你提供 SqlSession
。调用 getSqlSession()
方法你会得到一个 SqlSessionTemplate
,之后可以用于执行 SQL 方法,就像下面这样:
public class UserDaoImpl extends SqlSessionDaoSupport implements UserDao {
public User getUser(String userId) {
return getSqlSession().selectOne("org.mybatis.spring.sample.mapper.UserMapper.getUser", userId);
}
}
在这个类里面,通常更倾向于使用 MapperFactoryBean
,因为它不需要额外的代码。但是,如果你需要在 DAO 中做其它非 MyBatis 的工作或需要一个非抽象的实现类,那么这个类就很有用了。
SqlSessionDaoSupport
需要通过属性设置一个 sqlSessionFactory
或 SqlSessionTemplate
。如果两个属性都被设置了,那么 SqlSessionFactory
将被忽略。
假设类 UserMapperImpl
是 SqlSessionDaoSupport
的子类,可以编写如下的 Spring 配置来执行设置:
<bean id="userDao" class="org.mybatis.spring.sample.dao.UserDaoImpl">
<property name="sqlSessionFactory" ref="sqlSessionFactory" />
</bean>
10.3、整合一 SqlSessionTemplate
1、导入相关jar包
除了10.1中的jar包外,还要导入:
- mybatis和spring整合包
<!--mybatis和spring整合包-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.0.6</version>
</dependency>
2、配置Maven静态资源过滤问题!
同10.1中一样
3、代码实现
Dao实现类:UserMapperImpl.java
public class UserMapperImpl implements UserMapper{
//引入SqlSessionTemplate
//sqlSession不需要自已创建了
private SqlSessionTemplate sqlSessionTemplate;
public void setSqlSessionTemplate(SqlSessionTemplate sqlSessionTemplate) {
this.sqlSessionTemplate = sqlSessionTemplate;
}
@Override
public List<User> getAll() {
UserMapper mapper = sqlSessionTemplate.getMapper(UserMapper.class);
return mapper.getAll();
}
}
4、配置beans.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--
数据源DataSource
使用spring的数据源配置替换mybatis的配置
-->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.cj.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>
<!--配置SqlSessionFactory-->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<!--
配置实体类别名
替换了mybatis-config.xml中
<typeAliases><package name="com.ajun.pojo"/></typeAliases>
节点-->
<property name="typeAliasesPackage" value="com.ajun.pojo"/>
<!--
关联mybatis
mybatis-config.xml中的配置全部被替换掉,所以mybatis-config.xml文件可以删除了
-->
<!-- <property name="configLocation" value="classpath:mybatis-config.xml"/>-->
<!--
配置mapper,即dao
替换了mybatis-config.xml中<mappers>节点
-->
<property name="mapperLocations" value="classpath:com/ajun/mapper/*.xml"/>
</bean>
<!--
注册sqlSessionTemplate
关联sqlSessionFactory。只能用构造器注入。因为SqlSessionTemplate没有set方法
-->
<bean id="sqlSessionTemplate" class="org.mybatis.spring.SqlSessionTemplate">
<constructor-arg name="sqlSessionFactory" ref="sqlSessionFactory"/>
</bean>
<!--注册dao实现类bean-->
<bean id="userMapper" class="com.ajun.mapper.UserMapperImpl">
<property name="sqlSessionTemplate" ref="sqlSessionTemplate"/>
</bean>
</beans>
此时mybatis-config.xml中的配置内容:数据源、实体类、dao,都被整合到了beans.xml中,所以mybatis-config.xml也就可以删除了
5、测试
@Test
public void test() throws IOException {
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
UserMapper userMapper = context.getBean("userMapper", UserMapper.class);
List<User> users = userMapper.getAll();
for (User user: users){
System.out.println(user);
}
}
6、运行结果
7、注意事项
和10.1 中的注意事项一样
10.4、整合二 SqlSessionDaoSupport
在10.3的基础上做如下修改:
1、代码修改
UserMapperImpl.java
public class UserMapperImpl extends SqlSessionDaoSupport implements UserMapper {
@Override
public List<User> getAll() {
//UserMapper mapper = getSqlSession().getMapper(UserMapper.class);
//mapper.getAll();
return getSqlSession().getMapper(UserMapper.class).getAll();
}
}
继承SqlSessionDaoSupport抽象类
不需要定义SqlSessionTemplate
通过父类的getSqlSession()直接获取sqlSession
2、beans.xml修改
<bean id="userMapper" class="com.ajun.mapper.UserMapperImpl">
<!--<property name="sqlSessionTemplate" ref="sqlSessionTemplate"/>-->
<property name="sqlSessionFactory" ref="sqlSessionFactory"/>
</bean>
不需要注册SqlSessionTemplate,直接把SqlSessionFactory注入到Dao实现类中就可以
3、测试
测试方法和 10.3 中一致
4、运行结果
10.5、整合三 注解 (Spring Boot时再学)
11、声明式事务
1、事务概述
- 事务在项目开发过程非常重要,涉及到数据的一致性的问题,不容马虎!
- 事务管理是企业级应用程序开发中必备技术,用来确保数据的完整性和一致性。
- 事务就是把一系列的动作当成一个独立的工作单元,这些动作要么全部完成,要么全部失败
事务四个属性ACID
-
原子性(atomicity)
- 事务是原子性操作,由一系列动作组成,事务的原子性确保动作要么全部完成,要么完全不起作用
-
一致性(consistency)
- 一旦所有事务动作完成,事务就要被提交。数据和资源处于一种满足业务规则的一致性状态中
-
隔离性(isolation)
- 可能多个事务会同时处理相同的数据,因此每个事务都应该与其他事务隔离开来,防止数据损坏
-
持久性(durability)
- 事务一旦完成,无论系统发生什么错误,结果都不会受到影响。通常情况下,事务的结果被写到持久化存储器中
2、声明式事务
2.1、介绍
- 一般情况下比编程式事务好用。
- 将事务管理代码从业务方法中分离出来,以声明的方式来实现事务管理。
- 将事务管理作为横切关注点,通过aop方法模块化。Spring中通过Spring AOP框架支持声明式事务管理。
2.2、代码实现
在10.4整合MyBatis的基础上做如下修改:
Pojo实体类:User.java
增加构造器,方便增加用户
@Data
@NoArgsConstructor//无参构造器
public class User {
//有参构造器
//id是自增型
public User(String name,String ps){
this.name = name;
this.ps = ps;
}
private int id;
private String name;
private String ps;
}
Dao接口类:UserDao.java
增加两个方法:add()、delete()
public interface UserDao {
public List<User> getAll();
public int add(User user);
public int delete(int id);
}
Dao配置文件:UserDao.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ajun.dao.UserDao">
<select id="getAll" resultType="User">
select * from mybatis.user;
</select>
<insert id="add" parameterType="User">
insert into mybatis.user (id,name,ps) values (#{id},#{name},#{ps});
</insert>
<delete id="delete" parameterType="int">
delete from mybatis.user where id=#{id};
</delete>
</mapper>
Dao实现类:UserDaoImpl.java
public class UserDaoImpl extends SqlSessionDaoSupport implements UserDao {
@Override
public List<User> getAll() {
User u = new User("张三","1451123");
add(u);
delete(8);
return getSqlSession().getMapper(UserDao.class).getAll();
}
@Override
public int add(User user) {
return getSqlSession().getMapper(UserDao.class).add(user);
}
@Override
public int delete(int id) {
return getSqlSession().getMapper(UserDao.class).delete(id);
}
}
2.3、配置事务
在头文件中加入tx约束
xmlns:tx="http://www.springframework.org/schema/tx"
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
配置事务管理器
<!--事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
配置事务通知(增强)
<!--配置事务通知,即方法的增强-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<!--需要配置事务的方法-->
<!--事务的传播特性-->
<tx:attributes>
<tx:method name="getAll"/>
<tx:method name="add" propagation="REQUIRED"/>
<tx:method name="delete" propagation="REQUIRED"/>
<tx:method name="*" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
配置事务切入
需要在头文件中加aop约束
<!--配置事务切入-->
<aop:config>
<aop:pointcut id="txPointCut" expression="execution(* com.ajun.dao.*Impl.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="txPointCut"/>
</aop:config>
事务应配置在service业务层,本处是在dao层,仅为测试
2.4、事务传播特性
事务传播行为就是多个事务方法相互调用时,事务如何在这些方法间传播。spring支持7种事务传播行为:
- propagation_requierd:默认,可以不写。如果当前没有事务,就新建一个事务,如果已存在一个事务中,加入到这个事务中,这是最常见的选择。
- propagation_supports:支持当前事务,如果没有当前事务,就以非事务方法执行。
- propagation_mandatory:使用当前事务,如果没有当前事务,就抛出异常。
- propagation_required_new:新建事务,如果当前存在事务,把当前事务挂起。
- propagation_not_supported:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
- propagation_never:以非事务方式执行操作,如果当前事务存在则抛出异常。
- propagation_nested:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与propagation_required类似的操作
2.5、测试
配置了事务,故意制造错误,破坏getAll方法的一致性,检测事务操作是否成功
配置了事务,修改了人为错误,getAll方法可以完整执行,事务操作成功
取消了事务,故意制造错误,删除操作失败,但添加操作成功,getAll方法失去了原子性,数据不具有一致性