Spring

spring实战

Spring#

Spring框架是由于软件开发的复杂性而创建的,解决企业应用开发的复杂性

  • 是针对bean的生命周期进行管理的轻量级容器

  • Spring是一个轻量级控制反转(IoC)面向切面(AOP)的容器框架。

  • 事务管理

  • 集成各类型的工具,通过核心的Beanfactory实现了底层的类的实例化和生命周期的管理。

  • 在整个框架中,各类型的功能被抽象成一个个的 Bean,这样就可以实现各种功能的管理

1.七大模块#

img

核心容器(Spring Core)

  核心容器提供Spring框架的基本功能。Spring以bean的方式组织和管理Java应用中的各个组件及其关系。Spring使用BeanFactory来产生和管理Bean,它是工厂模式的实现。BeanFactory使用控制反转(IoC)模式将应用的配置和依赖性规范与实际的应用程序代码分开。

应用上下文(Spring Context)

  Spring上下文是一个配置文件,向Spring框架提供上下文信息。Spring上下文包括企业服务,如JNDI、EJB、电子邮件、国际化、校验和调度功能。

Spring面向切面编程(Spring AOP)

  通过配置管理特性,Spring AOP 模块直接将面向方面的编程功能集成到了 Spring框架中。所以,可以很容易地使 Spring框架管理的任何对象支持 AOP。Spring AOP 模块为基于 Spring 的应用程序中的对象提供了事务管理服务。通过使用 Spring AOP,不用依赖 EJB 组件,就可以将声明性事务管理集成到应用程序中。

JDBC和DAO模块(Spring DAO)

  JDBC、DAO的抽象层提供了有意义的异常层次结构,可用该结构来管理异常处理,和不同数据库供应商所抛出的错误信息。异常层次结构简化了错误处理,并且极大的降低了需要编写的代码数量,比如打开和关闭链接。

对象实体映射(Spring ORM)

  Spring框架插入了若干个ORM框架,从而提供了ORM对象的关系工具,其中包括了Hibernate、JDO和 IBatis SQL Map等,所有这些都遵从Spring的通用事物和DAO异常层次结构。

Web模块(Spring Web)

  Web上下文模块建立在应用程序上下文模块之上,为基于web的应用程序提供了上下文。所以Spring框架支持与Struts集成,web模块还简化了处理多部分请求以及将请求参数绑定到域对象的工作。

MVC模块(Spring Web MVC)

  MVC框架是一个全功能的构建Web应用程序的MVC实现。通过策略接口,MVC框架变成为高度可配置的。MVC容纳了大量视图技术,其中包括JSP、POI等,模型来有JavaBean来构成,存放于m当中,而视图是一个街口,负责实现模型,控制器表示逻辑代码,由c的事情。Spring框架的功能可以用在任何J2EE服务器当中,大多数功能也适用于不受管理的环境。Spring的核心要点就是支持不绑定到特定J2EE服务的可重用业务和数据的访问的对象,毫无疑问这样的对象可以在不同的J2EE环境,独立应用程序和测试环境之间重用。

2.IOC控制反转#

IOC控制反转

  • Ioc—Inversion of Control,即“控制反转”,不是什么技术,而是一种设计思想。
  • 想的是把对象的创建交给容器进行控制,不是自己在主动去管理

谁控制,控制什么

以前是我们人为的去new对象,现在用IOC容器去控制对象的创建。

控制了对象,控制外部资源的获取

谁反转,反转什么

以前获取对象的依赖对象,是我们在对象中主动去控制创建依赖对象,现在是IOC容器去创建依赖对象并注入,这是反转。

反转了依赖对象的创建和注入

解耦#

image-20200731084230293

DI依赖注入#

其实是IOC实现的一种方式

实现思路#

spring容器初始化时读取配置文件进行,将对象读取进容器,使用时在IOC容器读取出来.

image-20200731085031918

代码实现#

用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="user" class="com.cb.pojo.User">
        <!-- collaborators and configuration for this bean go here -->
        <property name="userName" value="他知道"/>
    </bean>



    <!-- more bean definitions go here -->

</beans>

获得应用上下文,获取Bean

public class Test1 {

    @Test
    public void test1(){
    ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
        User user = (User)context.getBean("user");
        System.out.println(user);
        // User(userName=他知道)
    }
}

IOC创建的Bean的过程#

  1. 默认为 无参构造

  2. 有参构造需要设置

    • 构造函数参数类型匹配\

    • <bean id="exampleBean" class="examples.ExampleBean">    <constructor-arg type="int" value="7500000"/>    <constructor-arg type="java.lang.String" value="42"/> </bean>
      
    • 构造函数参数索引

    • <bean id="exampleBean" class="examples.ExampleBean">
          <constructor-arg index="0" value="7500000"/>
          <constructor-arg index="1" value="42"/>
      </bean>
      
    • 构造函数参数名称

    • <bean id="exampleBean" class="examples.ExampleBean">    <constructor-arg name="years" value="7500000"/>    <constructor-arg name="ultimateAnswer" value="42"/> </bean>
      
  3. 在加载配置文件时,就已经创建Bean了

  • 测试时发现,所有Bean的 构造函数被调用。

