Spring
1. Spring
Spring是一个轻量级控制反转(IOC)和面向切面(AOP)的容器框架
1.1 简介
SSH(早期):Struct2 + Spring + Hibernate
SSM(现今):SpringMVC + Spring + Mybatis
- 下载地址
官网:https://spring.io/projects/spring-framework
官方下载地址:https://repo.spring.io/release/org/springframework/spring/
GitHub:https://github.com/spring-projects/spring-framework
- maven地址
<!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.2.3.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-jdbc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.2.3.RELEASE</version>
</dependency>
1.2 优点
- Spring是一个开源的免费的框架(容器)
- Spring是一个轻量级的、非入侵式(使用了Spring之后,对原代码没有影响,反而会变得更简单)的框架
- 控制反转(IOC),面向切面编程(AOP)(核心)
- 支持事务的处理,对框架整合的支持
总结:Spring就是一个轻量级的控制反转(IOC)和面向切面编程(AOP)的框架
1.3 组成
- 七大模块
1.4 拓展
Spring Boot :构建
Spring Cloud :协调
Spring Cloud Data Flow :连接
- Spring Boot
- 一个快速开发的脚手架
- 基于SpringBoot可以快速开发单个微服务
- 约定大于配置
- Spring Cloud
- SpringCloud是基于SpringBoot实现的
因为现在大多数公司都在使用SpringBoot进行快速开发,学习SpringBoot的前提,需要完全掌握Spring及SpringMVC,承上启下的作用
弊端:发展了太久之后,违背了原来的理念!配置十分繁琐,人称:” 配置地狱 “
2. IOC理论推导
是一种思想:
控制反转
- 传统业务实现
-
UserDao接口
public interface UserDao { void getUser(); }
-
UserDaoImpl实现类
public class UserDaoImpl implements UserDao { public void getUser() { System.out.println("默认获取用户的数据"); } }
-
UserService业务接口
public interface UserService { void getUser(); }
-
UserServiceImpl业务实现类
public class UserServiceImpl implements UserService{ private UserDao userDao = new UserDaoImpl(); public void getUser() { userDao.getUser(); } }
- 早期使用这种方式,在增加业务时,例如添加
public class UserDaoMysqlImpl implements UserDao{
public void getUser() {
System.out.println("Mysql获取用户数据");
}
}
此时用户需要调用UserMysqlImpl时,就需要在UserServiceImpl中更改UserDao new 的对象
-
在我们之前的业务中,用户的需求可能会影响我们原来的代码,我们需要根据用户的需求去修改源代码。如果程序代码量十分大,修改一次的成本其代价十分昂贵
-
如果在ServiceImpl中使用set接口实现,实现set注入,则程序不再具有主动性。更改需求时,程序不需要改变
private UserDao userDao; //利用set进行动态实现值的注入 public void setUserDao(UserDao userDao) { this.userDao = userDao; }
-
main方法
public static void main(String[] args) { //用户实际调用的是业务层,dao层他们不需要接触 UserService (UserServiceImpl)userService = new UserServiceImpl(); userService.setUserDao(new UserDaoMysqlImpl()); userService.getUser(); }
-
之前,程序主动创建对象,控制权在程序员手中
-
使用了set注入之后,程序不再具有主动性,而是变成了被动的接受对象
这种思想,从本质上解决了问题,我们程序员不用再去管理对象的创建了。系统的耦合性大大降低,可更加专注的在业务的实现上。这是IOC的原型!
3. IOC本质
ioc 就是控制反转,是一种设计思想。
在传统的java se中,都是在程序内部创建对象和对象之间的依赖关系,这提高了代码的耦合度。若要使用某个对象,需要自己在程序中创建,非常的麻烦。
而控制反转,就是将创建对象和对象之间的依赖关系交给ioc容器进行管理,需要使用某个对象,只需要从容器中获取就行了,不需要在程序中手动创建。大大降低了代码的耦合度。
在没有ioc之前,一个对象需要另一个对象时,要自己手动去创建,而有了ioc之后,当这个对象需要另一个对象时,ioc容器会主动创建这个对象并注入到需要这个对象的地方。对象之间获取依赖的方式从主动变为了被动,控制权反转,所以叫做“控制反转”。
控制反转 IoC(Inversion of Control),是一种设计思想,DI(依赖注入)是实现IoC的一种方法,也有人认为DI只是IoC的另一种说法。
没有IoC的程序中 , 我们使用面向对象编程 , 对象的创建与对象间的依赖关系完全硬编码在程序中,对象的创建由程序自己控制,控制反转后将对象的创建转移给第三方,个人认为所谓控制反转就是:获得依赖对象的方式反转了。
IoC是Spring框架的核心内容,使用多种方式完美的实现了IoC,可以使用XML配置,也可以使用注解,新版本的Spring也可以零配置实现IoC。
- Spring容器在初始化时先读取配置文件,根据配置文件或元数据创建与组织对象存入容器中,程序使用时再从Ioc容器中取出需要的对象。
-
创建实体类(get / set 方法)
-
编写Spring文件,beans.xml。模板从官网上查看的
网址:https://docs.spring.io/spring/docs/5.2.3.RELEASE/spring-framework-reference/core.html#spring-core
<?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"> <!--使用Spring来创建对象,在Spring中这些都称为Bean java写法 类型 变量名 = new 类型(); Hello hello = new Hello(); Spring写法 id = 变量名 class = new 的对象 property 相当于给对象中的属性设置一个值! --> <bean id="hello" class="com.leong.pojo.Hello"> <property name="str" value="Spring"></property> </bean> </beans>
-
进行测试
public class MyTest { public static void main(String[] args) { //解析beans.xml文件,生成管理相应的Bean对象 ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml"); //我们的对象现在都在Spring中管理了,我们要使用,直接去里面取出来就可以了 //getBean :参数即为Spring配置文件中bean的id Hello hello = (Hello) context.getBean("hello"); System.out.println(hello.toString()); } }
其中<beans></beans> 和 ApplicationContext … 为固定模板
以上是使用XML配置实现IOC的
这个过程就叫控制反转 :
-
控制 : 谁来控制对象的创建 , 传统应用程序的对象是由程序本身控制创建的 , 使用Spring后 , 对象是由Spring来创建的 .
-
反转 : 程序本身不创建对象 , 而变成被动的接收对象 .
-
依赖注入 : 就是利用set方法来进行注入的.
-
IOC是一种编程思想 , 由主动的编程变成被动的接收 .
可以通过newClassPathXmlApplicationContext去浏览一下底层源码 .
OK , 到了现在 , 我们彻底不用再程序中去改动了 , 要实现不同的操作 , 只需要在xml配置文件中进行修改 , 所谓的IoC,一句话搞定 : 对象由Spring 来创建 , 管理 , 装配 !
4. IOC创建对象的方式
-
使用无参构造创建对象,默认
-
假设我们要使用有参构造创建对象,三种方式
- 下标赋值
<!--有参构造创建对象--> <!--第一种:下标赋值,--> <bean id="hello" class="com.leong.pojo.Hello"> <constructor-arg index="0" value="10"/> </bean>
- 类型
<!--第二种:类型,不建议使用,一个是类型容易出错,还有就是当有两个相同类型的时候--> <bean id="hello" class="com.leong.pojo.Hello"> <constructor-arg type="java.lang.String" value="fee"/> </bean>
- 参数名
<!--第三种:参数名--> <bean id="hello" class="com.leong.pojo.Hello"> <constructor-arg name="name" value="leongfee"/> </bean>
总结:在配置文件加载的时候,容器中管理的对象就已经初始化了。
5. Spring配置
1. 别名(alias)
<!--bean的id名可以起长的规范的,别名可以变成简写-->
<alias name="hello" alias="begin"/>
2. Bean的配置
<bean id="hello" class="com.leong.pojo.Hello" name="begin">
<constructor-arg name="name" value="leongfee"/>
</bean>
<!--
id : beam 的唯一标识符,也就是我们的对象名
class : bean 对象所对应的的权限定名 : 包名 + 类型
name : 也是别名,而且可以同时取多个别名
例如 name="begin1,begin2,begin3"
-->
3. import
这个import一般用于团队开发使用,可以将多个配置文件,导入合并为一个
例如有三个人开发,这三个人赋值不同的类开发,不同的类需要注册在不同的bean中,我们就可以利用import将所有人的beans.xml合并为一个总的xml文件。
- beans.xml
- beans2.xml
- beans3.xml
- applicationContext.xml
<import resource="beans.xml"/>
<import resource="beans2.xml"/>
<import resource="beans3.xml"/>
使用的时候,直接使用总的配置就可以了。
6. 依赖注入
1. 构造器注入
前面的构造器注入方式,默认无参构造器
2. Set方式注入[重点]
- 依赖注入:Set注入!
- 依赖:bean对象的创建依赖于容器
- 注入:bean对象中的所有属性,由容器来注入
环境搭建:
-
复杂类型
public class Address { private String address; public String getAddress() { return address; } public void setAddress(String address) { this.address = address; } }
-
真实测试对象
public class Student{ private String name;//value private Address address;//ref private String[] books;//idref private List<String> hobbies;//list private Map<String,String> card;//map private Set<String> games;//set private String wife;//null private Properties info;//props }
-
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="student" class="com.ccsu.leong.Student"> <!--第一种,普通值注入,value--> <property name="name" value="leongfee"/> </bean> </beans>
-
测试类
public class MyTest { public static void main(String[] args) { ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml"); Student student = (Student) ctx.getBean("student"); System.out.println(student.getName()); } }
完善注入信息
<bean id="address" class="com.ccsu.leong.Address">
<property name="address" value="衡阳"/>
</bean>
<bean id="student" class="com.ccsu.leong.Student">
<!--第一种,普通值注入,value-->
<property name="name" value="leongfee"/>
<!--类比数组注入,这个普通注入也可以写为-->
<!--<property name="name">-->
<!--<value>leongfee</value>-->
<!--</property>-->
<!--第二种,Bean注入,ref-->
<property name="address" ref="address"/>
<!--数组注入,ref-->
<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>
</list>
</property>
<!--map-->
<property name="card">
<map>
<entry key="身份证" value="213"/>
<entry key="学号" value="422"/>
</map>
</property>
<!--set-->
<property name="games">
<set>
<value>LOL</value>
<value>CSGO</value>
</set>
</property>
<!--null,为null-->
<property name="wife">
<null/>
</property>
<!--为空值-->
<!--<property name="wife" value=""/>-->
<!--properties,key=value-->
<property name="info">
<props>
<prop key="姓名">小明</prop>
<prop key="性别">男</prop>
</props>
</property>
</bean>
3. 拓展方式注入
需要在xml头部导入约束
xmlns:p="…"
-
p 命名空间,可替换property
<!-- 相当于property,注入参数 --> <bean id="user" class="com.leongfee.User" p:age="18" p:name="leong"/>
-
c 命名空间,可替换constructor-arg
<!-- 相当于constructor-arg,有参构造器 --> <bean id="user" class="com.leongfee.User" c:age="18" c:name="leong"/>
4. Bean的作用域
单例模式(singleton,Spring默认机制)
get同一个对象的时候只会产生一个对象
<bean id="helloWorld" class="com.ccsu.leong.HelloWord" scope="singleton"/>
原型模式(prototype)
每次从容器中get的时候,都会产生一个新对象
<bean id="helloWorld" class="com.ccsu.leong.HelloWord" scope="prototype"/>
request
session
application
websocket
其余的request、session、application、websocket这些只能在web开发中使用到
7. Bean的自动装配
- 自动装配是Spring满足bean依赖的一种方式
- Spring会在上下文中自动寻找,并自动给bean装配属性
三种装配方式
- 在xml中显示的配置
- 在java中显示的配置
- 隐式的自动装配bean [重点]
使用autowire属性实现自动装配
byName byType
<bean id="cat" class="com.ccsu.leong.Cat"/>
<bean id="dog" class="com.ccsu.leong.Dog"/>
<!--
byName:会自动在容器上下文中查找,和自己对象set方法后面的值对应的beanId,要保证bean的id唯一
byType:会自动在容器上下文中查找,和自己对象属性类型相同的bean,有相同的两个类型,会报错,要保证bean的类型唯一
people中有cat个dog类
不需要在people这个bean中装配cat和dog了,根据name自动查找装配
-->
<bean id="people" class="com.ccsu.leong.People" autowire="byName"/>
使用注解实现自动装配
-
导入约束
xmlns:context="..." xsi:schemaLocation="..."
-
配置注解的支持
<context:annotation-config/>
@Autowired
和使用xml自动装配不同的是,注解在people类中的cat和dog属性上添加,替换autowire属性。
-
可以在属性上使用,也可以在set方法上使用
-
默认按类型进行装配,如果要允许null值,设置属性required为false
-
Autowired注解是首先是根据类型自动装配的,当拥有相同类型的时候,在Autowired的注解下需要添加一个注解==@Qualifier((value=)"[id]")==,实现byName
-
在后期,当有两个相同的类时,同样需要 @Qualifier 注解来实现区分
public class People { @Autowired @Qualifier(value="cat2") private Cat cat;
@Resource
-
默认按名称进行装配,可以通过name属性进行制定。
-
最好是将@Resource放在setter方法上,因为这样更符合面向对象的思想,通过set、get去操作属性,而不是直接去操作属性。
-
Resource装配顺序
①如果同时指定了name和type,则从Spring上下文中找到唯一匹配的bean进行装配,找不到则抛出异常。
②如果指定了name,则从上下文中查找名称(id)匹配的bean进行装配,找不到则抛出异常。
③如果指定了type,则从上下文中找到类似匹配的唯一bean进行装配,找不到或是找到多个,都会抛出异常。
④如果既没有指定name,又没有指定type,则自动按照byName方式进行装配;如果没有匹配,则回退为一个原始类型进行匹配,如果匹配则自动装配。
@Autowired和@Resource的区别
共同点
- 两者都可以写在字段和setter方法上。两者如果都写在字段上,那么就不需要再写setter方法。
不同点
-
@Autowired
- @Autowired先通过byType自动注入,由Spring提供,需要导入包org.springframework.beans.factory.annotation.Autowired
- 默认情况下它要求依赖对象必须存在,如果允许null值,可以设置它的required属性为false。
- 如果我们想使用按照名称(byName)来装配,需要结合@Qualifier注解一起使用
-
@Resource
- @Resource默认按照byName自动注入,由J2EE提供,需要导入包javax.annotation.Resource。
- @Resource有两个重要的属性:name和type,而Spring将@Resource注解的name属性解析为bean的名字,而type属性则解析为bean的类型。
-
@Resource的作用相当于@Autowired,只不过@Autowired按照byType自动注入。
8. 使用注解开发
环境搭建
-
spring4.0之后,使用注解需要导入aop依赖
-
xml配置文件导入context约束
-
在xml文件中,需要添加
<!-- 扫描指定的包,这个包下面的注解才会生效 --> <context:component-scan base-package="com.leong"/> <!-- 注解支持 --> <context:annotation-config/>
bean
在类中使用@Component注解,相当于xml中的<bean id=“user” class=“com.leong.entity.User”/>,该类成为一个bean
@Component // 组件,等价于<bean id="user" class="com.leong.entity.User"/>
public class User{
private String name;
}
属性值注入
@Component
public class User{
@Value("leong") // 等价于<property name="" value=""/>
private String name;
}
自动装配
- @Autowired
- @Resource
作用域
@Component
@Scope("prototype") // 等价于<bean scope="prototype"/>
public class User{
@Value("leong")
private String name;
}
小结
- xml更加万能,适用于任何场合,维护简单方便
- 注解不是自己的类使用不了,维护相对复杂
最佳实践:xml用来管理bean,注解只用来注入属性
使用注解,就必须要让注解生效,就必须要开启注解的支持
还需要扫描指定包
<context:annotation-config/>
<context:component-scan base-package=""/>
9. 使用Java的方式配置Spring
不使用Spring的xml配置spring,而交给java来实现
- JavaConfig是Spring的一个子项目,在spring4之后,成为一个核心功能
配置类
package com.leong.config;
// 这个也会由Spring容器管理,注册到容器中,因为他本来局势一个@Component
// @Configuration代表这是一个配置类,和之前的beans.xml一样
@Configuration
@ComponentScan("com.leong") // 自动扫描包
@Import(PeopleConfig.class) // 合并另一个配置类
public class UserConfig{
// 注册一个bean,就相当于之前的bean标签
// 这个方法的名字,就相当于bean标签中的id属性
// 这个返回的值,就相当于bean标签中的class属性
@Bean
public User getUser(){
return new User(); // 返回要注入到bean的对象
}
// 当开启了@ComponentScan时候,在扫描到的包中有交给spring容器管理的,那么这个@Bean可以不需要,类比beans.xml
}
测试类
public class MyTest{
public static void main(String[] args){
ApplicationContext context = new AnnotationConfigApplicationContext(UserConfgi.class);
user getUser = (User) context.getBean("user");
sout.getUser.getName();
}
}
- 纯 java 的配置方式,在SpringBoot中随处可见
10. 代理模式
这是Spring AOP底层用到的设计模式
参考博客:http://c.biancheng.net/view/1359.html
静态代理
由程序员创建代理类或特定工具自动生成源代码再对其编译,在程序运行前代理类的 .class 文件就已经存在了。
参考Java基础笔记中的接口的应用:代理模式
-
抽象角色:一般会使用接口或抽象类来解决
-
真实角色:被代理的角色
-
代理角色:代理真实角色,代理真实角色后,我们一般会做一些附属操作
-
客户:访问代理对象的人
-
客户在访问时,真实角色被隐藏,调用的都是代理角色中的方法
-
真实角色和代理角色共同使用抽象角色中的方法
-
真实角色和代理角色均实现抽象角色中的方法,但是代理角色可以扩展更多的方法
-
客户在访问时,直接对代理角色进行访问
优点
- 代理模式在客户端与目标对象之间起到一个中介作用和保护目标对象的作用;
- 代理对象可以扩展目标对象的功能;
- 代理模式能将客户端与目标对象分离,在一定程度上降低了系统的耦合度,增加了程序的可扩展性
缺点
- 代理模式会造成系统设计中类的数量增加
- 在客户端和目标对象之间增加一个代理对象,会造成请求处理速度变慢;
- 增加了系统的复杂度;
那么如何解决以上提到的缺点呢?答案是可以使用动态代理方式
动态代理
在程序运行时,运用反射机制动态创建而成
- 动态代理和静态代理的角色一样
- 动态代理的代理类是动态生成的,不是直接写好的
- 动态代理分为两大类
- 基于接口的动态代理:JDK 动态代理 [spring aop使用]
- 基于类的动态代理:cglib
- java字节码实现:javasist
Proxy类
InvocationHandler接口类
通过这两个类,分贝可以生成JDK动态代理类和动态代理对象
示例代码
- 接口类
public interface Rent {
void rent();
}
- 被代理类
public class Host implements Rent{
public void rent() {
System.out.println("房东要出租房子!");
}
}
- 动态生成代理类:真正使用的代理类,被代理的接口改为可变的,不是写成固定的
public class ProxyInvocationHandler_General 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);
}
// 处理代理实例上的方法调用,并返回结果
// invoke("调用该方法的对象","","")
// 隐式调用,在调用getProxy方法时,this参数的底层源码中,会调用invoke方法
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 附加操作写在invoke方法中
// 动态代理的本质,就是使用反射机制实现
Object result = method.invoke(target, args);
return result;
}
// 添加附加操作
}
- 测试类
public class Client {
public static void main(String[] args) {
// 真实角色
Host host = new Host();
// 代理是角色:现在没有。这是自己编写的处理程序
ProxyInvocationHandler_General pih = new ProxyInvocationHandler_General();
// 通过调用程序处理角色,来处理要调用的接口对象
// 设置要代理的对象
pih.setTarget(host);
// 获得动态生成的代理类
Rent proxy = (Rent) pih.getProxy();
proxy.rent();
}
}
11. AOP
AOP(Aspect Oriented Programming) 即面向切面编程。
通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。
AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。
利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
- 将保留业务逻辑,扩展其他功能,例如扩展日志等
- 利用动态代理对象,使得原有的业务逻辑不变,新增功能
AOP在Spring中的作用
提供声明式事务,允许用户自定义切面
- 横切关注点:跨越应用程序多个模块的方法或功能。即是,与我们业务逻辑无关的,但是我们需要关注的部分,就是横切关注点。如日志、安全、缓存、事务等。
- 切面( Aspect ):横切关注点被模块化的特殊对象。即,它是一个类。
- 通知( Advice ):切面必须要完成的工作。即,它是类中的一个方法。
- 前置通知和后置通知:在方法前和方法后添加方法
- 目标( Target ):被通知对象。即,接口或一个方法
- 代理( Proxy ):向目标对象应用通知之后创建的对象。即,生成的代理类
- 切入点( Pointcut ):切面通知执行的“地点”的定义。即,执行点
- 连接点( JointPoint ):与切入点匹配的执行点。
使用Spring实现AOP
- 使用AOP织入,需要导入一个依赖包:aspectjweaver
<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.4</version>
</dependency>
方式一:使用Spring API接口
在spring配置文件中,使用aop来进行配置
- 接口类
public interface UserService {
void add();
void delete();
void update();
void select();
}
- 接口实现类:不需要更改的原代码
public class UserServiceImpl implements UserService {
public void add() {
System.out.println("增加了一个用户");
}
public void delete() {
System.out.println("删除了一个用户");
}
public void update() {
System.out.println("修改了一个用户");
}
public void select() {
System.out.println("查询了一个用户");
}
}
- 新增的Log类,有前置通知和后置通知
/**
* @ClassName BeforeLog
* @Description 前置通知,即在方法前切入
* @Author Leongfee
* @Date 2020/11/19 12:02
* @Version V1.0
*/
public class BeforeLog implements MethodBeforeAdvice {
/**
* @param method 要执行的目标对象的方法
* @param args 参数
* @param target 目标对象
* @throws Throwable
*/
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.println(target.getClass().getName() + "的" + method.getName() + "被执行了");
}
}
/**
* @ClassName AfterLog
* @Description 后置通知,即在方法后企切入
* @Author Leongfee
* @Date 2020/11/19 12:07
* @Version V1.0
*/
public class AfterLog implements AfterReturningAdvice {
/**
* @param returnValue 返回值
* @param method
* @param args
* @param target
* @throws Throwable
*/
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
System.out.println("执行了" + method.getName() + "方法,返回结果为" + returnValue);
}
}
- 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"
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="life.leong.service.Impl.UserServiceImpl"/>
<!-- 方式一:使用Spring API接口 -->
<bean id="beforeLog" class="life.leong.log.BeforeLog"/>
<bean id="afterLog" class="life.leong.log.AfterLog"/>
<!-- 配置aop -->
<aop:config>
<!-- 切入点; expression="execution(*[返回的类型,任意类型] [类名].*[类下的所有方法]([方法的参数]))(要执行的位置)" -->
<aop:pointcut id="pointcut" expression="execution(* life.leong.service.Impl.UserServiceImpl.*(..))"/>
<!-- 执行环绕增加 -->
<aop:advisor advice-ref="beforeLog" pointcut-ref="pointcut"/>
<aop:advisor advice-ref="afterLog" pointcut-ref="pointcut"/>
</aop:config>
</beans>
- 测试类:使用 ApplicationContext 实现,也可以使用注解
public class MyTest {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
// 动态代理的是接口
UserService userService = (UserService) context.getBean("userService");
userService.add();
userService.delete();
}
}
方式二:使用自定义类
自定义一个类来实现aop
- 新增的自定义类
public class CustomLog {
public void before() {
System.out.println("在这个方法之前的");
}
public void after() {
System.out.println("在这个方法之后的");
}
}
- 使用方式二后的beans.xml配置文件
<bean id="userService" class="life.leong.service.Impl.UserServiceImpl"/>
<!--方式二:自定义类-->
<bean id="custom" class="life.leong.custom.CustomLog"/>
<aop:config>
<aop:aspect ref="custom">
<!--切入点-->
<aop:pointcut id="pointcut" expression="execution(* life.leong.service.Impl.UserServiceImpl.*(..))"/>
<!--通知-->
<aop:before method="before" pointcut-ref="pointcut"/>
<aop:after method="after" pointcut-ref="pointcut"/>
</aop:aspect>
</aop:config>
方式三:使用注解实现AOP
在类中使用注解实现aop
- 注解类
@Aspect
public class AnnotationLog {
@Before("execution(* life.leong.service.Impl.UserServiceImpl.*(..))")
public void before() {
System.out.println("在方法之前的");
}
@After("execution(* life.leong.service.Impl.UserServiceImpl.*(..))")
public void after() {
System.out.println("在方法之后的");
}
}
- beans.xml配置文件
<bean id="userService" class="life.leong.service.Impl.UserServiceImpl"/>
<!--方式三:使用注解-->
<bean id="annotationLog" class="life.leong.custom.AnnotationLog"/>
<aop:aspectj-autoproxy/><!--方式三:使用注解-->
<bean id="annotationLog" class="life.leong.custom.AnnotationLog"/>
<!--aop的注解支持-->
<aop:aspectj-autoproxy/>
11. 整合Mybatis
spring整合mybatis
- 简化mybatis配置文件,数据源交由spring管理
- 不需要MybatisUtil工具类,SqlSessionFactory的创建交给spring管理
- SqlSession由SqlSessionTemplate创建,在spring中将SqlSessionTemplate注入,编写实现类,注入实现类到spring中,参数为sqlSession
- 或者实现类继承SqlSessionDaoSupport,编写实现类,只需要注入实现类到spring中,参数为sqlSessionFactory
- 测试时仅需要获得spring容器,调用相应的方法,参入相应的参数即可
参考博客:https://www.jianshu.com/p/412051d41d73
参考官网:http://mybatis.org/spring/zh/
导入相关jar包
-
junit:测试、mybatis、mysql、spring、aop织入、mybatis-spring:专门用来整合的包、jdbc:spring操作数据库需要
<dependencies> <!--spring整合mybatis需要的包--> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>2.0.5</version> </dependency> <!--spring操作数据库需要,数据源中使用--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>5.1.18.RELEASE</version> </dependency> <!--spring aop织入需要--> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9.6</version> </dependency> <!--spring核心包--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>5.3.1</version> </dependency> <!--mybatis核心包--> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.6</version> </dependency> <!--连接mysql数据库--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.22</version> </dependency> <!--日志--> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version> </dependency> <!--lombok插件--> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.16</version> </dependency> </dependencies>
方式一(掌握)
实现类中注入SqlSessionTemplate
使用SqlSessionFactory作为构造器方法的参数来创建SqlSessionTemplate对象
-
编写数据源
<!--加载配置文件--> <!--“context:property-placeholder” 配置是用于读取工程中的静态属性文件,然后在其他配置中使用时,就可以采用 “${属性名}” 的方式获取该属性文件中的配置参数值。--> <!--加载SqlSessionFactory时,文件还没加载,所以报错--> <!--<context:property-placeholder location="classpath:db.properties"/>--> <!--使用spring的数据源替换mybatis的配置--> <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=Asia/Shanghai"/> <property name="username" value="root"/> <property name="password" value="root"/> </bean>
- 将mybatis中的数据源交给spring管理
- 在引用properties文件的变量时,无法引入成功,运行报错
- 某个类(暂不明确,网上说是MapperScannerConigurer,但未使用这个类也会失败),在spring启动过程中提前加载,这个时候引用sqlSessionFactory的话,会导致其提前加载,而properties还没加载完,所以变量没有被替换。
-
sqlSessionFactory
<!--配置 sqlSessionFactory 会话工厂对象,不需要在测试类中new工厂了--> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <!--加载数据源--> <property name="dataSource" ref="dataSource"/> <!--绑定mybatis配置文件--> <property name="configLocation" value="mybatis-config.xml"/> <!--还可以在这里配置更多mybatis的内容--> </bean>
- SqlSessionFactory交由spring管理创建,不用手动创建
-
sqlSessionTamplate
参考官网:http://mybatis.org/spring/zh/sqlsession.html#SqlSessionTemplate
<!--方式一:使用SqlSessionFactory作为构造器方法的参数来创建SqlSessionTemplate对象--> <bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate"> <!--只能使用构造器注入SqlSessionFactory,因为它没有set方法--> <constructor-arg index="0" ref="sqlSessionFactory"/> </bean>
- SqlSession的实现
- 作为 SqlSession 的一个实现,这意味着可以使用它无缝代替你代码中已经在使用的 SqlSession。 SqlSessionTemplate是线程安全的,可以被多个 DAO 或映射器所共享使用。
-
创建接口实现类
public class UserMapperImpl implements UserMapper { private SqlSessionTemplate sqlSession; public void setSqlSession(SqlSessionTemplate sqlSession) { this.sqlSession = sqlSession; } public List<User> getUserList() { return sqlSession.getMapper(UserMapper.class).getUserList(); } }
- 在实现类中执行查询的数据并返回,测试时只需要从spring中get即可
-
将实现类注入到bean中
<bean id="userMapperImpl" class="life.leong.mapper.impl.UserMapperImpl"> <property name="sqlSession" ref="sqlSession"/> </bean>
-
测试
public class MyTest02 { private Logger logger = Logger.getLogger(MyTest02.class); private ApplicationContext context; // 在执行测试方法之前首先获取 Spring 配置文件对象 // 注解@Before在执行本类所有测试方法之前先调用这个方法 // @Before是junit包下的 @Before public void setup() throws Exception { context = new ClassPathXmlApplicationContext("applicationContext.xml"); } @Test public void test01() { // 通过配置资源对象获取 userDAO 对象 UserMapper userMapper = context.getBean("userMapperImpl",UserMapper.class); // 调用 userMapper 的方法 List<User> userList = userMapper.getUserList(); logger.info(userList); } }
- 使用junit中的@Before注解,方便测试,在mybatis中就可以使用@Before和@After注解
方式二(简化操作)
实现类中继承SqlSessionDaoSupport,getSqlSession获得SqlSessionTemplate
使用SqlSessionDaoSupport 抽象的支持类,用来提供 SqlSession。调用 getSqlSession()方法得到一个 SqlSessionTemplate,之后可以用于执行 SQL 方法
-
实现类
// 方式二:使用SqlSessionDaoSupport public class StudentMapperImpl extends SqlSessionDaoSupport implements StudentMapper { public List<Student> getStudent() { return getSqlSession().getMapper(StudentMapper.class).getStudent(); } }
- 在spring-mybatis.xml配置文件中,就不需要注入SqlSessionTemplate了
-
将实现类注入到bean中
<bean id="studentMapperImpl" class="life.leong.mapper.impl.StudentMapperImpl"> <property name="sqlSessionFactory" ref="sqlSessionFactory"/> </bean>
-
测试类
@Test public void getStudentTest() { StudentMapper studentMapper = context.getBean("studentMapperImpl", StudentMapper.class); List<Student> student = studentMapper.getStudent(); logger.info(student); }
方式三:Mybatis plus 插件 及其其他方式
12. 声明式事务
当一组业务当成一个业务来做:要么都成功,要么都失败
事务在项目开发中,十分重要,涉及到数据的一致性问题,不能马虎
确保完整性和一致性
事务的ACID原则
-
原子性
- 事务是数据库的逻辑工作单位
- 事务中包含的各个操作,要么都做,要么都不做
-
一致性
- 事务执行的结果必须是使数据库从一个一致性状态变到另一个一致性状态
-
隔离性
- 一个事务在执行时,不受其它事务干扰
-
持久性
- 一个事务一旦提交,它对数据库中的数据的改变就是永久的,无论发生什么问题,结果都不会再被影响
-
Mybatis执行事务,不满足事务的四原则
-
要交给spring中的进行事务管理
交由Spring进行事务管理
- 在beans.xml中使用aop切面编程配置spring的事务管理
注解
@Autowired
标注在属性上
自动装配,默认使用byType装配,当有多个相同类型时,使用byName,需要Qualifier注解指定name
属性
- required
- 是否不为空,默认为true,不能为空
- @Autowired(required=“false”),对象可以为空
@Qualifier
标注在属性上
一般搭配@Autowired使用,指定name
属性
- value
- 指定name
- @Qualifier(“user01”) -> (value=“user01”)
@Resource
标注在属性或set上,建议在set上
自动装配,默认使用byName,没有匹配的name使用byType
属性
- name
- 指定name
- type
- 指定type
- 可以同时用name和type指定对象
@Nullable
标注在属性上
说明这个属性值可以为null
@Component
标注在类上
标注一个类为Spring容器的Bean,相当于配置文件中的<bean id="" class=""/>
说明这个类被Spring管理了,成为一个bean
衍生注解
- 功能与@Component一样,都是将某个类装配到spring容器中
@Repository:DAO层
@Service:Service层
@Controller:Controller层
@Configuration:java配置,底层也是一个Component
@Value
标注在属性上
属性值的注入,相当于配置文件中的|<property name="" value=""/>
- @Value(“xxx”)
- 一般在简单的属性中使用