以下内容为学习Spring框架时所记的笔记,现在翻出来复习、整理一下分享出来,如果内容有需要修正的地方,欢迎各位在评论区指正,如果转载请附带原文地址表明出处,谢谢大家。

在这里插入图片描述

Spring是什么?

Spring是一个轻量级的控制反转(IOC)和面向切面编程(AOP)的容器。能够解决企业级应用开发的业务逻辑层和其他各层之间的耦合问题,简化企业级应用程序开发的复杂性,即简化Java开发。
1、Spring是一个开源免费的框架 , 容器 .
2、Spring是一个轻量级的框架 , 非侵入式的 .
3、控制反转 IoC , 面向切面 Aop
4、对事物的支持 , 对框架的支持

Spring有两个核心特性分别是:IOC(控制反转)和AOP(面向切面编程)。

Spring由哪些模块组成?

(1)Spring Core :框架最基础的部分,提供IOC容器,对bean进行管理;

(2)Spring Context:基于bean,提供上下文对象,扩展出JNDI、EJB、电子邮件、校验调度等功能。

(3)Spring DAO:提供了JDBC的抽象层,它可以消除冗(rong)长的JDBC编码和解析数据库厂商特有的错误代码,还提供了声明式事务管理方法。

(4)Spring ORM:提供了常用的“对象/关系”映射APIs的集成。其中包括JPA、JDO、Mybatis等。

(5)Spring AOP:提供了符合AOP Alliance规范的面向切面的编程实现。

(6)Spring Web:提供了基础的Web开发的上下文信息,可与其他web进行集成。

(7)Spring Web MVC:提供了Web应用的Model-View-Controller全功能的实现。

Spring的特点?

  • 非侵入式:基于 Spring 开发的应用中的对象可以不依赖于 Spring 的 API。
  • 容器:Spring 是一个容器,因为它包含并且管理应用对象的生命周期。
  • 控制反转:IOC (Inversion of Control),指的是将对象的创建权交给 Spring 去创建。使用 Spring 之前,对象的创建都是由我们自己在代码中 new 创建。而使用 Spring之后。对象的创建都是由给了 Spring 框架。
  • 依赖注入:DI (Dependency Injection),是指依赖的对象不需要手动调用 setXX 方法去设置,而是通过配置赋值。
  • 面向切面编程:Aspect Oriented Programming——AOP。
  • 组件化:Spring 实现了使用简单的组件配置组合成一个复杂的应用。在 Spring 中可以使用 XML 和 Java 注解组合这些对象。
  • 一站式:在 IOC 和 AOP 的基础上可以整合各种企业应用的开源框架和优秀的第三方类库。

Spring IOC容器的介绍

IOC的思想

IOC (Inversion of Control) 是指在程序开发中,对象实例的创建不再由调用者管理,而是由 Spring 容器创建。Spring 容器会负责控制程序之间的关系,而不是由程序代码直接控制,因此,控制权由程序代码转移到了 Spring 容器中,控制权发生了反转,这就是 Spring 的IOC 思想。

IOC容器的概念

IOC 容器就是具有依赖注入功能的容器,IOC 容器负责实例化、定位、配置应用程序中的对象及建立这些对象间的依赖。应用程序无需直接在代码中 new 相关的对象,应用程序由 IOC 容器进行组装。在 Spring 中 BeanFactory 是 IOC 容器的实际代表者。

Bean的概念

在 Spring 中,被 Spring 容器所管理的对象称之为”Bean”对象。一个 Spring 的 Bean 对象可以是任何形式的 POJO。

Spring IOC 容器类型

Spring 提供了两种 IoC 容器,分别为 BeanFactory 和 ApplicationContext。

BeanFactory

BeanFactory 是基础类型的 IoC 容器。
它由 org.springframework.beans.facytory.BeanFactory 接口定义,并提供了完整的 IoC服务支持。简单来说,BeanFactory 就是一个管理 Bean 的工厂,它主要负责初始化各种Bean,并调用它们的生命周期方法。

ApplicationContext

ApplicationContext 是 BeanFactory 的子接口,也被称为应用上下文。该接口的全路径为 org.springframework.context.ApplicationContext,它不仅提供了 BeanFactory 的所有功能,还添加了对国际化、资源访问、事件传播等方面的良好支持。

ApplicationContext 接口有两个常用的实现类:

  • ClassPathXmlApplicationContext
    该 类 从 类 路 径 ClassPath 中 寻 找 指 定 的 XML 配 置 文 件 , 找 到 并 装 载 完 成ApplicationContext 的实例化工作。
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext(String configLocation);
    FileconfigLocation 参数用于指定 Spring 配置文件的名称和位置。

  • SystemXmlApplicationContext
    该类从指定的文件系统路径中寻找指定的 XML 配置文件,找到并装载完成
    ApplicationContext 的实例化工作。
    ApplicationContext applicationContext = new FileSystemXmlApplicationContext(String configLocation)
    它与 ClassPathXmlApplicationContext 的区别是:在读取 Spring 的配置文件时,FileSystemXmlApplicationContext 不再从类路径中读取配置文件,而是通过参数指定配置文件的位置,它可以获取类路径之外的资源,如“F:/workspaces/applicationContext.xml”;

使用IOC有什么好处?

概念:IOC,Inversion of Control(控制反转),它是一种设计思想,就是将你设计好的对象交给容器控制,而不是自己去创建。把创建和查找依赖对象的控制权交给IOC容器,由IOC容器进行注入、组合对象。