3.Spring配置#

xml配置#

<!--引入其他配置文件-->
<import resource="beans.xml"/>
<!--给Bean的别名  name指id -->
<alias name="user" alias="user2"/>

4.依赖注入#

依赖注入(DI)是一个过程,通过该过程,对象仅通过构造函数参数,工厂方法的参数或在构造或创建对象实例后在对象实例上设置的属性来定义其依赖关系(即,与它们一起工作的其他对象)。容器在创建bean时注入那些依赖项。

DI存在两个主要变体:基于构造函数的依赖注入基于Setter的依赖注入

xml方式#

复杂注入:

原值注入#

<bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
    <!-- results in a setDriverClassName(String) call -->
    <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
    <property name="url" value="jdbc:mysql://localhost:3306/mydb"/>
    <property name="username" value="root"/>
    <property name="password" value="misterkaoli"/>
</bean>

集合#

<bean id="moreComplexObject" class="example.ComplexObject">
    <!-- results in a setAdminEmails(java.util.Properties) call -->
    <property name="adminEmails">
        <props>
            <prop key="administrator">administrator@example.org</prop>
            <prop key="support">support@example.org</prop>
            <prop key="development">development@example.org</prop>
        </props>
    </property>
    <!-- results in a setSomeList(java.util.List) call -->
    <property name="someList">
        <list>
            <value>a list element followed by a reference</value>
            <ref bean="myDataSource" />
        </list>
    </property>
    <!-- results in a setSomeMap(java.util.Map) call -->
    <property name="someMap">
        <map>
            <entry key="an entry" value="just some string"/>
            <entry key ="a ref" value-ref="myDataSource"/>
        </map>
    </property>
    <!-- results in a setSomeSet(java.util.Set) call -->
    <property name="someSet">
        <set>
            <value>just some string</value>
            <ref bean="myDataSource" />
        </set>
    </property>
</bean>

The value of a map key or value, or a set value, can also be any of the following elements:

bean | ref | idref | list | set | map | props | value | null

p,c命名空间#

  • p对应 property
  • c对应 construct

p命名空间

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:p="http://www.springframework.org/schema/p"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean name="classic" class="com.example.ExampleBean">
        <property name="email" value="someone@somewhere.com"/>
    </bean>

    <bean name="p-namespace" class="com.example.ExampleBean"
        p:email="someone@somewhere.com"/>
</beans>

c命名空间

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:c="http://www.springframework.org/schema/c"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="beanTwo" class="x.y.ThingTwo"/>
    <bean id="beanThree" class="x.y.ThingThree"/>

    <!-- traditional declaration with optional argument names -->
    <bean id="beanOne" class="x.y.ThingOne">
        <constructor-arg name="thingTwo" ref="beanTwo"/>
        <constructor-arg name="thingThree" ref="beanThree"/>
        <constructor-arg name="email" value="something@somewhere.com"/>
    </bean>

    <!-- c-namespace declaration with argument names -->
    <bean id="beanOne" class="x.y.ThingOne" c:thingTwo-ref="beanTwo"
        c:thingThree-ref="beanThree" c:email="something@somewhere.com"/>

</beans>

5.Bean的作用域#

Scope Description
singleton (Default) Scopes a single bean definition to a single object instance for each Spring IoC container. 默认,且只创建一个
prototype Scopes a single bean definition to any number of object instances.为每一个都创建
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 Scopes a single bean definition to the lifecycle of an HTTP Session. Only valid in the context of a web-aware Spring ApplicationContext.
application Scopes a single bean definition to the lifecycle of a ServletContext. Only valid in the context of a web-aware Spring ApplicationContext.
websocket Scopes a single bean definition to the lifecycle of a WebSocket. Only valid in the context of a web-aware Spring ApplicationContext.

6.Bean三种的自动装配#

在 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 
  http://www.springframework.org/schema/beans/spring-beans.xsd
  http://www.springframework.org/schema/context" >
  
  <!-- configuration details go here />
	<bean id="compactDisc" class="soundsystem.SgtPeppers" />

	<bean id="cdPlayer" class="soundsystem.CDPlayer">
      <constructor-arg ref="compactDisc">
    </bean>
</beans>

在 Java 中进行显式配置。#

创建配置类#

CDPlayerConfig.java
package soundsystem;

import org.spingframework.context.annotation.Configuration;

@Configuration
public class CDPlayerConfig {
    
    @Bean
    public CompactDisc sgtPeppers() {
      return new SgtPeppers();
    }
    
