学习之前,先问自己三个问题:
为什么要学Spring?
- Spring技术是JavaEE开发必备技能,企业开发技术选型命中率>90%
- 简化开发,降低企业级开发的复杂度
- 框架整合,高效整合其他技术,提高企业级应用开发与运行的效率
我们要学什么呢?
- 简化开发
- IoC
- Aop
- 事务处理
- 框架整合
- Mybatis
- Mybatis-plus
- Struts
- Struts2
- Hibernate
我们怎么学习呢?
- 我们先学习Spring框架设计思想
- 学习基础操作,思考操作与时间的联系
- 学习案例,熟练应用操作的同时,体会思想
1 初识Spring
1.1 了解Spring大家族
- Spring官网
- Spring发展到今天已经形成了一种开发的生态圈,Spring提供了若干个项目,每个项目用于完成特定的功能
- 我们不用学习所有,学习
Spring Framework
(它是一个底层的框架)、Spring Boot
(用来简化开发,提高开发速度)、Spring Cloud
(分布式开发相关技术)就足够了
1.2 Spring Framework系统架构
- spring是开源的免费容器
- spring是一个轻量级的非入侵式的
- 要点:控制反转(IOC),面向切面编程(AOP)
- 支持事务处理,支持框架整合
总结:spring是一个轻量级的控制反转(IOC)
和面向切面编程(AOP)
的框架
2 IoC理论推导
测试案例准备:新建一个空白的maven项目
1、UserDao接口
public interface UserDao {
void getUser();
}
2、UserDao的实现类
public class UserDaoImpl implements UserDao{
@Override
public void getUser() {
System.out.println("默认获取用户的数据");
}
}
3、UserService的接口
public interface UserService {
void getUser();
}
4、UserService的实现类
public class UserServiceImpl implements UserService{
private UserDao userDao = new UserDaoImpl();
@Override
public void getUser() {
userDao.getUser();
}
}
测试一下:
@Test
public void test(){
UserService service = new UserServiceImpl();
service.getUser();
}
结果:
默认获取用户的数据
当我们想添加另一个实现类UserDaoMySqlImpl
时,我们使用时需要对service实现类
进行修改:
实现类:
public class UserDaoMysqlImpl implements UserDao{
@Override
public void getUser() {
System.out.println("MySql获取用户数据");
}
}
public class UserServiceImpl implements UserService {
private UserDao userDao = new UserDaoMySqlImpl();
@Override
public void getUser() {
userDao.getUser();
}
}
结果:
MySql获取用户数据
假设我们现在又要用Oracle呢,又需要对service实现类里面修改对应的实现,这样每次变动都需要改动源码的设计,耦合性太高。
我们如何去解决这个问题呢?
我们可以在需要用到他的地方,不去实现它,而是留出一个接口,利用set,修改代码为如下:
public class UserServiceImpl implements UserService{
private UserDao userDao;
//利用set进行动态实现值的注入
public void setUserDao(UserDao userDao){
this.userDao = userDao;
}
public void getUser(){
userDao.getUser();
}
}
再次进行测试:
public class MyTest {
public static void main(String[] args) {
UserService userService = new UserServiceImpl();
((UserServiceImpl) userService).setUserDao(new UserDaoImpl());
userService.getUser();
}
}
要使用MySql时((UserServiceImpl) userService).setUserDao(new UserDaoMysqlImpl())
即可。
这和之前的区别在哪里?区别在于,之前对象的创建是交给程序进行控制的,而现在穿件对象的主动权交给了调用者,程序不去管如何创建,怎么实现,它只负责提供一个接口。
这种思想,从本质上解决了问题,我们不用再去管对象的创建了,更多的去关注业务的实现,耦合性大大降低,这也就是IOC的原型
IOC本质
控制反转IoC(Inversion of Control),是一种设计思想,DI(依赖注入)是实现IoC的一种方法,也有人认为DI只是IoC的另一种说法。没有IoC的程序中 , 我们使用面向对象编程 , 对象的创建与对象间的依赖关系完全硬编码在程序中,对象的创建由程序自己控制,控制反转后将对象的创建转移给第三方,个人认为所谓控制反转就是:获得依赖对象的方式反转了。
IoC是Spring框架的核心内容,可以使用XML配置,也可以使用注解实现IoC新版本的Spring也可以零配置实现IoC
Spring容器在初始化时先读取配置文件,根据配置文件或元数据创建与组织对象存入容器中,程序使用时再从IoC容器中取出需要的对象。
采用XML方式配置Bean的时候,Bean的定义信息是和实现分离的,而采用注解的方式可以把二者合为一体,Bean的定义信息直接以注解的形式定义在实现类中,从而达到了零配置的目的。
**控制反转是一种通过描述(XML或注解)并通过第三方去生产或获取特定对象的方式。在Spring中实现控制反转的是IoC容器,其实现方法是依赖注入。
3 HelloSpring
首先要导入jar包
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.2.0.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.2.0.RELEASE</version>
</dependency>
1、编写一个Hello实体类
public class Hello {
private String str;
public String getStr() {
return str;
}
public void setStr(String str) {
this.str = str;
}
@Override
public String toString() {
return "Hello{" +
"str='" + str + '\'' +
'}';
}
}
2、编写sping配置文件,这里我们命名为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
https://www.springframework.org/schema/beans/spring-beans.xsd">
<!--bean就是java对象,由Spring创建和管理-->
<bean id="hello" class="com.lly.pojo.Hello">
<property name="str" value="hello"></property>
</bean>
</beans>
3、测试
public class Mytest {
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 hello = (Hello)context.getBean("hello");
System.out.println(hello);
}
}
对于上一个案例来说,如果使用配置文件的话,想要换数据库的话,修改配置文件就可以了
思考:
1、IoC管理什么?(Hello)
2、如何将被管理的对象告知IoC容器?(配置文件)
3、被管理的对象交给IoC容器,如何获取到IoC容器?(接口)
4、IoC容器得到后,如何从容器中获取bean?(接口方法)
5、使用Spring导入哪些坐标?(pom.xml)
4 IoC创建对象的方式
1、通过无参构造方法来创建
- User.java
package com.lly.pojo;
public class User {
private String name;
public String getName() {
return name;
}
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.lly.pojo.User">
<property name="name" value="lly"/>
</bean>
</beans>
- 测试类
@Test
public void test(){
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
//在执行getBean的时候,user已经创建好了,即上面读取配置文件后就创建了,通过无参构造
User user = (User)context.getBean("user");
//调用对象的方法
user.show();
}
2、通过有参构造的方式来创建
- UserT.java
- beans.xml有三种方式编写
第一种:根据index参数下标设置:
<bean id="userT" class="com.lly.pojo.UserT">
<!--index构造方法,下标从0开始-->
<constructor-arg index="0" value="lly2"></constructor-arg>
</bean>
第二种:根据参数名字设置(推荐使用)
<bean id="userT" class="com.lly.pojo.UserT">
<constructor-arg name="name" value="lly2"></constructor-arg>
</bean>
第三种:根据参数类型设置
<bean id="userT" class="com.lly.pojo.UserT">
<constructor-arg type="java.lang.String" value="lly2"></constructor-arg>
</bean>
- 测试
@Test
public void testT(){
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
UserT user = (UserT) context.getBean("userT");
user.show();
}
重点:在配置文件加载的时候,其中管理的对象都已经初始化了
5 Spring配置
1、别名
alias 设置别名 , 为bean设置别名 , 可以设置多个别名
<!--设置别名:在获取Bean的时候可以使用别名获取-->
<alias name="userT" alias="userNew"/>
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"></bean>
3、import
团队合作通过import
来实现
<import resource="{path}/beans.xml"/>
6 依赖注入(Dependency Injection,DI)
6.1 概念
- 依赖:指Bean对象的创建依赖于容器,Bean对象的依赖资源
- 注入:指Bean对象所依赖的资源,由容器来设置和装配
6.2 构造器注入
前面IoC创建对象的方式中讲了
6.3 Set注入(重点)
要求被注入的属性,必须有set
方法,set方法的方法名由set+属性首字母大写
,如果属性是boolean
类型,没有set方法,是is
测试:
Address.java
package com.lly.pojo;
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 + '\'' +
'}';
}
}
Student.java
package com.lly.pojo;
import java.util.*;
public class Student {
private String name;
private Address address;
private String[] books;
private List<String> hobbys;
private Map<String,String> card;
private Set<String> games;
private String wife;
private Properties info;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Address getAddress() {
return address;
}
public void setAddress(Address address) {
this.address = address;
}
public String[] getBooks() {
return books;
}
public void setBooks(String[] books) {
this.books = books;
}
public List<String> getHobbys() {
return hobbys;
}
public void setHobbys(List<String> hobbys) {
this.hobbys = hobbys;
}
public Map<String, String> getCard() {
return card;
}
public void setCard(Map<String, String> card) {
this.card = card;
}
public Set<String> getGames() {
return games;
}
public void setGames(Set<String> games) {
this.games = games;
}
public String getWife() {
return wife;
}
public void setWife(String wife) {
this.wife = wife;
}
public Properties getInfo() {
return info;
}
public void setInfo(Properties info) {
this.info = info;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", address=" + address.toString() +
", books=" + Arrays.toString(books) +
", hobbys=" + hobbys +
", card=" + card +
", games=" + games +
", wife='" + wife + '\'' +
", info=" + info +
'}';
}
}
<bean id="address" class="com.lly.pojo.Address"></bean>
<bean id="student" class="com.lly.pojo.Student">
第一种:普通值注入:value
<property name="name" value="lly"></property>
第二种:Bean注入:ref
<property name="address" ref="address"></property>
数组注入:array
<property name="books">
<array>
<value>西游记</value>
<value>水浒传</value>
<value>红楼梦</value>
<value>三国演义</value>
</array>
</property>
list注入
<property name="hobbys">
<list>
<value>篮球</value>
<value>足球</value>
<value>排球</value>
</list>
</property>
map注入
<property name="card">
<map>
<entry key="身份证" value="111111111111"></entry>
<entry key="银行卡" value="1234564151321"></entry>
</map>
</property>
Set注入
<property name="games">
<set>
<value>吃鸡</value>
<value>王者</value>
<value>LOL</value>
</set>
</property>
null注入
<property name="wife">
<null></null>
</property>
Properties注入
<property name="info">
<props>
<prop key="dirver">path</prop>
<prop key="url">path</prop>
<prop key="username">root</prop>
<prop key="password">root</prop>
</props>
</property>
</bean>
测试结果:
Student{name='lly',
address=Address{address='null'},
books=[西游记, 水浒传, 红楼梦, 三国演义],
hobbys=[篮球, 足球, 排球],
card={身份证=111111111111, 银行卡=1234564151321},
games=[吃鸡, 王者, LOL],
wife='null',
info={password=root, dirver=path, url=path, username=root}}
6.4 p命名和c命名注入
User.java:
package com.lly.pojo;
public class User {
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
- p命名空间注入:组要在头文件中加约束文件
xmlns:p="http://www.springframework.org/schema/p"
<!--p命名空间注入,可以直接注入属性的值:property-->
<bean id="user" class="com.lly.pojo.User" p:name="lly" p:age="18"></bean>
- c命名空间注入:需要在头文件中加入约束文件
xmlns:c="http://www.springframework.org/schema/c"
要有有参构造才行,加上:
public User(String name, int age) {
this.name = name;
this.age = age;
}
<!--c命名空间注入,可以直接注入属性的值:constructor-->
<bean id="user" class="com.lly.pojo.User" c:name="lly" c:age="18"/>
测试:
@Test
public void test2(){
ApplicationContext context = new ClassPathXmlApplicationContext("userbeans.xml");
User user = context.getBean("user",User.class);//在后面申明了类型就不用强转
System.out.println(user);
}
6.5 Bean的作用域
在Spring中,那些组成应用程序的主体及有Spring IoC容器所管理的对象,被称为bean
,简单的讲,bean就是由IoC容器初始化、装配及管理的对象。
用scope
来设置,如:
<bean id="user" class="com.lly.pojo.User" scope="singleton"></bean>
7 Bean的自动装配
7.1 自动装配说明
- 自动装配式使用spring满足bean依赖的一种方法
- spring会在应用上下文中为某个bean寻找其依赖的bean
Spring中的bean有三种装配机制,分别是:
- 在xml中显式配置(我们前面用的都是);
- 在java中显式配置;
- 隐式的bean发现机制和自动装配
这里我们主要讲第三种:自动化装配bean
Spring的自动装配需要从两个角度来实现:
- 组件扫描(component scanning):spring会自动发现上下文中所创建的bean
- 自动装配(autowiring):spring自动满足bean之间的依赖,也就是我们说的IoC/DI
推荐不使用自动装配xml配置,而使用注解
7.2 搭建测试环境
- 新建两个实体类,Cat,Dog,都有一个叫的方法
package com.lly.pojo;
public class Cat {
public void shout(){
System.out.println("汪");
}
}
package com.lly.pojo;
public class Dog {
public void shout(){
System.out.println("喵");
}
}
- 新建一个人类Pserson
package com.lly.pojo;
public class People {
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;
}
@Override
public String toString() {
return "People{" +
"cat=" + cat +
", dog=" + dog +
", name='" + name + '\'' +
'}';
}
}
- 编写Spring配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="cat" class="com.lly.pojo.Cat"></bean>
<bean id="dog" class="com.lly.pojo.Dog"></bean>
<bean id="person" class="com.lly.pojo.People">
<property name="cat" ref="cat"></property>
<property name="dog" ref="dog"></property>
<property name="name" value="lly"></property>
</bean>
</beans>
- 测试
@Test
public void testAutowire(){
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
People people = context.getBean("people",People.class);
people.getCat().shout();
people.getDog().shout();
}
结果正常输出,环境OK
7.3 aotowire byName(按名称自动装配)
测试:
- 修改bean配置,增加一个属性
autowire="byName"
<bean id="people" class="com.lly.pojo.People" autowire="byName">
<property name="name" value="lly"></property>
</bean>
- 再次测试,结果依旧输出成功
- 我们将cat的
bean id
修改为catxxx
- 再次测试,执行时报空指针
java.lang.NullPointerException
。因为按byName
规则找不到对应set方法
,真正的setCat
就没执行,对象就没有初始化,所以调用时就会报空指针错误
小结:
当一个bean节点带有autowire byName的属性时:
- 将查找其类中所有set方法名,例如setCat,获得将set去掉并且首字母小写的字符串
- 去spring容器中寻找是否有此字符串名称id的对象
- 如果有,就取出注入;如果没有,就报空指针异常
7.4 autowire by Type(按类型自动装配)
==使用autowore by Type首先需要保证:同一类型的对象,再spring容器中唯一。如果不唯一,会报不唯一的异常。
- 将person的bean配置修改为
autowire="byType"
- 测试,正常输出
- 再注册一个dog的bean对象
- 直接报错
- 删掉dog2,将dog的bean名称改掉,测试,因为是按类型装配,所以并不会报异常,甚至将id属性删掉,也不影响结果
7.5 使用注解
jdk1.5开始支持注解,spring2.5开始全面支持注解。
准备工作:
- 在spring配置文件中引入context文件头
<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
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
- 开启属性注解支持
<context:annotation-config></context:annotation-config>
@Autowired
- @Autowired是按类型自动装配的,不支持id匹配
- 需要导入sping-aop的包(一般都导上了,不行的话检查一下)
测试:
- 将Person中的set方法去掉,使用
@Autowired
注解(加在set上也行)
package com.lly.pojo;
import org.springframework.beans.factory.annotation.Autowired;
public class People {
@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;
}
@Override
public String toString() {
return "People{" +
"cat=" + cat +
", dog=" + dog +
", name='" + name + '\'' +
'}';
}
}
- 此时配置文件内容
<bean id="people" class="com.lly.pojo.People"></bean>
<bean id="dog" class="com.lly.pojo.Dog"></bean>
<bean id="cat" class="com.lly.pojo.Cat"></bean>
- 测试,成功输出结果
注:
@Autowired(required=false)
说明:false,对象可以为null;
true,对象必须存在,不能为null
//如果允许对象为null,设置required = false,默认为true
@Autowired(required = false)
private Cat cat;
@Qualifier
- @Autowired是根据类型自动装配的,加上
@Qualifier
则可以根据byName
的方法自动装配 - @Qualifier不能单独使用
测试:
- 修改配置文件内容,保证存在同类型对象,且名字不为默认名字
<bean id="people" class="com.lly.pojo.People"></bean>
<bean id="dog1" class="com.lly.pojo.Dog"></bean>
<bean id="dog2" class="com.lly.pojo.Dog"></bean>
<bean id="cat1" class="com.lly.pojo.Cat"></bean>
<bean id="cat2" class="com.lly.pojo.Cat"></bean>
- 没有加Qualifier测试,直接报错
- 在属性上添加Qualifier注解
@Autowired
@Qualifier(value = "cat2")
private Cat cat;
@Autowired
@Qualifier(value = "dog2")
private Dog dog;
- 测试,成功输出
@Resource
@Resource
如有指定的name属性,先按该属性进行byName
方式查找装配- 其次再进行默认的byName方式装配
- 如果以上都不成功,则按
byType
的方式自动装配 - 都不成功,则报异常
实体类:
package com.lly.pojo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import javax.annotation.Resource;
public class People {
@Resource(name="cat2")
private Cat cat;
@Resource
private Dog dog;
private String name;
@Override
public String toString() {
return "People{" +
"cat=" + cat +
", dog=" + dog +
", name='" + name + '\'' +
'}';
}*/
}
beans.xml
<bean id="people" class="com.lly.pojo.People"></bean>
<bean id="dog" class="com.lly.pojo.Dog"></bean>
<bean id="dog2" class="com.lly.pojo.Dog"></bean>
<bean id="cat1" class="com.lly.pojo.Cat"></bean>
<bean id="cat2" class="com.lly.pojo.Cat"></bean>
测试:结果OK
配置文件2:删掉cat2
<bean id="people" class="com.lly.pojo.People"></bean>
<bean id="dog" class="com.lly.pojo.Dog"></bean>
<bean id="dog2" class="com.lly.pojo.Dog"></bean>
<bean id="cat1" class="com.lly.pojo.Cat"></bean>
实体类只保留注解:
@Resource
private Cat cat;
@Resource
private Dog dog;
结果:本来应该成功的,现在报错
小结
@Autowired与@Resource异同:
- @Autowired与@Resource都可以用来装配bean。都可以写在字段上,或写在setter方法上
- @Autowired默认按类型装配(属于spring规范),默认情况下必须要求依赖对象必须存在,如果要允许null值,我们可以设置它的required属性为false,如:@Autowired(required=false) ,如果我们想使用名称装配可以结合@Qualifier注解进行使用
- @Resource,默认按照名称进行装配,名称可以通过name属性进行指定,如果没有指定的name属性,当注解写在字段上时,默认取字段名进行按名称查找,如果注解写在setter方法上默认取属性名进行装配,当找不到与名称匹配的bean时才按照类型进行装配,但是需要注意的是,name属性一旦指定,就只会按照名称进行装配.
它们的作用相同都是用注解方式注入对象,但执行顺序不同。@Autowired先ByType,@Resource先byName
8 使用注解开发
在sping4之后,想要使用注解形式,必须得要引入aop
的包
在配置文件总,还得要引入一个context约束
<?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
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<context:annotation-config></context:annotation-config>
</beans>
8.1 Bean的实现
我们之前都是使用bean的标签进行bean注入,但在实际开发中,我们一般都会使用注解
- 配置扫描哪些包下的注解
<context:component-scan base-package="com.lly.pojo"></context:component-scan>
- 在指定包下编写类,增加注解
@Component("user")
// 相当于配置文件中 <bean id="user" class="当前注解的类"/>
public class User {
//相当于<property name="name" value="lly"/>
public String name = "lly";
}
- 测试
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
User user = context.getBean("user",User.class);
System.out.println(user.name);
}
8.2 属性注入
使用注解注入属性
- 可以不用提供set方法,直接在直接名上添加
@value("值")
@Component
public class User {
@Value("lly")
//相当于<property name="name" value="lly"/>
public String name;
}
- 如果提供了set方法,在set方法上添加@value(“值”);
@Component
public class User {
//相当于<property name="name" value="lly"/>
public String name;
public String getName() {
return name;
}
@Value("lly")
public void setName(String name) {
this.name = name;
}
}
8.3 衍生注解
这些注解,就是代替了在配置文件当中的配置步骤而已,更加的方便快捷
@Component三个衍生注解
为了更好的进行分层,Spring可以使用其他单个注解,功能一样,目前使用哪一个功能都一样
- @Controller:web层
- @Service:service层
- @Repository:dao层
写上这些注解,就相当于将这个类交给Spring管理装配了
自动装配注解
在Bean的自动装配里已经讲到过了
作用域
@scope
- singleton:默认的,Spring会采用单例模式创建这个对象。关闭工厂,所有的对象都会销毁。
- prototype:多例模式,关闭工厂,所有的对象不会销毁,内部的垃圾回收机制会回收
@Component
@Scope("prototype")
public class User {
@Value("lly")
//相当于<property name="name" value="lly"/>
public String name;
8.4 小结
XML与注解比较
- XML可以适用任何场景,结构清晰,维护方便
- 注解不是自己提供的类使用不了,开发简单方便
XML与注解整合开发
- XML管理Bean
- 注解完成属性注入
- 使用过程中,可以不用扫描,扫描是为了类上的注解
<context:annotation-config></context:annotation-config>作用
- 进行注解驱动注册,从而使注解生效
- 用于激活那些已经在spring容器里注册过的bean上面注解,也就是显示的向Spring注册
- 如果不扫描包,就需要手动配置bean
- 如果不加注解驱动,则注入的值为null
9 采用Java的方式配置Spring
JavaConfig原来是Spring的一个子项目,它通过Java类的方式提供Bean的定义信息,在Spring4的版本中,JavaConfig已经成为Spring4的核心功能,这是一种纯注解的开发方式
测试:
- 编写一个实体类,Dog
@Component
//讲这个类标注为Spring的一个组件,放到容器中
public class Dog {
public String name = "dog";
}
- 新建一个config配置包,编写一个MyConfig配置类
@Configuration
//代表这是一个配置类
@ComponentScan("com.lly.pojo")//此注解只能添加一次,多个数据用数组格式
public class MyConfig {
}
也可以,但是我觉得上面那个更好懂一些:
@Configuration //代表这是一个配置类
public class MyConfig {
@Bean //通过方法注册一个bean,这里的返回值就Bean的类型,方法名就是bean的id!
public Dog dog(){
return new Dog();
}
}
- 测试
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(MyConfig.class);
Dog dog = context.getBean("dog",Dog.class);
System.out.println(dog.name);
}
- 成功输出结果
导入其他配置如何做呢?
- 再编写一个配置类
@Configuration //代表这是一个配置类
public class MyConfig2 {
}
- 再之前的配置类中我们来选择导入这个配置类
@Configuration
@Import(MyConfig2.class) //导入合并其他配置类,类似于配置文件中的 inculde 标签
public class MyConfig {
@Bean
public Dog dog(){
return new Dog();
}
}
关于这种Java类的配置方式,我们在之后的SpringBoot和SpringCloud中还会大量看到,我们需要知道这些注解的作用即可。
10 代理模式
为什么要学代理模式?因为这既是SpringAOP的底层(SpringAOP和SpringMVC)
代理模式的分类:
- 静态代理
- 动态代理
10.1 静态代理
角色分析:
- 抽象角色:一般会使用接口或抽象类来解决
- 真实角色:被代理角色
- 代理角色:代理真实角色,代理真实角色后,我们一般会做一些附属操作
- 客户:访问代理对象的人
代码实现:
Rent.java即抽象角色
package com.lly.demo01;
public interface Rent{
public void rent();
}
Host.java即真实角色
package com.lly.demo01;
public class Host implements Rent{
@Override
public void rent() {
System.out.println("房东要出租房子");
}
}
Proxy.java即代理角色
package com.lly.demo01;
public class Proxy {
private Host host;
public Proxy(){
}
public Proxy(Host host){
this.host = host;
}
public void rent(){
host.rent();
}
//看房
public void seeHouse(){
System.out.println("中介带你看房");
}
//收中介费
public void fare(){
System.out.println("收中介费");
}
//签合同
public void signContract(){
System.out.println("签租赁合同");
}
}
Client.java即客户
package com.lly.demo01;
public class Client {
public static void main(String[] args) {
//房东要出租房子
Host host = new Host();
//代理,中介帮房东租房子,但是呢?代理会有一些附属操作
Proxy proxy = new Proxy(host);
//你不用面对房东,直接找中介租房即可
proxy.rent();
}
}
分析:在这个过程中,你直接接触的就是中介,就如同现实生活中的样子,你看不到房东,但是你依旧租到了房东的房子,通过代理,这就是所谓的代理模式。
代理模式的好处:
- 可以使真实角色的操作更加纯粹,不用关注一些公共的业务
- 公共也就交给代理角色,实现了业务的分工
- 公共业务发生扩展的时候,方便集中管理
缺点:
- 一个真实角色就会产生一个代理角色,代码量会翻倍,开发效率会变低
10.2 静态代理再理解
- 创建一个抽象角色,比如我们平时做的用户业务,抽象起来就是增删改查
package com.lly.demo02;
public interface UserService {
void add();
void delete();
void update();
void query();
}
- 用一个真实对象来完成增删改查操作
package com.lly.demo02;
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 query() {
System.out.println("查询一个用户");
}
}
- 思考一下,这个时候我们来了一个需求,需要增加一个日志功能,怎么实现
- 思路1:在实现类上增加代码(麻烦)
- 思路2:使用代理来做,能够在不改变原来的业务的情况下,实现此功能
- 设置一个代理类来处理日志!代理角色
package com.lly.demo02;
public class UserServiceProxy implements UserService{
private UserServiceImpl userService;
public void setUserService(UserServiceImpl userService) {
this.userService = userService;
}
@Override
public void add() {
log("add");
userService.add();
}
@Override
public void delete() {
log("delete");
userService.delete();
}
@Override
public void update() {
log("update");
userService.update();
}
@Override
public void query() {
log("query");
userService.query();
}
public void log(String msg){
System.out.println("执行了"+msg+"方法");
}
}
- 测试访问类
package com.lly.demo02;
public class Client {
public static void main(String[] args) {
//真实业务
UserServiceImpl userService = new UserServiceImpl();
//代理类
UserServiceProxy proxy = new UserServiceProxy();
//使用代理类实现日志功能
proxy.setUserService(userService);
proxy.add();
}
}
我们在不改变原来代码的情况下,实现了对原有功能的增强,这是AOP中最核心的思想
聊聊AOP
10.3 动态代理
常用的动态代理技术
- JDK代理:基于接口的动态代理技术
- cglib代理:基于父类的动态代理技术
我对这里理解的不深入,等以后再来补充
11 AOP
11.1 什么是AOP?
AOP(Aspect Oriented Programming)
意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数编程的一种衍生典范。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发效率,同时也体现了Spring理念无侵入式。
11.2 AOP核心概念
- 连接点(JoinPoint):程序执行过程中的任意位置,粒度为执行方法,抛出异常,设置变量等
- 在SpringAOP中,理解为方法的执行
- 切入点(Pointcut):匹配连接点的式子
- 在SpringAop中,一个切入点可以只描述一个具体方法,也可以匹配多个方法
- 一个具体方法:com.hcx.dao包下的BookDao接口中的无形参返回值的save方法
- 匹配多个方法,所有的save方法,所有的get开头的方法,所有以Dao结尾的接口中的任意方法,所有带有一个参数的方法
- 通知(Advice):在切入点处执行的操作,也就是共性功能
- 在Spring中,功能最终以方法的形式呈现
- 通知类:定义通知的类
- 切面(Aspect):描述通知与切入点的对应关系
AOP提供声明式事务:允许用户自定义切面
11.3 AOP入门案例(注解版)
- 导入aop相关坐标
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.4</version>
</dependency>
说明:Spring-context坐标依赖spring-aop坐标
- 定义dao接口与实现类
package com.lly.dao;
public interface UserDao {
public void sava();
public void update();
}
package com.lly.dao;
import org.springframework.stereotype.Repository;
@Repository
public class UserDaoImpl implements UserDao{
@Override
public void sava() {
System.out.println(System.currentTimeMillis());
System.out.println("user dao save");
}
@Override
public void update() {
System.out.println("user dao update");
}
}
- 定义通知类,制作通知
package com.lly.advice;
public class MyAdvice {
public void before(){
System.out.println(System.currentTimeMillis());
}
}
- 定义切入点
package com.lly.advice;
import org.aspectj.lang.annotation.Pointcut;
public class MyAdvice {
@Pointcut("execution(void com.lly.dao.UserDao.update())")
private void pt(){}
}
说明:切入点的定义依托于一个不具有实际意义的方法进行,即无参数,无返回值,方法体现无实际逻辑
- 绑定切入点与通知的关系,并指定通知添加到原始连接点的具有执行位置
package com.lly.advice;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
public class MyAdvice {
@Pointcut("execution(void com.lly.dao.UserDao.update())")
private void pt(){}
@Before("pt()")
public void before(){
System.out.println(System.currentTimeMillis());
}
}
- 定义通知类受Spring容器管理,并且定义当前类为切面类
package com.lly.advice;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
@Component
@Aspect
public class MyAdvice {
@Pointcut("execution(void com.lly.dao.UserDao.update())")
private void pt(){}
@Before("pt()")
public void before(){
System.out.println(System.currentTimeMillis());
}
}
- 开启Spring对AOP注解驱动支持
package com.lly.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@Configuration
@ComponentScan("com.lly.*")
@EnableAspectJAutoProxy
public class SpringConfig {
}
- 测试
import com.lly.config.SpringConfig;
import com.lly.dao.UserDao;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class Mytest {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
UserDao userDao = context.getBean(UserDao.class);
userDao.update();
}
}
结果正常输出
错误:
我之前在获取bean时,先获取的是接口实现类,然后出现了以下错误
Exception in thread “main” org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type ‘com.lly.dao.UserDaoImpl’ available
查的另一位博主的解决办法
那个博主在评论区给出的原因是,上述代码是基于动态代理实现的aop,jdk动态代理
的底层其实就是反射机制生成代理接口的匿名类。所以jdk动态代理是基于接口的,而不是基于类的(这一点前面有张动态代理的图提到过),所以才会报错。
11.4 实现AOP的方式
第一种方式:通过Spring API实现
第二种方式:自定义类来实现Aop
第三种方式:使用注解实现(上面的就是)
个人认为,现在先掌握一种的使用就行了,其它的等以后如果又需要再学习补充
11.5 AOP工作流程
- Spring容器启动
- 实现所有切面配置中的切入点
@Component
@Aspect
public class MyAdvice {
@Pointcut("execution(void com.lly.dao.UserDao.update())")
private void pt(){}
@Pointcut("execution(void com.lly.dao.UserDao.sava())")
private void ptx(){}
@Before("pt()")
public void before(){
System.out.println(System.currentTimeMillis());
}
}
- 初始化bean,判定bean对应的类中的方法是否匹配到任意切入点
- 匹配失败,创建对象
- 匹配成功,创建原始对象(目标对象)的代理对象
- 获取bean执行方法
- 获取bean,调用方法并执行,完成
- 获取的bean时代理对象时,根据代理对象的运行模式运行原始方法与增强的内容,完成操作
11.6 AOP切入点表达式
- 切入点:要进行增强的方法
- 切入点表达式:要进行增强方法的描述方式
"execution(void com.lly.dao.UserDao.update())"
描述方法一:执行com.lly.dao包下的BookDao接口中的无参数update方法
描述方法二:执行com.lly.dao包下的BookDaoImpl类中的无参数update方法
切入点表达式标准格式:动作关键字(访问修饰符,返回值,包名,类/接口.方法名(参数)异常名)
11.7 AOP通知类型
AOP通知描述了抽取的共性功能,根据共性功能抽取位置不同,最终运行代码时要将其加入到合理的位置
AOP通知分为五种类型:
- 前置通知
- 后置通知
- 环绕通知(重点)
- 返回通知(了解)
- 抛出执行异常后通知(了解)
12 整合mybatis
步骤:
- 导入相关的包
- junit
- mybatis
- mysql数据库
- spring相关的
- aop导入
- mybatis-spring
- 编写配置文件
- 测试
12.1 回忆Mybatis
- 编写实体类
package com.lly.pojo;
import lombok.Data;
@Data
public class User {
private int id; //id
private String name; //姓名
private String pwd; //密码
}
- 编写核心配置文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-config.dtd">
<!--configuration核心配置文件-->
<configuration>
<typeAliases>
<package name="com.lly.pojo"/>
</typeAliases>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=true&useUnicode=true&characterEncoding=UTF-8"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</dataSource>
</environment>
</environments>
<!--每一个Mapper.XML都需要在Mybatis核心配置文件中注册-->
<mappers>
<mapper resource="com/lly/mapper/UserMapper.xml"/>
</mappers>
</configuration>
- 编写接口
public interface UserMapper {
public List<User> selectUser();
}
- 编写Mapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.lly.mapper.UserMapper">
<select id="selectUser" resultType="com.lly.pojo.User">
select * from mybatis.user;
</select>
</mapper>
- 测试
public class Mytest {
public static void main(String[] args) {
}
@Test
public void test() throws IOException {
String resource = "mybatis-config.xml";
InputStream in = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(in);
SqlSession sqlSession = sqlSessionFactory.openSession(true);
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<User> userList = mapper.selectUser();
for (User user : userList) {
System.out.println(user);
}
sqlSession.close();
}
}
结果:
User(id=1, name=张三, pwd=123456)
User(id=2, name=李四, pwd=654456)
User(id=3, name=王五, pwd=234567)
User(id=4, name=李五, pwd=123123)
更具体的可以去看
总之mybatis的重点就是:
- 编写核心配置文件,核心配置文件中最重要的就是
dataSource
- 编写SQL映射文件
- 用
SqlSessionFactory
加载核心配置文件 - 用
SqlSessionFactory
获取SqlSession
对象 - 用
SqlSession
获取Mapper
执行sql语句 - 释放SqlSession
12.2 MyBatis-Spring学习
整合spring和mybatis之前,我们需要了解一下mybatis-spring包中的一些重要的类:
为什么学MyBatis-Spring?
MyBatis-Spring会帮助你将Mybatis代码无缝的整合到Spring中
Mybatis-Spring的版本和MyBatis的版本要匹配:
MyBatis-Spring | MyBatis | Spring 框架 | Spring Batch | Java |
---|---|---|---|---|
2.0 | 3.5+ | 5.0+ | 4.0+ | Java 8+ |
1.3 | 3.4+ | 3.2.2+ | 2.1+ | Java 6+ |
导入坐标:
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.0.2</version>
</dependency>
mybatis-spring包中一些重要的类:
- SqlSessionFactoryBean
- SqlSessionTemplate
要和Spring一起使用MyBatis,需要在Sping应用上下文中定义至少两种东西:一个SqlSessionFactory
和至少一个数据库映射器类
.
在基础的 MyBatis 用法中,是通过 SqlSessionFactoryBuilder 来创建 SqlSessionFactory 的。而在Mybatis-Spring中,则使用SqlSessionFactoryBean
来创建SqlSessionFactory
。
SqlSessionFactory
有一个唯一的必要属性:用于JDBC的DataSource
。这可以是任意的DataSource对象,它的配置方法和其他Spring数据库连接是一样的。
一个常用的属性是configLocation
,它用来指定Mybatis的xml配置文件
路径,它在需要修改MyBatis的基础配置非常有用(< settings>,< typeAliases>)。
SqlSessionTemplate
是MyBatis-Spring的核心。作为SqlSession的一个实现,这意味着可以使用它无缝代替你代码中已经在使用的SqlSession。模版可以参与到Spring的事务管理中,并且由于其是线程安全的,所以最好用SqlSession来替换
Mybatis默认的DefaultSqlSession实现。在同一应用程序中的不同类之间混杂使用,可能会引起数据一致性的问题。
接下类我们来整合一下。
12.3 整合实现一
- 引入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">
</beans>
- 配置数据源替换mybatis的数据源
<!--DataSource:使用Spring的数据源替换Mybatis的配置 c3p0 dbcp druid
我们这里用Spring提供JDBC
-->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=false&useUnicode=true&characterEncoding=utf8"></property>
<property name="username" value="root"></property>
<property name="password" value="root"></property>
</bean>
- 配置SqlSessionFactory`,关联Mybatis
<!--配置SqlSessionFactory-->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<!--关联mybatis-->
<property name="configLocation" value="classpath:mybatis-config.xml"/>
<property name="mapperLocations" value="classpath:com/lly/mapper/UserMapper.xml"/>
</bean>
- 注册sqlSessionTemplate,关联sqlSessionFactory
<!--注册sqlSessionTemplate,关联sqlSessionFactory-->
<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
<!--利用构造器注入-->
<constructor-arg index="0" ref="sqlSessionFactory"/>
</bean>
- 增加实现类,私有化sqlSessionTemp
public class UserMapperImpl implements UserMapper{
//sqlSession不用我们自己创建了,Sping来管理
//原本要在实现的时候get以下这下就不用了
private SqlSessionTemplate sqlSession;
public void setSqlSession(SqlSessionTemplate sqlSession) {
this.sqlSession = sqlSession;
}
@Override
public List<User> selectUser() {
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
return mapper.selectUser();
}
}
- 注册bean实现
<!--注册bean实现-->
<bean id="userMapper" class="com.lly.mapper.UserMapperImpl">
<property name="sqlSession" ref="sqlSession"/>
</bean>
- 测试
@Test
public void test2(){
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
UserMapper mapper = (UserMapper) context.getBean("userMapper");
List<User> users = mapper.selectUser();
System.out.println(users);
}
结果输出成功。现在Mybatis配置文件的内容如下:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-config.dtd">
<!--configuration核心配置文件-->
<configuration>
<typeAliases>
<package name="com.lly.pojo"/>
</typeAliases>
</configuration>
这些也可以被Sping整合,但是看个人习惯及需要,也可以不整合。
如果出现了找不到配置文件的问题,看下边,虽然这个错误该怎么改早就被教过,但是还是会忘,这个玩意儿真的很重要,找了很久的错(微笑):
<!--pom文件中在build中配置resources,来防止我们资源导出失败的问题-->
<build>
<resources>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
</resource>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>true</filtering>
</resource>
</resources>
</build>
12.4 整合实现二
注意:mybatis-spring1.2.3版以上的才有这个
用mapper的实现类继承SqlSessionDaoSupport
,直接利用getSqlSession()获得,然后注入SqlSessionFactory
,比起方式一不需要管理SqlSessionTemplate,而且对事务的支持更友好。
测试:
- 再写一个UserMapperImpl
public class UserMapperImpl2 extends SqlSessionDaoSupport implements UserMapper {
@Override
public List<User> selectUser() {
UserMapper mapper = getSqlSession().getMapper(UserMapper.class);
return mapper.selectUser();
}
}
- bean配置
<bean id="userMapper2" class="com.lly.mapper.UserMapperImpl2">
<property name="sqlSessionFactory" ref="sqlSessionFactory"></property>
</bean>
- 测试
@Test
public void test3(){
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
UserMapper mapper = (UserMapper) context.getBean("userMapper2");
List<User> users = mapper.selectUser();
System.out.println(users);
}
13 Spring的事务
详细的可以看这篇文章
13.1 入门案例
下面是以转账为例的声明式事务的一个练习:
我们先来看看不用Spring的事务的时候,这里我尝试了一下大部分都用注解开发:
- 首先是建一个配置类
package com.lly.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
@Configuration
@ComponentScan("com.lly")
public class MyConfig {
}
@Configuration
是spring framework中的一个注解,表明这各类是一个配置类@ComponentScan("com.lly")
也是sping framework中的一个注解,用于扫描指定的包,它会扫描指定的包下的组件,并将其注册到Spring容器中
- 定义几个重要的Bean的配置
@Bean
public DataSource dataSource(){
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306/db1?useSSL=false&useUnicode=true&characterEncoding=utf8");
dataSource.setUsername("root");
dataSource.setPassword("root");
return dataSource;
}
@Bean
public SqlSessionFactoryBean sqlSessionFactory(DataSource dataSource){
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
sqlSessionFactoryBean.setTypeAliasesPackage("com.lly.pojo");
sqlSessionFactoryBean.setDataSource(dataSource);
return sqlSessionFactoryBean;
}
@Bean
public MapperScannerConfigurer mapperScannerConfigurer(){
MapperScannerConfigurer msc = new MapperScannerConfigurer();
msc.setBasePackage("com.lly.mapper");
return msc;
}
@Bean
详情,这是其他博主的文章DataSource Bean
,使用DriverManagerDataSource 实现,也可以使用别的DataSource实现,设置了数据库连接相关的属性SqlSessionFactoryBean Bean
,用于配置Mybatis的SqlSessionFactory。setTypeAliasesPackage("com.lly.pojo")
设置类型别名包的扫描路径setDataSource
设置数据源,数据域直接当参数传入即可
MapperScannerConfigurer Bean
用setBasePackage
配置Mybatis的Mapper接口扫描路径,将Mapper接口注册为Spring的bean
- 实体类
package com.lly.pojo;
import lombok.Data;
import org.springframework.stereotype.Component;
@Component
@Data
public class Account {
private int id;
private String name;
private double money;
}
@Component
表示这个类作为Spring容器管理的组件,即托管给Spring了,从而可以通过依赖注入等方式使用@Data
是lombok提供的一个注解,它能够自动生成类的 getter、setter、toString、equals 和 hashCode 方法,使用的话需要导入lombok
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.10</version>
</dependency>
- mapper层
AccountMapper
public interface AccountMapper {
void in(Map<String,Object> map);
void out(Map<String,Object> map);
}
AccountMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.lly.mapper.AccountMapper">
<update id="in" parameterType="map">
update db1.account set money = money+#{money} where name=#{name}
</update>
<update id="out" parameterType="java.util.Map">
update db1.account set money = money-#{money} where name=#{name}
</update>
</mapper>
这个文件的内容也可以用@update
注解配置,这样就是全部使用注解了
5. service层
public interface AccountService {
public void transfer(String out,String in,Double money);
}
@Service
public class AccountServiceImpl implements AccountService{
@Autowired
private AccountMapper mapper;
@Override
public void transfer(String out, String in, Double money) {
Map<String,Object> map = new HashMap<>();
map.put("money",money);
map.put("name",out);
mapper.out(map);
map.clear();
//int i = 1/0;
map.put("money",money);
map.put("name",in);
mapper.in(map);
}
}
@Service
和@Component作用是一样的,名字不一样是为了区分service层@Autowired
用于自动装配,告诉 Spring 在需要该类型的 bean 时,自动装配对应的 bean 实例,前面托管给Spring的bean就可以这样装配上了
- 测试
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = MyConfig.class)
public class Mytest {
@Autowired
private AccountService accountService;
@Test
public void test01(){
accountService.transfer("张三","李四", 100.0);
}
}
这里用Spring整合了Junit
导入spring-test
然后使用相应注解即可整合成功
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.2.0.RELEASE</version>
<scope>test</scope>
</dependency>
@RunWith(SpringJUnit4ClassRunner.class)
用于指定测试类的运行器,将测试类交给SpringJUnit4ClassRunner
来运行,SpringJUnit4ClassRunner 是 Spring 提供的测试运行器,它能够在 JUnit 测试运行时,自动创建 Spring 的应用上下文,并且管理测试类和测试方法的执行过程。使用该注解可以使测试类获得 Spring 容器的支持,在测试过程中能够使用依赖注入、AOP、事务管理等 Spring 功能,更加贴近实际应用环境。-
@ContextConfiguration(classes = MyConfig.class)
用于指定上下文的配置信息,告诉 Spring 在启动测试时应该加载哪个配置类来构建应用上下文。
- 测试结果
原:
int i = 1/0;
时运行测试类,控制台报错,数据库结果
将int i = 1/0注释掉,再运行
我们期望的结果是,如果出现错误,转账双方的账户的钱都不变,但现在,至少不多,原因是:
在上面的程序中mapper.out(map)
和mapper.in(map)
是被当做两个事务处理的,而要满足我们的期望,需要把他们当做一个事务处理,这个时候,就需要Spring事务
了。
- 一般事务的作用:在数据层保障一系列的数据库操作同成功同失败,上面的事务都是这种
- Spring事务作用:在**数据层或
业务层
**保障一系列的数据操作同成功同失败
我们进行以下三步,就可以添加Spring事务管理了:
第一步:创建Spring的事务管理器
@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource){
DataSourceTransactionManager ptm = new DataSourceTransactionManager();
ptm.setDataSource(dataSource);
return ptm;
}
第二步:使用@Transactional
注解标记需要被该事务管理器管理的方法
public interface AccountService {
@Transactional
public void transfer(String out,String in,Double money);
//这样那两个事务就并为一个事务了,就可以同成功同失败了
}
注意:Spring注解式事务通常添加在业务层接口中而不会添加到业务层实习类中,降低耦合度。注解式业务可以添加到业务方法上表示开启事务,也可以添加到接口上表示当前接口所有方法开启事务
第三步:
开启注解事务驱动
@Configuration
@ComponentScan("com.lly")
@EnableTransactionManagement
public class MyConfig {
}
这样就可以达到一个我们想要的效果了。
其实Spring事务充当的角色是事务管理员,及发起事务方,在Spring中通常指代业务层开启事务的方法,而原本的一般事务充当事务调解员的角色,即加入事务方,在Spring中通常带着数据层方法,也可以是业务层方法
13.2 事务相关配置
注意:IOException
不属于运行时异常,事务回滚不管,可以通过rollbackFor
设置
13.3 事务传播行为
- 事务传播行为:事务协调员对事务管理员所携带事务的处理态度
事务传播行为类型 | 说明 |
---|---|
REQUIRED | 如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。这是最常见的选择。 |
REQUIRES_NEW | 新建事务,如果当前存在事务,把当前事务挂起。 |
SUPPORTS | 支持当前事务,如果当前没有事务,就以非事务方式执行。 |
MANDATORY | 使用当前的事务,如果当前没有事务,就抛出异常。 |
NOT_SUPPORTED | 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。 |
NEVER | 以非事务方式执行,如果当前存在事务,则抛出异常。 |
NESTED | 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与REQUIRED类似的操作。 |
案例:
还是银行转账,无论成功还是失败都要实现数据库中记录日志。
我们如果向上面的例子来做,添加一个日志模块,然后这个日志功能就会和那两个事务一样同成功,同失败,不能实现我们想要的效果。
但是当我们把在日志模块业务层接口上添加Spring事务,并设置事务传播行为为REQUIRES_NEW
,那添加日志这个事务就会独立出来而且一定会执行
@Service
public class LogServiceImpl implements LogService{
@Autowired
private LogDao logDao;
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void log(String out,String in,Double money){
logDao.log("转账操作由"+out+"到"+in+",金额:"+money);
}
}
最后,Spring是什么?
Spring是一个轻量级的控制反转(IoC)
和面向切面(AOP)
的容器(框架)。