# Spring5学习 **Repository Path**: BY-KK/spring5-learning ## Basic Information - **Project Name**: Spring5学习 - **Description**: 此仓库记录Spring5的学习日常,笔记以及代码 - **Primary Language**: Java - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 1 - **Forks**: 0 - **Created**: 2022-03-01 - **Last Updated**: 2023-05-03 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # Spring [官方文档](https://docs.spring.io/spring-framework/docs/5.2.0.RELEASE/spring-framework-reference/core.html) ![spring](img/spring.jpg) ## 1、Spring框架概述 - Spring是轻量级的开源的JavaEE框架 - Spring可以解决企业应用开发的复杂性 - Spring有两个核心部分:IOC 和 AOP 1. IOC:控制反转,把创建对象过程交给Spring进行管理 2. AOP:面向切面,把不修改源代码进行功能增强 - Spring特点 1. 方便解耦,简化开发 2. AOP编程支持 3. 方便程序测试 4. 方便集成各种优秀框架 5. 降低JavaEE API 的使用难度 6. 方便进行事务操作 7. Java源码是经典学习规范 **总结一句话:Spring就是一个轻量级的控制反转(IoC)和面向切面编程(AOP)的框架!** ## 2、利用Spring管理对象初体验 **在父模块中导入jar包** ```xml org.springframework spring-webmvc 5.2.7.RELEASE ``` **pojo的Hello.java** ```java package 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 "Holle [str=" + str + "]"; } } ``` **在resource里面的xml配置** ```xml ``` **测试类MyTest** ```java package holle1; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import pojo.Hello; public class MyTest { public static void main(String[] args) { //获取Spring的上下文对象 ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml"); //我们的对象下能在都在spring·中管理了,我们要使用,直接取出来就可以了 Hello holle = (Hello) context.getBean("hello"); System.out.println(holle.toString()); } } ``` **核心用set注入,所以必须要有下面的set()方法** ```java //Hello类 public void setStr(String str) { this.str = str; } ``` **思考:** - Hello 对象是谁创建的 hello 对象是由Spring创建的 - Hello 对象的属性是怎么设置的 hello 对象的属性是由Spring容器设置的 这个过程就叫做控制反转: **控制**:谁来控制对象的创建,传统应用程序的对象是由程序本身控制创建的,使用Spring后,对象是由Spring来创建的。 **反转**:程序本身不创建对象,而变成被动的接受对象 **依赖注入**:就是利用set方法进行注入 IOC是一种编程思想,由主动的编程变成了被动的接收 **IOC:对象由Spring 来创建,管理,装配!** **总结:** 所有的类都要装配的beans.xml 里面; 所有的bean 都要通过容器去取; 容器里面取得的bean,拿出来就是一个对象,用对象调用方法即可; ### 3.1、IOC 概念和原理 **什么是IOC4** (1)控制反转,把对象创建和对象之间的调用过程,交给 Spring 进行管理 (2)使用 IOC 目的:为了降低耦合度 (3)做入门案例就是 IOC 实现 **IOC 底层原理** xml 解析、工厂模式、反射 ![ioc底层原理简图](img/ioc1.png) ### 3.2、IOC(BeanFactory 接口) IOC 思想基于 IOC 容器完成,IOC 容器底层就是对象工厂 Spring 提供 IOC 容器实现两种方式:(两个接口) (1)BeanFactory:IOC 容器基本实现,是 Spring 内部的使用接口,不提供开发人员进行使用 **\* 加载配置文件时候不会创建对象,在获取对象(使用)才去创建对象** (2)ApplicationContext:BeanFactory 接口的子接口,提供更多更强大的功能,一般由开发人员进行使用 **\* 加载配置文件时候就会把在配置文件对象进行创建** ## 3、IoC创建对象的方式 1. 使用无参构造创建对象,默认。 2. 使用有参构造(如下) 下标赋值 index指的是有参构造中参数的下标,下标从0开始; ```xml ``` 类型赋值(不建议使用) ```xml ``` 直接通过参数名(掌握) ```xml ``` > name方式需要无参构造和set方法,index和type只需要有参构造 **总结:在配置文件加载的时候,容器(< bean>)中管理的对象就已经初始化了** ## 4、IOC容器面试细节 #### 4.1、**扩展: 容器如何创建对象** IOC容器在准备创建对象时, 会判断是否有配置 factory-method方法 如果有配置 会调用factory-method所指向的方法构建对象. 如果没配置,会检查是否有配置构造参数 无构造参数: 调用默认构造器创建对象 有构造参数: 根据参数情况匹配对应的构造器 #### 4.2、**扩展: bean的生命周期** spring 容器中的bean的完整生命周期一共分为十一步完成。 1.bean对象的实例化 2.封装属性,也就是设置properties中的属性值 3.如果bean实现了BeanNameAware,则执行setBeanName方法,也就是bean中的id值 4.如果实现BeanFactoryAware或者ApplicationContextAware ,需要设置setBeanFactory或者上下文对象setApplicationContext 5.如果存在类实现BeanPostProcessor后处理bean,执行postProcessBeforeInitialization,可以在初始化之前执行一些方法 6.如果bean实现了InitializingBean,则执行afterPropertiesSet,执行属性设置之后的操作 7.调用执行指定的初始化方法 8.如果存在类实现BeanPostProcessor则执行postProcessAfterInitialization,执行初始化之后的操作 9.执行自身的业务方法 10.如果bean实现了DisposableBean,则执行spring的的销毁方法 11.调用执行自定义的销毁方法。 #### 4.3**扩展: bean的循环依赖问题** A 依赖 B B 依赖 A 产生闭环,称为循环依赖 ·Spring 默认允许单例对象的属性注入 所产生的循环依赖 单例对象的循环依赖 Spring通过3级缓存来解决 比如一个类A中有一个属性是B类,B类中有一个属性是A类,这时看Spring是怎么解决他们的相互依赖的。Spring注入一个类的大体步骤分为两部分,一是先完成对类的构造工作,二是会对类的属性进行设置和填充。首先Spring构造A类,通过AbstractAutowireCapableBeanFactory的doCreateBean方法中调用addSingletonFactory方法将A类曝光到singletonFactories中。这时完成A的构造后,需要填充B属性,继续第二步,发现B还没有构造,于是开始B流程的构造过程,构造的时候发现需要填充A,从第三层缓存singletonFactories中找到A(此时的A还没有完全构造完成,但是可以拿到A的一个引用),B拿到A的引用后,完成B自己的填充属性工作,完成初始化工作,把自己放到第一层缓存singletonObjects中。这时回到A的这边,在拿到B对象后,完成自己的填充属性工作。 | 源码 | 级别 | 描述 | | --------------------- | -------- | ------------------------------------------------------------ | | singletonObjects | 一级缓存 | 用于存放完全初始化好的 bean,从该缓存中取出的 bean 可以直接使用 | | earlySingletonObjects | 二级缓存 | 存放原始的 bean 对象(尚未填充属性),用于解决循环依赖 | | singletonFactories | 三级缓存 | 存放 bean 工厂对象,用于解决循环依赖 | - ·如果是构造器依赖属性 会报循环依赖异常 - ·如果对象都是多例对象 会报循环依赖异常 - ·如果设置allowCircularReferences为false 会报循环依赖异常 ```java protected void customizeBeanFactory(DefaultListableBeanFactory beanFactory) { if (this.allowBeanDefinitionOverriding != null) { beanFactory.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding); } if (this.allowCircularReferences != null) { beanFactory.setAllowCircularReferences(this.allowCircularReferences); } } ``` #### 4.4、**扩展: bean的覆盖问题** 默认情况: 同一个配置文件中出现id相同的bean会报错,不同的配置文件出现id相同的bean后加载的bean会将先加载的bean覆盖掉称为bean的覆盖,bean的覆盖不会报错,但可能影响我们的项目,可以通过属性设置不允许bean的覆盖,allowBeanDefinitionOverriding设置为false。 ## 5、Spring 配置 **给Bean取别名** ```xml ``` **Bean的配置** ```xml ``` import一般用于团队开发使用,它可以将多个配置文件,导入合并为一个 假设,现在项目中有多个人开发,这三个人复制不同的类开发,不同的类需要注册在不同的bean中,我们可以利 用import将所有人的beans.xml合并为一个总的! - 张三(beans.xm1) - 李四(beans2.xm1) - 王五(beans3.xm1) - applicationContext.xml ```xml ``` **使用的时候,直接使用总的配置就可以了** > 按照在总的xml中的导入顺序来进行创建,后导入的会重写先导入的,最终实例化的对象会是后导入xml中的那个 ## 6、依赖注入(DI) ### 6.1、构造器注入 第4点有提到 ### 6.2、set方式注入【重点】 依赖注入:set注入! - 依赖:bean对象的创建依赖于容器 - 注入:bean对象中的所有属性,由容器来注入 【环境搭建】 1. 复杂类型 Address类 ```java @Data public class Address { private String address; } ``` 2. 真实测试对象 Student类 ```java @Data public class Student { private String name; private Address address; private String[] books; private List hobbys; private Map card; private Set games; private String wife; private Properties info; } ``` 3. beans.xml ```xml 红楼 三国 music swimming coding CF LOL GTA 20200526 root root ``` 4. 测试 MyTest ```java public class MyTest { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); Student student = (Student) context.getBean("Student"); System.out.println(student); } } ``` ### 6.3、拓展注入 官方文档 >##### XML shortcut with the p-namespace > >The p-namespace enables you to use the `bean` element’s attributes, instead of nested `` elements, to describe your property values and/or collaborating beans. > >Spring supports extensible configuration formats [with namespaces](https://docs.spring.io/spring-framework/docs/5.0.0.RC3/spring-framework-reference/appendix.html#xsd-configuration), which are based on an XML Schema definition. The `beans`configuration format discussed in this chapter is defined in an XML Schema document. However, the p-namespace is not defined in an XSD file and exists only in the core of Spring. > >The following example shows two XML snippets that resolve to the same result: The first uses standard XML format and the second uses the p-namespace. >##### XML shortcut with the c-namespace > >Similar to the [XML shortcut with the p-namespace](https://docs.spring.io/spring-framework/docs/5.0.0.RC3/spring-framework-reference/core.html#beans-p-namespace), the *c-namespace*, newly introduced in Spring 3.1, allows usage of inlined attributes for configuring the constructor arguments rather then nested `constructor-arg` elements. > >Let’s review the examples from [Constructor-based dependency injection](https://docs.spring.io/spring-framework/docs/5.0.0.RC3/spring-framework-reference/core.html#beans-constructor-injection) with the `c:` namespace: > >The `c:` namespace uses the same conventions as the `p:` one (trailing `-ref` for bean references) for setting the constructor arguments by their names. And just as well, it needs to be declared even though it is not defined in an XSD schema (but it exists inside the Spring core). > >For the rare cases where the constructor argument names are not available (usually if the bytecode was compiled without debugging information), one can use fallback to the argument indexes: pojo增加User类 ```java @Data public class User { private String name; private int id; public User() { } public User(String name, int id) { this.name = name; this.id = id; } } ``` 注意: beans 里面加上这下面两行 使用p和c命名空间需要导入xml约束 xmlns:p=“http://www.springframework.org/schema/p” xmlns:c=“http://www.springframework.org/schema/c” ```xml ?xml version="1.0" encoding="UTF-8"?> ``` 测试: ```java @Test public void test2(){ ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); User user1 = context.getBean("user1", User.class); User user = context.getBean("user", User.class); System.out.println(user); System.out.println(user1); } ``` ### 6.4、Bean 的作用域 | Scope | Description | | :----------------------------------------------------------- | :----------------------------------------------------------- | | [singleton](https://docs.spring.io/spring-framework/docs/5.2.0.RELEASE/spring-framework-reference/core.html#beans-factory-scopes-singleton) | (Default) Scopes a single bean definition to a single object instance for each Spring IoC container. | | [prototype](https://docs.spring.io/spring-framework/docs/5.2.0.RELEASE/spring-framework-reference/core.html#beans-factory-scopes-prototype) | Scopes a single bean definition to any number of object instances. | | [request](https://docs.spring.io/spring-framework/docs/5.2.0.RELEASE/spring-framework-reference/core.html#beans-factory-scopes-request) | Scopes a single bean definition to the lifecycle of a single HTTP request. That is, each HTTP request has its own instance of a bean created off the back of a single bean definition. Only valid in the context of a web-aware Spring `ApplicationContext`. | | [session](https://docs.spring.io/spring-framework/docs/5.2.0.RELEASE/spring-framework-reference/core.html#beans-factory-scopes-session) | Scopes a single bean definition to the lifecycle of an HTTP `Session`. Only valid in the context of a web-aware Spring `ApplicationContext`. | | [application](https://docs.spring.io/spring-framework/docs/5.2.0.RELEASE/spring-framework-reference/core.html#beans-factory-scopes-application) | Scopes a single bean definition to the lifecycle of a `ServletContext`. Only valid in the context of a web-aware Spring `ApplicationContext`. | | [websocket](https://docs.spring.io/spring-framework/docs/5.2.0.RELEASE/spring-framework-reference/web.html#websocket-stomp-websocket-scope) | Scopes a single bean definition to the lifecycle of a `WebSocket`. Only valid in the context of a web-aware Spring `ApplicationContext`. | | Scope | Description | | :----------------------------------------------------------- | :----------------------------------------------------------- | | [singleton(单例)](https://docs.spring.io/spring-framework/docs/5.2.0.RELEASE/spring-framework-reference/core.html#beans-factory-scopes-singleton) | (默认)对于每个Spring IoC容器,将单个bean定义限定为单个对象实例。 | | [prototype(多例)](https://docs.spring.io/spring-framework/docs/5.2.0.RELEASE/spring-framework-reference/core.html#beans-factory-scopes-prototype) | 将单个bean定义的范围限定为任意数量的对象实例。 | | [request](https://docs.spring.io/spring-framework/docs/5.2.0.RELEASE/spring-framework-reference/core.html#beans-factory-scopes-request) | 将单个 bean 定义限定为单个 HTTP 请求的生命周期。 也就是说,每个 HTTP 请求都有自己的 bean 实例,该实例是在单个 bean 定义的后面创建的。 仅在 Web 感知 Spring `ApplicationContext` 的上下文中有效。 | | [session](https://docs.spring.io/spring-framework/docs/5.2.0.RELEASE/spring-framework-reference/core.html#beans-factory-scopes-session) | 将单个 bean 定义限定为 HTTP `Session` 的生命周期。 仅在 Web 感知 Spring `ApplicationContext` 的上下文中有效。 | | [application](https://docs.spring.io/spring-framework/docs/5.2.0.RELEASE/spring-framework-reference/core.html#beans-factory-scopes-application) | 将单个 bean 定义限定为 `ServletContext` 的生命周期。 仅在 Web 感知 Spring `ApplicationContext` 的上下文中有效。 | | [websocket](https://docs.spring.io/spring-framework/docs/5.2.0.RELEASE/spring-framework-reference/web.html#websocket-stomp-websocket-scope) | 将单个 bean 定义限定为 `WebSocket` 的生命周期。 仅在 Web 感知 Spring `ApplicationContext` 的上下文中有效。 | 如需设置Bean 的作用域,只需要在bean标签里增加一个scope属性即可 ```xml ``` >默认情况下是单例模式,request、session、application、websocket 这4个作用域只能在web项目中使用 ## 7、Bean 的自动装配 - 自动装配是Spring满足bean依赖的一种方式 - Spring会在上下文自动寻找,并自动给bean装配属性 在Spring中有三种装配的方式 1. 在xml中显示配置 2. 在java中显示配置 3. 隐式的自动装配bean 【重要】 ### 7.1、XML中设置自动装配 环境搭建:一个人有两个宠物 pojo的Cat类 ```java public class Cat { public void shut(){ System.out.println("miao"); } } ``` pojo的Dog类 ```java public class Dog { public void shut(){ System.out.println("wow"); } } ``` pojo的People类 ```java @Data public class People { private Cat cat; private Dog dog; private String name; } ``` **xml配置 -> byType 自动装配** ```xml ``` **xml配置 -> byName 自动装配** ```xml ``` > **byName只能取到小写,大写取不到** 总结: - byType自动装配:byType会自动查找,和自己对象set方法参数的类型相同的bean保证所有的class唯一(类为全局唯一) - byName自动装配:byName会自动查找,和自己对象set对应的值对应的id保证所有id唯一,并且和set注入的值一致 ### 7.2、注解方式 Bean 自动装配 The introduction of annotation-based configuration raised the question of whether this approach is “better” than XML.(翻译:基于注释的配置的引入提出了一个问题,即这种方法是否比XML“更好”) 1. 导入context约束 **xmlns:context="http://www.springframework.org/schema/context"** 2. 配置注解的支持:< context:annotation-config/> ```xml ``` #### 7.2.1、@Autowired **默认是byType方式,如果匹配不上,就会byName** 在属性上使用,也可以在set上使用 我们可以不用编写set方法了,前提是自动装配的属性在Spring容器里,且要符合ByName 自动装配 ```java public class People { @Autowired private Cat cat; @Autowired private Dog dog; private String name; } ``` > @Nullable 字段标记了这个注解,说明该字段可以为空 > > public name(@Nullable String name){ > > } ```java //源码 public @interface Autowired { boolean required() default true; } ``` **如果定义了Autowire的require属性为false,说明这个对象可以为null,否则不允许为空(false表示找不到装配,不抛出异常)** #### 7.2.2、@Autowired+@Qualifier **@Autowired不能唯一装配时,需要@Autowired+@Qualifier** 如果@Autowired自动装配环境比较复杂。自动装配无法通过一个注解完成的时候,可以使用**@Qualifier(value = “dog”)**去配合使用,指定一个唯一的id对象 ```java public class People { @Autowired private Cat cat; @Autowired @Qualifier(value = "dog") private Dog dog; private String name; } ``` **如果xml文件中同一个对象被多个bean使用,Autowired无法按类型找到,可以用@Qualifier指定id查找** #### 7.2.3、@Resource **默认是byName方式,如果匹配不上,就会byType** ```java public class People { Resource(name="cat") private Cat cat; Resource(name="dog") private Dog dog; private String name; } ``` - **Autowired是byType,@Autowired+@Qualifier = byType || byName** - **Autowired是先byteType,如果唯一則注入,否则byName查找。resource是先byName,不符合再继续byType** **@Resource和@Autowired区别:** - 都是用来自动装配的,都可以放在属性字段上 - @Autowired通过byType的方式实现,而且必须要求这个对象存在!【常用】 - @Resource默认通过byName的方式实现,如果找不到名字,则通过byType实现!如果两个都找不到的情况下,就报错!【常用】 - 执行顺序不同:@Autowired通过byType的方式实现。@Resource默认通过byName的方式实现 ## 8、使用注解开发 在spring4之后,使用注解开发,必须要保证aop包的导入 ![image-20200802201924490](E:\2021\个人仓库\Spring\Spring5课堂笔.assets\aHR0cHM6Ly9naXRlZS5jb20vd29fYmVsbC9QaWN0dXJlQmVkL3Jhdy9tYXN0ZXIvaW1hZ2UvaW1hZ2UtMjAyMDA4MDIyMDE5MjQ0OTAucG5n) 使用注解需要导入contex的约束 ```xml ``` ### 8.1、bean 弹幕评论: 有了< context:component-scan>,另一个< context:annotation-config/>标签可以移除掉,因为已经被包含进去了。 ```xml ``` ```java //@Component 组件 //等价于 @Component public class User { public String name ="秦疆"; } ``` ### 8.2、属性如何注入@value ```java @Component public class User { //相当于 @value("kuangshen") public String name; //也可以放在set方法上面 //@value("kuangshen") public void setName(String name) { this.name = name; } } ``` ### 8.3、衍生的注解 @Component有几个衍生注解,会按照web开发中,mvc架构中分层。 - dao (@Repository) - service(@Service) - controller(@Controller) **这四个注解的功能是一样的,都是代表将某个类注册到容器中** ### 8.4、自动装配置 @Autowired:默认是byType方式,如果匹配不上,就会byName @Nullable:字段标记了这个注解,说明该字段可以为空 @Resource:默认是byName方式,如果匹配不上,就会byType ### 8.5、作用域@scope ```java //原型模式prototype,单例模式singleton //scope("prototype")相当于 @Component @scope("prototype") public class User { //相当于 @value("kuangshen") public String name; //也可以放在set方法上面 @value("kuangshen") public void setName(String name) { this.name = name; } } ``` ### 8.6、小结 **xml与注解:** - xml更加万能,维护简单,适用于任何场合 - 注解,不是自己的类使用不了,维护复杂 **最佳实践:** - xml用来管理bean - 注解只用来完成属性的注入 - 要开启注解支持 ## 9、使用Java的方式配置Spring 不使用Spring的xml配置,完全交给java来做! Spring的一个子项目,在spring4之后,,,它成为了核心功能 ![image-20200802215752868](E:\2021\个人仓库\Spring\Spring5课堂笔.assets\aHR0cHM6Ly9naXRlZS5jb20vd29fYmVsbC9QaWN0dXJlQmVkL3Jhdy9tYXN0ZXIvaW1hZ2UvaW1hZ2UtMjAyMDA4MDIyMTU3NTI4NjgucG5n) **实体类:pojo的User.java** ```java //这里这个注解的意思,就是说明这个类被Spring接管了,注册到了容器中 @component public class User { private String name; public String getName() { return name; } //属性注入值 @value("QINJIANG') public void setName(String name) { this.name = name; } @Override public String toString() { return "user{" + "name='" + name + '\''+ '}'; } } ``` 弹幕评论:要么使用@Bean,要么使用@Component和ComponentScan,两种效果一样 **配置文件:config中的kuang.java** @Import(KuangConfig2.class),用@import来包含KuangConfig2.java ```java //这个也会Spring容器托管,注册到容器中,因为他本米就是一个@Component // @Configuration表这是一个配置类,就像我们之前看的beans.xml,类似于标签 @Configuration @componentScan("com.Kuang.pojo") //开启扫描 //@Import(KuangConfig2.class) public class KuangConfig { //注册一个bean , 就相当于我们之前写的一个bean 标签 //这个方法的名字,就相当于bean 标签中的 id 属性 ->getUser //这个方法的返同值,就相当于bean 标签中的class 属性 ->User //@Bean public User getUser(){ return new User(); //就是返回要注入到bean的对象! } } ``` 弹幕评论:ComponentScan、@Component("pojo”) 这两个注解配合使用 **测试类** ```java public class MyTest { public static void main(String[ ] args) { //如果完全使用了配置类方式去做,我们就只能通过 Annotationconfig 上下文来获取容器,通过配置类的class对象加载! ApplicationContext context = new AnnotationConfigApplicationContext(KuangConfig.Class); //class对象 User getUser =(User)context.getBean( "getUser"); //方法名getUser System.out.Println(getUser.getName()); } } ``` **会创建两个相同对象问题的说明:** **弹幕总结 - -> @Bean是相当于< bean>标签创建的对象,而我们之前学的@Component是通过spring自动创建的这个被注解声明的对象,所以这里相当于有两个User对象被创建了。一个是bean标签创建的(@Bean),一个是通过扫描然后使用@Component,spring自动创建的User对象,所以这里去掉@Bean这些东西,然后开启扫描。之后在User头上用@Component即可达到spring自动创建User对象了** ```java //这个也会Spring容器托管,注册到容器中,因为他本米就是一个@Component // @Configuration表这是一个配置类,就像我们之前看的beans.xml,类似于标签 @Configuration @componentScan("com.Kuang.pojo") //开启扫描 //@Import(KuangConfig2.class) public class KuangConfig { //注册一个bean , 就相当于我们之前写的一个bean 标签 //这个方法的名字,就相当于bean 标签中的 id 属性 ->getUser //这个方法的返同值,就相当于bean 标签中的class 属性 ->User //@Bean public User getUser(){ return new User(); //就是返回要注入到bean的对象! } } ``` 弹幕评论:ComponentScan、@Component("pojo”) 这两个注解配合使用 **测试类** ```java public class MyTest { public static void main(String[ ] args) { //如果完全使用了配置类方式去做,我们就只能通过 Annotationconfig 上下文来获取容器,通过配置类的class对象加载! ApplicationContext context = new AnnotationConfigApplicationContext(KuangConfig.Class); //class对象 User getUser =(User)context.getBean( "getUser"); //方法名getUser System.out.Println(getUser.getName()); } } ``` **会创建两个相同对象问题的说明:** **弹幕总结 - -> @Bean是相当于< bean>标签创建的对象,而我们之前学的@Component是通过spring自动创建的这个被注解声明的对象,所以这里相当于有两个User对象被创建了。一个是bean标签创建的(@Bean),一个是通过扫描然后使用@Component,spring自动创建的User对象,所以这里去掉@Bean这些东西,然后开启扫描。之后在User头上用@Component即可达到spring自动创建User对象了** ## 10、动态代理 代理模式是SpringAOP的底层 分类:动态代理和静态代理 ![image-20200803101427846](E:\2021\个人仓库\Spring\Spring5课堂笔.assets\aHR0cHM6Ly9naXRlZS5jb20vd29fYmVsbC9QaWN0dXJlQmVkL3Jhdy9tYXN0ZXIvaW1hZ2UvaW1hZ2UtMjAyMDA4MDMxMDE0Mjc4NDYucG5n) ### 10.1、静态代理 ![image-20200803101621868](E:\2021\个人仓库\Spring\Spring5课堂笔.assets\aHR0cHM6Ly9naXRlZS5jb20vd29fYmVsbC9QaWN0dXJlQmVkL3Jhdy9tYXN0ZXIvaW1hZ2UvaW1hZ2UtMjAyMDA4MDMxMDE2MjE4NjgucG5n) 代码步骤: 1、接口 ```java package pojo; public interface Host { public void rent(); } ``` 2、真实角色 ```java package pojo; public class HostMaster implements Host{ public void rent() { System.out.println("房东要出租房子"); } } ``` 3、代理角色 ```java package pojo; public class Proxy { public Host host; public Proxy() { } public Proxy(Host host) { super(); this.host = host; } public void rent() { seeHouse(); host.rent(); fee(); sign(); } //看房 public void seeHouse() { System.out.println("看房子"); } //收费 public void fee() { System.out.println("收中介费"); } //合同 public void sign() { System.out.println("签合同"); } } ``` 4、客户端访问代理角色 ```java package holle4_proxy; import pojo.Host; import pojo.HostMaster; import pojo.Proxy; public class My { public static void main(String[] args) { //房东要出租房子 Host host = new HostMaster(); //中介帮房东出租房子,但也收取一定费用(增加一些房东不做的操作) Proxy proxy = new Proxy(host); //看不到房东,但通过代理,还是租到了房子 proxy.rent(); } } ``` ![image-20200803105229478](E:\2021\个人仓库\Spring\Spring5课堂笔.assets\aHR0cHM6Ly9naXRlZS5jb20vd29fYmVsbC9QaWN0dXJlQmVkL3Jhdy9tYXN0ZXIvaW1hZ2UvaW1hZ2UtMjAyMDA4MDMxMDUyMjk0NzgucG5n) 代码翻倍:几十个真实角色就得写几十个代理 AOP横向开发 ![image-20200803111539621](E:\2021\个人仓库\Spring\Spring5课堂笔.assets\aHR0cHM6Ly9naXRlZS5jb20vd29fYmVsbC9QaWN0dXJlQmVkL3Jhdy9tYXN0ZXIvaW1hZ2UvaW1hZ2UtMjAyMDA4MDMxMTE1Mzk2MjEucG5n) ### 10.2、动态代理 动态代理和静态角色一样,动态代理底层是反射机制 动态代理类是动态生成的,不是我们直接写好的! 动态代理(两大类):基于接口,基于类 - 基于接口:JDK的动态代理【使用ing】 - 基于类:cglib - java字节码实现:javasisit 了解两个类 1、Proxy:代理 2、InvocationHandler:调用处理程序 ![image-20200803112619868](E:\2021\个人仓库\Spring\Spring5课堂笔.assets\aHR0cHM6Ly9naXRlZS5jb20vd29fYmVsbC9QaWN0dXJlQmVkL3Jhdy9tYXN0ZXIvaW1hZ2UvaW1hZ2UtMjAyMDA4MDMxMTI2MTk4NjgucG5n) 实例: 接口 Host.java ```java //接口 package pojo2; public interface Host { public void rent(); } ``` 接口Host实现类 HostMaster.java ```java //接口实现类 package pojo2; public class HostMaster implements Host{ public void rent() { System.out.println("房东要租房子"); } } ``` 代理角色的处理程序类 ProxyInvocationHandler.java ```java package pojo2; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; ///用这个类,自动生成代理 public class ProxyInvocationHandler implements InvocationHandler { // Foo f =(Foo) Proxy.NewProxyInstance(Foo. Class.GetClassLoader(), // new Class[] { Foo.Class }, // handler); // 被代理的接口 public HostMaster hostMaster ; public void setHostMaster(HostMaster hostMaster) { this.hostMaster = hostMaster; } // 得到生成的代理类 public Object getProxy() { // newProxyInstance() -> 生成代理对象,就不用再写具体的代理类了 // this.getClass().getClassLoader() -> 找到加载类的位置 // hostMaster.getClass().getInterfaces() -> 代理的具体接口 // this -> 代表了接口InvocationHandler的实现类ProxyInvocationHandler return Proxy.newProxyInstance(this.getClass().getClassLoader(), hostMaster.getClass().getInterfaces(), this); // 处理代理实例并返回结果 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { seeHouse(); // 动态代理的本质,就是使用反射机制实现的 // invoke()执行它真正要执行的方法 Object result = method.invoke(hostMaster, args); fee(); return result; } public void seeHouse() { System.out.println("看房子"); } public void fee() { System.out.println("收中介费"); } } ``` 用户类 My2.java ```java package holle4_proxy; import pojo2.Host; import pojo2.Host2; import pojo2.HostMaster; import pojo2.ProxyInvocationHandler; public class My2 { public static void main(String[] args) { //真实角色 HostMaster hostMaster = new HostMaster(); //代理角色,现在没有;用代理角色的处理程序来实现Host接口的调用 ProxyInvocationHandler pih = new ProxyInvocationHandler(); //pih -> HostMaster接口类 -> Host接口 pih.setHostMaster(hostMaster); //获取newProxyInstance动态生成代理类 Host proxy = (Host) pih.getProxy(); proxy.rent(); } } ``` 弹幕评论: 什么时候调用invoke方法的? 代理实例调用方法时invoke方法就会被调用,可以debug试试 改为**万能代理类** ```java ///用这个类,自动生代理 public class ProxyInvocationHandler implements InvocationHandler { // 被代理的接口 public 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 { // 动态代理的本质,就是使用反射机制实现的 // invoke()执行它真正要执行的方法 Object result = method.invoke(target, args); return result; } } ``` ![image-20200803133035484](E:\2021\个人仓库\Spring\Spring5课堂笔.assets\aHR0cHM6Ly9naXRlZS5jb20vd29fYmVsbC9QaWN0dXJlQmVkL3Jhdy9tYXN0ZXIvaW1hZ2UvaW1hZ2UtMjAyMDA4MDMxMzMwMzU0ODQlMjAtJTIwJUU1JThBJUE4JUU2JTgwJTgxJUU0JUJCJUEzJUU3JTkwJTg2LnBuZw) ## 11、AOP ### 11.1、什么是AOP ![image-20200803134502169](E:\2021\个人仓库\Spring\Spring5课堂笔.assets\aHR0cHM6Ly9naXRlZS5jb20vd29fYmVsbC9QaWN0dXJlQmVkL3Jhdy9tYXN0ZXIvaW1hZ2UvaW1hZ2UtMjAyMDA4MDMxMzQ1MDIxNjklMjAtJTIwQU9QLnBuZw) ### 11.2、AOP在Spring中的使用 提供声明式事务,允许用户自定义切面 - 横切关注点:跨越应用程序多个模块的方法或功能。即是,与我们业务逻辑无关的,但是我们需要关注的部分,就是横切关注点。如日志,安全,缓存,事务等等… - 切面(Aspect):横切关注点 被模块化的特殊对象。即,它是一个类。(Log类) - 通知(Advice):切面必须要完成的工作。即,它是类中的一个方法。(Log类中的方法) - 目标(Target):被通知对象。(生成的代理类) - 代理(Proxy):向目标对象应用通知之后创建的对象。(生成的代理类) - 切入点(PointCut):切面通知执行的”地点”的定义。(最后两点:在哪个地方执行,比如:method.invoke()) - 连接点(JointPoint):与切入点匹配的执行点。 ![image-20200803154043909](E:\2021\个人仓库\Spring\Spring5课堂笔.assets\aHR0cHM6Ly9naXRlZS5jb20vd29fYmVsbC9QaWN0dXJlQmVkL3Jhdy9tYXN0ZXIvaW1hZ2UvaW1hZ2UtMjAyMDA4MDMxNTQwNDM5MDkucG5n) SpringAOP中,通过Advice定义横切逻辑,Spring中支持5种类型的Advice: ![image-20200803135937435](E:\2021\个人仓库\Spring\Spring5课堂笔.assets\aHR0cHM6Ly9naXRlZS5jb20vd29fYmVsbC9QaWN0dXJlQmVkL3Jhdy9tYXN0ZXIvaW1hZ2UvaW1hZ2UtMjAyMDA4MDMxMzU5Mzc0MzUucG5n) **即AOP在不改变原有代码的情况下,去增加新的功能。**(代理) ### 11.3、使用Spring实现AOP 导入jar包 ```xml org.aspectj aspectjweaver 1.9.4 ``` #### 11.3.1、方法一:使用原生spring接口 springAPI接口实现 applicationContext.xml ```xml ``` execution(返回类型,类名,方法名(参数)) -> execution(* com.service.*,*(…)) UserService.java ```java package service; public interface UserService { public void add() ; public void delete() ; public void query() ; public void update(); } ``` UserService 的实现类 UserServiceImp.java ```java package service; public class UserServiceImpl implements UserService { public void add() { System.out.println("add增"); } public void delete() { System.out.println("delete删"); } public void update() { System.out.println("update改"); } public void query() { System.out.println("query查"); } } ``` 前置Log.java ```java package log; import org.springframework.aop.MethodBeforeAdvice; import java.lang.reflect.Method; public class Log implements MethodBeforeAdvice { //method:要执行的目标对象的方法 //args:参数 //target:目标对象 public void before(Method method, Object[] args, Object target) throws Throwable { System.out.println(target.getClass().getName()+"的"+method.getName()+"被执行了"); } } ``` 后置AfterLog.java ```java package log; import java.lang.reflect.Method; import org.springframework.aop.AfterReturningAdvice; public class AfterLog implements AfterReturningAdvice { //returnVaule: 返回值 public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable { System.out.println("执行了"+method.getName()+"方法,返回值是"+returnValue); } } ``` 测试类MyTest5 ```java import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import service.UserService; public class MyTest5 { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); //注意:动态代理代理的是接口 UserService userService = (UserService) context.getBean("userservice"); userService.add(); } } ``` #### 11.3.2、方法二:自定义类实现AOP ```xml ``` ```java package diy; public class DiyPointcut { public void before(){ System.out.println("插入到前面"); } public void after(){ System.out.println("插入到后面"); } } ``` ```java //测试 public class MyTest5 { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); //注意:动态代理代理的是接口 UserService userService = (UserService) context.getBean("userservice"); userService.add(); } } ``` #### 11.3.3、方法三:使用注解实现 ```xml ``` DiyAnnotation.java ```java package diy; 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; @Aspect //标注这个类是一个切面 public class DiyAnnotation { @Before("execution(* service.UserServiceImpl.*(..))") public void before(){ System.out.println("=====方法执行前====="); } @After("execution(* service.UserServiceImpl.*(..))") public void after(){ System.out.println("=====方法执行后====="); } //在环绕增强中,我们可以给地暖管一个参数,代表我们要获取切入的点 @Around("execution(* service.UserServiceImpl.*(..))") public void around(ProceedingJoinPoint joinPoint) throws Throwable { System.out.println("环绕前"); Object proceed = joinPoint.proceed(); System.out.println("环绕后"); } } ``` 测试 ```java public class MyTest5 { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); //注意:动态代理代理的是接口 UserService userService = (UserService) context.getBean("userservice"); userService.add(); } } ``` 输出结果: ![image-20200803175642064](E:\2021\个人仓库\Spring\Spring5课堂笔.assets\aHR0cHM6Ly9naXRlZS5jb20vd29fYmVsbC9QaWN0dXJlQmVkL3Jhdy9tYXN0ZXIvaW1hZ2UvaW1hZ2UtMjAyMDA4MDMxNzU2NDIwNjQucG5n) ## 12、整合mybatis mybatis-spring官网:https://mybatis.org/spring/zh/ **mybatis的配置流程:** 1. 编写实体类 2. 编写核心配置文件 3. 编写接口 4. 编写Mapper.xmi 5. 测试 ### 12.1、mybatis-spring-方式一 1. 编写数据源配置 2. sqISessionFactory 3. sqISessionTemplate(相当于sqISession) 4. 需要给接口加实现类【new】 5. 将自己写的实现类,注入到Spring中 6. 测试! 先导入jar包 ```xml org.springframework spring-webmvc 5.2.7.RELEASE org.aspectj aspectjweaver 1.9.4 org.springframework spring-jdbc 5.2.7.RELEASE org.mybatis mybatis 3.5.2 org.mybatis mybatis-spring 2.0.4 mysql mysql-connector-java 8.0.12 org.projectlombok lombok 1.18.12 src/main/java **/*.properties **/*.xml false src/main/resources **/*.properties **/*.xml false ``` ![文件路径](E:\2021\个人仓库\Spring\Spring5课堂笔.assets\aHR0cHM6Ly9naXRlZS5jb20vd29fYmVsbC9QaWN0dXJlQmVkL3Jhdy9tYXN0ZXIvaW1hZ2UvaW1hZ2UtMjAyMDA4MDQxMjMyMTA1NjAucG5n) **编写顺序:** **User -> UserMapper -> UserMapper.xml -> spring-dao.xml -> UserServiceImpl -> applicationContext.xml -> MyTest6** **代码步骤:** pojo实体类 User ```java package pojo; import lombok.Data; @Data public class User { private int id; private String name; private String pwd; } ``` mapper目录下的 UserMapper、UserMapperImpl、UserMapper.xml 接口UserMapper ```java package mapper; import java.util.List; import pojo.User; public interface UserMapper { public List getUser(); } ``` UserMapperImpl ```java package mapper; import java.util.List; import org.mybatis.spring.SqlSessionTemplate; import pojo.User; public class UserMapperImpl implements UserMapper{ //我们的所有操作,在原来都使用sqlSession来执行,现在都使用SqlSessionTemplate; private SqlSessionTemplate sqlSessionTemplate; public void setSqlSessionTemplate(SqlSessionTemplate sqlSessionTemplate) { this.sqlSessionTemplate = sqlSessionTemplate; } public List getUser() { UserMapper mapper = sqlSessionTemplate.getMapper(UserMapper.class); return mapper.getUser(); } } ``` UserMapper.xml (狂神给面子才留下来的) ```xml ``` resource目录下的 mybatis-config.xml、spring-dao.xml、applicationContext.xml mybatis-config.xml ```xml ``` spring-dao.xml ```xml ``` applicationContext.xml ```xml ``` 测试类 ```java import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import mapper.UserMapper; import pojo.User; public class MyTest6 { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); UserMapper userMapper = (UserMapper) context.getBean("userMapper"); for (User user : userMapper.getUser()) { System.out.println(user); } } } ``` ### 12.2、mybatis-spring-方式二 UserServiceImpl2 ```java package mapper; import pojo.User; import org.apache.ibatis.session.SqlSession; import org.mybatis.spring.support.SqlSessionDaoSupport; import java.util.List; //继承SqlSessionDaoSupport 类 public class UserMapperImpl2 extends SqlSessionDaoSupport implements UserMapper { public List getUser() { SqlSession sqlSession = getSqlSession(); UserMapper mapper = sqlSession.getMapper(UserMapper.class); return mapper.getUser(); //或者一句话:return getSqlSession().getMapper(UserMapper.class).getUser(); } } ``` spring-dao.xml ```xml ``` applicationContext.xml ```xml ``` 测试 ```java public class MyTest6 { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); UserMapper userMapper = (UserMapper) context.getBean("userMapper2"); for (User user : userMapper.getUser()) { System.out.println(user); } } } ``` ## 13. 声明式事务 - 把一组业务当成一个业务来做;要么都成功,要么都失败! - 事务在项目开发中,十分的重要,涉及到数据的一致性问题 - 确保完整性和一致性 事务的ACID原则: 1、原子性 2、隔离性 3、一致性 4、持久性 ACID参考文章:https://www.cnblogs.com/malaikuangren/archive/2012/04/06/2434760.html Spring中的事务管理 - 声明式事务:AOP - 编程式事务:需要再代码中,进行事务管理 **声明式事务** 先导入jar包 ```xml org.springframework spring-webmvc 5.2.7.RELEASE org.aspectj aspectjweaver 1.9.4 org.springframework spring-jdbc 5.2.7.RELEASE org.mybatis mybatis 3.5.2 org.mybatis mybatis-spring 2.0.4 mysql mysql-connector-java 8.0.12 org.projectlombok lombok 1.18.12 src/main/java **/*.properties **/*.xml false src/main/resources **/*.properties **/*.xml false ``` **代码步骤:** pojo实体类 User ```java package pojo; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @Data @AllArgsConstructor @NoArgsConstructor public class User { private int id; private String name; private String pwd; } ``` mapper目录下的 UserMapper、UserMapperImpl、UserMapper.xml 接口UserMapper ```java package mapper; import java.util.List; import org.apache.ibatis.annotations.Param; import pojo.User; public interface UserMapper { public List getUser(); public int insertUser(User user); public int delUser(@Param("id") int id); } ``` UserMapperImpl ```java package mapper; import pojo.User; import org.apache.ibatis.session.SqlSession; import org.mybatis.spring.support.SqlSessionDaoSupport; import java.util.List; public class UserMapperImpl extends SqlSessionDaoSupport implements UserMapper { public List getUser() { User user = new User(5,"你好","ok"); insertUser(user); delUser(5); SqlSession sqlSession = getSqlSession(); UserMapper mapper = sqlSession.getMapper(UserMapper.class); return mapper.getUser(); //或者return getSqlSession().getMapper(UserMapper.class).getUser(); } //插入 public int insertUser(User user) { return getSqlSession().getMapper(UserMapper.class).insertUser(user); } //删除 public int delUser(int id) { return getSqlSession().getMapper(UserMapper.class).delUser(id); } } ``` UserMapper.xml ```xml insert into mybatis.mybatis (id,name,pwd) values (#{id},#{name},#{pwd}) deleteAAAAA from mybatis.mybatis where id = #{id} ``` resource目录下的 mybatis-config.xml、spring-dao.xml、applicationContext.xml mybatis-config.xml ```xml ``` spring-dao.xml(已导入约束) ```xml ``` applicationContext.xml ```xml ``` 测试类 ```java import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import mapper.UserMapper;import pojo.User; public class MyTest7 { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); UserMapper userMapper = (UserMapper) context.getBean("userMapper"); for (User user : userMapper.getUser()) { System.out.println(user); } } } ``` **思考:** 为什么需要事务? - 如果不配置事务,可能存在数据提交不一致的情况下; - 如果不在spring中去配置声明式事务,我们就需要在代码中手动配置事务! - 事务在项目的开发中非常重要,涉及到数据的一致性和完整性问题!