    //借助 JavaConfig 实现注入
    @Bean
    public CDPlayer cdPlayer(CompactDisc compactDisc) {
      return new CDPlayer(compactDisc);
    }
}

隐式的 bean 发现机制和自动装配。#

  • 组件扫描(component scanning):Spring 会自动发现应用上下文中所创建的 bean。
  • 自动装配(autowiring):Spring 自动满足 bean 之间的依赖。

@Component 注解。这个简单的注解表明该类会作为组件类,@Autowired实现自动装配

@Autowired 自动装配就是让 Spring 自动满足 bean 依赖的一种方法,在满足依赖的过程中,会在 Spring 应用上下文中寻找匹配某个 bean 需求的其他 bean。 通过Type class选择

package soundsystem;

import org.springframework.stereotype.Component;

@Component
public class SgtPeppers implements CompactDisc {

  private String title = "Sgt. Pepper's Lonely Hearts Club Band";  
  private String artist = "The Beatles";
    
  @Autowired
  public CDPlayer(CompactDisc cd) {
    this.cd = cd;
  }
  public void play() {
    System.out.println("Playing " + title + " by " + artist);
  }
    
  
}

组件扫描默认是不启用的。我们还需要显式配置一下 Spring, 从而命令它去寻找带有 @Component

@ComponentScan 注解启用了组件扫描:

package soundsystem;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@ComponentScan
public class CDPlayerConfig { 
}

你更倾向于使用 XML 来启用组件扫描的话,那么可以使用 Spring context 命名空间的元素。以下展示了启用组件扫描的最简洁 XML 配置:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns:context="http://www.springframework.org/schema/context"
  xmlns:c="http://www.springframework.org/schema/c"
  xmlns:p="http://www.springframework.org/schema/p"
  xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

  <context:component-scan base-package="soundsystem" />
  <context:annotation-config/>

</beans>

7.常用注解#

1.组件
@Component:配置Bean
衍生出
@Service:业务层
@Repository:数据库访问层
@Controller

2.属性
@Value():设置对象的属性
3.Bean作用域
@Scope

3.合并配置类
@Import



8.代理模式#

参考:

10分钟看懂动态代理设计模式

作用:AOP的底层实现

image-20200731151626437

静态代理#

使用 聚合 实现:

  1. 实现同一接口
  2. 代理对象
  3. 被代理对象

模拟一只鸟在空中的飞行

public interface Flyable {
    void fly();
}
 
public class Bird implements Flyable {
 