优点:实现了对象与对象之间的松耦合、便于测试、功能可复用,减少了对象的创建和内存的消耗,使得程序的整体结构变得更方便维护、灵活性更高、扩展性更强。

大白话解释:
IOC就是控制反转,就是让一个对象的创建不用自己去new了,使用Spring之后对象可以自动的产生,这其实就是利用了java中的反射,反射其实就是在运行时动态的去创建、调用对象,Spring就是在运行的时候,在Spring的配置文件,也就是XML文件中来动态的创建对象和调用对象中的方法。

ioc的思想最核心的地方在于,资源不由使用资源的双方管理,而由不使用资源的第三方管理,这可以带来很多好处。
第一,资源集中管理,实现资源的可配置和易管理。第二,降低了使用资源双方的依赖程度,也就是我们说的耦合度。

举例子说:
比如甲方需要一双袜子,而乙方它卖一双袜子,它要把袜子卖出去,但是乙方要告诉别人我要卖一双袜子。甲乙双方进行交易活动,但是他们都不需要自己直接去找卖家,相当于程序内部开放接口,卖家由第三方作为参数传入。甲乙互相不依赖,而且只有在进行交易活动的时候,甲才和乙产生联系。反之亦然。这样做什么好处么呢,甲乙可以在对方不真实存在的情况下独立存在,而且保证不交易时候无联系,想交易的时候可以很容易的产生联系。甲乙交易活动不需要双方见面,避免了双方的互不信任造成交易失败的问题。因为交易由第三方来负责联系,而且甲乙都认为第三方可靠。那么交易就能很可靠很灵活的产生和进行了。

SpringIOC创建对象的方式有哪些?

(1)通过无参构造器创建对象;

<bean id="stu2" class="com.oldou.spring2.Student"></bean>

需要类中提供无参构造。
(2)通过有参构造器创建对象;

<!--【B】使用有参构造创建对象-->
    <!--
       Student  stu3=new Student(18,zs,男);
       注意
         [1]name属性和形参的名称保持一致的
         [2]形参的顺序不用和标签的顺序一致
         [3]我们使用name属性进行调用
            但是除了name属性还有index(从0)
            type :数据类型
            建议三者都写上即可
    -->
    <bean id="stu3" class="com.oldou.spring2.Student">
        <!--<constructor-arg name="a" value="18"></constructor-arg>
        <constructor-arg name="name" value="zs"></constructor-arg>
        <constructor-arg name="sex" value=""></constructor-arg>-
    </bean>

需要类中提供有参构造器。
(3)通过工厂模式创建对象。

<!-- Factory  factory=new Factory();-->
    <bean id="factory" class="com.oldou.spring3.Factory"></bean>
    <!-- factory.getInstance("tea");-->
    <bean id="be" factory-bean="factory" factory-method="getInstance">
        <constructor-arg name="param" value="stu"></constructor-arg>
    </bean>
    <!--Factory.getInstance2('stu')-->
    <bean id="be2" class="com.oldou.spring3.Factory" factory-method="getInstance2">
        <constructor-arg name="param" value="stu"></constructor-arg>
    </bean>

SpringIOC的三种注入(DI)方式?

依赖注入(Dependency Injection,DI)
依赖 : 指Bean对象的创建依赖于容器 . Bean对象的依赖资源 .
注入 : 指Bean对象所依赖的资源 , 由容器来设置和装配 .

Spring注入方式:

  • 通过构造器注入
  • 通过set方法注入
  • 通过注解自动注入(后面讲)

通过构造器注入

需要提供有参构造方法

<bean id="别名" class="全限定路径(包名+类名)">
    <constructor-arg name="属性名" value=""></constructor-arg>
</bean>

通过Set方法注入

需要提供属性的set方法。
这里列举以下几种类型:

public class Student {
    private String name;//属性
    private Address address;//对象
    private String[] books;//数组
    private List<String> hobbys;//List集合
    private Map<String,String> card;//Map集合
    private Set<String> games;//Set集合
    private String wife;//Null
    private Properties info;//配置文件信息
}
 public class Address {
    private String address;
}

1、常量注入

<bean id="addr" class="com.oldou.pojo.Address">
    <property name="address" value="长沙"/>
</bean>

2、Bean注入

<bean id="addr" class="com.kuang.pojo.Address">
     <property name="address" value="重庆"/>
 </bean>
 
 <bean id="student" class="com.kuang.pojo.Student">
     <property name="name" value="小明"/>
     <property name="address" ref="addr"/>
 </bean>

3、数组注入

 <bean id="student" class="com.kuang.pojo.Student">
     <property name="name" value="小明"/>
     <property name="address" ref="addr"/>
     <property name="books">
         <array>
             <value>西游记</value>
             <value>红楼梦</value>
             <value>水浒传</value>
         </array>
     </property>
 </bean>

4、List注入

 <property name="hobbys">
     <list>
         <value>听歌</value>
         <value>看电影</value>
         <value>爬山</value>
     </list>
 </property>

5、Map注入

 <property name="card">
     <map>
         <entry key="中国邮政" value="456456456465456"/>
         <entry key="建设" value="1456682255511"/>
     </map>
 </property>

6、set注入

 <property name="games">
     <set>
         <value>LOL</value>
         <value>BOB</value>
         <value>COC</value>
     </set>
 </property>

7、Null注入

 <property name="wife"><null/></property>

8、Properties注入

 <property name="info">
     <props>
         <prop key="学号">20190604</prop>
         <prop key="性别"></prop>
         <prop key="姓名">小明</prop>
     </props>
 </property>

