# Spring study **Repository Path**: magic304/spring-study ## Basic Information - **Project Name**: Spring study - **Description**: Spring study全部测试用例 - **Primary Language**: Java - **License**: GPL-3.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 1 - **Forks**: 0 - **Created**: 2022-09-11 - **Last Updated**: 2023-05-21 ## Categories & Tags **Categories**: Uncategorized **Tags**: Spring ## README # Spring ## 1 Spring ### 1.1 简介 - 2002,首次推出了Spring框架的雏形:interface21框架! - Spring框架即以interface21框架为基础,经过重新设计,并不断丰富其内涵,于2004年3月24日发布了1.0正式版。 - Rod Johnson,Spring Framework创始人,著名作者。他是悉尼大学的博士,然而他的专业不是计算机,而是音乐学。 - Spring理念:使现有的技术更加容易使用,本身是一个大杂烩,整合了现有的技术框架! - SSH:Struct2 + Spring + Hibernate! - SSM:SpringMVC + Spring + Mybatis! 官网:[https://spring.io/projects/spring-framework#overview](https://spring.io/projects/spring-framework#overview) 官方下载地址:[https://repo.spring.io/release/org/springframework/spring/](https://repo.spring.io/release/org/springframework/spring/) GitHub:[https://github.com/spring-projects/spring-framework](https://repo.spring.io/release/org/springframework/spring/) ```xml org.springframework spring-webmvc 5.3.22 org.springframework spring-jdbc 5.3.22 ``` ### 1.2 优点 - Spring是一个开源的免费的框架(容器)! - Spring是一个轻量级的、非入侵式的框架! - 控制反转(IOC),面向切面编程(AOP)! - 支持事务的处理,对框架整合的支持! ==总结一句话:Spring就是一个轻量级的控制反转(IOC)和面向切面编程(AOP)的框架!== ### 1.3 组成 ![](https://img-blog.csdn.net/20180914091500764?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0h1YW5nMTE3ODM4Nzg0OA==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70) ### 1.4 拓展 在Spring的官网有这个介绍:现代化的Java开发!说白了就是基于Spring的开发 ![image.png](https://s2.loli.net/2022/08/25/cbFAUvItrh7guNT.png) - Spring Boot - 一个快速开发的脚手架。 - 基于SpringBoot可以快速的开发单个微服务。 - 约定大于配置。 - Spring Cloud - SpringCloud是基于SpringBoot实现的。 因为现在大多数公司都在使用SpringBoot进行快速开发,学习SpringBoot的前提,需要完全掌握Spring及SpringMVC **承上启下的作用!** **弊端:发展了太久之后,违背了原来的理念!配置十分繁琐,人称:“配置地狱!”** ## 2 IOC理论推导 之前的业务实现: 1. UserDao 接口 ```java public interface UserDao { void getUser(); } ``` 2. UserDaoImpl 实现类 ```java public class UserDaoImpl implements UserDao { public void getUser() { System.out.println("默认获取用户数据"); } } ``` 3. UserService 业务接口 ```java public interface UserService { void getUser(); } ``` 4. UserServiceImpl 业务实现类 ```java public class UserServiceImpl implements UserService { private UserDao userDao = new UserDaoImpl(); public void getUser() { userDao.getUser(); } } ``` 5. 测试 ```java public class MyTest { public static void main(String[] args) { //用户实际调用的是业务层,dao层他们不需要接触! UserService userService = new UserServiceImpl(); userService.getUser(); } } ``` 在我们之前的业务中,用户的需求可能会影响我们原来的代码,我们需要根据用户的需求去修改原代码!如果程序代码量十分大,修改一次的成本代价十分昂贵! ![image.png](https://s2.loli.net/2022/08/26/F6I8RL9r5mM1Cac.png) 我们使用一个Set接口实现,已经发生了革命性的变化! ```java private UserDao userDao; //利用set进行动态实现值的注入! public void setUserDao(UserDao userDao) { this.userDao = userDao; } ``` - 之前,程序是主动创建对象!控制权在程序猿手上! - 使用了set注入后,程序不再具有主动性,而是变成了被动的接收对象! ![image.png](https://s2.loli.net/2022/08/26/JQDvbECT6jUVwGO.png) 这种思想,从本质上解决了问题,我们程序猿不用再去管理对象的创建了。系统的耦合性大大降低~,可以更加专注的在业务的实现上!这是IOC的原型! ### 2.1 IOC本质 **控制反转IoC(Inversion of Control),是一种设计思想,DI(依赖注入)是实现IoC的一种方法,**也有人认为DI只是IoC的另一种说法。没有IoC的程序中,我们使用面向对象编程,对象的创建与对象间的依赖关系完全硬编码在程序中,对象的创建由程序自己控制,控制反转后将对象的创建转移给第三方,个人认为所谓控制反转就是:获得依赖对象的方式反转了。 ![image.png](https://s2.loli.net/2022/08/26/NPIOD7GstyfoRr9.png) 采用XML方式配置Bean的时候,Bean的定义信息是和实现分离的,而采用注解的方式可以把两者合为一体,Bean的定义信息直接以注解的形式定义在实现类中,从而达到了零配置的目的。 **控制反转是一种通过描述(XML或注解)并通过第三方去生产或获取特定对象的方式。在Spring中实现控制反转的是IoC容器,其实现方法是依赖注入(Dependency Injection,DI)。** ## 3 HelloSpring 1. 新建一个maven项目,编写实体类 ```java package com.swust.pojo; public class Hello { private String str; public String getStr() { return str; } public void setStr(String str) { this.str = str; } @Override public String toString() { return "Hello{" + "str='" + str + '\'' + '}'; } } ``` 2. 编写xml配置文件 ```xml ``` 3. 测试 ```java ``` **思考问题?** - Hello对象是谁创建的? Hello对象是由Spring创建的。 - Hello对象的属性是怎么设置的? Hello对象的属性是由Spring容器设置的。 **控制**:谁来控制对象的创建,传统应用程序的对象是由程序本身控制创建的,使用Spring后,对象是由Spring来创建的。 **反转**:程序本身不创建对象,而变成被动的接收对象。 **依赖注入**:就是利用set方法来进行注入的。 IOC是一种编程思想,由主动的编程变成被动的接收。 可以通过new ClassPathXmlApplicationContext去浏览一下底层源码。 **OK,到了现在,我们彻底不用在程序中去改动了,要实现不同的操作,只需要在xml配置文件中进行修改,所谓的IOC,一句话搞定:对象由Spring来创建,管理,装配!** ## 4 IOC创建对象的方式 1. 使用无参构造创建对象,默认! 2. 假设我们要使用有参构造创建对象。 1. 下标赋值 ```xml ``` 2. 类型 ```xml ``` 3. 参数名 ```xml ``` 总结:在配置文件加载的时候,容器中管理的对象就已经初始化了! ## 5 Spring配置 ### 5.1 别名 ```xml ``` ### 5.2 Bean的配置 ```xml ``` ### 5.3 import 这个import。一般用于团队开发使用,它可以将多个配置文件,导入合并为一个。 假设,现在项目中有多个人开发,这三个人负责不同的类开发,不同的类需要注册在不同的bean中,我们可以利用import将所有人的beans.xml合并为一个总的! - 张三 - 李四 - 王五 - applicationContext.xml ```xml ``` 使用的时候,直接使用总的配置就可以了。 ## 6 依赖注入 ### 6.1 构造器注入 前面已经介绍过,参考**4 IOC创建对象的方式** ### 6.2 Set方式注入【重点】 - 依赖注入:Set注入 - 依赖:bean对象的创建依赖于容器! - 注入:bean对象中的所有属性,由容器来注入! 【环境搭建】 1. 复杂类型 ```java package com.swust.pojo; public class Address { private String address; public String getAddress() { return address; } public void setAddress(String address) { this.address = address; } } ``` 2. 真实测试对象 ```java package com.swust.pojo; import java.util.*; public class Student { private String name; private Address address; private String[] books; private List hobbies; private Map card; private Set games; private String wife; private Properties info; public String getName() { return name; } public void setName(String name) { this.name = name; } public Address getAddress() { return address; } public void setAddress(Address address) { this.address = address; } public String[] getBooks() { return books; } public void setBooks(String[] books) { this.books = books; } public List getHobbies() { return hobbies; } public void setHobbies(List hobbies) { this.hobbies = hobbies; } public Map getCard() { return card; } public void setCard(Map card) { this.card = card; } public Set getGames() { return games; } public void setGames(Set games) { this.games = games; } public String getWife() { return wife; } public void setWife(String wife) { this.wife = wife; } public Properties getInfo() { return info; } public void setInfo(Properties info) { this.info = info; } @Override public String toString() { return "Student{" + "name='" + name + '\'' + ", address=" + address + ", books=" + Arrays.toString(books) + ", hobbies=" + hobbies + ", card=" + card + ", games=" + games + ", wife='" + wife + '\'' + ", info=" + info + '}'; } } ``` 3. beans.xml ```xml ``` 4. 测试类 ```java public class MyTest { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml"); Student student = (Student) context.getBean("student"); System.out.println(student); } } ``` 5. 完善注入信息 ```xml 红楼梦 西游记 水浒传 三国演义 打篮球 看电影 敲代码 LOL COC BOB 20190525 102.0913.524.4585 root 123456 ``` 6. 测试结果: ```java Student{name='张三', address=Address{address='西安'}, books=[红楼梦, 西游记, 水浒传, 三国演义], hobbies=[打篮球, 看电影, 敲代码], card={身份证=123456789987456321, 银行卡=359419496419481649}, games=[LOL, COC, BOB], wife='null', info={user=张三, password=123456, url=102.0913.524.4585, driver=20190525}} ``` ### 6.3 拓展方式注入 我们可以使用p命名空间和c命名空间进行注入 官方解释: ![image.png](https://s2.loli.net/2022/08/27/ZlKuBtbA73UkIoL.png) 使用: 测试: 注意点:p命名和c命名空间不能直接使用,需要导入xml约束! 使用: ```xml ``` 测试: ```java @Test public void test2(){ ApplicationContext context = new ClassPathXmlApplicationContext("userbeans.xml"); User user = context.getBean("user",User.class); System.out.println(user); User user2 = context.getBean("user2",User.class); System.out.println(user2); } ``` 注意点:p命名和c命名空间不能直接使用,需要导入xml约束! ```xml xmlns:p="http://www.springframework.org/schema/p" xmlns:c="http://www.springframework.org/schema/c" ``` ### 6.4 bean的作用域 ![image.png](https://s2.loli.net/2022/08/27/lEGfV3gzTUD5RFe.png) 1. 单例模式(Spring默认机制) ![](https://docs.spring.io/spring-framework/docs/current/reference/html/images/singleton.png) ```xml ``` user = user2 2. 原型模式:每次从容器中get的时候,都会产生一个新对象! ![](https://docs.spring.io/spring-framework/docs/current/reference/html/images/prototype.png) ```xml ``` user != user2 3. 其余的request、session、application、这些只能在web开发中使用到! ## 7 Bean的自动装配 - 自动装配是Spring满足bean依赖一种方式! - Spring会在上下文中自动寻找,并自动给bean装配属性! **在Spring中有三种装配的方式:** 1. 在xml中显式的配置; 2. 在java中显式配置; 3. **隐式的自动装配bean【重要】** ### 7.1 测试 环境搭建:创建项目,一个人有两个宠物! ```xml ``` ### 7.2 ByName自动装配 ```xml ``` ### 7.3 ByType自动装配 ```xml ``` 小结: - ByName的时候,需要保证所有bean的id唯一,并且这个bean需要和自动注入的属性的set方法的值一致! - ByType的时候,需要保证所有bean的class唯一,并且这个bean需要和自动注入的属性的类型一致! ### 7.4 使用注解实现自动装配 jdk1.5支持的注解,Spring2.5就支持注解了! 要使用注解须知: 1. 导入约束 context 约束 2. ==配置注解的支持== `` ```xml ``` **@Autowired** 直接在属性上使用即可!也可以在set方法上使用! 使用Autowired我们就可以不用编写set方法了,前提是你这个自动配置的属性在IOC(Spring)容器中存在,且符合名字ByName **科普:** ```xml @Nullable 字段标记了了这个注解,说明这个字段可以为null; ``` ```java public @interface Autowired { boolean required() default true; } ``` 测试代码: ```java public class People { //如果显式定义了Autowired的required属性为false,说明这个对象可以为null,否则不允许为空 @Autowired(required = false) private Cat cat; @Autowired private Dog dog; private String name; } ``` 如果@Autowired自动装配的环境比较复杂,自动装配无法通过一个注解【@Autowired】完成的时候,我们可以使用@Qualifier(value = “xxx”)去配置@Autowired的使用,指定一个唯一的bean对象注入! ```java public class People { @Autowired @Qualifier(value = "cat") private Cat cat; @Autowired @Qualifier(value = "dog") private Dog dog; private String name; } ``` **@Resource** ```java public class People { @Resource private Cat cat; @Resource private Dog dog; } ``` 小结: @Resource和@Autowired的区别: - 都是用来自动装配的,都可以放在属性字段上 - @Autowired通过byType的方式实现,而且必须要求这个对象存在!【常用】 - @Resource默认通过byName的方式实现,如果找不到名字,则通过byType实现!如果两个都找不到的情况下,就报错!【常用】 - 执行顺序不同:@Autowired通过byType的方式实现。 ## 8 使用注解开发 在Spring4之后,要使用注解开发,必须要保证aop的包导入了 ![image.png](https://s2.loli.net/2022/08/29/6dFzRnBT5rgjEHo.png) 使用注解需要导入约束,配置注解的支持 ```xml ``` 1. bean 2. 属性如何注入 ```java @Component public class User { //相当于 //也可以放在set方法上 @Value("张三") public String name; } ``` 3. 衍生的注解 @Component有几个衍生注解,我们在web开发中,会按照mvc三层架构分层 - dao 【@Repository】 - service 【@Service】 - controller 【@Controller】 ==这四个注解功能都是一样的,都是代表将某个类注册到Spring中,装配Bean== 4. 自动装配置 - @Autowired:自动装配通过类型,名字。如果Autowired不能唯一自动装配上属性,则需要通过@Qualifier(value = “xxx”) - @Nullable 字段标记了了这个注解,说明这个字段可以为null; - @Resource:自动装配通过名字。类型 5. 作用域 ```java @Component @Scope("singleton") public class User { //相当于 //也可以放在set方法上 @Value("张三") public String name; } ``` 6. 小结 **xml 与 注解:** - xml更加万能,适用于任何场合!维护简单方便 - 注解不是自己类使用不了,维护相对复杂! **xml与注解最佳实践:** - xml用来管理bean; - 注解只负责完成属性的注入; - 我们在使用的过程中,只需要注意一个问题:必须让注解生效,就需要开启注解的支持 ```xml ``` ## 9 使用Java的方式配置Spring 我们现在要完全不使用Spring的xml配置了,全权交给Java来做! JavaConfig是Spring的一个子项目,在Spring4之后,它成为了一个核心功能! ![image.png](https://s2.loli.net/2022/08/29/NjRlJAYP3xX1IaW.png) 实体类: ```java @Component public class User { @Value("张三") private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } @Override public String toString() { return "User{" + "name='" + name + '\'' + '}'; } } ``` 配置文件 ```java // @Configuration 代表这是一个配置类,就和我们之前的beans.xml一样 @Configuration //这个也会被spring容器托管,注册到容器中,因为他本来就是一个@Components @ComponentScan("com.swust.pojo") @Import(MyConfig2.class) public class MyConfig { //注册一个bean,就相当于我们之前写的一个bean标签 //这个方法的名字,就相当于bean标签中的id属性 //这个方法的返回值,就相当于bean标签中的class属性 @Bean public User getUser(){ return new User(); } } ``` 测试类 ```java public class MyTest { public static void main(String[] args) { //如果完全使用了配置类去做,我们就只能通过AnnotationConfig上下文来获取容器,通过配置类的class对象加载 ApplicationContext context = new AnnotationConfigApplicationContext(MyConfig.class); User getUser = (User) context.getBean("getUser"); System.out.println(getUser.getName()); } } ``` 这种纯Java的配置方式,在SpringBoot中随处可见! ## 10 代理模式 为什么要学习代理模式?因为这就是SpringAOP的底层!【SpringAOP和SpringMVC】 代理模式的分类: - 静态代理 - 动态代理 ![image.png](https://s2.loli.net/2022/08/29/sIOfXeGhic1Ctx6.png) ### 10.1 静态代理 角色分析: - 抽象角色:一般会使用接口或者抽象类来解决 - 真实角色:被代理的角色 - 代理角色:代理真实角色,代理真实角色后,我们一般会做一些附属操作 - 客户:访问代理对象的人! 代码步骤: 1. 接口 ```java public interface Rent { public void rent(); } ``` 2. 真实角色 ```java public class Host implements Rent { @Override public void rent() { System.out.println("房东要出租房子"); } } ``` 3. 代理角色 ```java public class Proxy implements Rent{ private Host host; public Proxy() { } public Proxy(Host host) { this.host = host; } @Override public void rent() { seeHouse(); host.rent(); hetong(); fare(); } //看房 public void seeHouse(){ System.out.println("中介带你看房"); } //收中介费 public void fare(){ System.out.println("中介收中介费"); } //合同 public void hetong(){ System.out.println("签租赁合同"); } } ``` 4. 客户端访问代理角色 ```java public class Client { public static void main(String[] args) { //房东要租房 Host host = new Host(); //代理,中介帮房东租房子,但是代理角色一般有一些附属操作 Proxy proxy = new Proxy(); //你不用面对房东,直接找中介租房即可 proxy.rent(); } } ``` 代理模式的好处: - 可以使真实角色的操作更加纯粹!不用去关注一些公共的业务 - 公共角色就交给代理角色!实现了业务的分工! - 公共业务发生扩展的时候,方便集中管理! 缺点: - 一个真实角色就会产生一个代理角色,代码量会翻倍,开发效率会变低~ ### 10.2 加深理解 代码步骤: 1. 接口 ```java public interface UserService { public void add(); public void delete(); public void update(); public void query(); } ``` 2. 真实角色 ```java public class UserServiceImpl implements UserService{ @Override public void add() { System.out.println("增加了一个用户"); } @Override public void delete() { System.out.println("删除了一个用户"); } @Override public void update() { System.out.println("修改了一个用户"); } @Override public void query() { System.out.println("查询了一个用户"); } //1.改动原有的业务代码,在公司中是大忌 } ``` 3. 代理角色 ```java public class UserServiceProxy implements UserService{ private UserServiceImpl userService; public void setUserService(UserServiceImpl userService) { this.userService = userService; } @Override public void add() { log("add"); userService.add(); } @Override public void delete() { log("delete"); userService.delete(); } @Override public void update() { log("update"); userService.update(); } @Override public void query() { log("query"); userService.query(); } //日志方法 public void log(String msg){ System.out.println("使用了"+msg+"方法"); } } ``` 4. 客户端访问代理角色 ```java public class Client { public static void main(String[] args) { UserServiceImpl userService = new UserServiceImpl(); UserServiceProxy proxy = new UserServiceProxy(); proxy.setUserService(userService); proxy.add(); } } ``` 聊聊AOP ![image.png](https://s2.loli.net/2022/08/29/Vl1yrqOwjfA6eKp.png) ### 10.3 动态代理 - 动态代理和静态代理角色一样 - 动态代理的代理类是动态生成的,不是我们直接写好的! - 动态代理分为两大类:基于接口的动态代理,基于类的动态代理 - 基于接口 ---- JDK动态代理【我们在这里使用】 - 基于类:cglib - java字节码实现:javassist 需要了解两个类:**Proxy:代理**;**InvocationHandler:调用处理程序。** 代码步骤: 1. 接口 ```java public interface Rent { public void rent(); } ``` 2. 真实角色 ```java public class Host implements Rent{ public void rent() { System.out.println("房东要出租房子!"); } } ``` 3. ProxyInvocationHandler类 ```java //我们会用这个类,自动生成代理类! public class ProxyInvocationHandler implements InvocationHandler { //被代理的接口 private Rent rent; public void setRent(Rent rent) { this.rent = rent; } //生成得到代理类 public Object getProxy(){ return Proxy.newProxyInstance(this.getClass().getClassLoader(), rent.getClass().getInterfaces(),this); } //处理代理实例,并返回结果 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //动态代理的本质,就是使用反射机制实现! Object result = method.invoke(rent, args); seeHose(); fee(); return result; } public void seeHose(){ System.out.println("中介带着看房子!"); } public void fee(){ System.out.println("中介收取费用!"); } } ``` 4. 测试 ```java public class Client { public static void main(String[] args) { //真实角色 Host host = new Host(); //代理角色:现在没有 ProxyInvocationHandler pih = new ProxyInvocationHandler(); //通过调用程序处理角色来处理我们要调用的接口对象! pih.setRent(host); Rent proxy = (Rent) pih.getProxy(); //这里的proxy就是动态生成的,我们并没有写 proxy.rent(); } } ``` 在此,我们可以提炼出ProxyInvocationHandler作为工具类 ```java //用这个类自动生成代理类! public class ProxyInvocationHandler implements InvocationHandler { //被代理的接口 private Object target; public void setTarget(Object target) { this.target = target; } //生成得到代理类 public Object getProxy(){ return Proxy.newProxyInstance(this.getClass().getClassLoader(), target.getClass().getInterfaces(),this); } //处理代理实例,并返回结果 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { log(method.getName()); Object result = method.invoke(target, args); return result; } public void log(String msg){ System.out.println("[Debug] 使用了一个"+msg+"方法"); } } ``` **动态代理的好处:** - 可以使真实角色的操作更加纯粹!不用去关注一些公共的业务 - 公共角色就交给代理角色!实现了业务的分工! - 公共业务发生扩展的时候,方便集中管理! - 一个动态代理类代理的是一个接口,一般就是对应的一类业务 - 一个动态代理类可以代理多个类,只要是实现了同一个接口即可! ## 11 AOP ### 11.1 什么是AOP AOP(Aspect Oriented Programming)意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。 ![image.png](https://s2.loli.net/2022/08/31/rx3CE6BW7QzpJl4.png) ### 11.2 AOP在Spring中的作用 ==提供声明式事务;允许用户自定义切面== 横切关注点:跨越应用程序多个模块的方法或功能。即是,与我们业务逻辑无关的,但是我们需要关注的部分,就是横切关注点。如日志,安全,缓存,事务等等… - 切面(ASPECT):横切关注点被模块化的特殊对象。即,它是一个类。 - 通知(Advice):切面必须要完成的工作。即,它是类中的一个方法。 - 目标(Target):被通知对象。 - 代理(Proxy):向目标对象应用通知之后创建的对象。 - 切入点(PointCut):切面通知执行的“地点”的定义。 - 连接点(JointPoint):与切入点匹配的执行点。 ![image.png](https://s2.loli.net/2022/08/31/gJzKkt8ensyv4oM.png) SpringAOP中,通过Advice定义横切逻辑,Spring中支持5种类型的Advice: ![image.png](https://s2.loli.net/2022/08/31/wYmvlGMCZoxgNDS.png) 即AOP在不改变原有代码的情况下,去增加新的功能。 ### 11.3 使用Spring实现AOP 【重点】使用AOP植入,需要导入一个依赖包! ```xml org.aspectj aspectjweaver 1.9.4 ``` **方式一:** 使用Spring的API接口【主要是SpringAPI接口实现】 1. 在service包下,定义UserService业务接口和UserServiceImpl实现类 ```java public interface UserService { public void add(); public void delete(); public void update(); public void select(); } ``` ```java public class UserServiceImpl implements UserService{ @Override public void add() { System.out.println("增加了一个用户"); } @Override public void delete() { System.out.println("删除了一个用户"); } @Override public void update() { System.out.println("修改了一个用户"); } @Override public void select() { System.out.println("查找了一个用户"); } } ``` 2. 在log包下,定义我们的增强类,一个Log前置增强和一个AfterLog后置增强类 ```java public class Log implements MethodBeforeAdvice { //method:要执行的目标对象的方法 //args:参数 //target:目标对象 @Override public void before(Method method, Object[] args, Object target) throws Throwable { System.out.println(target.getClass().getName()+"的"+method.getName()+"被执行了"); } } ``` ```java public class AftertLog implements AfterReturningAdvice { //returnValue;返回值 @Override public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable { System.out.println("执行了"+method.getName()+"方法,返回结果为"+returnValue); } } ``` 3. 最后去spring的文件中注册 , 并实现aop切入实现 , 注意导入约束,配置applicationContext.xml文件 ```xml ``` 4. 测试 ```java public class MyTest { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); //动态代理代理的是接口,注意点 UserService userService = (UserService) context.getBean("userService"); userService.add(); } } ``` ![image.png](https://s2.loli.net/2022/08/31/mUJ3eDIBAMqHogz.png) **方式二:** 自定义类来实现AOP【主要是切面定义】 1. 在diy包下定义自己的DiyPointCut切入类 ```java public class DiyPointCut { public void before(){ System.out.println("=======方法执行前=========="); } public void after(){ System.out.println("=======方法执行后=========="); } } ``` 2. 去spring中配置文件 ```xml ``` 3. 测试 ```java public class MyTest { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); //动态代理代理的是接口,注意点 UserService userService = (UserService) context.getBean("userService"); userService.add(); } } ``` ![image.png](https://s2.loli.net/2022/08/31/azOV6y27IifKJxe.png) **方式三:** 使用注解实现! 1. 在diy包下定义注解实现的AnnotationPointCut增强类 ```java @Aspect //标注这个类是个切面 public class AnnotationPointCut { @Before("execution(* com.swust.service.UserServiceImpl.*(..))") public void before(){ System.out.println("========方法执行前======="); } @After("execution(* com.swust.service.UserServiceImpl.*(..))") public void after(){ System.out.println("=======方法执行后=========="); } //在环绕增强中,我们可以给定一个参数,代表我们要获取处理切入的点 @Around("execution(* com.swust.service.UserServiceImpl.*(..))") public void around(ProceedingJoinPoint joinPoint) throws Throwable { System.out.println("环绕前"); //执行方法 Object proceed = joinPoint.proceed(); System.out.println("环绕后"); } } ``` 2. 在Spring配置文件中,注册bean,并增加支持注解的配置。 ```xml ``` 3. 测试 ```java public class MyTest { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); //动态代理代理的是接口,注意点 UserService userService = (UserService) context.getBean("userService"); userService.add(); } } ``` ## 12 整合Mybatis 步骤: 1. 导入相关jar包 - junit - mybatis - mysql数据库 - spring相关 - aop织入器 - mybatis-spring整合包【重点】在此还导入了lombok包。 - 配置Maven静态资源过滤问题! 2. 编写配置文件 3. 测试 ### 12.1 回忆mybatis 1. 编写pojo实体类 ```java @Data public class User { private int id; private String name; private String pwd; } ``` 2. 编写实现mybatis的配置文件 ```xml ``` 3. 编写UserMapper接口 ```java public interface UserMapper { public List selectUser(); } ``` 4. 编写UserMapper.xml文件 ```xml ``` 5. 测试 ```java public class MyTest { @Test public void test() throws IOException { String resources = "mybatis-config.xml"; InputStream in = Resources.getResourceAsStream(resources); SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(in); SqlSession sqlSession = sessionFactory.openSession(true); UserMapper mapper = sqlSession.getMapper(UserMapper.class); List userList = mapper.selectUser(); for (User user : userList) { System.out.println(user); } sqlSession.close(); } } ``` ### 12.2 Mybatis-Spring **什么是MyBatis-Spring?** MyBatis-Spring 会帮助你将 MyBatis 代码无缝地整合到 Spring 中。 文档链接:[http://mybatis.org/spring/zh/index.html](http://mybatis.org/spring/zh/index.html) 如果使用 Maven 作为构建工具,仅需要在 pom.xml 中加入以下代码即可: ```xml org.mybatis mybatis-spring 2.0.2 ``` **整合实现一:** 1. 引入Spring配置文件spring-dao.xml ```xml ``` 2. 配置数据源替换mybaits的数据源 ```xml ``` 3. 配置SqlSessionFactory,关联MyBatis ```xml ``` 4. 注册sqlSessionTemplate,关联sqlSessionFactory ```xml ``` 5. 需要UserMapper接口的UserMapperImpl 实现类,私有化sqlSessionTemplate ```java public class UserMapperImpl implements UserMapper{ //我们的所有操作,都使用sqlSession,在原来,现在都是用SqlSessionTemplate; private SqlSessionTemplate sqlSession; public void setSqlSession(SqlSessionTemplate sqlSession) { this.sqlSession = sqlSession; } @Override public List selectUser() { UserMapper mapper = sqlSession.getMapper(UserMapper.class); return mapper.selectUser(); } } ``` 6. 将自己写的实现类,注入到Spring配置文件中。 ```xml ``` 7. 测试使用即可! ```java public class MyTest { @Test public void test() throws IOException { ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); UserMapper userMapper = context.getBean("userMapper", UserMapper.class); for (User user : userMapper.selectUser()) { System.out.println(user); } } } ``` 8. 结果成功输出!现在我们的Mybatis配置文件的状态!发现都可以被Spring整合! ```xml ``` 我们可以把固定写法保存到spring-dao.xml中,创建总的applicationContext.xml来调用它: - spring-dao.xml ```xml ``` - applicationContext.xml ```xml ``` **整合实现二:** mybatis-spring1.2.3版以上的才有这个,官方文档截图: dao继承Support类 , 直接利用 getSqlSession() 获得 , 然后直接注入SqlSessionFactory . 比起整合方式一 , 不需要管理SqlSessionTemplate , 而且对事务的支持更加友好 . 可跟踪源码查看。 > **SqlSessionDaoSupport** `SqlSessionDaoSupport` 是一个抽象的支持类,用来为你提供 `SqlSession`。调用 `getSqlSession()` 方法你会得到一个 `SqlSessionTemplate`,之后可以用于执行 SQL 方法,就像下面这样: ```java public class UserDaoImpl extends SqlSessionDaoSupport implements UserDao { public User getUser(String userId) { return getSqlSession().selectOne("org.mybatis.spring.sample.mapper.UserMapper.getUser", userId); } } ``` 在这个类里面,通常更倾向于使用 `MapperFactoryBean`,因为它不需要额外的代码。但是,如果你需要在 DAO 中做其它非 MyBatis 的工作或需要一个非抽象的实现类,那么这个类就很有用了。 `SqlSessionDaoSupport` 需要通过属性设置一个 `sqlSessionFactory` 或 `SqlSessionTemplate`。如果两个属性都被设置了,那么 `SqlSessionFactory` 将被忽略。 假设类 `UserMapperImpl` 是 `SqlSessionDaoSupport` 的子类,可以编写如下的 Spring 配置来执行设置: ```xml ``` 测试: 1. 将我们上面写的UserMapperImpl修改一下 ```java public class UserMapperImpl2 extends SqlSessionDaoSupport implements UserMapper{ @Override public List selectUser() { SqlSession sqlSession = getSqlSession(); UserMapper mapper = sqlSession.getMapper(UserMapper.class); return mapper.selectUser(); } } ``` 2. 注入到Spring配置文件中 ```xml ``` 3. 测试 ```java public class MyTest { @Test public void test() throws IOException { ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); UserMapper userMapper = context.getBean("userMapper2", UserMapper.class); for (User user : userMapper.selectUser()) { System.out.println(user); } } } ``` ## 13 声明式事务 ### 13.1 回顾事务 - 把一组业务当成一个业务来做;要么都成功,要么都失败! - 事务在项目开发中,十分的重要,涉及到数据的一致性问题,不能马虎! - 确保完整性和一致性。 **事务ACID原则:** - 原子性 - 一致性 - 隔离性 - 多个业务可能操作同一个资源,防止数据损坏 - 持久性 - 事务一旦提交,无论系统发生什么问题,结果多不会在被影响,被持久化的写到存储器中 测试: 将上面的代码拷贝到一个新项目中 在之前的案例中,我们给userMapper接口新增两个方法,删除和增加用户; ```java public interface UserMapper { public List selectUser(); //添加一个用户 public int addUser(User user); //删除一个用户 public int deleteUser(int id); } ``` UserMapper文件,我们故意把 deletes 写错,测试! ```xml insert into mybatis.user (id, name, pwd) values (#{id},#{name},#{pwd}); deletes from mybatis.user where id=#{id}; ``` 编写接口的UserMapperImpl实现类,在实现类中,我们去操作一波 ```java public class UserMapperImpl extends SqlSessionDaoSupport implements UserMapper{ @Override public List selectUser() { User user = new User(5, "小王", "123456"); UserMapper mapper = getSqlSession().getMapper(UserMapper.class); mapper.addUser(user); mapper.deleteUser(5); return mapper.selectUser(); } @Override public int addUser(User user) { return getSqlSession().getMapper(UserMapper.class).addUser(user); } @Override public int deleteUser(int id) { return getSqlSession().getMapper(UserMapper.class).deleteUser(id); } } ``` 测试 ```java public class MyTest { @Test public void test1(){ ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); UserMapper userMapper = context.getBean("userMapper", UserMapper.class); for (User user : userMapper.selectUser()) { System.out.println(user); } } } ``` 测试结果: - 报错:sql异常,delete写错了 - 结果 :数据库结果显示插入成功! 没有进行事务的管理;我们想让他们都成功才成功,有一个失败,就都失败,我们就应该需要事务! 以前我们都需要自己手动管理事务,十分麻烦! 但是**Spring给我们提供了事务管理,我们只需要配置即可;** ### 13.2 Spring中的事务管理 Spring在不同的事务管理API之上定义了一个抽象层,使得开发人员不必了解底层的事务管理API就可以使用Spring的事务管理机制。Spring支持编程式事务管理和声明式的事务管理。 **编程式事务管理** - 将事务管理代码嵌到业务方法中来控制事务的提交和回滚 - 缺点:必须在每个事务操作业务逻辑中包含额外的事务管理代码 **声明式事务管理** - 一般情况下比编程式事务好用。 - 将事务管理代码从业务方法中分离出来,以声明的方式来实现事务管理。 - 将事务管理作为横切关注点,通过aop方法模块化。Spring中通过Spring AOP框架支持声明式事务管理。 1. 使用Spring管理事务,注意头文件的约束导入 : tx ```xml xmlns:tx="http://www.springframework.org/schema/tx" http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd"> ``` 2. JDBC事务 ```xml ``` 3. 配置好事务管理器后我们需要去配置事务的通知 ```xml ``` **spring事务传播特性:** 事务传播行为就是多个事务方法相互调用时,事务如何在这些方法间传播。spring支持7种事务传播行为: - **propagation_requierd:**如果当前没有事务,就新建一个事务,如果已存在一个事务中,加入到这个事务中,这是最常见的选择 - **propagation_supports:**支持当前事务,如果没有当前事务,就以非事务方法执行。 - **propagation_mandatory:**使用当前事务,如果没有当前事务,就抛出异常。 - **propagation_required_new:**新建事务,如果当前存在事务,把当前事务挂起。 - **propagation_not_supported:**以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。 - **propagation_never:**以非事务方式执行操作,如果当前事务存在则抛出异常。 - **propagation_nested:**如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与propagation_required类似的操作。 Spring 默认的事务传播行为是 PROPAGATION_REQUIRED,它适合于绝大多数的情况。 就好比,我们刚才的几个方法存在调用,所以会被放在一组事务当中! 4. 配置AOP,导入aop的头文件 ```xml ``` 5. 删掉刚才插入的数据,再次测试! ```java public class MyTest { @Test public void test1(){ ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); UserMapper userMapper = context.getBean("userMapper", UserMapper.class); for (User user : userMapper.selectUser()) { System.out.println(user); } } } ``` **思考:** 为什么需要事务? - 如果不配置事务,可能存在数据提交不一致的情况; - 如果我们不在Spring中去配置声明式事务,我们就需要在代码中手动配置事务! - 事务在项目的开发中十分重要,涉及到数据的一致性和完整性问题,不容马虎!