Spring笔记
一、Spring了解
Spring理念:使现有的技术更加容易使用,整合了现有的技术框架,简化企业级应用开发;
优点:
Spring是一个免费开源的框架
Spring是一个轻量级、非入侵式(在原先代码上加上Spring框架后没有影响)的框架
支持事务的处理,对框架整合的支持
缺点:
配置繁琐
核心:
控制反转(IOC),面向切面编程(AOP)
总结:
Spring就是一个轻量级的控制反转(IOC)和面向切面编程的框架;
二、Spring核心1:控制反转(IOC)理论
概念理解:
-
百度百科:
控制反转(Inversion of Control,缩写为IoC),是面向对象编程中的一种设计原则,可以用来减低计算机代码之间的耦合度。其中最常见的方式叫做依赖注入(Dependency Injection,简称DI),还有一种方式叫“依赖查找”(Dependency Lookup)。通过控制反转,对象在被创建的时候,由一个调控系统内所有对象的外界实体将其所依赖的对象的引用传递给它。也可以说,依赖被注入到对象中。
-
搜集资料:
控制反转是一种设计思想,就是把对象的创建,赋值,管理工作都交给代码之外的容器(可以简单理解为Spring的容器)实现, 也就是对象的创建是由其它外部资源(这里外部资源就是Spring)完成
控制: 创建对象,对象的属性赋值,对象之间的关系管理;
反转: 把原来的开发人员管理,创建对象的权限转移给代码之外的容器(Spring)实现; 由容器代替开发人员管理对象、创建对象、给属性赋值。 -
自我总结:
控制反转(IOC):将自己创建对象的控制权力转移到第三方,第三方通过配置文件或注解获取对象;
Spring中实现控制反转的是IOC容器;
IOC的主要实现方式是依赖注入(DI);
IOC在这里简单来说就是:对象由Spring创建、管理、装配,不再需要自己new对象了;
其实之前学的servlet中也涉及到了IOC原理,当把一个Servlet实现类配置到web.xml文件中后,创建Servlet对象的工作就不再需要我们自己了,而是交给了WEB服务器(Tomcat)来创建管理,包括Filter、Listener都是由Tomcat容器统一控制实例化、初始化、执行方法、销毁等等;
Spring 容器是一个超级大工厂,负责创建、管理所有的 Java 对象,这些 Java 对象被称 为 Bean;Spring 容器管理着容器中 Bean 之间的依赖关系,Spring 使用“依赖注入”的方式 来管理 Bean 之间的依赖关系,使用 IOC实现对象之间的解耦和;
使用IOC目的:
开闭原则(对拓展开放,对修改关闭);实现代码的解耦合;
通过IOC来创建对象:
1,首先需要一个XML配置文件(一般命名为:applicationContext.xml
)
该文件基本结构:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="..." class="...">
<!-- collaborators and configuration for this bean go here -->
</bean>
<bean id="..." class="...">
<!-- collaborators and configuration for this bean go here -->
</bean>
<!-- more bean definitions go here -->
</beans>
可以看到 <beans> 标签下有很多 <bean> 标签,这些bean标签就代表位于Spring容器中的一个个对象;
自己的理解:Spring容器就像一个加工咖啡豆的盒子(beans),我可以把需要的对象(bean)交给它来加工配置;这样盒子中(beans)就有了各种各样的咖啡豆(bean),当我需要哪个咖啡豆(bean)时直接从这个咖啡豆盒子(beans)中取出来就可以了;
就是一个标签对应一个对象
2,假设定义了一个Hello类:
public class Hello {
private String name;
Hello() {
System.out.println("Hello的无参构造方法");
}
// 有参构造
Hello(String str) {
this.name = str;
System.out.println("Hello的有参构造方法");
}
// set和get方法
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
// sayHello方法
public void sayHello() {
System.out.println("Hello " + name);
}
}
注意:这里定义的类如果想要通过Spring容器创建对象,那么必须写上其中所有属性的set方法(get方法最好也写上,反正idea自动生成不废劲),只有这样Spring容器才可以通过配置文件的信息设置相应对象的属性值;
3,在applicationContext.xml文件中配置该类创建对象
使用默认构造器:
<bean id="hello" class="com.yang.service.Hello">
<property name="name" value="Spring"/>
</bean>
这就已经代表一个Hello对象了,相当于java代码:
Hello hello = new Hello();
hello.setName("Spring");
通过和实际java代码对比来解释一下标签中的元素:
- id:所创建的对象的名称,就是变量名,是自定义的,程序通过 id 属性访问 bean,bean 与 bean 间的依赖关系也是通过 id 属性关联的。
- class:创建的对象类型(使用全限定名称),这里只能是类,不能是接口。
其中的 <property> 标签作用:
-
property:给该对象中的对应属性赋值
- name:赋值的属性名称
- value:所赋的值
当有多个属性时可以写多个 <property> 标签,一个标签对应一个属性;
如果是使用有参构造:
<bean id="hello" class="com.yang.service.Hello">
<constructor-arg name="str" value="Java"/>
<property name="name" value="Spring"/>
</bean>
那么就等同于java代码:
Hello hello = new Hello("Java");
hello.setName("Spring");
可以看出来:
<constructor-arg> 标签的作用:
-
constructor-arg:当使用对象的有参构造时给对应的形参赋值
- name:对应有参构造方法中形参的名称
- vaule:对应形参所赋的值
当构造方法有多个参数时,可以写多个 <constructor-arg> 标签,一个标签对应一个形参
4,测试代码
通过第三步其实就已经创建了我需要的Hello对象存放到了Spring容器中了,接下来测试时就需要从容器中获取对应的对象了;
@Test
public void springTest01() {
// 定位到配置所需对象的xml文件
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
// 从Spring容器中获取配置好的Hello对象,getBean("hello")中的hello就是配置的bean的id值
Hello hello = (Hello) context.getBean("hello");
hello.sayHello(); // 调用方法测试
}
这里额外说一点:Hello hello = (Hello) context.getBean("hello");
这段代码等效于下面这段代码:
Hello hello = context.getBean("hello", Hello.class);
这只是getBean()方法的重载版本,至于用哪一种就要根据实际情况了;
通过这个例子来总结一下:
- Hello对象由Spring来自动创建,不需要我们管,我们只需要把想要创建的对象配置到xml文件就可以了;
- 创建好的Hello对象的初始化、赋值等一系列操作也是由Spring完成的,我们也只需要配置好就可以了;
三、依赖注入
依赖:bean对象的创建依赖于容器
注入:bean对象中的所有属性值由容器来注入
构造器注入(掌握):
这里还是引用上面的Hello的例子:
其实就是使用 <constructor-arg> 标签,这里就不过多展示了;
Set方式注入(掌握):
1,假设这里自己定义了两个类:Student类和Adress类
Adress类:
public class Address {
private String address;
// 下面还要有一系列属性对应的set和get方法,这里就不写了
}
Student类:
public class Student {
private String name;
private Address address;
private String[] books;
private List<String> hobbies;
private Set<String> games;
private Map<String,String> card;
private String wife;
private Properties info;
// 下面还要有一系列属性对应的set和get方法,这里就不写了
}
2,现在需要给对应的applicationContext.xml配置文件配置信息;
注意Student对象中各个类型属性的配置方式;
<?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">
<!--引用类型对象(一个Address对象)-->
<bean name="addressBean" class="com.yang.pojo.Address">
<property name="address" value="北京"/>
</bean>
<!--一个Student对象-->
<bean name="student" class="com.yang.pojo.Student">
<!--第一种:普通值注入,value-->
<property name="name" value="张三"/>
<!--第二种:引用类型注入,ref-->
<property name="address" ref="addressBean"/>
<!--第三种:数组类型注入-->
<property name="books">
<array>
<value>三国演义</value>
<value>水浒传</value>
<value>西游记</value>
<value>红楼梦</value>
</array>
</property>
<!--第四种:list集合注入-->
<property name="hobbies">
<list>
<value>篮球</value>
<value>足球</value>
<value>羽毛球</value>
<value>乒乓球</value>
</list>
</property>
<!--第五种:set集合注入-->
<property name="games">
<set>
<value>LOL</value>
<value>CF</value>
<value>CS</value>
<value>BOB</value>
</set>
</property>
<!--第六种:map集合注入-->
<property name="card">
<map>
<entry key="身份证" value="666666666"/>
<entry key="学生证" value="000000000"/>
<entry key="银行卡" value="111111111"/>
</map>
</property>
<!--第七种:空字符串注入-->
<property name="wife">
<null/>
</property>
<!--第八种:properties类型注入-->
<property name="info">
<props>
<prop key="driver">Driver</prop>
<prop key="url">mysql</prop>
<prop key="root">root</prop>
<prop key="password">020216</prop>
</props>
</property>
</bean>
</beans>
一共就这八种形式,没事就看看混个眼熟,其中有几个新标签,多看看知道怎么用就行,idea都有语法提示,一定要有个印象;
拓展方式注入(了解):
这个了解就行了,知道有这种方式,实际不会用;(可以跳过)
这里就不过多解释了,直接借用官方文档了:
XML Shortcut with the p-namespace
p-namespace就是给属性赋值,p就代表: <property> 标签的效果;
使用前需要在xml文件上面加上这句:
xmlns:p="http://www.springframework.org/schema/p"
看看官方的配置:
<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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean name="classic" class="com.example.ExampleBean">
<property name="email" value="someone@somewhere.com"/>
</bean>
<bean name="p-namespace" class="com.example.ExampleBean" p:email="someone@somewhere.com"/>
</beans>
p:email="someone@somewhere.com"
这句就等同于
<property name="email" value="someone@somewhere.com"/>
并且还要注意p:email="someone@somewhere.com"
这句代码是写在 <bean> 内的;
XML Shortcut with the c-namespace
c-namespace就是给有参构造器中的属性赋值了,等同 <constructor-arg> 标签的效果;
使用前需要在xml文件上面加上这句:
xmlns:c="http://www.springframework.org/schema/c"
官方配置:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
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="beanTwo" class="x.y.ThingTwo"/>
<bean id="beanThree" class="x.y.ThingThree"/>
<!-- traditional declaration with optional argument names -->
<bean id="beanOne" class="x.y.ThingOne">
<constructor-arg name="thingTwo" ref="beanTwo"/>
<constructor-arg name="thingThree" ref="beanThree"/>
<constructor-arg name="email" value="something@somewhere.com"/>
</bean>
<!-- c-namespace declaration with argument names -->
<bean id="beanOne" class="x.y.ThingOne" c:thingTwo-ref="beanTwo"
c:thingThree-ref="beanThree" c:email="something@somewhere.com"/>
</beans>
可以看到构造器中的形参名为:thingTwo、thingThree、email,
<constructor-arg name="thingTwo" ref="beanTwo"/>
<constructor-arg name="thingThree" ref="beanThree"/>
<constructor-arg name="email" value="something@somewhere.com"/>
<!--上面三句等同于下面的:-->
c:thingTwo-ref="beanTwo" c:thingThree-ref="beanThree" c:email="something@somewhere.com"
<!--当然这句代码也是放在bean标签中的-->
四、Spring的配置
Spring有一些配置,比如起别名等;这里只说一个比较重要的:
import导入其他xml配置文件
因为实际开发过程中是团队协作开发,每个人会负责一个模块,而每个人都有一个xml配置文件,不同的类注册在不同的xml文件中;最后将所有的模块合到一块,同样这些配置文件也需要合到一起,这就需要import把所有配置文件合成一个;
合并后的配置文件名:applicationContext.xml
合并六个配置文件到该文件:
applicationContext.xml
<!--在`applicationContext.xml`文件中导入这几个配置文件-->
<import resource="bean01.xml"/>
<import resource="bean02.xml"/>
<import resource="bean03.xml"/>
<import resource="bean04.xml"/>
<import resource="bean05.xml"/>
<import resource="bean06.xml"/>
五、Bean 的作用域(scopes)
官方给的有六个,这里就先说两个,分别是:singleton和prototype
Singleton Scope
这个就是单例模式,一个对象在Spring中创建后会使用多次;也是Spring创建对象默认的一种模式;
由图可以清楚的看到,一个对象实例只会被创建一次并在多个地方使用;
声明作用域只需要在bean标签中加上scope元素即可;
官方示例:
<bean id="accountService" class="com.something.DefaultAccountService"/>
<!-- the following is equivalent, though redundant (singleton scope is the default) -->
<bean id="accountService" class="com.something.DefaultAccountService" scope="singleton"/>
因为单例模式是默认的,所以上下两个bean声明的都是一样的;
可以做个小测试:(还是上面使用的Hello样例)
这里我在bean中多加了scope元素,不加也是一样默认单例;
<bean id="hello" class="com.yang.service.Hello" scope="singleton">
<constructor-arg name="str" value="Java"/>
<property name="name" value="Spring"/>
</bean>
java测试代码:
@Test
public void springTest01() {
ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
Hello hello01 = (Hello) context.getBean("hello");
Hello hello02 = (Hello) context.getBean("hello");
System.out.println(hello01 == hello02);
}
因为是单例模式,所以hello01和hello02肯定是从Spring中取出同一个对象;
结果是true
Prototype Scope
这个是原型模式,每次从Spring中获取时,都会创建一个新的对象,如图:
官方示例:
<bean id="accountService" class="com.something.DefaultAccountService" scope="prototype"/>
就是scope中写成prototype就行了;
下面还是hello小示例:
bean声明:
<bean id="hello" class="com.yang.service.Hello" scope="prototype">
<constructor-arg name="str" value="Java"/>
<property name="name" value="Spring"/>
</bean>
java测试代码(和上面一样):
@Test
public void springTest01() {
ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
Hello hello01 = (Hello) context.getBean("hello");
Hello hello02 = (Hello) context.getBean("hello");
System.out.println(hello01 == hello02);
}
结果和单例模式相反,输出值为false,这就说明虽然hello01和hello02都是通过一个id获取的对象,但是每次获取都创建了一个新的对象,就是原型模式的特点;
小总结
在实际中主要还是使用默认的情况,就是单例模式,因为使用原型模式每次获取都创建一个对象也很占内存;所以没有什么特殊情况就不要管作用域了;
六、引用类型自动注入
对于引用类型属性的注入,可不在配置文件中显示的注入;而是通过为标签设置 autowire 属性值,为引用类型属性进行隐式自动注入(默认是不自动注入引用类型属 性);
根据自动注入判断标准的不同,可以分为两种:
- byName:根据名称自动注入
- byType: 根据类型自动注入
这个就简单说说,实际情况下不建议使用,可读性不高;
这里也就不过多阐述了,直接用网上找的资料了;
ByName自动注入
byName:会自动在容器上下文中查找和自己对象set方法形参值对应的bean id
ByType自动注入
byType:会自动在容器上下文中查找,和自己对象属性类型相同的bean
小总结
这两个方法各有优缺点,但是实际情况下还是不建议使用,了解一下,看到别人项目如果有这个能认出来就行;
- ByName:需要保证所有bean的id唯一,并且这个bean需要和自动注入的属性的set方法的形参值一致
- ByType:需要保证所有bean的class唯一(如果有多个重复类型bean肯定不行),并且这个bean需要和自动注入的属性的类型一致
七、基于注解的 DI
Spring2.5开始支持注解,进一步简化了配置;;
需要在applicationContext.xml
基本结构上加上些东西才能支持注解
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
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实现byType 自动注入
在引用属性上使用注解@Autowired,该注解默认使用按类型自动装配(ByType) Bean 的方式;
使用该注解完成属性注入时,类中不再需要些set方法,但是还是建议写上;
@Autowired可以写在属性上,也可以写在方法上;
示例:
Student类和Address类(set和get还是建议写上,这里就不写那么多了):
public class Address {
private String address;
}
public class Student {
@Autowired
private Address addressName;
}
applicationContext.xml文件:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
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/>
<bean id="student" class="com.yang.pojo.Student"/>
<bean id="address" class="com.yang.pojo.Address">
<property name="address" value="郑州"/>
</bean>
</beans>
可以从配置文件中发现,Student类的实例student并没有给addressName属性赋值,但是在Student类中使用了@Autowired,就代表默认情况下addressName会被xml文件中Address类型的实例赋值,也就是xml文件中的address对象会自动赋给addressName;
当然@Autowired是按类型(ByType)自动装配的,上面也说过按类型自动装配的缺点:当有多个重复类型的bean就无法判断了,这时可以考率按id自动装配;
@Autowired 与@Qualifier实现byName 自动注入
如果按ByName的方式只需要联合使用注解@Autowired 与@Qualifier就可以了;
@Qualifier的value属性可以用来制定匹配的bean的id;
示例:
还是上面的例子,这里就只把不同之处写出来了;
public class Student {
@Autowired
@Qualifier(value = "address")
private Address addressName;
}
这样就可以指定唯一的bean对象注入了;
这里再提最后一点:
@Autowired还有一个属性 required,默认值为 true,表示当匹配失败后,会终止程序运行;若将其值设置为 false,则匹配失败,将被忽略,未匹配的属性值为 null;
有个印象就行;
JDK 注解@Resource 自动注入
Spring提供了对 jdk中@Resource注解的支持;@Resource 注解既可以按名称匹配Bean, 也可以按类型匹配 Bean;默认是按名称(ByName)注入;使用该注解,要求 JDK 必须是 6 及以上版本; @Resource 可在属性上,也可在 set 方法上;
还是刚才的示例,修改一下:
public class Student {
@Resource
private Address addressName;
}
如果这样写就是默认情况,那么就会从Spring容器中找到底有没有id为addressName的bean,从上面的applicationContext.xml文件中可以看到并没有id为addressName的bean,只有id为address的bean,所以这样就会报错;但是如果有对应id的bean,就可以自动注入成功;
那么没有对应id的bean怎么办?@Resource 也是有一个name属性可以指定id的
可以这样写:
public class Student {
@Resource(name = "address")
private Address addressName;
}
这样就可以注入成功了;
@Resource注解无法引入的问题
我在使用@Resource
时遇到了无法引入的问题,查了一下资料发现JDK11
以后移除了javax
扩展,所以不能使用@Resource
注解;
解决方法也很简单,直接加个对应的maven依赖就好了;
<dependency>
<groupId>javax.annotation</groupId>
<artifactId>javax.annotation-api</artifactId>
<version>1.3.2</version>
</dependency>
注解@Value对简单数据类型注入
对于String、Integer等简单数据类型,可以使用注解@Value的value属性进行注入,这样更简单;
但是对于像list、Map等复杂的数据类型,最好还是使用property注入;
示例:
import org.springframework.beans.factory.annotation.Value;
public class User {
// 使用@Value进行注入
@Value(value = "张三")
private String name;
}
上面就等同于applicationContext.xml中的:
<property name="name" value="张三"/>
注解@Component定义bean
既然属性可以通过注解自动注入,那么有没有注解可以实现定义bean的功能呢?
注解@Component就可以实现定义bean的功能,这样再通过注解@Value的配合,在一些简单场合下就不再需要向配置文件写<bean>标签了;
@Component注解表明一个类会作为组件类,并告知Spring要为这个类创建bean;
首先使用前需要先在applicationContext.xml中添加配置
<context:component-scan base-package="扫描的包路径"/>
这个就是在applicationContext.xml中配置了一个扫描器,它可以扫描对应包下的所有注解;
假设我有一个com.yang包,扫描它以下的注解:
applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<!--扫描com.yang包下的所有注解-->
<context:component-scan base-package="com.yang"/>
<context:annotation-config/>
</beans>
配置文件写好后,如果使用@Component注解,这样你就不用再在这个配置文件中配置了
看看@Component注解在代码中如何使用:
User.java
package com.yang.pojo;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component // 默认id值为user
public class User {
@Value(value = "张三")
private String name;
@Value(value = "18")
private Integer age;
}
标注上@Component注解后就意味着User就是一个组件类,所以Spring会自动创建一个User对象,这个对象的 id 默认是类名首字母小写:user
(如果不想使用默认id,@Component有一个value属性可以设置id值 @Component(value = "自定义id值")
)
所以上面的注解达到的效果 等同于 在applicationContext.xml
文件中这样配置:
<bean id="user" class="com.yang.pojo.User">
<property name="name" value="张三"/>
<property name="age" value="18"/>
</bean>
下面可以用测试代码测试一下:
@Test
public void annotationTest01() {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
User user = context.getBean("user", User.class);
System.out.println(user);
}
Spring同样提供了三个创建对象的注解:
-
@Repository 用于对 DAO 实现类进行注解 (数据访问层)
-
@Service 用于对 Service 实现类进行注解 (业务逻辑层)
-
@Controller 用于对 Controller 实现类进行注解(界面层)
这三个和@Component有什么区别呢?
其实在功能上没有一点区别,用法作用一模一样,这一点一定要记住;
那为啥还要整这么多花样?通过名字就可以猜到,它们只是分别用在mvc三层不同的架构,目的就是为了通过不同的名字区分开来;
注解和XML配置的对比
注解优点是: 方便 、 直观 、高效(代码少,没有配置文件的书写那么复杂);
弊端也显而易见:以硬编码的方式写入到 Java 代码中,修改是需要重新编译代码的;
XML 方式优点是: 配置和代码是分离的;在 xml 中做修改,无需编译代码,只需重启服务器即可将新的配置加载。;
xml 的缺点是:编写麻烦,效率低,大型项目过于复杂;
拓展:使用纯Java程序进行配置
JavaConfig本来是Spring的一个子项目,但是在Spring4之后成为了核心功能;
这是一种使用Spring的新方式,完全使用注解来配置所有内容;
但是实际情况下使用Spring开发并不会完全抛弃XML配置,一般是混合着使用,那么为什么在这提这一点呢?
因为在Springboot中这种方式是随处可见的,提前了解为了以后的Springboot打基础;
注解@Configuration
如果被@Configuration注解标注的类就代表这是一个配置类,那么这个类就等同于applicationContext.xml
配置文件;
被@Configuration注解标注的类也会Spring容器托管,注册到容器中,因为它本来就是一个@Component,可以看一下它的源码:
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package org.springframework.context.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.core.annotation.AliasFor;
import org.springframework.stereotype.Component;
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Configuration {
@AliasFor(
annotation = Component.class
)
String value() default "";
boolean proxyBeanMethods() default true;
}
示例演示:
下面示范一下如何使用;
1,在com.yang.pojo包下创建一个User类:
package com.yang.pojo;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.util.Objects;
@Component
public class User {
@Value(value = "张三")
private String name;
@Value(value = "10")
private Integer age;
// 下面重写的toString等方法就不放出来了
}
2,创建一个配置类MyConfig
package com.yang.config;
import com.yang.pojo.User;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration // 声明该类为配置类
public class MyConfig {
// 注册一个bean,就相当于一个bean标签
// 方法的名字,就相当于bean标签中id属性
// 方法的返回值,就相当于bean标签中的class属性
@Bean // 给User类配置bean,id默认是方法名:getUser
public User getUser() {
return new User();
}
}
一旦这个类添加了@Configuration注解那么就是配置类,XML配置文件有的它都可以通过注解实现;
@Bean是用来注册一个bean的,因为目前了解不多,也只知道这种写法,可以模仿尝试一下;
@Bean有一个value属性,可以指定id值,这样就不用使用默认方法名了,value用法和其他注解都一样;
3,测试:
@Test
public void annotationTest01() {
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MyConfig.class);
User user01 = applicationContext.getBean("getUser", User.class);
System.out.println(user01 + " 哈希值:" + user01.hashCode());
User user02 = applicationContext.getBean("user", User.class);
System.out.println(user02 + " 哈希值:" + user02.hashCode());
}
这里和使用配置文件有一点不同:
配置文件在生成ApplicationContext
对象时使用的是:ClassPathXmlApplicationContext("配置文件名")
而使用配置类生成ApplicationContext
对象时使用的是:AnnotationConfigApplicationContext(配置类的Class对象)
获取bean对象还是一样的,都是通过getBean方法获取的;
在这里这种方法只是作为了解,在Springboot中才会应用到;
补充(关于@Bean和@Component):
@Bean和@Component都可以在spring容器中注入bean,那么有什么区别呢?
就比如上面的例子,可以发现通过@Bean和@Component两种方法注册了bean,所以在spring容器中实际存在两个User对象,下面测试中我也使用输出哈希值的方法证明了这一点(可以尝试一下,哈希值不同,前提是没有重写hashCode方法);
下面说一下区别:
@Component
用于自动检测和使用类路径扫描自动配置bean,注释类和bean之间存在隐式的一对一映射(即每个类一个bean);这种方法对需要进行逻辑处理的控制非常有限,因为它纯粹是声明性的;
@Bean
用于显式声明单个bean,而不是让Spring像上面那样自动执行它,它将bean的声明与类定义分离,并允许您精确地创建和配置bean;
如果想将第三方的类变成组件,又没有没有源代码,也就没办法使用@Component
进行自动配置,这种时候使用@Bean
就比较合适了(也可以通过xml方式来定义)
另外@Bean注解的方法返回值是对象,可以在方法中为对象设置属性;
八、AOP面向切面编程
基本概念
百度百科:
在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
简单描述: AOP就是可以在不改变源代码的基础上,给核心代码部分增加新的功能;
底层原理:
AOP底层原理就是动态规划(这里不多介绍,但是如果想理解深入的话这个是必须了解的)
术语
连接点(join point)
类中哪些方法可以增强,那么这些方法就是连接点;
切入点(point cut)
类中实际增强的方法,称为切入点;
切入点表达式
切入点表达式在下面写代码时会用到,现在了解一下,看到代码知道是什么什么意思就行;
作用:表示对哪个类的中的哪个方法进行增强;
语法结构:
execution(权限修饰符 返回类型 类全路径.方法名称(参数列表))
假设在com.yang.dao下有个User的类,想要增强类中的 public void add()方法
execution(public void com.yang.dao.User.add())
但是这样只能指定一个类中的对应方法,如果想灵活使用,要学会通配符'*'的用法
其实 访问修饰符 可以省略,并且返回值使用通配符'*'
execution(* com.yang.dao.User.*(..))
参数列表中写的'..'表示有无参数均可,有参数时可以是任意类型
这句表达式意思就是:增强com.yang.dao.User类中的所有方法
切入点表达式要会灵活运用,不清楚的话可以自己摸索尝试一下
通知(advice)
实际用来增强的逻辑部分(功能,一般是一个增强方法),称为通知;
通知有多种类型:
- 前置通知
- 后置通知
- 环绕通知
- 异常通知
- 返回通知(最终通知)
切面(aspect)
共有功能的实现(日志切面、事务切面等),就是一个Java类,在配置中将其指定为切面;切面本质上是通知和切入点的结合;
通知规定了增加什么功能,什么时候增加;
切入点规定了在哪里增加功能;
引入(introduction)
引入允许我们向现有的类添加新方法或属性;
织入(weaving)
把切面应用到目标对象并创建新的代理对象的过程,切面在指定的连接点被织入到目标对象中;在目标对象的生命周期里有多个点可以进行织入:编译期,类加载期,运行期;
这几个概念中着重了解切入点、通知、切面,在代码操作中只有属性它们的概念才能知道每一步是在做什么;
AOP操作演示
不管用什么方法,首先得需要把AOP对应的jar包导入进去,对应的maven依赖:
<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>版本号</version>
</dependency>
在applicationContext.xml
文件中需要加上:
xmlns:aop="http://www.springframework.org/schema/aop"
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd
创建一个user包,创建一个UserService接口和对应实现类
UserService.java
package com.yang.user;
public interface UserService {
// 增
void add();
// 删
void delete();
// 改
void update();
// 查
void select();
}
UserServiceImp.java
package com.yang.user;
public class UserServiceImp 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("查询了一条数据");
}
}
下面的三种放法都是基于这两个类进行的操作;
第一种方法
增强类实现java内置接口;
增强部分(我感觉这就是通知):
在目标方法前增强部分,实现了MethodBeforeAdvice
接口,这个接口的意思就是前置通知,实现before方法,该方法就是增强的内容,这个类就是一个切面
package com.yang.log;
import org.springframework.aop.MethodBeforeAdvice;
import java.lang.reflect.Method;
public class BeforeLog implements MethodBeforeAdvice {
// target:目标对象(被代理对象)
// args:目标方法的参数
// method:目标方法
@Override
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.println(target.getClass().getName() + " 的对象调用了:" + method.getName() + "方法");
}
}
在目标方法后增强部分,实现了AfterReturningAdvice
接口
package com.yang.log;
import org.springframework.aop.AfterReturningAdvice;
import java.lang.reflect.Method;
public class AfterLog implements AfterReturningAdvice {
//returnValue: 返回值
@Override
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
System.out.println("执行了" + method.getName() + "方法,返回值为:" + returnValue);
}
}
有了增强方法,下面就是织入过程了,这一步在配置文件中完成:
<!--目标类,其中的方法就是连接点-->
<bean id="userServiceImp" class="com.yang.user.UserServiceImp"/>
<!--增强类-->
<bean id="before" class="com.yang.log.BeforeLog"/>
<bean id="after" class="com.yang.log.AfterLog"/>
<!--第一种方法:实现内部接口进行切入-->
<aop:config>
<!--设置切入点(需要增强的方法)-->
<aop:pointcut id="pointcut" expression="execution(* com.yang.user.UserServiceImp.*(..))"/>
<!--增强作用(通知)配置到对应的切入点(方法)上-->
<!--环绕增强-->
<aop:advisor advice-ref="before" pointcut-ref="pointcut"/>
<aop:advisor advice-ref="after" pointcut-ref="pointcut"/>
</aop:config>
这里设置的切入点就是UserServiceImp.java
中的所有方法,所以对应的切入点表达式就是:execution(* com.yang.user.UserServiceImp.*(..))
※ 注意一点:目标类一定要配置对应的bean,因为你要以它内部的一些方法为切入点,只有配置到spring中后,在<aop:pointcut />
标签设置切入点的时候才能找到对应的方法;
测试代码:
@Test
public void springTest01() {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
// 目前猜测这里创建的userService是个代理类,所以要声明成接口类型
UserService userService = context.getBean("userServiceImp", UserService.class);
userService.select(); // 代理类执行目标类方法
}
因为下一个方法也是使用这个测试方法,所以下面就不再写测试代码了;
输出结果:
com.yang.user.UserServiceImp 的对象调用了:select方法
查询了一条数据
执行了select方法,返回值为:null
这种方法优点就是可以通过实现内置类从而重写对应方法,而对应方法中的参数可以调用,这样就可以获取到对应的信息;
但是正因为需要实现内置方法,这也成为了它的缺点;
第二种方法
第二种方法和第一种的主要区别就在于:第一种方法增强部分是实现的接口,但是第二种方法使用的是自己定义的类中的方法作为切面;
自定义切面:
package com.yang.secondmethod;
public class MyDefinition {
// 该方法作为前置通知
public void before() {
System.out.println("=====before=====");
}
// 该方法作为后置通知
public void after() {
System.out.println("=====after=====");
}
}
下面就是在xml文件中配置:
<!--第二种方法:使用自定义类进行切入-->
<bean id="definition" class="com.yang.secondmethod.MyDefinition"/>
<aop:config>
<!--自定义切面-->
<aop:aspect ref="definition">
<!--切入点-->
<aop:pointcut id="pointcut" expression="execution(* com.yang.user.UserServiceImp.*(..))"/>
<!--增强作用(通知)配置到对应的切入点(方法)上-->
<aop:before method="before" pointcut-ref="pointcut"/>
<aop:after method="after" pointcut-ref="pointcut"/>
</aop:aspect>
</aop:config>
测试输出:
=====before=====
查询了一条数据
=====after=====
这种方法最大的好处就是你可以通过自定义切面实现自己想要实现的功能,拓展性更好,并且如果需要多种增强方式就不像第一种那样一种增强方法实现一个接口那样复杂了;
但是同样的第二种就是失去了第一种获取内部信息的渠道,所以还是要根据实际情况使用;一般情况下第二种用的比较多;
第三种方法
该方法就是使用注解来实现了;
当然可以注解和配置文件混合使用,但是在这里我就使用纯注解来做示范了;
配置类:
package com.yang.annotation;
import com.yang.service.UserServiceImp;
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.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@Configuration // 声明为配置类
@EnableAspectJAutoProxy // 开启AOP注解支持
@Aspect // 使用注解将该类声明为一个切面
public class MyAnnotation {
// 注册bean
@Bean(value = "userService")
public UserServiceImp userServiceImp() {
return new UserServiceImp();
}
@Before("execution(* com.yang.service.UserServiceImp.add(..))")
public void before() {
System.out.println("==========before==========");
}
@After("execution(* com.yang.service.UserServiceImp.delete(..))")
public void after() {
System.out.println("==========after==========");
}
@Around("execution(* com.yang.service.UserServiceImp.update(..))")
public void around(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("------环绕通知前------");
joinPoint.proceed();
System.out.println("------环绕通知后------");
}
}
将该类声明为了配置类,并作为切面使用,内部定义了各种增强方法和通知;
注意区分我在切入点表达式种对不同方法的不同通知类型;
然后直接测试就行:
@Test
public void springTest01() {
ApplicationContext context = new AnnotationConfigApplicationContext(MyAnnotation.class);
UserService userService = context.getBean("userService", UserService.class);
userService.add();
System.out.println("\n");
userService.delete();
System.out.println("\n");
userService.update();
}
输出结果:
==========before==========
增加了一条数据
删除了一条数据
==========after==========
------环绕通知前------
更新了一条数据
------环绕通知后------
现掌握这三种方法就行了,尤其是第二种,第一次接触很容易糊涂,各个概念理不清楚,所以首先一定要清楚那几个术语,再把它们带入到代码中去理解;
AOP还是需要在实际项目中能体会到它的神奇,这些小案例只会让人觉得很麻烦,所以学完ssm后一定要做一个小项目;
Mybatis-Spring整合
学习Spring框架肯定不是单独使用的,spring框架主要用于业务逻辑层,mybatis框架用于数据访问层,在一个项目中它们两者之间比然会有交集,那么如何将两者整合在一起使用呢?下面我就来演示一下整合步骤;
准备工作(mybatis回顾)
首先还是要先导入对应的jar包,这里使用maven加入相应的依赖即可:
因为spring对应依赖也比较多,所以我展示一些目前pom.xml文件的所有内容,需添加的会标注;
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.yang</groupId>
<artifactId>spring-learning</artifactId>
<packaging>pom</packaging>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>16</maven.compiler.source>
<maven.compiler.target>16</maven.compiler.target>
</properties>
<dependencies>
<!--下面是使用spring需要导入的依赖-->
<!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.3.15</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.7</version>
</dependency>
<!--下面是使用mybatis额外添加的依赖-->
<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis-spring -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.0.6</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-jdbc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.3.14</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.7</version>
</dependency>
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.27</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.22</version>
</dependency>
</dependencies>
<build>
<resources>
<resource>
<!--所在的目录-->
<directory>src/main/java</directory>
<!--包括目录下的.properties,.xml 文件都会扫描到-->
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>false</filtering>
</resource>
</resources>
</build>
</project>
导入对应的jar包后,还需要一个数据库表来进行操作,还是使用之前学习mybatis的表:
接下来还是mybatis的几个固定步骤:
创建一个student类,属性名对应表的字段名,并提供对应的set、get等方法;
student.java
package com.yang.pojo;
import java.util.Objects;
public class Student {
private int id;
private String name;
private String email;
private int age;
public Student() {
}
public Student(int id, String name, String email, int age) {
this.id = id;
this.name = name;
this.email = email;
this.age = age;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"id=" + id +
", name='" + name + '\'' +
", email='" + email + '\'' +
", age=" + age +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Student)) return false;
Student student = (Student) o;
return id == student.id && age == student.age && Objects.equals(name, student.name) && Objects.equals(email, student.email);
}
@Override
public int hashCode() {
return Objects.hash(id, name, email, age);
}
}
这里就以一个简单的查询为例,同样需要一个StudentDao接口和一个对应的mapper文件:StudentDao.xml
StudentDao.java
package com.yang.dao;
import com.yang.pojo.Student;
import java.util.List;
public interface StudentDao {
// 查询方法
List<Student> selectStudent();
}
StudentDao.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.yang.dao.StudentDao">
<select id="selectStudent" resultType="com.yang.pojo.Student">
select * from student
</select>
</mapper>
接下来就是最后一步了,配置主配置文件config.xml
<?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>
<!-- <properties resource="jdbc.properties"/>-->
<!--settings:控制mybatis全局行为-->
<settings>
<!--设置mybatis输出日志-->
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
<!-- <environments default="test">-->
<!-- <environment id="test">-->
<!-- <transactionManager type="JDBC"/>-->
<!-- <dataSource type="POOLED">-->
<!-- <!–数据库的驱动类名–>-->
<!-- <property name="driver" value="${jdbc.driver}"/>-->
<!-- <!–连接数据库的url字符串–>-->
<!-- <property name="url" value="${jdbc.url}"/>-->
<!-- <!–访问数据库的用户名–>-->
<!-- <property name="username" value="${jdbc.username}"/>-->
<!-- <!–密码–>-->
<!-- <property name="password" value="${jdbc.password}"/>-->
<!-- </dataSource>-->
<!-- </environment>-->
<!-- </environments>-->
<!-- sql mapper(sql映射文件)的位置-->
<!-- <mappers>-->
<!-- <mapper resource="com/yang/dao/StudentDao.xml"/>-->
<!-- </mappers>-->
</configuration>
对应的jdbc.properties文件为:
jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/ssm_learning?serverTimezone=UTC
jdbc.username=root
jdbc.password=020216
可能会发现在我的config配置文件中好多部分注释掉了,为什么呢?因为接下来我将使用spring来配置这部分内容,这样就可以对比来看我提取了什么东西;
注:这里提前说一下:其实这个config配置文件完全没有存在必要的,因为spring完全可以配置完所有东西;但是为了体现用到了mybatis,可以加上该文件,只在该文件中声明别名和设置相关内容;
实现代码
第一点不同就是需要对StudentDao接口有一个额外的实现,虽然看起来麻烦,但是最后测试实际使用却可以简单很多;
StudentDaoImp.java
package com.yang.dao;
import com.yang.pojo.Student;
import org.mybatis.spring.SqlSessionTemplate;
import java.util.List;
public class StudentDaoImp implements StudentDao{
private SqlSessionTemplate session;
// 这个session将会在spring配置文件中配置,所以提供一个set方法
public void setSession(SqlSessionTemplate session) {
this.session = session;
}
@Override
public List<Student> selectStudent() {
// 直接调用session
StudentDao studentDao = session.getMapper(StudentDao.class);
return studentDao.selectStudent();
}
}
配置spring对应的配置文件:
spring-mybatis.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
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/context
https://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<context:annotation-config/>
<!--读取jdbc.properties配置文件的支持-->
<context:property-placeholder location="classpath:jdbc.properties"/>
<!--DataSource:使用Spring数据源替换Mybatis的配置-->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
<!--SqlSessionFactory-->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<!--载入数据源dataSource-->
<property name="dataSource" ref="dataSource"/>
<!--绑定mybatis的config主配置文件-->
<property name="configLocation" value="classpath:mybatis.xml"/>
<!--绑定mybatis的mapper配置文件-->
<property name="mapperLocations" value="classpath:com/yang/dao/*.xml"/>
</bean>
<!--如果使用SqlSessionDaoSupport那么就不需要这一步了-->
<!--SqlSessionTemplate-->
<bean id="sqlSessionTemplate" class="org.mybatis.spring.SqlSessionTemplate">
<!--使用构造器注入sqlSessionFactory,因为SqlSessionTemplate没有set方法-->
<constructor-arg ref="sqlSessionFactory"/>
</bean>
<!--StudentDaoImp的bean的配置-->
<bean id="studentDaoImp" class="com.yang.dao.StudentDaoImp">
<!--直接调用上面配置的SqlSessionTemplate就可以了-->
<property name="session" ref="sqlSessionTemplate"/>
</bean>
</beans>
我来解释一下该配置文件:
-
第一部分:获取数据源DataSource,这就代替了mybatis中config主配置文件中的<environments>标签,这个使用的class是java内置的
DriverManagerDataSource
类,直接调用就行,前提导入相应的jar包;属性就是那几个,没什么好介绍的; -
第二部分:获取SqlSessionFactory对象,在执行mybatis项目中,我们就需要SqlSessionFactory对象用来创建SqlSession对象,一般我们会把它写在一个工具类中,在这里只需要配置相应的bean即可;
它的属性第一个是数据源,直接使用上面声明过的即可;第二个是和mybatis的主配置文件绑定,在mybatis中是通过java代码绑定的:
String config = "mybatis.xml"; InputStream in = Resources.getResourceAsStream(config); SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
所以在这里一定要对比联想学习,不然会越来越乱;
第三个参数是mapper文件,在mybatis中mapper文件就是配置到主配置文件中的;这里建议直接绑定一个包下的mapper文件,因为属性标签不能有重复,如果有多个mapper文件就不能像在mybatis中的主配置文件中使用<mapper>标签来配置多个;
-
第三部分:获取SqlSessionTemplate对象,这个是什么呢?其实就是和SqlSession对象一样,只不过在spring中变成了这样,所以还是按照SqlSession对象使用即可;
这里用到了构造器注入,可以看一下SqlSessionTemplate的源码,会发现其中没有set方法,所以只能使用构造器注入;
-
第四部分:配置StudentDaoImp的bean,这个就没什么说的了,配置到spring中最后测试时直接获取对应的bean对象调用方法即可;
测试代码:
@Test
public void selectStudent02() {
ApplicationContext context = new ClassPathXmlApplicationContext("spring-mybatis.xml");
// 创建代理类
StudentDao studentDao = context.getBean("studentDaoImp", StudentDao.class);
List<Student> students = studentDao.selectStudent();
students.forEach(stu-> System.out.println(stu));
}
这就是一个完整的整合步骤,看起来很麻烦(确实有点…),但是在实际开发项目中已经节省了很多步骤了;
补充:不写dao接口实现类方式
上面说可以不用写dao接口实现类也可以注册dao接口的bean,其实很简单,只需要在mybatis的主配置文件config.xml中加入以下代码即可;
<!--
新操作:配置dao接口扫描包,动态实现Dao接口自动注入到Spring中
之前都是:
<bean id="sqlSessionTemplate" class="org.mybatis.spring.SqlSessionTemplate">
<constructor-arg ref="sqlSessionFactory"/>
</bean>
对应接口的实现类
<bean id="BooksDaoImp" class="com.yang.dao.BooksDaoImp">
<property name="session" ref="sqlSessionTemplate"/>
</bean>
需要在dao接口额外写实现类,因为接口无法注入到Spring中,只能通过额外的实现类注入,
但是下面的操作就可以实现不用写实现类就可以自动注入
-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<!--这里为什么要用value呢?因为该属性name注入的参数是一个String类型,
可以点开sqlSessionFactoryBeanName看看源码的参数-->
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
<!--要扫描的dao包,会把dao包下的所有mapper.xml文件扫描到,所以上面就不用写了-->
<property name="basePackage" value="com.yang.dao"/>
</bean>
这样不仅不需要写实现类,而且也不用关心dao接口的注入问题,所有事情都由MapperScannerConfigurer
完成了;
Spring中事务管理
事务是在数据库接触的一个概念,对于数据库修改的操作都需要考虑到事务的处理,要么都成功,要么都失败;事物的处理在开发中至关重要!
同样如果在spring中定义的一个方法中会同时执行两个操作:先删除后增加,如果没有事务处理,那么假设删除操作执行成功后,增加操作由于sql语句错误没有执行,这样造成的后果就好像方法只执行了一半;这样是万万不可的;
可以想象如果去银行取钱,取完钱后余额没有减少,那么银行不久亏死;
说这么多就是希望以后写有关数据库修改操作的代码时一定要考虑到事务的管理;
实际操作
编程式事务管理
将事务管理代码嵌到业务方法中来控制事务的提交和回滚
缺点:必须在每个事务操作业务逻辑中包含额外的事务管理代码
声明式事务管理
一般情况下比编程式事务好用
将事务管理代码从业务方法中分离出来,以声明的方式来实现事务管理
将事务管理作为横切关注点,通过aop方法模块化,Spring中通过Spring AOP框架支持声明式事务管理
我们可以通过AOP很轻松实现事务的织入;
下面就演示一下具体操作;
还是上面student表,这里只是演示一下增加事务的地方;
首先这里的StudentDaoImp.java
中selectStudent方法有些不同:
@Override
public List<Student> selectStudent() {
// 直接调用session
StudentDao studentDao = session.getMapper(StudentDao.class);
studentDao.insertStudent(new Student(1013, "小二", "xiaoer@123.com", 12));
studentDao.deleteStudent(1012);
return studentDao.selectStudent();
}
在这里我查询方法中需要先执行insertStudent方法插入学生后再执行deleteStudent方法删除学生,为了保证这两个方法要么都成功,要么都失败,就需要事务管理;
spring-mybatis.xml
在该配置文件中加入头文件的约束:
xmlns:tx="http://www.springframework.org/schema/tx"
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">
<!--配置声明事务-->
<!--JDBC事务-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!--使用AOP实现事务织入-->
<!--配置事务通知-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<!--给哪些方法配置事务-->
<!--配置事务的传播特性-->
<!--method中的name是对应sql语句的方法名-->
<tx:attributes>
<tx:method name="insertStudent" propagation="REQUIRED"/>
<tx:method name="deleteStudent" propagation="REQUIRED"/>
<tx:method name="selectStudent" propagation="REQUIRED"/>
<!--下面这一行可以涵盖所有情况-->
<!-- <tx:method name="*" propagation="REQUIRED"/>-->
</tx:attributes>
</tx:advice>
<!--配置事务切入-->
<aop:config>
<!--dao包下所有类的所有方法都作为切入点-->
<aop:pointcut id="txPointcut" expression="execution(* com.yang.dao.*.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut"/>
</aop:config>
首先就是配置一个DataSourceTransactionManager
的bean;
然后通过AOP织入事务处理;
spring事务传播特性:
事务传播行为就是多个事务方法相互调用时,事务如何在这些方法间传播。spring支持7种事务传播行为:
-
propagation_requierd:如果当前没有事务,就新建一个事务,如果已存在一个事务中,加入到这个事务中,这是最常见的选择;
-
propagation_supports:支持当前事务,如果没有当前事务,就以非事务方法执行;
-
propagation_mandatory:使用当前事务,如果没有当前事务,就抛出异常;
-
propagation_required_new:新建事务,如果当前存在事务,把当前事务挂起;
-
propagation_not_supported:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起;
-
propagation_never:以非事务方式执行操作,如果当前事务存在则抛出异常;
-
propagation_nested:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与propagation_required类似的操作;
Spring 默认的事务传播行为是 PROPAGATION_REQUIRED,它适合于绝大多数的情况;
再次强调为什么需要事务?
- 如果不配置事务,可能存在数据提交不一致的情况;
- 如果我们不在Spring中去配置声明式事务,我们就需要在代码中手动配置事务;
- 事务在项目的开发中十分重要,涉及到数据的一致性和完整性问题,数据的处理是需要万分谨慎的;
若有新知识点会持续补充。。。
参考:Spring官方文档