测试代码

ApplicationContext context  =  new ClassPathXmlApplicationContext("applicationContext.xml");
Student student = (Student) context.getBean("student");
System.out.println(student.toString());

输出结果:
在这里插入图片描述

p命名和c命名注入(扩展)

实例代码:提供get、set、toString、带参、无参方法。

public class People {
    private String name;
    private int age;
    private String sex;
}

1、P命名空间注入 : 需要在头文件中加入约束文件

需要在Spring配置文件头信息中添加约束: xmlns:p="http://www.springframework.org/schema/p"

<!--P命名空间的使用,需要添加约束-->
<!--P(属性:properties)命名空间,这里要求属性要设置set方法-->
<bean id="people" class="com.oldou.pojo.People" p:name="小欧" p:age="18" p:sex=""/>

以上代码等价于以下:

<bean id="people" class="com.oldou.pojo.People">
    <property name="name" value="小欧"></property>
    <property name="age" value="18"></property>
    <property name="sex" value=""></property>
</bean>

2、c 命名空间注入 : 需要在头文件中加入约束文件,同时提供构造方法

需要导入约束:xmlns:c="http://www.springframework.org/schema/c"

<!--C命名空间的注入,需要在头文件中添加约束-->
<!--C(构造: Constructor)命名空间 , 属性依然要设置set方法-->
<bean id="people2" class="com.oldou.pojo.People" c:name="oldou" c:age="22"  c:sex="" />

以上代码等效于:

<bean id="people2" class="com.oldou.pojo.People">
    <constructor-arg name="name" value="oldou"></constructor-arg>
    <constructor-arg name="age" value="22"></constructor-arg>
    <constructor-arg name="sex" value=""></constructor-arg>
</bean>

Spring Bean的作用域

在Spring中,那些组成应用程序的主体及由Spring IoC容器所管理的对象,被称之为bean。简单地讲,bean就是由IoC容器初始化、装配及管理的对象 .

在Spring的配置文件中,通过在标签中的scope 属性设置作用域。

作用域的种类

(1)singleton:单例模式,使用单例模式定义的Bean在Spring容器中只有一个实例,这是Bean默认的作用域。

(2)prototype:原型模式,每次通过Spring容器获取prototype定义的Bean时,容器都会创建一个新的Bean实例,也就是创建新的对象。

(3)request:在一次HTTP请求中,容器会返回该Bean的同一个实例。而对于不同的HTTP请求,会返回不同的实例,
该作用域仅在当前的HTTP Request中有效,也就是当前HTTP请求中有效。

(4)session:在一次HTTP Session中,容器会返回该Bean的同一个实例。而对于不同的HTTP请求,会返回不同的实例。
该作用域仅在当前的HTTP Session中有效。

(5)global session:在一个全局的 HTTP Session 中,容器会返回该 Bean 的同一个实例。该作用域仅在使用 portlet context 时有效。

以上五种作用域中,单例模式和原型模式的作用域是最常用的两种。

Singleton作用域

Singleton 是 Spring 容器默认的作用域,当一个 Bean 的作用域为 singleton 时,Spring 容器中只会存在一个共享的 Bean 实例,并且所有对 Bean 的请求,只要 id 与该 Bean 定义相匹配,就只会返回 Bean 的同一个实例

在 Spring 配置文件中,可以使用 元素的 scope 属性,将 Bean 的作用域定义成 singleton,其配置方式如下所示:

<bean id="person" class="com.oldou.pojo.Person" scope="singleton"/>

在项目的 src 目录下创建一个名为 com.oldou.pojo 的包,在该包下创建 Person 类,类中不需要添加任何成员,然后创建 Spring 的配置文件 applicationContext.xml,将上述 Bean 的定义方式写入配置文件中,最后创建一个名为 PersonTest 的测试类,编辑后如下所示。

// 定义Spring配置文件路径		
String xmlPath = "com/mengma/scope/applicationContext.xml";
// 初始化Spring容器,加载配置文件,并对bean进行实例化
ApplicationContext applicationContext = new ClassPathXmlApplicationContext(xmlPath);
// 输出获得实例
 System.out.println(applicationContext.getBean("person"));
 System.out.println(applicationContext.getBean("person"));

输出结果为:
在这里插入图片描述
从输出结果可以得知,两次获取的对象是一样的,这就说明了该作用域下Spring容器只创建了一个Person实例,由于Spring容器Bean的默认作用域就是Singleton,如果不设置scope=“singleton”,则其输出结果也是一个实例。

Prototype作用域

使用prototype作用域时,每次请求该Bean都会创建一个新的Bean实例。
在Spring配置文件中,要将Bean定义为prototype作用域,只需要在元素的scope属性中将值设置为prototype就可以了,代码如下所示:

<bean id="person" class="com.mengma.scope.Person" scope="prototype"/>

测试代码如上一致,结果为
在这里插入图片描述
从图中可以证明,在该作用域下Spring容器创建了两个不同的实例。

Spring的bean是什么?它是线程安全的吗?

  • Spring bean是那些形成Spring应用主干的Java对象,它们被SpringIOC容器初始化、装配和管理,这些beans通过在容器中配置的元数据创建。
  • 默认的Spring容器中的bean是单例的,当单例中存在竞争条件时,会有线程安全问题。
  • Spring管理的bean的线程安全跟bean的创建作用域和bean所在的使用环境是否存在竞争有关,Spring并不能保证bean的线程安全。

Spring的Bean自动装配的三种方式