    @Override
    public void fly() {
        System.out.println("Bird is flying...");
        try {
            Thread.sleep(new Random().nextInt(1000));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

代理记录一只鸟在空中的飞行时间

public class BirdLogProxy implements Flyable {
    private Flyable flyable;
 
    public BirdLogProxy(Flyable flyable) {
        this.flyable = flyable;
    }
 
    @Override
    public void fly() {
        System.out.println("Bird fly start...");
 
        flyable.fly();
 
        System.out.println("Bird fly end...");
    }
}

代理记录日志 鸟在空中的飞行时间

public class BirdLogProxy implements Flyable {
    private Flyable flyable;
 
    public BirdLogProxy(Flyable flyable) {
        this.flyable = flyable;
    }
 
    @Override
    public void fly() {
        System.out.println("Bird fly start...");
 
        flyable.fly();
 
        System.out.println("Bird fly end...");
    }
}

神奇的事是 需要先记录日志,再获取飞行时间,可以在调用的地方这么做

 public static void main(String[] args) {
        Bird bird = new Bird();
        BirdLogProxy p1 = new BirdLogProxy(bird);
        BirdTimeProxy p2 = new BirdTimeProxy(p1);
 
        p2.fly();
    }

反过来,可以这么做: 先获取飞行时间,再记录日志

public static void main(String[] args) {
        Bird bird = new Bird();
        BirdTimeProxy p2 = new BirdTimeProxy(bird);
        BirdLogProxy p1 = new BirdLogProxy(p2);
 
        p1.fly();
 }

缺点:

  • 如果 Bird类的方法有n个,我们就需要生产n个代理类,即静态代理一个功能需要一个代理类。

思考:

能不能用一个代理类 代理任意对象的任意方法(重点)---------------- 动态代理

动态代理设计模式#

可以动态生成TimeProxy这个代理类,并且动态编译。然后,再通过反射创建对象并加载到内存中

image-20200731163753107

第一步生成 代理类的源码 大概如下

package com.youngfeng.proxy;
 
import java.lang.Override;
import java.lang.reflect.Method;
 
public class TimeProxy implements Flyable {
  private InvocationHandler handler;
 
  public TimeProxy(InvocationHandler handler) {
    this.handler = handler;
  }
 
  @Override
  public void fly() {
    try {
        Method method = com.youngfeng.proxy.Flyable.class.getMethod("fly");
        this.handler.invoke(this, method, null);
    } catch(Exception e) {
        e.printStackTrace();
    }
  }
}

  1. 增加InvocationHandler接口 实现代理方法的逻任意辑
public interface InvocationHandler {
    void invoke(Object proxy, Method method, Object[] args);
}
proxy => 这个参数指定动态生成的代理类,这里是TimeProxy
method => 这个参数表示传入接口中的所有Method对象
args => 这个参数对应当前method方法中的参数
public class MyInvocationHandler implements InvocationHandler {
    private Bird bird;
 
    public MyInvocationHandler(Bird bird) {
        this.bird = bird;
    }
 
    @Override
    public void invoke(Object proxy, Method method, Object[] args) {
        long start = System.currentTimeMillis();
 
        try {
            method.invoke(bird, new Object[] {});
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
 
        long end = System.currentTimeMillis();
        System.out.println("Fly time = " + (end - start));
    }
}

TimeProxy真正代理的对象就是InvocationHandler

如何使用(重点)#

上面这段解释是告诉你在执行Proxy->newProxyInstance方法的时候真正发生的事情,而在实际使用过程中你完全可以忘掉上面的解释。按照设计者的初衷,我们做如下简单归纳:

  • Proxy->newProxyInstance(infs, handler) 用于生成代理对象
  • InvocationHandler:这个接口主要用于自定义代理逻辑处理
  • 为了完成对被代理对象的方法拦截,我们需要在InvocationHandler对象中传入被代理对象实例。

引入了InvocationHandler接口之后,我们的调用顺序应该变成了这样:

InvocationHandler myinvocation = new Myinvocation(new Bird());
        Flyable o = (Flyable)Proxy.newProxyInstance(Test1.class.getClassLoader(), Test1.class.getInterfaces(), myinvocation);
        o.fly();
 
方法执行流:proxy.fly() => handler.invoke()

思维图:

image-20200731172618035

代码实现动态代理#

动态代理分类两类:

  • 基于接口:JDK动态代理
  • j基于类:cglib

JDK动态代理#

//同一接口
public interface Flyable {
    void fly() throws NoSuchMethodException;
}

//被代理对象
public class Bird implements Flyable {
    @Override
    public void fly() throws NoSuchMethodException {

            System.out.println("飞行了10秒");

    }
}


package com.cb.test;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class Test {

    public static void main(String[] args) throws NoSuchMethodException {
        
        InvocationHandler invocationHander = new MyInvocationHander(new Bird());
        //代理对象生产
        Flyable proxyInstance = (Flyable) Proxy.newProxyInstance(Bird.class.getClassLoader(), Bird.class.getInterfaces(), invocationHander);
        proxyInstance.fly();
    }
}
//逻辑方法加强
class MyInvocationHander implements InvocationHandler{

    //被代理接口
    private Flyable flyable;

    public MyInvocationHander() {
    }

    public MyInvocationHander(Flyable flyable) {
        this.flyable = flyable;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        //处理逻辑加强
        System.out.println("小鸟开始飞了");

        Object invoke = method.invoke(flyable, args);
        //处理逻辑处理逻辑加强
        System.out.println("小鸟飞完了");
        return invoke;
    }
}

实验结果

小鸟开始飞了
飞行了10秒
小鸟飞完了

CGLIB(Code Generator Library)#

9.AOP(面向切面编程)#

AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,可以通过预编译方式运行期动态代理实现在不修改源代码的情况下给程序动态统一添加功能的一种技术。

提供声明式事务:允许用户自定义切面

概念:

AOP采用一种称为“横切”的技术,将涉及多业务流程的通用功能抽取并单独封装,形成独立的切面,在合适的时机将这些切面横向切入到业务流程指定的位置中

主要功能#

将日志记录,性能统计,安全控制,事务处理,异常处理等代码从业务逻辑代码中划分出来,通过对这些行为的分离,我们希望可以将它们独立到非指导业务逻辑的方法中,进而改变这些行为的时候不影响业务逻辑的代码。

日志记录,性能统计,安全控制,事务处理,异常处理等等。

image-20200731175642443

术语#

image-20200731181248381

切面(Aspect)

当抄表员开始一天的工作时,他知道自己要做的事情(报告用电量)和从哪些房屋收集信息。因此,他知道要完成工作所需要的一切东西。 切面是通知和切点的结合。通知和切点共同定义了切面的全部内容 —— 它是什么,在何时和何处完成其功能。

切点(Poincut)

如果让一位抄表员访问电力公司所服务的所有住户,那肯定是不现实的。实际上,电力公司为每一个抄表员都分别指定某一块区域的住户。类似地,一个切面并不需要通知应用的所有连接点。切点有助于缩小切面所通知的连接点的范围。

如果说通知定义了切面的“什么”和“何时”的话,那么切点就定义了“何处”。切点的定义会匹配通知所要织入的一个或多个连接点。我们通常使用明确的类和方法名称,或是利用正则表达式定义所匹配的类和方法名称来指定这些切点。有些 AOP 框架允许我们创建动态的切点,可以根据运行时的决策(比如方法的参数值)来决定是否应用通知。

连接点(Join point)

电力公司为多个住户提供服务,甚至可能是整个城市。每家都有一个电表,这些电表上的数字都需要读取,因此每家都是抄表员的潜在目标。抄表员也许能够读取各种类型的设备,但是为了完成他的工作,他的目标应该房屋内所安装的电表。

同样,我们的应用可能也有数以千计的时机应用通知。这些时机被称为连接点。连接点是在应用执行过程中能够插入切面的一个点。这个点可以是调用方法时、抛出异常时、甚至修改一个字段时。切面代码可以利用这些点插入到应用的正常流程之中,并添加新的行为。

通知(Advice)

Spring 切面可以应用 5 种类型的通知:

  • 前置通知(Before):在目标方法被调用之前调用通知功能;
  • 后置通知(After):在目标方法完成之后调用通知,此时不会关心方法的输出是什么;
  • 返回通知(After-returning):在目标方法成功执行之后调用通 知;
  • 异常通知(After-throwing):在目标方法抛出异常后调用通知;
  • 环绕通知(Around):通知包裹了被通知的方法,在被通知的方法调用之前和调用之后执行自定义的行为。

引入(Introduction)

引入允许我们向现有的类添加新方法或属性。例如,我们可以创建一个 Auditable 通知类,该类记录了对象最后一次修改时的状态。这很简单,只需一个方法,setLastModified(Date),和一个实例变量来保存这个状态。然后,这个新方法和实例变量就可以被引入到现有的类中,从而可以在无需修改这些现有的类的情况下,让它们具 有新的行为和状态。

织入(Weaving)

织入是把切面应用到目标对象并创建新的代理对象的过程。切面在指定的连接点被织入到目标对象中。在目标对象的生命周期里有多个点可以进行织入:

  • 编译期:切面在目标类编译时被织入。这种方式需要特殊的编译器。AspectJ 的织入编译器就是以这种方式织入切面的。
  • 类加载期:切面在目标类加载到 JVM 时被织入。这种方式需要特殊的类加载器(ClassLoader),它可以在目标类被引入应用之前增强该目标类的字节码。AspectJ 5 的加载时织入(load-time weaving,LTW)就支持以这种方式织入切面。
  • 运行期:切面在应用运行的某个时刻被织入。一般情况下,在织入切面时,AOP 容器会为目标对象动态地创建一个代理对象。Spring AOP 就是以这种方式织入切面的。

Spring 对 AOP 的支持#

Spring AOP 构建在动态代理基础之上,因此,Spring 对 AOP 的支持局限于方法拦截。

例如拦截对象字段的修改。而且它不支持构造器连接点,我们就无法在 bean 创建时应用通知。

Spring 提供了 4 种类型的 AOP 支持:

  • 基于代理的经典 Spring AOP;
  • 纯 POJO 切面;
  • @AspectJ 注解驱动的切面;
  • 注入式 AspectJ 切面(适用于 Spring 各版本)。

代码实现#

依赖

<!-- https://mvnrepository.com/artifact/org.springframework/spring-aop -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aop</artifactId>
    <version>5.2.7.RELEASE</version>
</dependency>


<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.9.5</version>
</dependency>


切面

@Aspect
//切面
public class MyAspect {
    //切点
    @Pointcut("execution(* com.cb.test.Bird.*(..))")
    void Pointcut(){}

    //- 后置通知(After):在目标方法完成之后调用通知,此时不会关心方法的输出是什么;
    @After("Pointcut()")
    void after(){
        System.out.println("after执行后");
    }

    //- 前置通知(Before):在目标方法被调用之前调用通知功能;
    @Before("Pointcut()")
    void before(){
        System.out.println("before执行");
    }

    //- 环绕通知(Around):通知包裹了被通知的方法,在被通知的方法调用之前和调用之后执行自定义的行为
    @Around("Pointcut()")
    void around(ProceedingJoinPoint ps) throws Throwable {
        System.out.println("around执行前");
        Object proceed = ps.proceed();
        System.out.println("around执行后");
    }

    //- 返回通知(After-returning):在目标方法成功执行之后调用通 知;
    @AfterReturning("Pointcut()")
    void afterReturning(){
        System.out.println("afterReturning执行");
    }

    //- 异常通知(After-throwing):在目标方法抛出异常后调用通知;
    @AfterThrowing("Pointcut()")
    void afterThrowing(){
        System.out.println("afterThrowing执行");
    }
}

配置文件

@Configuration
@EnableAspectJAutoProxy
@ComponentScan(basePackageClasses = {MyAspect.class, Bird.class})
public class MyJavaConfig {
	//需要主动配置 并开启@EnableAspectJAutoProxy
    @Bean
    public MyAspect myAspect(){
        return  new MyAspect();
    }

}

测试

@Test
public void test2() throws NoSuchMethodException {
    ApplicationContext context = new AnnotationConfigApplicationContext(MyJavaConfig.class);
   Object user1 = context.getBean("myAspect");
    System.out.println(user1);
    Flyable user = (Flyable)context.getBean("bird");
    user.fly();
}

通知方法的执行顺序#

执行结果

around执行前
before执行
飞行了10秒
afterReturning执行
after执行后
around执行后

10.整合mybatis#

MyBatis-Spring#

  • MyBatis-Spring 会帮助你将 MyBatis 代码无缝地整合到 Spring 中。它
  • 将允许 MyBatis 参与到 Spring 的事务管理之中,创建映射器 mapper 和 SqlSession 并注入到 bean 中,以及将 Mybatis 的异常转换为 Spring 的 DataAccessException
  • 最终,可以做到应用代码不依赖于 MyBatis,Spring 或 MyBatis-Spring。

maven依赖#

<dependency>
  <groupId>org.mybatis</groupId>
  <artifactId>mybatis-spring</artifactId>
  <version>2.0.5</version>
</dependency>

1.使用SqlSessionFactoryBean创建SqlSessionFactory#

在 MyBatis-Spring 中,可使用 SqlSessionFactoryBean来创建 SqlSessionFactory。 要配置这个工厂 bean,只需要把下面代码放在 Spring 的 XML 配置文件中:

<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
  <property name="dataSource" ref="dataSource" />
</bean>
@Bean
public SqlSessionFactory sqlSessionFactory() throws Exception {
  SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
  factoryBean.setDataSource(dataSource());
  return factoryBean.getObject();
}
  • SqlSessionFactory 有一个唯一的必要属性用于 JDBC 的 DataSource
  • 一个常用的属性是 configLocation,它用来指定 MyBatis 的 XML 配置文件路径。它在需要修改 MyBatis 的基础配置非常有用。通常,基础配置指的是 <settings><typeAliases> 元素。
  • 这个配置文件并不需要是一个完整的 MyBatis 配置。确切地说,任何环境配置(<environments>),数据源(<DataSource>)和 MyBatis 的事务管理器(<transactionManager>)都会被忽略SqlSessionFactoryBean 会创建它自有的 MyBatis 环境配置(Environment),并按要求设置自定义环境的值。

注意:SqlSessionFactory 需要一个 DataSource(数据源)。 这可以是任意的 DataSource,只需要和配置其它 Spring 数据库连接一样配置它就可以了。

配置映射器类和映射器 XML 文件#

第一种是手动在 MyBatis 的 XML 配置文件中的 部分中指定 XML 文件的类路径;第二种是设置工厂 bean 的 mapperLocations 属性。

mapperLocations 属性接受多个资源位置。这个属性可以用来指定 MyBatis 的映射器 XML 配置文件的位置。属性的值是一个 Ant 风格的字符串,可以指定加载一个目录中的所有文件,或者从一个目录开始递归搜索所有目录。比如:

<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
  <property name="dataSource" ref="dataSource" />
  <property name="mapperLocations" value="classpath*:sample/config/mappers/**/*.xml" />
</bean>

2.SqlSessionTemplate代替SqlSession#

SqlSessionTemplate 是 MyBatis-Spring 的核心。作为 SqlSession 的一个实现,这意味着可以使用它无缝代替你代码中已经在使用的 SqlSessionSqlSessionTemplate 是线程安全的,可以被多个 DAO 或映射器所共享使用。

当调用 SQL 方法时(包括由 getMapper() 方法返回的映射器中的方法),SqlSessionTemplate 将会保证使用的 SqlSession 与当前 Spring 的事务相关。此外,它管理 session 的生命周期,包含必要的关闭、提交或回滚操作。另外,它也负责将 MyBatis 的异常翻译成 Spring 中的 DataAccessExceptions

由于模板可以参与到 Spring 的事务管理中,并且由于其是线程安全的,可以供多个映射器类使用,你应该总是SqlSessionTemplate 来替换 MyBatis 默认的 DefaultSqlSession 实现。在同一应用程序中的不同类之间混杂使用可能会引起数据一致性的问题。

可以使用 SqlSessionFactory 作为构造方法的参数来创建 SqlSessionTemplate 对象。

<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
  <constructor-arg index="0" ref="sqlSessionFactory" />
</bean>
@Bean
public SqlSessionTemplate sqlSession() throws Exception {
  return new SqlSessionTemplate(sqlSessionFactory());
}

3.注入映射器和发现映射器#

注入映射器

mybatis配置文件可以

发现映射器

不需要一个个地注册你的所有映射器。你可以让 MyBatis-Spring 对类路径进行扫描来发现它们。

有几种办法来发现映射器:

  • 使用 <mybatis:scan/> 元素
  • 使用 @MapperScan 注解
  • 在经典 Spring XML 配置文件中注册一个 MapperScannerConfigurer

4.代码实现#

spring配置文件

package com.cb.config;

import com.cb.aspect.MyAspect;
import com.cb.dao.UserMapper;
import com.cb.test.Bird;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import org.springframework.transaction.annotation.EnableTransactionManagement;



import javax.sql.DataSource;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;

@EnableTransactionManagement
@Configuration
@EnableAspectJAutoProxy
@ComponentScan(basePackageClasses = {MyAspect.class, Bird.class, UserMapper.class, com.cb.Service.Impl.UserService.class})
@MapperScan(basePackages = {"com.cb.dao"})
public class MyJavaConfig {

    @Bean
    public MyAspect myAspect(){
        return  new MyAspect();
    }

    @Bean
    public DataSource dataSource() throws IOException {
        //创建datasource源
        DriverManagerDataSource dataSource = new DriverManagerDataSource();
        //加载db.properties文件
        InputStream resourceAsStream = Resources.getResourceAsStream("db.properties");
        Properties properties = new Properties();
        properties.load(resourceAsStream);


        dataSource.setDriverClassName(properties.getProperty("driver"));
        dataSource.setUrl(properties.getProperty("url"));
        dataSource.setUsername(properties.getProperty("username"));
        dataSource.setPassword(properties.getProperty("password"));

        return dataSource;
    }

    @Bean
    public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
        //通过sqlSessionFactoryBean获取SqlSessionFactory
        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();


        //设置datasource源
        sqlSessionFactoryBean.setDataSource(dataSource);

        //设置 mybatis配置文件位置
        ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
        Resource resource = resourcePatternResolver.getResource("mybatis-config.xml");
        sqlSessionFactoryBean.setConfigLocation(resource);

        return sqlSessionFactoryBean.getObject();

    }

    @Bean
    public SqlSessionTemplate  sqlSessionTemplate( SqlSessionFactory sqlSessionFactory){

        SqlSessionTemplate sqlSessionTemplate = new SqlSessionTemplate(sqlSessionFactory);
        return sqlSessionTemplate;
    }

    @Bean
    public DataSourceTransactionManager transactionManager(DataSource dataSource){
        DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager(dataSource);
        return dataSourceTransactionManager;
    }


}

dao包

UserMapper

package com.cb.dao;

import com.cb.pojo.User;
import org.springframework.stereotype.Repository;

import java.util.List;

@Repository
public interface UserMapper {

    List<User> getAllUSers();
}

UserMapperImpl

package com.cb.dao;

import com.cb.pojo.User;
import org.apache.ibatis.session.SqlSession;
import org.mybatis.spring.SqlSessionTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.List;

@Component
public class UserMapperImpl implements UserMapper{

    @Autowired
    private SqlSession sqlSession;

    public void UserMapperImpl(SqlSession sqlSession) {
        this.sqlSession = sqlSession;
    }



    @Override
    public List<User> getAllUSers() {

        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        List<User> users = mapper.getAllUSers();
        for (User user : users) {
            System.out.println(user);
        }
        return users;
    }
}

11.声明事务#

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

Spring事务分为:

  • 编程式事务
  • 声明式事务

事务#

一组任务要么都成功,要么都不成功。

事务ACID#

参考: ACID

  • 原子性 (atomicity)

    一个事务要么全部提交成功,要么全部失败回滚,不能只执行其中的一部分操作,这就是事务的原子性

  • 一致性 (consistency)

    事务的执行不能破坏数据库数据的完整性和一致性,一个事务在执行之前和执行之后,数据库都必须处于一致性状态。

  • 隔离性 (isolation)

    事务的隔离性是指在并发环境中,并发的事务时相互隔离的,一个事务的执行不能不被其他事务干扰。不同的事务并发操作相同的数据时,每个事务都有各自完成的数据空间,即一个事务内部的操作及使用的数据对其他并发事务时隔离的,并发执行的各个事务之间不能相互干扰。

  • 持久性 (durability)

    一旦事务提交,那么它对数据库中的对应数据的状态的变更就会永久保存到数据库中。--即使发生系统崩溃或机器宕机等故障,只要数据库能够重新启动,那么一定能够将其恢复到事务成功结束的状态

Spring事务#

一个使用 MyBatis-Spring 的其中一个主要原因是它允许 MyBatis 参与到 Spring 的事务管理中。而不是给 MyBatis 创建一个新的专用事务管理器,MyBatis-Spring 借助了 Spring 中的 DataSourceTransactionManager 来实现事务管理。

一旦配置好了 Spring 的事务管理器,你就可以在 Spring 中按你平时的方式来配置事务。并且支持 @Transactional 注解和 AOP 风格的配置。在事务处理期间,一个单独的 SqlSession 对象将会被创建和使用。当事务完成时,这个 session 会以合适的方式提交或回滚。

事务配置好了以后,MyBatis-Spring 将会透明地管理事务。这样在你的 DAO 类中就不需要额外的代码了。

标准配置开启 Spring 的事务处理功能#

要开启 Spring 的事务处理功能,在 Spring 的配置文件中创建一个 DataSourceTransactionManager 对象:

@EnableTransactionManagement //开启 Spring 的事务处理功能
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
  <constructor-arg ref="dataSource" />
</bean>
    
@Bean
public DataSourceTransactionManager transactionManager() {
  return new DataSourceTransactionManager(dataSource());
}

传入的 DataSource 可以是任何能够与 Spring 兼容的 JDBC DataSource。包括连接池和通过 JNDI 查找获得的 DataSource

注意:为事务管理器指定的 DataSource 必须和用来创建 SqlSessionFactoryBean 的是同一个数据源,否则事务管理器就无法工作了。

使用声明式事务#

Spring Boot 使用事务非常简单,

  • 首先使用注解 @EnableTransactionManagement 开启事务支持后,

  • 然后在访问数据库的Service方法上添加注解 @Transactional 便可。

参考Spring之@Transactional注解原理以及走过的坑

@Transactional注解详解

@Transactional注解可以作用于接口、接口方法、类以及类方法上

@Transactional注解的可用参数
readOnly

该属性用于设置当前事务是否为只读事务,设置为true表示只读,false则表示可读写,默认值为false

rollbackFor

该属性用于设置需要进行回滚的异常类数组,当方法中抛出指定异常数组中的异常时,则进行事务回滚。例如:

rollbackForClassName

该属性用于设置需要进行回滚的异常类名称数组,当方法中抛出指定异常名称数组中的异常时,则进行事务回滚。例如:

noRollbackFor

该属性用于设置不需要进行回滚的异常类数组,当方法中抛出指定异常数组中的异常时,不进行事务回滚

noRollbackForClassName

参照上方的例子

timeout

该属性用于设置事务的超时秒数,默认值为-1表示永不超时

propagation

该属性用于设置事务的传播行为

事物传播行为介绍:

  1. @Transactional(propagation=Propagation.REQUIRED) 如果有事务, 那么加入事务, 没有的话新建一个(默认)
  2. @Transactional(propagation=Propagation.NOT_SUPPORTED) 容器不为这个方法开启事务
  3. @Transactional(propagation=Propagation.REQUIRES_NEW) 不管是否存在事务,都创建一个新的事务,原来的挂起,新的执行完毕,继续执行老的事务
  4. @Transactional(propagation=Propagation.MANDATORY) 必须在一个已有的事务中执行,否则抛出异常
  5. @Transactional(propagation=Propagation.NEVER) 必须在一个没有的事务中执行,否则抛出异常(与Propagation.MANDATORY相反)
  6. @Transactional(propagation=Propagation.SUPPORTS) 如果其他bean调用这个方法,在其他bean中声明事务,那就用事务.如果其他bean没有声明事务,那就不用事务
isolation

该属性用于设置底层数据库的事务隔离级别

事务隔离级别介绍:

  1. @Transactional(isolation = Isolation.READ_UNCOMMITTED)读取未提交数据(会出现脏读, 不可重复读) 基本不使用
  2. @Transactional(isolation = Isolation.READ_COMMITTED)读取已提交数据(会出现不可重复读和幻读)
  3. @Transactional(isolation = Isolation.REPEATABLE_READ)可重复读(会出现幻读)
  4. @Transactional(isolation = Isolation.SERIALIZABLE)串行化

什么是脏读、幻读、不可重复读?

  1. 脏读 : 一个事务读取到另一事务未提交的更新数据
  2. 不可重复读 : 在同一事务中, 多次读取同一数据返回的结果有所不同, 换句话说, 后续读取可以读到另一事务已提交的更新数据. 相反, "可重复读"在同一事务中多次读取数据时, 能够保证所读数据一样, 也就是后续读取不能读到另一事务已提交的更新数据
  3. 幻读 : 一个事务读到另一个事务已提交的insert数据

其中MySQL默认使用的隔离级别为REPEATABLE_READ、Oracle的为READ_COMMITTED

作者:Esofar

出处:https://www.cnblogs.com/firsthelloworld/p/13417335.html

版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。

posted @   我不想学编丿程  阅读(149)  评论(0编辑  收藏  举报
编辑推荐:
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
阅读排行:
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
more_horiz
keyboard_arrow_up light_mode palette
选择主题
menu
点击右上角即可分享
微信分享提示