简介
- Spring:春天–>给软件行业带来了春天!
- 2002,首次推出了Spring框架的雏形:interface21框架!
- Spring框架即以interface21框架为基础,经过重新设计,并不断丰富内涵,于2004年3月24日,发布了1.0正式版。
- Rod Johnson,Spring Framework创始人,著名作者。很难想象其学历,真的让好多人大吃一惊,他是悉尼大学的博士,然而他的专业不是计算机,而是音乐学。
- spring理念:使现有的技术更加容易使用,本身是一个大杂烩,整合了现有的技术框架。
- SSH:Struct2+Spring+Hibernate!
- SSM:SpringMVC+Spring+Mybatis!
- 官方文档|官网|官方下载地址|Github
Maven仓库:导入webmvc包会自动导入相关依赖;jdbc用于和Mybatis整合。
<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>
优点
组成
拓展
在Spring的官网有这个介绍:现代化的java开发!说白了就是基于Spring的开发!
- Spring Boot
- 一个快速开发的脚手架。
- 基于Spring Boot可以快速的开发单个微服务。
- 约定大于配置!
- Spring Cloud
- SpringCloud是基于SpringBoot实现的。
因为现在大多数公司都在使用SpringBoot进行快速开发,学习SpringBoot的前提,需要完全掌握Spring以及SpringMVC!承上启下的作用。
弊端:发展了太久之后,违背了原来的理念!配置十分繁琐,人称:“配置地狱”。
IOC
IOC理论推导
-
UserDao接口 -
UserDaoImpl实现类 -
UserService业务接口 -
UserServiceImpl业务实现类
在我们之前的业务中,用户的需求可能会影响我们原来的代码,我们需要根据用户的需求去修改源代码!如果程序代码量十分大,修改一次的成本代价十分昂贵!
我们使用一个Set接口实现,已经发生了革命性的变化!
private UserDao userDao;
public void setUserDao(UserDao userDao){
this.userDao = userDao;
}
- 之前,程序是主动创建对象!控制权在程序员手上!
- 使用了set注入后,程序不再具有主动性,而是变成了被动的接收对象!
这种思想,从本质上解决了问题,我们程序员不用再去管理对象的创建了。系统的耦合性大大降低,可以更加专注在业务的实现上。这是IOC的原型!
IOC本质
控制反转IOC(Inversion of Control),是一种设计思想,DI(依赖注入)是实现IOC的一种方法,也有人认为DI是IoC的另一种说法。没有IoC的程序中,我们使用面向对象编程,对象的创建与对象间的依赖关系完全硬编码在程序中,对象的创建由程序自己控制,控制反转后将对象的创建转移给第三方,个人认为所谓控制反转就是:获得依赖对象的方式反转了。
采用XML方式配置Bean的时候,Bean的定义信息是和实现分离的,而采用注解的方式可以把两者合为一体,Bean的定义信息直接以注解的形式定义在实现类中,从而达到了零配置的目的。
控制反转是一种通过描述(XML或注解)并通过第三方去生产或获取特定对象的方式。在Spring中实现控制反转的是IoC容器,其实现方式是依赖注入(Dependency Injection,DI)。
HelloSpring
步骤
- 导入依赖。Spring项目,需要导入的依赖:(导入webmvc,其他相关的也会被导入)
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.2.0.RELEASE</version>
</dependency>
- 在resources文件夹下,创建beans.xml配置文件,采用XML方式配置Bean:(官方建议配置文件起名为“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">
</beans>
- 添加bean对象:(beans.xml中)
<bean id="hello" class="com.qsdbl.pojo.Hello">
<property name="str" value="hello world!"/>
</bean>
上边用到的实体类Hello,java代码如下:
package com.qsdbl.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Hello {
private String str;
}
- 测试:
@Test
public void test() {
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
Hello hello = (Hello)context.getBean("hello");
System.out.println(hello.getStr());
}
思考问题?
这个过程就叫做控制反转:
控制:谁来控制对象的创建,传统应用程序的对象是由程序本身控制创建的,使用Spring后,对象是由Spring来创建的。
反转:程序本身不创建对象,而变成被动的接收对象。
依赖注入:就是利用set方法来进行注入。
IoC是一种编程思想,由主动的编程变成被动的接收。
可以通过new ClassPathXmlApplicationContext 去浏览一下底层源码。
OK,到了现在,我们彻底不用在程序中去改动了,要实现不同的操作,只需要在xml配置文件中进行修改,所谓的IoC,一句话搞定:对象由Spring来创建,管理,装配!
IOC创建对象的方式
无参构造器
Spring容器,默认使用无参构造器创建对象
package com.qsdbl.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
public class Hello {
private String str;
public Hello() {
System.out.println("使用无参构造器,创建对象");
}
public Hello(String str) {
this.str = str;
System.out.println("使用有参构造器,创建对象");
}
public String getStr() {
return str;
}
public void setStr(String str) {
this.str = str;
}
}
测试:(beans.xml没有修改,跟上边一样)
@Test
public void test() {
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
Hello hello = (Hello)context.getBean("hello");
}
运行结果:
使用无参构造器,创建对象
Process finished with exit code 0
有参构造器
在beans.xml文件中,指定使用有参构造器创建对象。
使用有参构造器,有三种方式赋值:(在constructor-arg 标签中设置)
-
index index指(有参构造器的)参数下标 , 下标从0开始。(可以写多个参数,若构造器可以接收多个参数)
<bean id="hello" class="com.qsdbl.pojo.Hello">
<constructor-arg index="0" value="hello world!"/>
</bean>
使用有参构造器,创建对象
Process finished with exit code 0
-
type type根据参数类型设置。(不建议使用)
<bean id="hello" class="com.qsdbl.pojo.Hello">
<constructor-arg type="java.lang.String" value="hello world!"/>
</bean>
使用有参构造器,创建对象
Process finished with exit code 0
-
name 根据参数名称来设置。
<bean id="hello" class="com.qsdbl.pojo.Hello">
<constructor-arg name="str" value="hello world"/>
</bean>
使用有参构造器,创建对象
Process finished with exit code 0
小结
- 在配置文件加载的时候(
new ClassPathXmlApplicationContext("beans.xml"); ),容器中管理的对象就已经初始化(创建)了。
Spring配置
前边的beans.xml文件中进行配置。
别名
- 使用alias标签起别名
- name,对应bean标签的id。
- alias,我们给bean设置的别名。
<bean id="hello" class="com.qsdbl.pojo.Hello">
<constructor-arg name="str" value="hello world"/>
</bean>
<alias name="hello" alias="myhello"/>
//使用(例子见上边”HelloSpring“的“测试”步骤):
Hello hello = (Hello)context.getBean("hello");
等价于:
Hello hello = (Hello)context.getBean("myhello");
bean配置
-
bean就是java对象,由Spring创建和管理。使用bean标签进行配置(注册bean到Spring中) -
id是bean的唯一标识符,如果没有配置id,name就是默认标识符
- 如果配置了id,又配置了name,那么name是别名。name可以设置多个别名,可以用逗号,分号,空格隔开
- 如果不配置id和name,可以根据
applicationContext.getBean(xxx.class) 获取对象; -
class是bean对象所对应的类型的全限定名=包名+类名
<bean id="hello" name="helloworld h1,h2;h3" class="com.qsdbl.pojo.Hello">
</bean>
import
这个import,一般用于团队开发使用,他可以将多个配置文件,导入合并为一个。
假设,现在项目中有多个人开发,这三个人负责不同的类开发,不同的类需要注册在不同的bean中,我们可以利用import将所有人的beans.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 http://www.springframework.org/schema/beans/spring-beans.xsd">
<import resource="beans.xml"/>
<import resource="beans2.xml"/>
<import resource="beans3.xml"/>
</beans>
使用的时候,直接使用总的配置就可以了。
依赖注入
我的对象的『依赖』是注入进来的,而和它的构造方式解耦了。构造它这个『控制』操作也交给了第三方,也就是控制反转。
个人理解:依赖可以理解为类各属性的具体值,现在创建对象不是提前将各属性值写死在程序中(new的方式)而是交给框架,调用者通过调用框架将”依赖“注入再创建。对类的属性进行赋值(依赖注入),可以通过构造器或setter方法。
构造器注入
前边已经介绍过。(IOC创建对象的方式)
Set方式注入【重点】
- 依赖注入:Set注入!
- 依赖:bean对象的创建依赖于容器!
- 注入:bean对象中的所有属性,由容器来注入!
环境搭建
-
引用数据类型(Lombok的使用,可以参考这篇博客) package com.qsdbl.pojo;
import lombok.Data;
@Data
public class Address {
private String address;
}
-
测试对象Student package com.qsdbl.pojo;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import lombok.Data;
@Data
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;
}
-
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="address" class="com.qsdbl.pojo.Address">
</bean>
<bean id="student" class="com.qsdbl.pojo.Student">
<property name="name" value="轻率的保罗"/>
<property name="address" ref="address"/>
</bean>
</beans>
-
测试Student类 import com.qsdbl.pojo.Student;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MyTest {
@Test
public void test(){
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
Student student = (Student)context.getBean("student");
System.out.println(student.toString());
}
}
注入方式
下边展示了各种数据类型的注入方式,有:基本数据类型、引用数据类型(自定义的Bean、数组、List集合、map键值对、set集合、null、property(类似map))
详细介绍见官方文档。
<bean id="address" class="com.qsdbl.pojo.Address">
<property name="address" value="北京市"/>
</bean>
<bean id="student" class="com.qsdbl.pojo.Student">
<property name="name" value="轻率的保罗"/>
<property name="address" ref="address"/>
<property name="books">
<array>
<value>红楼梦</value>
<value>西游记</value>
</array>
</property>
<property name="hobbys">
<list>
<value>听歌</value>
<value>看电影</value>
</list>
</property>
<property name="card">
<map>
<entry key="身份证" value="62341234123"/>
<entry key="银行卡" value="23452352345"/>
</map>
</property>
<property name="games">
<set>
<value>和平精英</value>
<value>王者荣耀</value>
</set>
</property>
<property name="wife">
<null/>
</property>
<property name="info">
<props>
<prop key="学号">2019252343</prop>
<prop key="性别">男</prop>
</props>
</property>
</bean>
测试结果:(数据均成功注入到对象中)
Student(name=轻率的保罗, address=Address(address=北京市), books=[红楼梦, 西游记], hobbys=[听歌, 看电影], card={身份证=62341234123, 银行卡=23452352345}, games=[和平精英, 王者荣耀], wife=null, info={学号=2019252343, 性别=男})
Process finished with exit code 0
注入-拓展
可以使用p命名空间(属性)和c命名空间(构造器)进行注入(可以理解为一种便捷方式)。详细介绍见官方文档。
演示:(Spring配置文件中)
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:c="http://www.springframework.org/schema/c"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="user" class="com.tian.pojo.User" p:name="test" p:age="20"/>
<bean id="user2" class="com.tian.pojo.User" c:name="test1" c:age="22"/>
</beans>
注意点:使用p命名空间和c命名空间需要导入xml约束!(头部)
xmlns:p="http://www.springframework.org/schema/p"
xmlns:c="http://www.springframework.org/schema/c"
Bean
Spring Bean是被实例的,组装的及被Spring 容器管理的Java对象。
Spring 容器会自动完成@bean对象的实例化。
创建应用对象之间的协作关系的行为称为:装配(wiring),这就是依赖注入的本质。
Bean配置
Spring配置文件中,使用bean标签,配置bean。
Bean作用域
详细介绍,见官方文档。
Scope | Description |
---|
singleton | (Default) Scopes a single bean definition to a single object instance for each Spring IoC container. | prototype | Scopes a single bean definition to any number of object instances. | request | Scopes a single bean definition to the lifecycle of a single HTTP request. That is, each HTTP request has its own instance of a bean created off the back of a single bean definition. Only valid in the context of a web-aware Spring ApplicationContext . | session | Scopes a single bean definition to the lifecycle of an HTTP Session . Only valid in the context of a web-aware Spring ApplicationContext . | application | Scopes a single bean definition to the lifecycle of a ServletContext . Only valid in the context of a web-aware Spring ApplicationContext . | websocket | Scopes a single bean definition to the lifecycle of a WebSocket . Only valid in the context of a web-aware Spring ApplicationContext . |
-
单例模式(Spring默认机制) 一般单线程下使用。
<bean id="address" class="com.qsdbl.pojo.Address">
<property name="address" value="北京市"/>
</bean>
<bean id="address" class="com.qsdbl.pojo.Address" scope="singleton">
<property name="address" value="北京市"/>
</bean>
-
原型模式:每次从容器中get的时候,都会产生一个新对象! 一般多线程下使用。 <bean id="address" class="com.qsdbl.pojo.Address" scope="prototype">
<property name="address" value="北京市"/>
</bean>
-
其余的request、session、application、websocket这些只能在web开放中使用!
自动装配Bean
依赖注入与自动装配的关系:
-
依赖注入的本质就是装配,装配是依赖注入的具体行为。 -
《spring实战》中给装配下了一个定义:创建应用对象之间协作关系的行为称为装配。也就是说当一个对象的属性是另一个对象时,实例化时,需要为这个对象属性进行实例化。这就是装配。 -
自动装配是为了将依赖注入“自动化”的一个简化配置的操作。
- 手动装配,在前边依赖注入中也有涉及,在Spring配置文件中配置bean时手动设置各个属性的值(这一步就是set方式的依赖注入,不过自动装配一般都是null),现在通过自动装配不需要在Spring配置文件中手动设置。(自动装配允许null,所以使用自动装配一般都是null,而手动装配可以设置各种值)
- 自动装配是Spring满足bean依赖的一种方式
- Spring会在上下文自动查找,并自动给bean装配属性
在Spring中有三种装配方式:
- 在xml中显示配置
- 在Java中显示配置
- 隐式的自动装配bean【重要】
测试
手动装配
手动装配:通过property标签,手动设置属性address的值。
<?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="address" class="com.qsdbl.pojo.Address" scope="prototype">
<property name="address" value="北京市"/>
</bean>
<bean id="student" class="com.qsdbl.pojo.Student">
<property name="address" ref="address"/>
</bean>
</beans>
测试:
@Test
public void test(){
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
Student student = context.getBean("student", Student.class);
System.out.println(student.getAddress());
}
测试结果:(Spring配置文件中的第7行,Address对象设置了默认值“北京市”)
Address(address=北京市)
Process finished with exit code 0
自动装配
byName:会自动在容器上下文中查找,和自己对象set方法后面的值(set方法名后边部分,小写开头)对应的bean的id!
例如:address属性,set方法名为setAddress,byName自动装配查找的bean id为address(小写开头)
<bean id="address" class="com.qsdbl.pojo.Address" scope="prototype">
<property name="address" value="北京市"/>
</bean>
<bean id="student" class="com.qsdbl.pojo.Student" autowire="byName">
byType:需要保证所有bean的class唯一,并且这个bean需要和自动注入的属性的类型一致!
<bean id="address" class="com.qsdbl.pojo.Address" scope="prototype">
<property name="address" value="北京市"/>
</bean>
<bean id="student" class="com.qsdbl.pojo.Student" autowire="byType">
</bean>
<bean class="com.qsdbl.pojo.Address" scope="prototype">
<property name="address" value="北京市"/>
</bean>
小结
- byName:需要保证所有bean的id唯一,并且这个bean需要和自动注入的属性的set方法的值一致!
- byType:需要保证所有bean的class唯一,并且这个bean需要和自动注入的属性的类型一致!
注解实现自动装配
此处了解即可,示例见下边的使用注解开发。
jdk1.5支持注解,Spring2.5就支持注解了!
The introduction of annotation-based configuration raised the question of whether this approach is “better” than XML. 基于注解的配置的引入引发了这样一个问题:这种方法是否比XML“更好”。
步骤
使用注解须知:
1.导入约束:context约束
xmlns:context="http://www.springframework.org/schema/context"
"http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd"
2.配置注解的支持:<context:annotation-config/>
<?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/>
</beans>
@Autowired
直接在属性上使用即可!也可以在set方法上使用! 使用Autowired我们可以不用编写Set方法了,前提是你这个自动装配的属性在IOC(Spring)容器中存在(属性的类型,在Spring配置文件中已经进行了bean配置),Autowired默认使用byType方式进行自动装配。
测试代码:
public class Person {
@Autowired(required = false)
private Cat cat;
@Autowired
private Dog dog;
private String name;
}
public @interface Autowired {
boolean required() default true;
}
@Nullable
拓展:
public People(@Nullable String name){
this.name = name;
}
@Qualifier
如果@Autowired自动装配(默认使用byType方式)的环境比较复杂,自动装配无法通过一个注解【@AutoWired】完成的时候,我们可以使用@Qualifier(value = "xxx") 去配合使用(byName方式),指定一个唯一的bean对象(bean的id、name)注入!
public class Person {
@Autowired
private Cat cat;
@Autowired
@Qualifier(value = "dog222")
private Dog dog;
private String name;
}
@Resource
public class Person {
@Resource(name="cat2")
private Cat cat;
@Resource
private Dog dog;
private String name;
}
小结
@Resource和@Autowired的区别:
- 都是用来自动装配的,都可以放在属性字段上;
- @Autowired通过byType的方式实现,而且必须要求这个对象存在!【常用】
- @Resource默认通过byName的方式实现,如果找不到名字,则通过byType实现!如果两个都找不到的情况下,就报错!
- 执行顺序不同:
- @Autowired默认通过byType的方式实现(要使用byName方式,需要使用@Qualifier注解)。
- @Resource默认通过byName的方式实现,找不到再通过类型查找。
使用注解开发
在spring4之后,要使用注解开发,必须要保证aop的包导入了。使用注解需要导入context约束,增加注解的支持!
bean配置/注册
bean配置。Spring配置文件中添加context:component-scan 标签,扫描包。在要注册bean的类上使用@Component 注解。即可将bean注册到Spring容器中,通过使用类名(小写开头)即可获取bean。
Spring配置文件(springboot项目不需要此配置):
<context:annotation-config/>
<context:component-scan base-package="com.qsdbl.pojo"/>
在包中使用@Component注解”配置“bean,代替原来的<bean id="address" class="com.qsdbl.pojo.Address"></bean>
类、方法上使用注解,将bean注册到spring容器中:
package com.qsdbl.pojo;
import lombok.Data;
import org.springframework.stereotype.Component;
@Component
@Data
public class Address {
private String address;
}
@Bean(name = "myrsa")
private RSA init() {
if (publicKey.length() <= 10 || privateKey.length() <= 10) {
KeyPair pair = SecureUtil.generateKeyPair("RSA");
publicKey = Base64.encode(pair.getPublic().getEncoded());
privateKey = Base64.encode(pair.getPrivate().getEncoded());
}
return new RSA(privateKey, publicKey);
}
拓展:衍生的注解
@Component有几个衍生注解,我们在web开发中,会按照mvc三层架构分层!
- dao【@Repository】
- service【@Service】
- controller【@Controller】
这四个注解功能都是一样的,都是代表将某个类注册到Spring中,装配Bean!
属性注入
属性如何注入。使用@Value 注解,添加在属性上或者setter方法上。
package com.qsdbl.pojo;
import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component
@Data
public class Address {
@Value("北京市")
private String address;
}
代替原来的XML配置(Spring配置文件):
<bean id="address" class="com.qsdbl.pojo.Address">
<property name="address" value="北京市"/>
</bean>
自动装配
从spring容器中获取我们注册进去的bean来使用。
使用注解实现自动装配,使用@Autowired 注解,添加在属性上或者setter方法上。自动装配可能不是很好理解,可以复习一下前边关于Bean的笔记。
作用域
使用注解配置bean的作用域,使用@Scope 注解,添加在类上。(更多作用域介绍,点这里查看)
package com.qsdbl.pojo;
import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
@Component
@Data
@Scope("prototype")
public class Address {
@Value("北京市")
private String address;
}
小结
xml与注解:
- xml更加万能,适用于任何场合!维护简单方便。
- 注解,不是自己的类使用不了,维护相对复杂!
xml与注解最佳实践:
- xml用来管理bean;
- 注解只负责完成属性的注入;
- 我们在使用的过程中,只需要注意一个问题:必须让注解生效,就需要开启注解的支持。导入context约束和添加如下配置:
<!--指定要扫描的包,这个包下的注解会生效-->
<context:component-scan base-package="com.qsdbl"/>
<!--配置注解的支持-->
<context:annotation-config/>
JavaConfig
使用java的方式配置Spring。我们现在要完全不使用Spring的xml配置了,全权交给java来做!(使用java类代替xml配置文件)
javaConfig是Spring的一个子项目,在Spring4之后,它成为了一个核心功能。
实体类:
@Component
public class User {
@Value("qsdbl")
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
'}';
}
}
配置类 - 配置文件:
import com.kuang.pojo.User;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
@Configuration
@ComponentScan("com.kuang.pojo")
@Import(KuangConfig2.class )
public class KuangConfig {
@Bean("user")
public User getUser(){
return new User();
}
}
测试类:从Spring容器中获取bean
public class MyTest {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(KuangConfig.class);
User user = (User) context.getBean("user");
System.out.println(user.getName());
}
}
这种纯java的配置方式,在SpringBoot中随处可见!(这里了解即可,到SpringBoot中再详细学习)
AOP
代理模式
为什么要学习代理模式?因为这就是SpringAOP的底层!【SpringAOP 和 SpringMVC 面试必问】
代理模式的分类:
静态代理
- 抽象角色 : 一般使用接口或者抽象类来实现。
- 真实角色 : 被代理的角色。
- 代理角色 : 代理真实角色 ; 代理真实角色后 , 一般会做一些附属的操作 。
- 客户 : 使用代理角色来进行一些操作 。
例子一
- 接口
public interface Rent {
public void rent();
}
- 真实角色
public class Host implements Rent{
@Override
public void rent() {
System.out.println("房东要出租房子!");
}
}
- 代理角色
public class Proxy implements Rent{
private Host host;
public Proxy() {
}
public Proxy(Host host) {
this.host = host;
}
@Override
public void rent() {
seeHouse();
host.rent();
fare();
}
public void seeHouse(){
System.out.println("带客户看房子!");
}
public void fare(){
System.out.println("收中介费!");
}
}
- 客户端访问代理角色
public class Client {
public static void main(String[] args) {
Host host=new Host();
Proxy proxy=new Proxy(host);
proxy.rent();
}
}
例子二
例子二:传送门
例子三
- 抽象角色(接口)
public interface UserService {
public void add();
public void delete();
public void update();
public void query();
}
- 真实角色
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("查询一个用户!");
}
}
- 代理角色
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("[Debug]执行了"+msg+"方法");
}
}
- 客户端访问代理角色
public class Client {
public static void main(String[] args) {
UserServiceImpl userService = new UserServiceImpl();
UserServiceProxy proxy = new UserServiceProxy();
proxy.setUserService(userService);
proxy.add();
}
}
小结
- 【开闭原则】Software entities like classes,modules and functions should be open for extension but closed for modifications.(一个软件实体如类、模块和函数应该对扩展开放,对修改关闭。)
- 我们在不改变原有代码的情况下,实现了对原有功能的增强,这是AOP中最核心的思想!
静态代理的好处:
- 可以使得我们的真实角色更加纯粹 。不再去关注一些公共的事情。
- 公共的业务由代理来完成。. 实现了业务的分工。
- 公共业务发生扩展时变得更加集中和方便 。
缺点 :
- 一个真实角色就会产生一个代理角色, 工作量变大了 . 开发效率会降低 .
我们想要静态代理的好处,又不想要静态代理的缺点,所以 , 就有了动态代理 !
动态代理
- 动态代理的角色和静态代理的一样 。
- 动态代理的代理类是动态生成的 ,静态代理的代理类是我们提前写好的。
- 动态代理分为两类 : 一类是基于接口的动态代理 , 一类是基于类的动态代理。
-
- 基于接口的动态代理:JDK动态代理
- 基于类的动态代理:cglib
- 现在用的比较多的是 javasist 来生成动态代理【Java字节码】
- 我们这里使用JDK的原生代码来实现,其余的道理都是一样的!
用到的类
需要了解两个类:InvocationHandler 和 Proxy
【InvocationHandler:调用处理程序】
InvocationHandler接口是proxy代理实例的调用处理程序实现的一个接口,每一个proxy代理实例都有一个关联的调用处理程序;在代理实例调用方法时,方法调用被编码分派到调用处理程序的invoke方法。
Object invoke(Object proxy, 方法 method, Object[] args);
【Proxy : 代理】
public Object getProxy(){
return Proxy.newProxyInstance(this.getClass().getClassLoader(),
target.getClass().getInterfaces(),this);
}
public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)
关于InvocationHandler与Proxy详细介绍,见这篇博客。
例子
抽象角色:同上边例子三。
真实角色:同上边例子三。
代理角色:区别于上边例子三中的静态代理角色。
类MyProxyInvocation,用于生成动态代理实例:
package com.qsdbl.pojo;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Date;
public class MyProxyInvocation 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);
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String targetInterface = (target.getClass().getInterfaces())[0].getSimpleName();
if (targetInterface.equals("UserService")){
mylog(targetInterface,method.getName());
}
Object result = method.invoke(target, args);
return result;
}
public void mylog(String targetInterface,String methodName){
System.out.println("【日志记录】:执行了接口\""+targetInterface+"\"的\""+methodName+"\"方法! -- "+new Date());
}
}
客户端访问代理角色:
@Test
public void test3(){
UserService userService = new UserServiceImpl();
MyProxyInvocation proxyInvocation = new MyProxyInvocation();
proxyInvocation.setTarget(userService);
UserService proxy = (UserService)proxyInvocation.getProxy();
proxy.add();
}
【日志记录】:执行了接口"UserService"的"add"方法! -- Mon Feb 01 21:41:34 CST 2021
添加一个用户!
Process finished with exit code 0
动态代理的好处:
- 可以使得我们的真实角色更加纯粹。不再去关注一些公共的事情。
- 公共的业务由代理来完成。 实现了业务的分工 。
- 公共业务发生扩展时变得更加集中和方便 。
- 一个动态代理 , 一般代理某一类业务。
- 一个动态代理可以代理多个类,代理的是接口!
AOP
什么是AOP
AOP(Aspect Oriented Programming)意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生泛型,利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的频率。
AOP在Spring中的作用
提供声明式事务;允许用户自定义切面
- 横切关注点:跨越应用程序多个模块的方法或功能。即使与我们业务逻辑无关的,但是我们需要关注的部分,就是横切关注点,如日志、安全、缓存、事务等等……
- 切面(ASPECT):横切关注点被模块化的特殊对象,即是一个类。
- 通知(Advice):切面必须要完成的工作,即是类中的一个方法。
- 目标(Target):被通知对象。
- 代理(Proxy):向目标对象应用通知之后创建的对象。
- 切入点(PointCut):切面通知执行的“地点”的定义。
- 连接点(jointPoint):与切入点匹配的执行点。
在SpringAOP中,通过Advice定义横切逻辑,Spring中支持5种类型的Advice:
即 Aop 在 不改变原有代码的情况下 , 去增加新的功能。
使用Spring实现AOP
【重点】使用AOP织入,需要导入一个依赖包!
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.4</version>
</dependency>
Spring中有三种方式实现AOP:
1、Spring API
方式一:通过Spring API实现【主要是Spring API接口的实现】
抽象角色(业务接口):同上边例子三。
真实角色(实现类):同上边例子三。
要新增的功能:BeforeLog类与AfterLog类的日志功能,实现不同的Advice接口定义横切逻辑(见上边的Advice表),注意看后边的测试结果。(MethodBeforeAdvice – 方法前,AfterReturningAdvice – 方法后)
package com.qsdbl.log;
import org.springframework.aop.MethodBeforeAdvice;
import java.lang.reflect.Method;
public class BeforeLog implements MethodBeforeAdvice {
public void before(Method method, Object[] objects, Object o) throws Throwable {
System.out.println( o.getClass().getName() + "的" + method.getName() + "方法被执行了");
}
}
package com.qsdbl.log;
import org.springframework.aop.AfterReturningAdvice;
import java.lang.reflect.Method;
public class AfterLog implements AfterReturningAdvice {
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
System.out.println("执行了" + target.getClass().getName()
+"的"+method.getName()+"方法,"
+"返回值:"+returnValue);
}
}
Spring核心配置文件:配置aop,定义(新增的功能,即两个日志类)切入点。切入点是UserServiceImpl实现类的所有方法(该类的所有方法,都会被增加两个Log类的日志功能)。
<?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 https://www.springframework.org/schema/aop/spring-aop.xsd">
<bean id="userServiceImpl" class="com.qsdbl.service.UserServiceImpl"></bean>
<bean id="beforelog" class="com.qsdbl.log.BeforeLog"/>
<bean id="afterlog" class="com.qsdbl.log.AfterLog"/>
<aop:config>
<aop:pointcut id="mypointcut01" expression="execution(* com.qsdbl.service.UserServiceImpl.*(..))"/>
<aop:advisor advice-ref="beforelog" pointcut-ref="mypointcut01"/>
<aop:advisor advice-ref="afterlog" pointcut-ref="mypointcut01"/>
</aop:config>
</beans>
代理角色:区别于上边例子三中的静态代理角色,这里由Spring动态生成。(上边的aop配置 - 重点)
测试类:
@Test
public void testAOP(){
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
UserService us = (UserService)context.getBean("userServiceImpl");
us.add();
}
正常的使用bean即可,但是通过观察运行结果可以看到两个Log类的日志功能已经被增加了进去。并且根据Log类所实现的Advice接口实现了不同的横切逻辑。(关于execution表达式,见下边的笔记)
运行结果:
com.qsdbl.service.UserServiceImpl的add方法被执行了
添加一个用户!
执行了com.qsdbl.service.UserServiceImpl的add方法,返回值:null
Process finished with exit code 0
Spring的Aop就是将公共的业务 (日志 , 安全等) 和领域业务结合起来 , 当执行领域业务时 , 将会把公共业务加进来 . 实现公共业务的重复利用 . 领域业务更纯粹 , 程序猿专注领域业务 , 其本质还是动态代理。
2、定义切面
方式二:自定义类实现【主要是切面定义】
切面,即要增加的功能(业务),该自定义类没有实现Spring API接口(Advice相关接口)。
抽象角色、真实角色同上。
Spring配置文件:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<bean id="userService" class="com.tian.service.UserServiceImpl"></bean>
<bean id="log" class="com.tian.log.Log"/>
<bean id="afterlog" class="com.tian.log.AfterLog"/>
<bean id="diy" class="com.tian.log.DiyPointCut"/>
<aop:config>
<aop:aspect ref="diy">
<aop:pointcut id="point" expression="execution(* com.tian.service.UserServiceImpl.*(..))"/>
<aop:before method="before" pointcut-ref="point"/>
<aop:after method="after" pointcut-ref="point"/>
</aop:aspect>
</aop:config>
</beans>
【自定义类-切面】
public class DiyPointCut {
public static void before(){
System.out.println("===方法执行前===");
}
public static void after(){
System.out.println("===方法执行后===");
}
}
3、注解
方式三:使用注解实现AOP
抽象角色、真实角色同上。
Spring配置文件:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<context:annotation-config/>
<context:component-scan base-package="com.qsdbl"/>
<aop:aspectj-autoproxy/>
</beans>
类AnnotationPointCut,作为一个切面(@Aspect),其中的方法使用注解定义横切逻辑(切入点。指定要执行的位置)。
package com.qsdbl.diy;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
@Component
@Aspect
public class AnnotationPointCut {
@Before("execution(* com.qsdbl.service.UserServiceImpl.*(..))")
public void before(){
System.out.println("====before,方法执行前====");
}
@After("execution(* com.qsdbl.service.UserServiceImpl.*(..))")
public void after(){
System.out.println("====after,方法执行后====");
}
@Around("execution(* com.qsdbl.service.UserServiceImpl.*(..))")
public void around(ProceedingJoinPoint jp) throws Throwable {
System.out.println("around,环绕前");
Object proceed = jp.proceed();
System.out.println("around,环绕后");
}
}
测试:
@Test
public void testAOP2(){
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
UserService userService = (UserService)context.getBean("userServiceImpl");
userService.add();
}
运行结果:
around,环绕前
====before,方法执行前====
添加一个用户!
around,环绕后
====after,方法执行后====
Process finished with exit code 0
execution表达式
execution表达式:【execution(* com.sample.service.impl…*.*(…))】
符号 | 含义 |
---|
execution() | 表达式的主体; | 第一个”*“符号 | 表示返回值的类型任意; | com.sample.service.impl | AOP所切的服务的包名,即,我们的业务部分 | 包名后面的”…“ | 表示当前包及子包 | 第二个”*“ | 表示类名,*即所有类。此处可以自定义, | .*(…) | 表示任何方法名,括号表示参数,两个点表示任何参数类型 |
springboot中实现
笔记摘自CSDN1、CSDN2、CSDN3
在springboot中实现aop,一般是采用注解方式。
导入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
定义切面
Spring采用@Aspect注解对POJO进行标注,该注解表明该类不仅仅是一个POJO,还是一个切面。切面是切点和通知的结合,那么定义一个切面就需要编写切点和通知。在代码中,只需要添加@Aspect注解即可。
package com.qsdbl.nazox_demo.configuration;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.context.annotation.Configuration;
@Configuration
@Aspect
public class MyAdviceConfig {
@Before(value = "execution(* com.qsdbl.nazox_demo.testAuto.service..*.*(..))")
public void before(JoinPoint joinPoint) {
System.out.println("before开始执行查询.......");
System.out.println("正在执行的目标类是: " + joinPoint.getTarget());
System.out.println("正在执行的目标方法是: " + joinPoint.getSignature().getName());
}
@After(value = "execution(* com.qsdbl.nazox_demo.testAuto.service..*.*(..))")
public void after(JoinPoint joinPoint) {
System.out.println("after查询结束.......");
}
@Around(value = "execution(* com.qsdbl.nazox_demo.testAuto.service..*.*(..))")
public Object aroud(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
System.out.println("aroud环绕通知开始.......");
System.out.println("执行的目标类 = " + proceedingJoinPoint.getTarget());
System.out.println("执行的目标方法 = " + proceedingJoinPoint.getSignature().getName());
Object proceed = proceedingJoinPoint.proceed();
System.out.println("aroud环绕通知结束.......");
return proceed;
}
}
上边spring中的注解实现aop,在此处替换该类也能正常运行,因为都是基于“aspectjweaver”实现。
其他
整合MyBatis
MyBatis笔记,点这里复习。
环境搭建
mybatis-spring,官方文档: https://mybatis.org/spring/zh/
步骤:
- 导入相关jar包
- junit
- mybatis相关
- mysql数据库
- spring相关
- aop织入
- mybatis-spring【整合包】
- 编写配置文件
- 测试
依赖:
<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.2</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.2.0.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.4</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.1.9.RELEASE</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.0.4</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.10</version>
</dependency>
</dependencies>
<build>
<resources>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>true</filtering>
</resource>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>true</filtering>
</resource>
</resources>
</build>
回顾Mybatis
-
编写实体类(对应一张数据表)。关于Lombok的使用,可以点这里复习一下。 package com.qsdbl.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private int id;
private String name;
private String passwd;
private String type = "common";
private String time;
private String timeup;
}
-
编写核心配置文件mybatis-config.xml 。(resources文件夹下) <?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.qsdbl.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/mypos?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai"/>
<property name="username" value="root"/>
<property name="password" value="123Cyj--"/>
</dataSource>
</environment>
</environments>
<mappers>
<package name="com.qsdbl.mapper"/>
</mappers>
</configuration>
-
编写接口(mapper层接口) package com.qsdbl.mapper;
import com.qsdbl.pojo.User;
import java.util.List;
public interface UserMapper {
List<User> selectUsers();
}
-
编写mapper配置文件(尽量与接口同名。放在resources文件夹下,新建与接口包名同级的目录,resources/com/qsdbl/mapper/UserMapper.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.qsdbl.mapper.UserMapper">
<select id="selectUsers" resultType="user">
select * from user
</select>
</mapper>
-
测试 @Test
public void test() throws IOException {
String resources = "mybatis-config.xml";
InputStream in = Resources.getResourceAsStream(resources);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(in);
SqlSession sqlSession = sqlSessionFactory.openSession(true);
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<User> users = mapper.selectUsers();
for (User user : users) {
System.out.println(user);
}
}
User(id=1, name=马克思, passwd=123456, type=common, time=2021-01-27 10:09:21.0, timeup=2021-01-27 13:56:32.0)
User(id=2, name=小黑, passwd=123456, type=common, time=2021-01-27 10:09:21.0, timeup=2021-01-27 10:09:21.0)
User(id=3, name=pete, passwd=123456, type=common, time=2021-01-27 10:09:21.0, timeup=2021-01-27 10:09:21.0)
User(id=4, name=花花, passwd=123456, type=common, time=2021-01-27 10:09:21.0, timeup=2021-01-27 10:09:21.0)
User(id=5, name=小鸭子, passwd=123456, type=common, time=2021-01-27 10:09:21.0, timeup=2021-01-27 10:09:21.0)
Process finished with exit code 0
Mybatis-Spring(整合)
方式一:SqlSessionTemplate
方式二:SqlSessionDaoSupport(使用起来更加方便)
SqlSessionTemplate
整合mybatis配置
编写一个(Spring)配置文件(名字自定义),用于放Spring整合mybatis的一些配置,单独放在一个文件中不需要修改。sqlSessionFactory中,可完成大部分的mybatis配置。(在Spring配置文件中,也可以跟在MyBatis配置文件中一样通过引入数据库文件的方式配置datasource)
配置文件spring-dao.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="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mypos?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai"/>
<property name="username" value="root"/>
<property name="password" value="123Cyj--"/>
</bean>
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="configLocation" value="classpath:mybatis-config.xml"/>
<property name="mapperLocations" value="classpath:com/qsdbl/mapper/*.xml"/>
</bean>
<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
<constructor-arg index="0" ref="sqlSessionFactory"/>
</bean>
</beans>
Spring主配置文件
编写Spring的“主配置文件”applicationContext.xml。用于放Spring的配置,和导入MyBatis的配置。(下边的“注册bean”,可以通过扫描Dao接口包动态注册到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
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 导入整合mybatis的一些配置-->
<import resource="spring-dao.xml"/>
<!-- 注册bean(DAO接口的实现类)到Spring的IOC容器-->
<bean id="userMapperImpl" class="com.qsdbl.mapper.UserMapperImpl">
<property name="sqlSession" ref="sqlSession"/>
</bean>
</beans>
MyBatis配置文件
MyBatis的配置文件还是前边“回顾MyBatis“中的mybatis-config.xml文件,不过进行了精简,将在Spring中配置了的内容去掉(前边的spring-dao.xml文件)。精简后的mybatis-config.xml文件(可保留可去掉,配置都写在“整合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.qsdbl.pojo"/>
</typeAliases>
</configuration>
整合mybatis接口
- 普通mybatis项目中,mapper接口+mapper配置文件(配置sql语句)就可以了。但是要整合到Spring中,需要注册bean到Spring的IOC容器中,所以我们需要给mapper接口编写对应的实现类。
- 实现类中使用前边“整合mybatis配置“中注册到Spring容器中的SqlSessionTemplate(可看作sqlSession),完成对数据库的操作。
- 在Spring中注册实现类时使用set注入的方式注入SqlSessionTemplate实例。(见前边的Spring主配置文件中的配置)
mapper接口的实现类:
package com.qsdbl.mapper;
import com.qsdbl.pojo.User;
import org.mybatis.spring.SqlSessionTemplate;
import java.util.List;
public class UserMapperImpl implements UserMapper {
private SqlSessionTemplate sqlSession;
public void setSqlSession(SqlSessionTemplate sqlSession) {
this.sqlSession = sqlSession;
}
public List<User> selectUsers() {
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
return mapper.selectUsers();
}
}
测试
测试:直接使用userMapperImpl实现类实例即可(注意对比与原mybatis项目中的使用方式)
@Test
public void test2() {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
UserMapper mapper = context.getBean("userMapperImpl", UserMapper.class);
for (User user : mapper.selectUsers()) {
System.out.println(user);
}
}
@Test
public void test() throws IOException {
String resources = "mybatis-config.xml";
InputStream in = Resources.getResourceAsStream(resources);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(in);
SqlSession sqlSession = sqlSessionFactory.openSession(true);
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<User> users = mapper.selectUsers();
for (User user : users) {
System.out.println(user);
}
}
User(id=1, name=马克思, passwd=123456, type=common, time=2021-01-27 10:09:21.0, timeup=2021-01-27 13:56:32.0)
User(id=2, name=小黑, passwd=123456, type=common, time=2021-01-27 10:09:21.0, timeup=2021-01-27 10:09:21.0)
User(id=3, name=pete, passwd=123456, type=common, time=2021-01-27 10:09:21.0, timeup=2021-01-27 10:09:21.0)
User(id=4, name=花花, passwd=123456, type=common, time=2021-01-27 10:09:21.0, timeup=2021-01-27 10:09:21.0)
User(id=5, name=小鸭子, passwd=123456, type=common, time=2021-01-27 10:09:21.0, timeup=2021-01-27 10:09:21.0)
Process finished with exit code 0
SqlSessionDaoSupport
整合mybatis配置
与上边基本相同,不同点在于SqlSessionTemplate标签的配置,可以保留也可以去掉。
<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
<constructor-arg index="0" ref="sqlSessionFactory"/>
</bean>
Spring主配置文件
与上边基本相同,不同点在于注册mapper接口到Spring容器时注入的依赖。
<bean id="userMapperImpl" class="com.qsdbl.mapper.UserMapperImpl">
<property name="sqlSession" ref="sqlSession"/>
</bean>
<bean id="userMapperImpl02" class="com.qsdbl.mapper.UserMapperImpl02">
<property name="sqlSessionFactory" ref="sqlSessionFactory"/>
</bean>
MyBatis配置文件
与上边相同。
整合mybatis接口
与上边基本相同,不同点如下:
- 不使用SqlSessionTemplate而是SqlSessionDaoSupport
- 使用方式发生改变,由原来的定义SqlSessionTemplate变量通过set注入改为了继承SqlSessionDaoSupport通过set注入sqlSessionTemplate或sqlSessionFactory
- 若注入sqlSessionFactory,则前边“整合mybatis配置”中的“SqlSessionTemplate标签”可去掉
- 若注入SqlSessionTemplate,则可保留
- 若两个属性都注入,那么
SqlSessionFactory 将被忽略 - 下边的案例中(增加实现类UserMapperImpl02),注入sqlSessionFactory。注意看“Spring主配置文件”中的变化。
package com.qsdbl.mapper;
import com.qsdbl.pojo.User;
import org.apache.ibatis.session.SqlSession;
import org.mybatis.spring.support.SqlSessionDaoSupport;
import java.util.List;
public class UserMapperImpl02 extends SqlSessionDaoSupport implements UserMapper {
public List<User> selectUsers() {
return getSqlSession().getMapper(UserMapper.class).selectUsers();
}
}
测试
与上边相同。获取的bean不一样而已。
@Test
public void test2() {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
UserMapper mapper = context.getBean("userMapperImpl02", UserMapper.class);
for (User user : mapper.selectUsers()) {
System.out.println(user);
}
}
声明式事务
回顾事务
- 把一组业务当成一个业务来做;要么都成功,要么都失败!
- 事务在项目开发中,十分的重要,涉及到数据的一致性和完整性问题,不能马虎!
- 确保完整性和一致性!
事务ACID原则:
- 原子性
- 一致性
- 隔离性
- 持久性
- 事务一旦提交,无论系统发生什么问题,结果都不会再被影响,被持久的写到存储器中!
问题
在mapper接口中,新增加方法addUser、deleteUser
package com.qsdbl.mapper;
import com.qsdbl.pojo.User;
import org.apache.ibatis.annotations.Param;
import java.util.List;
public interface UserMapper {
List<User> selectUsers();
int addUser(User user);
int deleteUser(@Param("id") int id);
}
对应的mapper配置文件中增加SQL语句。delete写成deletes,人为制造错误。
<insert id="addUser" parameterType="user">
insert into user (name,passwd) values (#{name},#{passwd});
</insert>
<delete id="deleteUser" parameterType="int">
deletes from user where id = #{id}
</delete>
实现类,添加具体方法实现(注意看selectUsers方法的改变)
package com.qsdbl.mapper;
import com.qsdbl.pojo.User;
import org.apache.ibatis.session.SqlSession;
import org.mybatis.spring.support.SqlSessionDaoSupport;
import java.util.List;
public class UserMapperImpl02 extends SqlSessionDaoSupport implements UserMapper {
public List<User> selectUsers() {
SqlSession sqlSession = getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user = new User("小花", "13243");
mapper.addUser(user);
mapper.deleteUser(3);
return mapper.selectUsers();
}
public int addUser(User user) {
return getSqlSession().getMapper(UserMapper.class).addUser(user);
}
public int deleteUser(int id) {
return getSqlSession().getMapper(UserMapper.class).deleteUser(id);
}
}
public User(String name, String passwd) {
this.name = name;
this.passwd = passwd;
}
测试:新增用户-小花,并且删除id为3的用户。
@Test
public void test2() {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
UserMapper mapper = context.getBean("userMapperImpl02", UserMapper.class);
for (User user : mapper.selectUsers()) {
System.out.println(user);
}
}
运行结果
运行出现异常:
org.springframework.jdbc.BadSqlGrammarException:
...
运行前的数据:
1,马克思,123456,common,2021-01-27 10:09:21,2021-01-27 13:56:32
2,小黑,123456,common,2021-01-27 10:09:21,2021-01-27 10:09:21
3,pete,123456,common,2021-01-27 10:09:21,2021-01-27 10:09:21
4,花花,123456,common,2021-01-27 10:09:21,2021-01-27 10:09:21
5,小鸭子,123456,common,2021-01-27 10:09:21,2021-01-27 10:09:21
运行后的数据:
1,马克思,123456,common,2021-01-27 10:09:21,2021-01-27 13:56:32
2,小黑,123456,common,2021-01-27 10:09:21,2021-01-27 10:09:21
3,pete,123456,common,2021-01-27 10:09:21,2021-01-27 10:09:21
4,花花,123456,common,2021-01-27 10:09:21,2021-01-27 10:09:21
5,小鸭子,123456,common,2021-01-27 10:09:21,2021-01-27 10:09:21
6,小花,13243,common,2021-02-03 12:24:00,2021-02-03 12:24:00
程序运行过程出现了异常,没有正常执行“新增用户小花,并且删除id为3的用户”的操作,仅仅是新增了用户小花。我们希望程序“要么都成功,要么都失败!”,而不是完成一部分。为此我们需要引入事务管理。
引入事务管理
注意:在SpringBoot中开启事务,只需要在方法上添加注解@Transactional 即可。
事务管理是对于一系列数据库操作进行管理,一个事务包含一个或多个SQL语句,是逻辑管理的工作单元(原子单元)。Spring中的事务管理分为声明式事务、编程式事务。
- 声明式事务:AOP的应用
- 编程式事务:需要在代码中,进行事务的管理
需要的AOP依赖:
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.4</version>
</dependency>
使用方法
使用Spring中的声明式事务(不改变原有代码的基础上,增加事务),将方法com.qsdbl.mapper.UserMapperImpl02.selectUsers 中的多个(数据库)操作合为一个事务,解决上边的问题。(Spring API方式实现的AOP)
在Spring配置文件中,添加如下配置:我添加在了spring-dao.xml文件中。
<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="*"/>
</tx:attributes>
</tx:advice>
<aop:config>
<aop:pointcut id="txPointCut" expression="execution(* com.qsdbl.mapper.UserMapperImpl02.selectUsers())"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="txPointCut"/>
</aop:config>
要在哪个方法中启用Spring的事务管理,配置切入点即可。
Spring中事务传播行为(默认required):
传播行为 | 解释 |
---|
REQUIRED | 业务方法需要在一个事务中运行,如果方法运行时,已处在一个事务中,那么就加入该事务,否则自己创建一个新的事务.这是spring默认的传播行为. | SUPPORTS | 如果业务方法在某个事务范围内被调用,则方法成为该事务的一部分,如果业务方法在事务范围外被调用,则方法在没有事务的环境下执行. | MANDATORY | 只能在一个已存在事务中执行,业务方法不能发起自己的事务,如果业务方法在没有事务的环境下调用,就抛异常. | REQUIRES_NEW | 业务方法总是会为自己发起一个新的事务,如果方法已运行在一个事务中,则原有事务被挂起,新的事务被创建,直到方法结束,新事务才结束,原先的事务才会恢复执行. | NOT_SUPPORTED | 声明方法需要事务,如果方法没有关联到一个事务,容器不会为它开启事务.如果方法在一个事务中被调用,该事务会被挂起,在方法调用结束后,原先的事务便会恢复执行. | NEVER | 声明方法绝对不能在事务范围内执行,如果方法在某个事务范围内执行,容器就抛异常.只有没关联到事务,才正常执行. | NESTED | 如果一个活动的事务存在,则运行在一个嵌套的事务中.如果没有活动的事务,则按REQUIRED属性执行.它使用了一个单独的事务, 这个事务拥有多个可以回滚的保证点.内部事务回滚不会对外部事务造成影响, 它只对DataSourceTransactionManager 事务管理器起效. |
测试
测试:测试部分与前边的一样,只是在Spring配置文件中增加了事务配置。
@Test
public void test2() {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
UserMapper mapper = context.getBean("userMapperImpl02", UserMapper.class);
for (User user : mapper.selectUsers()) {
System.out.println(user);
}
}
运行结果:
依然是运行时出现异常,但是数据库中并没有新增用户“小花”。实现了“要么都成功,要么都失败!”
org.springframework.jdbc.BadSqlGrammarException:
...
将mapper配置文件中的deletes改回正确的delete,再次运行:成功执行了“新增用户-小花,并且删除id为3的用户”
User(id=1, name=马克思, passwd=123456, type=common, time=2021-01-27 10:09:21.0, timeup=2021-01-27 13:56:32.0)
User(id=2, name=小黑, passwd=123456, type=common, time=2021-01-27 10:09:21.0, timeup=2021-01-27 10:09:21.0)
User(id=4, name=花花, passwd=123456, type=common, time=2021-01-27 10:09:21.0, timeup=2021-01-27 10:09:21.0)
User(id=5, name=小鸭子, passwd=123456, type=common, time=2021-01-27 10:09:21.0, timeup=2021-01-27 10:09:21.0)
User(id=12, name=小花, passwd=13243, type=common, time=2021-02-03 13:53:04.0, timeup=2021-02-03 13:53:04.0)
Process finished with exit code 0
小结
为什么需要事务?
- 如果不配置事务,可能存在数据提交不一致的情况;
- 如果我们不在Spring中配置声明式事务,就需要在代码中手动配置事务!
- 事务在项目开发中,十分的重要,涉及到数据的一致性和完整性问题,不能马虎!
注解-总结
自动装配:
IOC:
AOP:
@Aspect - 切面
学习自B站遇见狂神说 原文链接:Spring5汇总
|