自动装配说明

  • 自动装配是使用spring满足bean依赖的一种方法
  • spring会在应用上下文中为某个bean寻找其依赖的bean。
  • Spring配置文件中通过在b标签中autowire属性进行设置

实例代码测试:

public class Cat {
   public void shout() {
       System.out.println("miao~");
  }
}
public class Dog {
   public void shout() {
       System.out.println("wang~");
  }
}
public class User {
   private Cat cat;
   private Dog dog;
   private String str;
}

自动装配方式一:byName(autowire byName,按照名称自动装配)

按照Bean的属性名来匹配要装配的Bean。

装配过程:当一个bean节点带有autowire="byName"的属性时,首先就会去其类中查找所有的set方法名,例如setCat(),获得除去set并且首字母小写的字符串,即cat,在配置文件中找bean中的id为cat的对象,如果有就取出注入,如果没有就会报空指针异常。

<bean id="dog" class="com.kuang.pojo.Dog"/>
<bean id="cat" class="com.kuang.pojo.Cat"/>

<bean id="user" class="com.kuang.pojo.User" autowire="byName"/>

自动装配方式二:byType (autowire byType,按照类型自动装配)

按照bean的类型来匹配要装配的bean,相当于匹配Spring上下文中标签中的Class属性中的值,例如com.oldou.pojo.Cat中Cat。
装配过程:
当一个bean节点带有autowire="byType"的属性时,首先就会匹配该类的类型,比如说是Cat类,那么就去bean中的Class节点中按照类型做匹配,如下代码所示:

<bean class="com.kuang.pojo.Dog"/>
<bean class="com.kuang.pojo.Cat"/>

<bean id="user" class="com.kuang.pojo.User" autowire="byType"/>

直接会将去匹配User类下的属性对象和上下文中bean标签中的Class去对比。

自动装配方式三:注解自动装配@Autowired

DK1.5支持的注解,Spring2.5就支持注解了。

1、在spring配置文件中引入context文件头

xmlns:context="http://www.springframework.org/schema/context"
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd

2、开启属性注解支持!

<context:annotation-config/>

@Autowired注解
@Autowired是按类型自动转配的,不支持id匹配。
使用注解需要导入 spring-aop的包!
直接在属性上使用即可,也可以在set方式上使用!
使用Autowired我们可以不用编写Set方法了,前提是你这个自动装配的属性在IOC(Spring)容器中存在,且符合名字byname!

如果@Autowired自动装配的环境比较复杂,自动装配无法通过一个注解【@Autowired】完成的时候、我们可以使用@Qualifier(value=“xxx”)去配合@Autowired的使用,指定一个唯一的bean对象注入!

@Qualifier注解
@Autowired是根据类型自动装配的,加上@Qualifier则可以根据byName的方式自动装配
@Qualifier不能单独使用。

实例代码:
在这里插入图片描述
@Resource注解(java的注解)

  • @Resource如有指定的name属性,先按该属性进行byName方式查找装配;
  • 其次再进行默认的byName方式进行装配;
  • 如果以上都不成功,则按byType的方式自动装配。
  • 都不成功,则报异常。

Resource和@ Autowired的区别

1、@Autowired与@Resource都可以用来装配bean。都可以写在字段上,或写在setter方法上。

2、@Autowired默认按类型装配(属于spring规范),默认情况下必须要求依赖对象必须存在,如果要允许null 值,可以设置它的required属性为false,如:@Autowired(required=false) ,如果我们想使用名称装配可以结合@Qualifier注解进行使用

3、@Resource,默认按照名称进行装配,名称可以通过name属性进行指定。如果没有指定name属性,当注解写在字段上时,默认取字段名进行按照名称查找,如果注解写在setter方法上默认取属性名进行装配。当找不到与名称匹配的bean时才按照类型进行装配。但是需要注意的是,如果name属性一旦指定,就只会按照名称进行装配。

Spring装配Bean的方式

(1)no方式,默认的方式,不自动装配,需要使用到节点或者参数;

(2)byName:根据bean的名称进行装配;

(3)byType:通过参数的数据类型进行装配;

(4)constructor:根据构造函数进行装配。

(5)Spring配置文件中节点的Autowire参数可以控制bean自动装配的方式。

Spring 框架中都用到了哪些设计模式?

(1)工厂模式:BeanFactory就是简单工厂模式的体现,用来创建对象的实例;

(2)单例模式:Bean默认为单例模式,还可以设置原型模式。

(3)代理模式:Spring的AOP功能用到了JDK的动态代理和CGLIB字节码生成技术;

(4)模板方法:用来解决代码重复的问题。比如. RestTemplate, JmsTemplate, JpaTemplate。

(5)观察者模式:定义对象键一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都会得到通知被制动更新,如Spring中listener的实现–ApplicationListener。

Spring中的注解详解(重点)

使用注解时,需要配置:
<context:component-scan base-package="扫描注解的包名"/>

Bean对象依赖注入注解

(1)@Autowired:自动装配,通过类型byType。如果不能唯一自动装配,那么就需要通过@Qualifier(value=“xxx”)

(2)@Qualifier:与 @Autowired 注解配合使用,会将默认的按 Bean 类型装配修改为按 Bean 的实例名称装配,Bean 的实例名称由 @Qualifier 注解的参数指定。

(3)@Nullable:字段标记了这个注解,说明了这个字段允许为null。

(4)@Resource:自动装配通过名字byName,如果名字装配不上就通过类型,如果都装配不上就报异常。

(5)@Value:可获取 Properties 文件中的值。还可以用来注入。

//等价于<property name= "name" value="oldou"/>
@Value("oldou")
public String name;

2、注解Bean对象注解

(1)@Component:可以使用此注解描述 Spring 中的 Bean,但它是一个泛化的概念,仅仅表示一个组件(Bean), 并且可以作用在任何层次。使用时只需将该注解标注在相应类上即可。

//相当于<bean id="user" cLass="com.oldou.pojo.User" />
@Component
public class User{}

(2)@Service:通常作用在业务层(Service 层),用于将业务层的类标识为 Spring 中的 Bean,其功能与 @Component 相同。

(3)@Repository:于将数据访问层(DAO 层)的类标识为 Spring 中的 Bean,其功能与 @Component 相同。

(4)@Controller:通常作用在控制层(如 Spring MVC 的 Controller),用于将控制层的类标识为 Spring 中的 Bean,其功能与 @Component 相同。

3、Spring配置相关注解

(1)@Configuration:声明当前类为配置类,相当于 xml 形式的 Spring 配置中的,该注解需要添加到类上。

(2)@Bean:注解在方法上,声明当前方法的返回值为一个 Bean 对象,该对象会被添加 SpringIOC容器中。和标签作用的相同。Bean 的实例名称由 @Qualifier 注解的参数指定。这个方法的名字就相当于bean标签的id属性,返回值就相当于bean标签中的class属性。

(3)@Import({AutoConfigurationImportSelector.class}):注解在类上,引入其他的配置类,类似于bean标签中的ref

(4)@ComponentScan:注解在方法上或者注解上,扫描包的注解,组件扫描,可自动发现和装配一些 Bean。

(4)@Scope:设置作用域,标注在类上,等价于在配置文件中bean中的作用域配置。

<bean id="person" class="com.oldou.pojo.Person" scope="singleton"/>

(5)@Transactional:可以实现事务控制的注解

以上的注解需要重点掌握,因为在SpringBoot的学习中需要了解以下注解底层的实现等等,上面的注解不是很全,后面用到了再补充。

代理模式

什么是代理?

代理是一种常用的设计模式,其目的就是为其他对象提供一个代理以控制对某个对象的访问。代理类负责为委托类预处理消息,过滤消息并转发消息,以及进行消息被委托类执行后的后续处理。

简单结构示意图
在这里插入图片描述

为什么要学习代理模式?

因为代理模式是SpringAOP的核心。
为了保持行为的一致性,代理类和委托类通常会实现相同的接口,所以在访问者看来两者没有丝毫的区别。
通过代理类这中间一层,能有效控制对委托类对象的直接访问,也可以很好地隐藏和保护委托类对象,同时也为实施不同控制策略预留了空间,从而在设计上获得了更大的灵活性。
Java 动态代理机制以巧妙的方式近乎完美地实践了代理模式的设计理念。

下面我们通过代理模式实现以下案例
在这里插入图片描述

一、静态代理模式

角色分析:

  • 抽象角色:一般会使用接口或者抽象类来解决
  • 真实角色:被代理的角色
  • 代理角色:代理真实角色,代理真实角色后,我们一般会做一些附属操作
  • 客户:访问代理对象的人

代理模式的好处:

  • 可以使真实角色的操作更加纯粹!不用去关注一些公共的业务
  • 公共也就就交给代理角色!实现了业务的分工!
  • 公共业务发生扩展的时候,方便集中管理!
    缺点:
  • 一个真实角色就会产生一个代理角色;代码量会翻倍开发效率会变低

代码实现:

1、创建一个抽象角色,比如咋们平时做的用户业务,抽象起来就是增删改查!

//抽象角色:增删改查业务
public interface UserService {
   void add();
   void delete();
   void update();
   void query();
}

2、我们需要一个真实对象来完成这些增删改查操作

//真实对象,完成增删改查操作的人
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 query() {
       System.out.println("查询了一个用户");
  }
}

3、需求来了,现在我们需要增加一个日志功能,怎么实现!

  • 思路1 :在实现类上增加代码 【麻烦!】
  • 思路2:使用代理来做,能够不改变原来的业务情况下,实现此功能就是最好的了!

4、设置一个代理类来处理日志!代理角色

//代理角色,在这里面增加日志的实现
public class UserServiceProxy implements UserService {
   private UserServiceImpl userService;

   public void setUserService(UserServiceImpl userService) {
       this.userService = userService;
  }

   public void add() {
       log("add");
       userService.add();
  }

   public void delete() {
       log("delete");
       userService.delete();
  }

   public void update() {
       log("update");
       userService.update();
  }

   public void query() {
       log("query");
       userService.query();
  }

   public void log(String msg){
       System.out.println("执行了"+msg+"方法");
  }

}

5、测试访问类:

public class Client {
   public static void main(String[] args) {
       //真实业务
       UserServiceImpl userService = new UserServiceImpl();
       //代理类
       UserServiceProxy proxy = new UserServiceProxy();
       //使用代理类实现日志功能!
       proxy.setUserService(userService);
       proxy.add();
  }
}

二、动态代理模式

在动态代理中分为两种实现方式:

  • 使用 JDK 的 Proxy 类实现动态代理
  • 使用 CGLIB 实现动态代理

1、JDK动态代理的实现

ava动态代理类位于java.lang.reflect包下,一般主要涉及到以下两个类:
JDK动态代理需要了解到InvocationHandlerProxy这两个核心的类。
(1) Interface InvocationHandler:该接口中仅定义了一个方法

public Object invoke(Object proxy, Method method, Object[] args)
第一个参数proxy一般是指代理类,
method指的是被代理方法的方法对象,
第三个参数args为该方法的参数列表。

在实际使用中,有三个参数,这个抽象方法invoke()在代理类中动态实现。
(2) Proxy:该类为动态代理类

public static Class<?> getProxyClass(ClassLoader loader,Class<?>... interfaces)

获得一个代理类,其中loader表示类加载器,interface是真实类所拥有的全部接口。

public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)

返回一个代理类的实例,其中loader表示类加载器,interface表示真实类所拥有的接口,h表示调用处理程序,可以在里面编写代码实现对目标类的代理。

所谓的动态代理DynamicProxy是在运行期间生成的class,在生成它的时候必须要提供一组接口interface给它,然后该class就宣称它实现了这些接口interface。DynamicProxy实际上就是一个Proxy,他不会替你做实质性的工作,在生成它的实例时必须要提供一个handler,有这个handler接管实际的工作。因此在使用动态代理时,必须要实现InvocationHandler接口。

动态代理实现步骤:

  • 第一步:创建需要被代理的类和接口
  • 第二步:创建一个代理类,并且通过Proxy的静态方法newProxyInstance()去动态创建一个代理
  • 第三步:通过静态内部类的方式实现Proxy的静态方法中的InvocationHandler,并且重写invoke()方法
  • 第四步:通过代理调用方法

代码测试:
(1)创建抽象角色,租房

 //抽象角色,租房
public interface Rent {
    void renting();
}

(2)创建真实角色,房东 要出租房子

 //真实角色:房东   要出租房子
public class FD implements Rent {
    @Override
    public void renting() {
        System.out.println("Oldou 有房出租........");
    }
}

(3)创建要对代理对象扩展的功能

 //对目标对象做拓展的功能或者增强功能的对象
public class MyAspect {
    public void before(){
        System.out.println("带领房客看房......签署租房协议..");
    }
    public void after(){
        System.out.println("售后服务");
    }
}

(4)创建代理角色,中介(这里的中介是JDK动态代理实现)

 //代理角色:中介
public class ProxyFactory {

    //动态生成代理对象   target表示为谁生成代理对象
        public static Object getProxyBean(Object target){
        //获取目标类的class对象
               Class clazz = target.getClass();
        MyAspect myAspect = new MyAspect();

        //在JDK动态代理中生成的代理对象的方法,参数为:代理对象的类加载器,代理对象实现的接口,调用处理的程序
               return Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), new InvocationHandler() {
            /** 动态生成代理对象中的方法
                        * @param proxy  动态生成的代理对象
                        * @param method 目标方法的方法对象
                        * @param args   传递到目标方法中的参数列表
                        * @return
             * @throws Throwable
             */
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                myAspect.before(); //扩展的功能
                             //目标代理对象
                             Object obj = method.invoke( target, args);
                myAspect.after(); //扩展的功能
                             return obj;
            }
        });
    }
}

(5)测试代码:

 public class TestJdkProxy {
    public static void main(String[] args) {
        //创建房东对象
                Rent rent = new FD();
        //将房东对象传入,相当于代理房东去租房,得到代理对象
                Rent rent1  = (Rent) ProxyFactory.getProxyBean(rent);
        //通过产生的代理对象去租房
                rent1.renting();
    }
}

输出结果:
在这里插入图片描述

2、CGlib动态代理的实现

什么是CGLIB?

CGLIB(Code Generation Library)是一个高性能开源的代码生成包,它被许多框架所使用,
其底层是通过使用一个小而快的字节码处理框架 ASM(Java 字节码操控框架)转换字节码并生成新的类。
因此 CGLIB 要依赖于 ASM 的包。

JDK 的动态代理机制只能代理实现了接口的类,而对于没有实现接口的类就不能使用。

JDK 的 Proxy 类生成代理对象,cglib 是针对类来实现代理的,他的原理是对指定的目标类生成一个子类并通过回调的方式来实现增强,但因为采用的是继承,所以不能对 final 修饰的类进行代理。

MyBatis框架就是用了CGLIB动态代理来为Mapper接口动态生成代理对象。

代码测试:
(1)创建抽象角色,租房

 //抽象角色,租房
public interface Rent {
    void renting();
}

(2)创建真实角色,房东 要出租房子

 //真实角色  房东  要出租房子
public class FD implements Rent {
    @Override
    public void renting() {
        System.out.println("XiaoOu有房出租");
    }
}

(3)创建要对代理对象扩展的功能

 //对目标对象做拓展的功能或者增强功能的对象
public class CglibMyAspect {
    public void before(){
        System.out.println("带领房客看房......签署租房协议..");
    }
    public void after(){
        System.out.println("售后服务");
    }
}

(4)创建代理角色

 //代理角色:中介
public class CglibProxyFactory {
    //生成代理对象的方法   rent表示需要代理对象的接口
    public static Object getProxyBean(Rent target){
        CglibMyAspect myAspect = new CglibMyAspect();
        //创建加强器,用来创建动态代理类
                Enhancer enhancer = new Enhancer();
        //为加强器指定要代理的业务类(即:为下面生成的代理类指定父类)
                enhancer.setSuperclass(target.getClass());
        //设置回调:对于代理类上所有方法的调用,都会调用CallBack,而Callback则需要实现intercept()方法代理对象的扩展
                enhancer.setCallback(new MethodInterceptor() {
            /** 实现回调方法
                        * @param o           代理对象的引用
                        * @param method      目标对象的方法对象
                        * @param objects     目标方法的参数列表
                        * @param methodProxy 目标方法的方法对象的代理对象
                        */
            @Override
            public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                myAspect.before();
                Object obj = method.invoke(target, objects);
                myAspect.after();
                return obj;
            }
        });
        // 创建动态代理类对象并返回
                return enhancer.create();
    }
}

(5)测试代码:

 public class TestJdkProxy {
    public static void main(String[] args) {
        //创建房东对象
                Rent rent = new FD();
        //将房东对象传入,相当于代理房东去租房,得到代理对象
                Rent rent1  = (Rent) ProxyFactory.getProxyBean(rent);
        //通过产生的代理对象去租房
                rent1.renting();
    }
}

输出结果:
在这里插入图片描述

介绍一下SpringAOP的动态代理模式

SpringAOP的动态代理模式主要有两种,分别是JDK动态代理和CGLIB动态代理。
(1)JDK动态代理只提供接口的代理,不支持类代理。核心为InvocationHandler接口和Proxy类,InvocationHandler接口通过invoke()方法反射来调用目标类中的代码,动态地将横切逻辑和业务逻辑编织在一起,接着Proxy就利用InvocationHandler动态创建一个符合某一接口的实例,生成目标类的代理对象。

(2)CGLIB动态代理,代理类没有实现InvocationHandler接口,它是一个代码生成的类库,可以在运行时动态地生产指定类的一个子类对象,并且覆盖其中特定方法来增强代码,从而实现代理;CGLIB是通过继承的方式做的动态代理,如果某个类被标记为final,那么它就无法使用CGLIB做动态代理的。

静态代理和动态代理的区别在于生成代理对象的时机不一样,相对来说静态代理AspectJ的静态代理方式具有更好的性能。

SpringAOP的学习

Spring AOP的概念

AOP 的全称是 Aspect Oriented Programming,即面向切面编程,它将业务逻辑的各个部分进行隔离,使开发人员在编写业务逻辑时可以专心于核心业务,从而提高了开发效率。
AOP 采取横向抽取机制,取代了传统纵向继承体系的重复性代码,其应用主要体现在事务处理、日志管理、权限控制、异常处理等方面。
目前最流行的 AOP 技术有两个,分别为 Spring 框架的 AOP 和 AspectJ 框架。
在这里插入图片描述

Spring AOP的名词解释

在这里插入图片描述

  • 横切关注点:跨越应用程序多个模块的方法或功能。即是,与我们业务逻辑无关的,但是我们需要关注的部分,就是横切关注点。如日志 , 安全 , 缓存 , 事务等等 …

  • 切面(ASPECT):横切关注点 被模块化 的特殊对象。即,它是一个类。

  • 通知(Advice):切面必须要完成的工作。即,它是类中的一个方法。

  • 目标(Target):被通知对象。

  • 代理(Proxy):向目标对象应用通知之后创建的对象。

  • 切入点(PointCut):切面通知 执行的 “地点”的定义。

  • 连接点(JointPoint):与切入点匹配的执行点。
    在这里插入图片描述

SpringAOP的通知类型介绍

在这里插入图片描述

Spring的事务

什么是事务?

事务是作为一个逻辑单元执行的一系列操作。一个事务工作单元必须有四个特性:原子性、一致性、隔离性、持久性。只有这样才能成为一个事务。

事务的作用

事物对于数据库的作用是对数据的一系列操作,要么全部成功,要么全部失败,防止中间状态的出现,以确保数据库中的数据始终处于正确的状态。

事务的四大特性

把一组事务当成一个业务来执行,要么都成功,要么都失败。

  • 原子性(atomicity):一个事务要么全部提交成功,要么全部失败回滚,不会执行其中的一部分。

  • 一致性(consistency):一个事务在执行前和执行后,数据库的状态必须保持一致。

  • 隔离性(isolation):在并发情况下,并发的事务是相互隔离互不干扰的,,一个事务的执行不会被其他事务所干扰。

  • 持久性(durability):一旦事务提交,那么数据库中对应数据的状态变更就会永久性的保存在数据库中。

Spring 实现声明式事务管理主要有两种方式:

  • 基于 XML 文件方式的声明式事务管理。
  • 通过 Annotation 注解方式的事务管理。

介绍一下Spring支持的事务管理类型?

Spring支持两种两种事物管理,一种是编程式事务管理,另外一种是声明式事务管理。
(1)编程式事务管理:指的是通过编码方式实现事务,允许用户在代码中精确定义事务的边界。即类似于JDBC编程实现事务管理。

(2)声明式事务管理:管理建立在AOP之上的。其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务。声明式事务最大的优点就是不需要通过编程的方式管理事务,这样就不需要在业务逻辑代码中掺杂事务管理的代码,只需在配置文件中做相关的事务规则声明(或通过基于@Transactional注解的方式),便可以将事务规则应用到业务逻辑中。

声明式事务最大的优点就是不需要在业务逻辑代码中掺杂事务管理的代码,只需在配置文件中做相关的事务规则声明或通过@Transactional注解的方式,便可以将事务规则应用到业务逻辑中。

声明式事务管理要优于编程式事务管理,这正是spring倡导的非侵入式的开发方式,使业务代码不受污染,只要加上注解就可以获得完全的事务支持。唯一不足地方是,最细粒度只能作用到方法级别,无法做到像编程式事务那样可以作用到代码块级别。

Spring中配置文件中声明式事务的配置

<!--配置声明式事务-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"/>
</bean>

<!--结合AOP实现事务的织入-->
<!--配置事务的通知-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
    <!--给哪些方法配置事务-->
    <!--配置事务的传播特性-->
    <tx:attributes>
        <tx:method name="add" propagation="REQUIRED"/>
        <tx:method name="delete" propagation="REQUIRED"/>
        <tx:method name="update" propagation="REQUIRED"/>
        <tx:method name="query" read-only="true"/>
        <tx:method name="*" propagation="REQUIRED"/>
    </tx:attributes>
</tx:advice>

<!--配置事务的织入-->
<aop:config>
    <aop:pointcut id="txPointcut" expression="execution(* com.oldou.mapper.*.*(..))"/>
    <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut"/>
</aop:config>
  • 要开启 Spring 的事务处理功能,在 Spring 的配置文件中创建一个 DataSourceTransactionManager 对象

以上代码中,我们可以通过AOP给代码添加事务,因为AOP式横向扩展,给原有代码添加新的功能,Spring的事务就是使用AOP实现的,
在以上配置事务的通知时,里面name属性就是给哪些方法配置事务,其中在propagation属性配置事务的传播行为,事务的传播特性有以下七种,七种最常用的也是Spring默认的事务传播行为是REQUIRED

事务的七种传播行为

  • REQUIRED(默认):支持当前事务,如果当前没有事务,就新建一个事务。这是最常见的选择。

  • SUPPORTS:支持当前事务,如果当前没有事务,就以非事务方式执行。

  • MANDATORY:支持当前事务,如果当前没有事务,就抛出异常。

  • REQUIRES_NEW:新建事务,如果当前存在事务,把当前事务挂起。

  • NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。

  • NEVER:以非事务方式执行,如果当前存在事务,则抛出异常。

  • NESTED:支持当前事务,如果当前事务存在,则执行一个嵌套事务,如果当前没有事务,就新建一个事务。

声明式事务中的属性解释

  • name 属性
    name=”方法名称|方法名称*”
    指定受事务控制的方法,支持*通配符。

  • propagation 属性
    propagation=”传播行为名称”
    配置事务的传播行为。

  • isolation 属性
    Isolation=”隔离级别名称”
    配置事物的隔离级别。

  • readonly 属性
    readonly=”true|false”
    是否为只读事务。
    true:开启只读事务。由于只读事务不存在数据的修改,因此数据库将会为只读事务提供一些优化手段,会对性能有一定提升。建议在查询中开启只读事务。
    false:不开启只读事务,默认为 false。

  • timeout 属性
    timeout=”秒”
    timeout 是设置超时属性。以秒为单位。当在限定的时间内不能完成所有操作,就会抛异常。

  • rollback-for 属性
    rollback-for=”异常名称”
    Spring 默认情况下会对 RunTimeException 类型异常进行事务回滚。如果是 Exception 类型的异常则不回滚。
    注意:如果异常被 try{}catch{}了,事务就不回滚了,如果想让事务回滚则必须再抛出异常。

  • no-rollback-for 属性
    no-rollback-for=”异常名称”
    当出现什么异常时不滚回事务。

Spring的事务隔离级别

Spring有五大事务隔离级别,其中Spring默认的事务隔离级别为:ISOLATION_DEFAULT(使用的是数据库的设置),其他的四个事务隔离级别和数据库的隔离级别一致。
(1)ISOLATION_DEFAULT:用的底层数据库的设置隔离级别,数据库设置的是什么就用什么。

(2)ISOLATION_READ_UNCOMMITTED:未提交读,最低的事务隔离级别,事务未提交前就可以被其他事务读取,会出现幻读、脏读,不可重复读。

(3)ISOLATION_READ_COMMITTED :已提交读,一个事务提交后才能被其他事务读取到(可能会造成幻读、不可重复读),SQL Server的默认级别(Oracle的默认隔离级别)。

(4)ISOLATION_REPEATABLE_READ:可重复读,保证多次读取同一个数据时,其值都和事务开始的时候的内容一致,禁止读取到别的事务未提交的数据(可能会造成幻读),这是MYSQL的默认隔离级别。

(5)ISOLATION_SERIALIZABLE:序列化,代价最高的但是最可靠的隔离级别,该级别能够防止脏读、不可重复读、幻读。

脏读:表示一个事务能够读取另一个事务未提交的数据;
幻读:指的是同一个事务多次查询返回的结果集不一样。
不可重复读:指的是在一个事务中多次读取同一个数据。

@Transactional 介绍

  • @Transactional 注解 可以作用于接口、接口方法、类以及类方法上。当作用于类上时,该类的所有 public 方法将都具有该类型的事务属性,同时,我们也可以在方法级别使用该注解来覆盖类级别的定义。

  • 虽然@Transactional 注解可以作用于接口、接口方法、类以及类方法上,但是 Spring 建议不要在接口或者接口方法上使用该注解,因为这只有在使用基于接口的代理时它才会生效。另外, @Transactional 注解应该只被应用到 public 方法上,这是由 Spring AOP 的本质决定的。如果你在 protected、private 或者默认可见性的方法上使用@Transactional 注解,这将被忽略,也不会抛出任何异常。

配置文件中的配置

<!-- 配置事务管理器的切面 -->
<bean id="txTransactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 注册事务管理驱动 -->
<tx:annotation-driven transaction-manager="txTransactionManager"/>

本文为初学Spring时所写的笔记,不是很完整,还有一部分笔记正在整理,如果文中有需要修正的地方,请各位大佬指正,一起学习,相互进步,本文若要转载请标明文章来源出处,谢谢大家。

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