Spring

1. Spring介绍

Spring是一个轻量级的控制反转和面向切面的容器框架,用来解决企业项目开发的复杂度问题–解耦

  • 轻量级:体积小,对代码没有入侵性
  • 控制反转:IoC,把创建对象的工作交给Spring完成,Spring在创建对象的同时可以完成对对象属性赋值(DI,依赖注入)
  • 面向切面:AOP,面向切面编程,是OOP(面向对象编程)的升级,可以在不改变原有的业务逻辑的情况下实现对业务的增强
  • 容器:实例的容器,管理创建的对象

2. SrpingIOC-基于XML

2.1 创建Maven项目

java工程

web工程

2.2 添加SpringIoC依赖

在这里插入图片描述

  • core
  • beans
  • aop
  • expression
  • context
<dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>5.2.13.RELEASE</version>
    </dependency>

2.3创建Spring的配置文件

  • 在resources下创建applicationContext.xml(文件名可以自定义)
    • 通过配置文件“告诉”spring容器该创建什么对象,给对象赋什么值

resources–右键new XML Configuration File–Spring Config 创建文件即可

<?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">
    <!--通常一个框架为了让开发者正确的配置 都会提供xml的规范文档(dtd/xsd)-->
</beans>

3.SpringIoC的使用

使用SpringIoC组件创建并管理对象

3.1创建一个实体类(暂时不使用lombok,可以看原里)

public class Strudent {
    private String num;
    private String name;
    private String gender;
    private int age;
    private Date birth;
}

3.2使用Spring配置实体类

<?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">
    <!--通常一个框架为了让开发者正确的配置 都会提供xml的规范文档(dtd/xsd)-->
    <!--通过bean标签,将实体类配置给Spring容器管理,id实体类的唯一标识(不可以重复)-->
    <bean id="stu1" class="com.qf.pojo.Strudent">
        <!--DI依赖注入,使用spring容器对对象的属性进行赋值-->
        <property name="num" value="10000"></property>
        <property name="name" value="tom"></property>
        <property name="age" value="17"></property>
        <property name="gender" value=""></property>
    </bean>
</beans>

3.3初始化 Spring对象工厂,获取对象

ClassPathXmlApplicationContext

 @Test
    public void test01(){
        //1.初始化Spring容器,加载 spring配置文件
        ClassPathXmlApplicationContext cxa = new ClassPathXmlApplicationContext("applicationContext.xml");
        //2.通过Spring容器获取其内部管理的对象
        Strudent stu1 =(Strudent) cxa.getBean("stu1");
        //3.使用获取到的对象
        System.out.println(stu1);
        System.out.println(stu1.getName());
    }

4.IoC和DI

  • IoC控制反转,通过Spring工厂完成对象的创建
  • DI依赖注入,Spring完成对象创建的同时,可以完成属性进行赋值

4.1IoC

Spring管理创建的对象,Spring会在第一次创建时,创建内部的所有bean的对象

4.2DI

通过Spring容器为对象属性赋值,SpringDI是默认调用了对象的set方法为属性赋值的

5.DI依赖注入

Spring容器加载配置文件后,通过反射创建类的对象,并给属性赋值的

Spring容器通过反射实现属性注入的方式:

  • set方法注入
  • 构造方法注入

5.1set方法注入

使用set方法进行注入

注入类型

简单类型和String类型

<property name="num" value="10000"></property>
<property name="name" value="tom"></property>

除String外的其他引用类型 使用ref进行引入

  • 除String外的其他引用类型 使用ref进行引入

    • 方式一

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mi7GFW2D-1675334095395)(pic\02-引入其他的bean.png)]

    • 方式二

    使用的子标签来指定对象

    <property name="birth">
        <bean class="java.util.Date"></bean>
    </property>
    

集合类型:需要util命名空间配合使用(标签)

<util:可以添加命名空间
list集合

<!--注入person-->
<bean id="p1" class="com.qf.pojo.Person">
    <property name="name" value="卡了"></property>
    <property name="age" value="20"></property>
    <!--引入car-->
    <property name="cars" ref="cars"></property>
</bean>
<!--注入car-->
<bean id="car" class="com.qf.pojo.Car">
    <property name="brand" value="BMW"></property>
    <property name="price" value="20"></property>
</bean>
<!--注入list-->
<util:list id="cars">
    <!--引入已知的bean-->
    <ref bean="car"></ref>
    <ref bean="car"></ref>
    <ref bean="car"></ref>
    <ref bean="car"></ref>
</util:list>
<bean id="p2" class="com.qf.pojo.Person">
    <property name="name" value="你卡了"></property>
    <property name="age" value="20"></property>
    <!--引入car-->
    <property name="cars">
        <!--直接使用list标签-->
        <list>
            <ref bean="car"></ref>
            <ref bean="car"></ref>
            <ref bean="car"></ref>
            <ref bean="car"></ref>
        </list>
    </property>
</bean>

set集合类似与list

map集合

<bean id="p3" class="com.qf.pojo.PersonMap">
        <property name="name" value="你卡了"></property>
        <property name="age" value="20"></property>
        <property name="cars" ref="cars2"></property>
    </bean>
    <util:map id="cars2">
        <entry key="aa" value-ref="car"></entry>
        <entry key="bb" value-ref="car"></entry>
        <entry key="cc" value-ref="car"></entry>
    </util:map>

p:命名空间
简化属性注入的方式

<bean id="xx" class="xx" p:xx="" p:xx-ref=""></bean>

文档约束:
xmlns:p=“http://www.springframework.org/schema/p”

<bean id="p1" class="com.qf.pojo.Person"
  	  p:name="tom" p:age="29" p:cars-ref="cars">
</bean>

5.2构造注入

使用构造方法,进行注入

<!--使用构造注入 注入时就是按照构造的参数顺序进行注入-->
    <!--调用Car中的对应构造方法-->
    <bean id="c1" class="com.qf.pojo.Car">
        <constructor-arg value="byd"></constructor-arg>
        <constructor-arg value="20"></constructor-arg>
    </bean>
<!--可以使用index指定参数下标 使用index控制参数的顺序-->
    <bean id="s1" class="com.qf.pojo.Strudent">
        <constructor-arg value="29" index="3"></constructor-arg>
        <constructor-arg value="10000" index="0"></constructor-arg>
        <constructor-arg value="tom" index="1"></constructor-arg>
        <constructor-arg value="" index="2"></constructor-arg>
        <constructor-arg index="4">
            <bean class="java.util.Date"></bean>
        </constructor-arg>
    </bean>
<!--使用type控制参数的类型-->
<bean id="s2" class="com.qf.pojo.Strudent">
        <constructor-arg value="tom"></constructor-arg>
        <constructor-arg value="29" type="int"></constructor-arg>
    </bean>

5.3工厂注入

使用工厂模式进行注入

工厂模式:静态工厂和实例工厂

静态工厂创建

package com.qf.pojo;

import java.util.HashMap;
import java.util.Map;

public class CarStaticFactory {
    public static Map<String,Car> cars = new HashMap<String,Car>();

    static {
        cars.put("audi",new Car("audi",20));
        cars.put("audi",new Car("audi",20));
        cars.put("audi",new Car("audi",20));
        cars.put("audi",new Car("audi",20));
    }

    public static Car getCar(String brand){
        return cars.get(brand);
    }
}

静态工厂注入

<!--静态工厂 factory-method声明工厂方法-->
    <bean id="c1" class="com.qf.pojo.CarStaticFactory" factory-method="getCar">
        <!--为方法参数传值-->
        <constructor-arg value="audi"></constructor-arg>
    </bean>

实例工厂创建

package com.qf.pojo;

import java.util.HashMap;
import java.util.Map;

public class CarInstanceFactory {
    public Map<String,Car> cars = new HashMap<>();

    public CarInstanceFactory(){
        cars.put("audi",new Car("audi",20));
        cars.put("audi",new Car("audi",20));
        cars.put("audi",new Car("audi",20));
        cars.put("audi",new Car("audi",20));
    }
    public Car getCar(String brand){
        return cars.get(brand);
    }
}

实例工厂注入

<!--实例工作-->
    <!--配置实例工厂对象-->
    <bean id="carInstanceFactory" class="com.qf.pojo.CarInstanceFactory"></bean>
    <!--factory-bean:指向工厂bean -->
    <bean id="c2" factory-bean="carInstanceFactory" factory-method="getCar">
        <constructor-arg value="audi"></constructor-arg>
    </bean>

6.Bean的作用域

在bean标签可以通过scope属性指定对象的作用域

  • scope=“singleton” 表示当前bean为单例模式(默认是饿汉式,设置lazy-init="true"可以变为懒汉式)

    • ​ bean默认是饿汉的单例模式
<!--默认为单例的,容器加载时创建,所有引用公用一个地址-->
    <bean id="b1" class="com.qf.pojo.Book">
        <property name="name" value="java从门到放弃"></property>
        <property name="type" value="玄学"></property>
    </bean>
  • scope=“prototype” 表示当前bean为原型的,每次通过Spring容器获取bean时都会创建一个新的对象(和new的形式类似)
<!--使用scope="prototype"设置bean是每次获取时创建,每个引用都一个新的对象-->
    <bean id="b2" class="com.qf.pojo.Book" scope="prototype">
        <property name="name" value="mysql从删库到跑路"></property>
        <property name="type" value="神学"></property>
    </bean>

7.Bean的生命周期方法

在bean中通过init-method属性指定当前bean的初始化方法,初始化方法在构造方法之后执行,通过destory-method属性指定当前bean的销毁方法,在对象销毁前执行

public void init(){
        System.out.println("init...");
    }
    public void destory(){
        System.out.println("destory");
    }
<bean id="b1" class="com.qf.pojo.Book" init-method="init" destroy-method="destory">
        <property name="name" value="java从门到放弃"></property>
        <property name="type" value="玄学"></property>
    </bean>

8.自动装配

自动装配:Spring在实例化当前bean的时候,从Spring容器中找到与之匹配的实例赋值给当前bean的属性

不能自动装配的数据类型:Object、基本数据类型(Date、CharSequence、Number、URI、URL、Class、int)等

自动装配共有两种方式

  • byName:根据当前bean属性的名字在spring容器中寻找匹配的对象,若根据name找到了bean但类型不匹配则抛出异常
<bean id="car" class="com.qf.pojo.Car">
        <property name="brand" value="aa"></property>
        <property name="price" value="20"></property>
    </bean>
    <bean id="car111" class="com.qf.pojo.Book"></bean>

    <!--byName:匹配和Person中car属性名字一致的bean
        没有对应的bean,则赋值为null
        有,但类型不对应,则抛出异常
    -->
    <bean id="p1" class="com.qf.pojo.Person22" autowire="byName">
        <property name="name" value="tom"></property>
        <property name="age" value="19"></property>
    </bean>
  • byType:根据当前bean属性的类型在spring容器中寻找匹配的对象,若类型找到了多个则抛出异常
<!--
        byType:匹配和Person中的car为一个类型
        同一个类型不能有多个bean在容器中
   -->
    <bean id="p1" class="com.qf.pojo.Person22" autowire="byType">
        <property name="name" value="tom"></property>
        <property name="age" value="19"></property>
    </bean>

9.SpringIoC工作原理

在这里插入图片描述
Bean的获取有两种方式

  • cta.getBean(“id”);
    • 通过id去获取,需要向下造型
  • cta.getBean(xxx.class);
    • 通过类型获取,要求一个类型只能有一个bean

10.SpringIoC-基于注解

创建工程,引入依赖,创建配置文件

SpringIoC的使用,需要通过配置的形式或者注解的形式

使用注解简化开发步骤

<?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"
       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:annotation-config/>
    <!-- 声明Spring工厂注解的扫描范围 -->
    <context:component-scan base-package="com.qf"/>
</beans>

10.1常用注解

@Component

  • 使用此注解声明的类默认添加到springIoC容器中
  • id默认为类名首字母小写
  • 可以使用注解的value属性自定义id,若使用默认id则可以省略
  • 可以使用@Value对属性进行注入

@Service @Controller @Repository

  • 这三个注解与@Component没有任何的差别,语义上的不同
  • @Service服务层,处理业务逻辑的
  • @Controller控制层,类似Servlet
  • @Repository持久层,用于处理数据的
  • 处理控制器,service,dao外都使用@Component

@Scope

  • 用于声明当前类为单例模式还是非单例的

@Lazy

  • 当前单例的bean为懒汉式还是饿汉式(默认为饿汉式)

@PostConstruct

  • 方法注解,相当于bean标签的 init-method 属性
  • 声明当前类的初始化方法

@PreDestory

  • 方法注解,相当于bean标签的 destory-method 属性
  • 声明当前类的销毁方法

@Autowired

  • 自动装配,默认是byType

@Resource

  • 自动装配,默认是byName

11.代理设计模式


将通用性的工作交给代理对象完成,被代理的对象只专业做自己的核心业务

11.1静态代理

代理类只能为特定的类产生代理,不能代理任意类,局限性特别大
在这里插入图片描述
使用代理的好处

  • 被代理的类只用关注核心业务实现,将通过的管理逻辑和业务逻辑分开
  • 将通过的的代理放在代理类中,提高了代码的复用率
  • 通过代理类添加业务逻辑,可以实现对原有业务逻辑的扩展

11.2动态代理

几乎可以为所有的类产生代理对象

动态代理的实现方式有2种:

  • JDK动态代理
  • CGLib动态代理

11.2.1JDK动态代理

  • 创建一个类,实现InvocationHandler接口,重写invoke方法
  • 在类中定义一个Object类型的变量,并提供这个变量的有参构造方法,用于将被代理对象传递进来
  • 定义getProxy方法,用于创建并返回代理对象

创建代理

package com.qf;

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

/**
 * - 创建一个类,实现InvocationHandler接口,重写invoke方法
 * - 在类中定义一个Object类型的变量,并提供这个变量的有参构造方法,用于将被代理对象传递进来
 * - 定义getProxy方法,用于创建并返回代理对象
 */
public class JDKDynamicProxy implements InvocationHandler {
    //被代理的对象
    private Object obj;
    public JDKDynamicProxy(Object obj){
        this.obj = obj;
    }

    //生成代理对象,返回值为代理对象
    public Object getProxy(){
        //1.获取被代理对象的类加载器
        ClassLoader classLoader = obj.getClass().getClassLoader();
        //2.获取被代理对象的类实现的接口
        Class<?>[] interfaces = obj.getClass().getInterfaces();
        //3.产生代理对象(通过被代理对象的类加载器以及实现的接口)
        //参数1:被代理对象的类加载器
        //参数2:被代理对象的类实现的接口
        //参数3:使用产生代理对象调用方法时,用于拦截执行方法的处理器

        Object proxy = Proxy.newProxyInstance(classLoader,interfaces,this);
        return proxy;
    }

    /**
     * invoke方法的方向执行,有handler自行调用
     * proxy:代理对象
     * method:被代理者调用的方法
     * args:正在执行的方法,的方法参数
     * @return Object是当前方法的返回值
     * */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        begin();
        Object returnValue = method.invoke(obj,args);//执行method的方法
        commit();
        return returnValue;
    }

    public void begin(){
        System.out.println("...开启事务");
    }
    public void commit(){
        System.out.println("...事务提交");
    }
}

测试

package com.qf;

public class Test02 {
    public static void main(String[] args) {
        //被代理者
        UserDaoImpl udi = new UserDaoImpl();

        //使用动态代理
        JDKDynamicProxy jDKDynamicProxy = new JDKDynamicProxy(udi);/*将被代理者传入*/

        //获取代理对象
        UserDao proxy = (UserDao)jDKDynamicProxy.getProxy();
        //com.sun.proxy.$Proxy0
        System.out.println(proxy.getClass().getName());

        //使用代理执行方法
        proxy.delete();
        proxy.update();
        proxy.insert();
    }
}

11.2.2CGLib动态代理

  • 添加CGLib依赖
<!-- https://mvnrepository.com/artifact/cglib/cglib -->
    <dependency>
      <groupId>cglib</groupId>
      <artifactId>cglib</artifactId>
      <version>3.3.0</version>
    </dependency>
  • 创建一个类,实现MethodInterceptor接口,重写intercept方法
  • 在类中定义一个Object类型的变量,并提供这个变量的有参构造方法,用于将被代理对象传递进来
  • 定义getProxy方法,用于创建并返回代理对象
package com.qf;

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

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

public class CGLibDynamicProxy implements MethodInterceptor {
    //被代理的对象
    private Object obj;

    public CGLibDynamicProxy(Object obj) {
        this.obj = obj;
    }

    //生成代理对象,返回值为代理对象
    public Object getProxy() {
        Enhancer enhancer = new Enhancer();
        //传入当前类的类型
        enhancer.setSuperclass(obj.getClass());
        //传入this
        enhancer.setCallback(this);
        //创建代理对象
        Object o = enhancer.create();
        return o;
    }

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        begin();
        //执行method的方法
        Object invoke = method.invoke(obj, objects);
        commit();
        return invoke;
    }

    public void begin() {
        System.out.println("事务开启");
    }

    public void commit() {
        System.out.println("事务提交");
    }
}

测试

package com.qf;

public class Test03 {
    public static void main(String[] args) {
        //被代理者
        UserDaoImpl udi = new UserDaoImpl();
        //使用动态代理
        CGLibDynamicProxy cGLibDynamicProxy = new CGLibDynamicProxy(udi);
        //获取代理对象
        UserDao proxy = (UserDao) cGLibDynamicProxy.getProxy();
//       com.qf.UserDaoImpl$$EnhancerByCGLIB$$1f20b26d
        System.out.println(proxy.getClass().getName());
        //使用代理执行方法
        proxy.insert();
        proxy.delete();
        proxy.update();
    }
}

12.SpringAOP

AOP:面向切面编程,是一种利用"横切"的技术(底层实现就是动态代理).对原有的业务逻辑进行拦截,并且可以在这个拦截的切面上添加特定的业务逻辑,对原业务进行增强。

基于动态代理模式的,在不改变原有的业务逻辑的情况下对代码进行增强.

相关名词:

  • 连接点(JoinPoint):程序中的方法
  • 切入点(PointCut):被Spring横切的方法
  • 通知/增强:配置新增的业务的配置方式
  • 切点:添加到切入点新增的方法
  • 切面:定义切点方法的类

12.1SpringAOP部署

创建Maven项目

添加依赖
在这里插入图片描述

  • context
  • aspects
<!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>5.2.13.RELEASE</version>
    </dependency>
    <!-- https://mvnrepository.com/artifact/org.springframework/spring-aspects -->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-aspects</artifactId>
      <version>5.2.13.RELEASE</version>
    </dependency>

创建spring的配置配置文件

  • 需要引入aop命名空间
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/aop
       https://www.springframework.org/schema/aop/spring-aop.xsd">

    <aop:config></aop:config>
</beans>

12.2AOP配置基于xml

AOP开发流程:

  • 创建切面类,在切面类中定义切点方法
  • 将切面类配置给SpringIoC容器
  • 声明切入点
  • 配置aop的通知策略

创建一个Dao及其实现

package com.qf.dao;

public interface UserDao {
    void insert();
    void update();
    void delete();
}
package com.qf.dao.impl;

import com.qf.dao.UserDao;

public class UserDaoImpl implements UserDao {
    @Override
    public void insert() {
        System.out.println("insert user");
    }

    @Override
    public void update() {
        System.out.println("update user");
    }

    @Override
    public void delete() {
        System.out.println("delete user");
    }
}

创建一个类,定义要添加的业务逻辑

package com.qf.aspect;

public class TxMangerAspect {
    public void begin(){
        System.out.println("....开启事务");
    }
    public void commit(){
        System.out.println("....提交事务");
    }
}

配置aop

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/aop
       https://www.springframework.org/schema/aop/spring-aop.xsd">
    <!--使用xml的形式配置aop-->
    <!--配置实现到ioc容器中-->
    <!--     pointcut:切谁,expression:切入的方法是啥-->
    <bean id="userDaoImpl" class="com.qf.dao.impl.UserDaoImpl"></bean>
    <!--配置切面到ioc容器中-->
    <bean id="txMangerAspect" class="com.qf.aspect.TxMangerAspect"></bean>

    <!--配置aop-->
    <aop:config>
        <!--声明切入点-->
        <aop:pointcut id="pointcut" expression="execution(* com.qf.dao.impl.UserDaoImpl.*())"/>
        <!--声明切面-->
        <aop:aspect ref="txMangerAspect">
            <!--配置通知/增强-->
            <aop:before method="begin" pointcut-ref="pointcut"></aop:before>
            <aop:after method="commit" pointcut-ref="pointcut"></aop:after>
        </aop:aspect>
    </aop:config>
</beans>

测试

 @Test
    public void test01()
    {
        ClassPathXmlApplicationContext cxa = new ClassPathXmlApplicationContext("applicationContext.xml");
        UserDao udi = (UserDao)cxa.getBean("userDaoImpl");
//        com.sun.proxy.$Proxy4
        System.out.println(udi.getClass().getName());
        udi.delete();
        udi.insert();
        udi.update();
    }

12.3切入点的声明(切点表达式)

<!--声明切入点-->
        <!--execution:表达式-->
        <!--使用aop:pointcut标签声明切入点 :切入点可以是一个方法-->
<!--        <aop:pointcut id="pointcut" expression="execution(* com.qf.dao.impl.UserDaoImpl.update())"/>-->

        <!--UserDaoImpl无参无返回值的方法-->
<!--        <aop:pointcut id="pointcut" expression="execution(void com.qf.dao.impl.UserDaoImpl.*())"/>-->

        <!--具体返回值类型 *表示任意返回值-->
        <!--<aop:pointcut id="pointcut" expression="execution(int com.qf.dao.impl.UserDaoImpl.*())"/>-->
        
        <!--任意类型-->
<!--        <aop:pointcut id="pointcut" expression="execution(void com.qf.dao.impl.*.*())"/>-->

        <!--参数:单参的int类型-->
<!--        <aop:pointcut id="pointcut" expression="execution(void com.qf.dao.impl.*.*(int))"/>-->

        <!--参数:两个参数都为int int-->
        <!--<aop:pointcut id="pointcut" expression="execution(void com.qf.dao.impl.*.*(int,double))"/>-->
        
        <!--任意参数.. 大于等于0个参数-->
        <!--<aop:pointcut id="pointcut" expression="execution(void com.qf.dao.impl.*.*(..))"/>-->
        
        <!--匹配所有的方法-->
        <aop:pointcut id="pointcut" expression="execution(* *(..))"/>

12.4AOP通知策略

AOP通知:就是声明切面中的切点方法如何织入到切入点

  • before:前置通知 方法执行之前

  • after:后置通知 方法执行之后

  • after-throwing:异常通知 方法执行时产生的异常

  • after-returning:返回通知 方法正常的结果

  • around:环绕通知 包含上面的四个通知 (同一个切面中around 不要和其他的四个一起使用)

<!--声明切面-->
        <aop:aspect ref="myAspect">
            <!--前置通知-->
            <aop:before method="before" pointcut-ref="pointcut"></aop:before>
            <!--后置通知:无论是否有异常都会执行的通知-->
            <aop:after method="after" pointcut-ref="pointcut"></aop:after>
            <!--异常通知,产生的异常,赋值给afterThrowing()方法变量e-->
            <aop:after-throwing method="afterThrowing" pointcut="execution(* *(..))" throwing="e"></aop:after-throwing>
            <!--返回通知,将返回的结果赋值给result-->
            <aop:after-returning method="afterReturning" pointcut-ref="pointcut" returning="result"></aop:after-returning>
            <!--环绕通知-->
            <aop:around method="around" pointcut-ref="pointcut"></aop:around>
        </aop:aspect>
package com.qf.aspect;

import org.aspectj.lang.JoinPoint;

import java.util.Arrays;

public class MyAspect {
    //JoinPoint封装了该执行方法的信息
    public void before(JoinPoint joinPoint){
        String methodName = joinPoint.getSignature().getName();
        System.out.println("当前执行的方法是:"+methodName);
        Object[] args = joinPoint.getArgs();
        System.out.println("方法执行参数为:"+ Arrays.asList(args));
        System.out.println("前置通知");
    }

    public void after(JoinPoint joinPoint){
        String methodName = joinPoint.getSignature().getName();
        System.out.println(methodName+"执行完成...");
        System.out.println("后置通知");
    }

    //在异常通知中,得到异常信息
    public void afterThrowing(JoinPoint joinPoint,Throwable e){
        String methodName = joinPoint.getSignature().getName();
        System.out.println(methodName+"异常...为:"+e);
        System.out.println("异常通知");
    }

    public void afterReturning(JoinPoint joinPoint,Object result){
        String methodName = joinPoint.getSignature().getName();
        System.out.println(methodName+"正常执行,结果为:"+result);
        System.out.println("返回通知");
    }
    
    /**
     * 1.必须携带一个ProceedingJoinPoint类型参数
     * 2.必须有Object类型的返回值
     * 3.在前后增强的业务逻辑之间必须执行Object v = point.proceed();
     * 4.方法最后的返回值必须是v
     */
    public Object around(ProceedingJoinPoint point)  {
        Object v = null;
        System.out.println("===="+point.getSignature().getName()+"方法执行,参数:"+Arrays.asList(point.getArgs()));//before
        try {
            v = point.proceed();
            System.out.println("===="+point.getSignature().getName()+"方法正常执行,结果为:"+v);//after-returning
        } catch (Throwable e) {
//            e.printStackTrace();
            System.out.println("===="+point.getSignature().getName()+"方法异常执行,异常为:"+e);//after-throwing
        } finally {
            System.out.println("===="+point.getSignature().getName()+"方法执行完毕");//after
        }
        return v;
    }
}

12.5多切面的执行顺序

使用order属性 order的值越低优先级越高

<!--声明切面-->
<aop:aspect ref="txMangerAspect" order="0">
    <!--配置通知/增强-->
    <aop:before method="begin" pointcut-ref="pointcut"></aop:before>
    <!-- <aop:after method="commit" pointcut-ref="pointcut"></aop:after>-->
</aop:aspect>
 <!--声明切面-->
<aop:aspect ref="myAspect" order="1">
    <!--前置通知-->
    <aop:before method="before" pointcut-ref="pointcut"></aop:before>
</aop:aspect>

12.6AOP配置基于注解

创建Maven工程

添加依赖

<!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.2.13.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-aspects -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aspects</artifactId>
    <version>5.2.13.RELEASE</version>
</dependency>

Spring的配置文件

<?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:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context
       https://www.springframework.org/schema/context/spring-context.xsd
       http://www.springframework.org/schema/aop
       https://www.springframework.org/schema/aop/spring-aop.xsd">

    <!--使用注解-->
    <context:annotation-config></context:annotation-config>
    <!--包扫描,将添加注解的类注入到容器中-->
    <context:component-scan base-package="com.qf"></context:component-scan>
    <!--基于注解配置的aop代理-->
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>

aop注解配置的案例

package com.qf.aspect;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import java.util.Arrays;

//将切面添加到容器中
@Component
//声明为切面
@Aspect
//切面的优先级
@Order(0)
public class MyAspect {
    //切点表达式的定义
    @Pointcut("execution(* com.qf.dao.impl.UserDaoImpl.*(..))")
    public void pointcut(){

    }

    //前置通知
    @Before("pointcut()")
    public void before(JoinPoint joinPoint){
        String methodName = joinPoint.getSignature().getName();
        System.out.print("当前执行的方法是:"+methodName);
        Object[] args = joinPoint.getArgs();
        System.out.println("方法执行参数为:"+ Arrays.asList(args));
    }
    //后置通知
    @After("pointcut()")
    public void after(JoinPoint joinPoint){
        String methodName = joinPoint.getSignature().getName();
        System.out.println(methodName+"执行完成...");
    }

    //异常通知
    @AfterThrowing(value = "pointcut()", throwing = "e")
    public void afterThrowing(JoinPoint joinPoint,Throwable e){
        String methodName = joinPoint.getSignature().getName();
        System.out.println(methodName+"异常...为:"+e);
    }

    //返回通知
    @AfterReturning(value = "pointcut()",returning = "result")
    public void afterReturning(JoinPoint joinPoint,Object result){
        String methodName = joinPoint.getSignature().getName();
        System.out.println(methodName+"正常执行,结果为:"+result);
    }

    //环绕通知
//    @Around("pointcut()")
    public Object around(ProceedingJoinPoint point)  {
        Object v = null;
        System.out.println("===="+point.getSignature().getName()+"方法执行,参数:"+Arrays.asList(point.getArgs()));//before
        try {
            v = point.proceed();
            System.out.println("===="+point.getSignature().getName()+"方法正常执行,结果为:"+v);//after-returning
        } catch (Throwable e) {
//            e.printStackTrace();
            System.out.println("===="+point.getSignature().getName()+"方法异常执行,异常为:"+e);//after-throwing
        } finally {
            System.out.println("===="+point.getSignature().getName()+"方法执行完毕");//after
        }
        return v;
    }
}

注意:

注解使用起来方便,但需要添加到源码上,因此自定义的切面提倡使用注解,若使用第三方提供的类则需要使用xml

13.Spring整合MyBatis

Spring两大核心:SpringIoC 和SpringAOP

IoC:控制反转 spring容器可以完成对对象的创建 属性注入 对象管理等工作

AOP:面向切面编程,在不修改原有业务逻辑的情况下,进行业务的增强

13.1Spring可以对MyBatis提供哪些支持?

IoC支持:SpringIoC可以为MyBatis完成DataSource,SqlSessionFactory,SqlSession以及DAO对象的创建

​ 帮助管理MyBatis所需的bean

AOP支持:使用Spring提供的事务管理切面类完成对MyBatis的数据库操作中的事务管理

​ 进行事务管理

13.2Spring整合MyBatis的准备工作

创建Maven项目

部署MyBatis框架

  • 添加依赖
    • MySQL依赖
    • MyBatis依赖
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.49</version>
</dependency>
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.4.6</version>
</dependency>

创建MyBatis的配置文件(创建文件之后无需任何配置)

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    
</configuration>

部署Spring框架

  • 添加依赖
    • spring-context
    • spring-aspects
    • spring-jdbc
<!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.2.13.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-aspects -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aspects</artifactId>
    <version>5.2.13.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-jdbc -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-jdbc</artifactId>
    <version>5.2.13.RELEASE</version>
</dependency>

创建Spring配置文件:applicationContext.xml

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

</beans>

添加Spring整合MyBatis的依赖

mybatis-spring就是mybatis提供兼容spring补丁

<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis-spring -->
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis-spring</artifactId>
    <version>1.3.1</version>
</dependency>

13.3Spring整合MyBatis的IoC配置

整合Druid连接池(数据源)

  • 添加druid的依赖
<!-- https://mvnrepository.com/artifact/com.alibaba/druid -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.1.11</version>
</dependency>
  • 添加druid.properties属性文件
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/mysql?characterEncoding=utf-8
username=root
password=root
  • 在applicationContext.xml中配置DruidDataSource
<!--加载druid.properties属性文件-->
<context:property-placeholder location="druid.properties"></context:property-placeholder>
<!--依赖Spring容器完成对数据源DruidDataSource的注入-->
<bean id="druidDataSource" class="com.alibaba.druid.pool.DruidDataSource">
    <property name="driverClassName" value="${driver}"></property>
    <property name="url" value="${url}"></property>
    <property name="username" value="${username}"></property>
    <property name="password" value="${password}"></property>
</bean>
  • 使用SpringIoC完成SqlSessionFactory的注入
<!--依赖Spring容器完成对SqlSessionFactory的注入-->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
    <!--配置数据源-->
    <property name="dataSource" ref="druidDataSource"></property>
    <!--管理所有的映射文件:类路径下mappers文件夹中所有一Mapper.xml结尾的文件-->
    <property name="mapperLocations" value="classpath:mappers/*Mapper.xml"></property>
    <!--配置MyBatis的主配置文件,可选-->
    <property name="configLocation" value="classpath:mybatis-config.xml"></property>
    <!--配置需要定义的实体类的别名,可选-->
    <property name="typeAliasesPackage" value="com.qfedu.pojo"></property>
</bean>

13.4创建Mapper

<!--加载dao包中所有DAO接口 ,通过SqlSessionFactory获取SqlSession,然后创建所有DAO接口对象,存在于Spring容器中-->
<!--将Mybatis的代理"实现"交给Spring容器管理 -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
    <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"></property>
    <!--dao的bean的id名字为XxxDao类名首字母小写-->
    <property name="basePackage" value="com.qfedu.dao"></property>
</bean>

13.5Spring整合MyBatis的AOP配置

使用Spring提供的事务管理切面类,完成对DAO中增删改的事务管理

13.5.1事务的隔离级别

ACID:原子性 一致性 隔离性 持久性

隔离级别英文描述
串行化SERIALIZABLET1在执行的过程中,T2不能读也不能写(安全性最高,效率是最低)
可重复读REPEATABLE_READT1在执行的过程中,T2能读不能改,T2可以添加的数据(幻读/虚读)(实际开发中使用最多的)
读已提交READ_COMMITTEDT1在执行的过程中,T2可以读也可以写,但T1只能读到T2以提交的数据(不可重复读)(幻读)
读未提交READ_UNCOMMITTEDT1在执行的过程中,T2可以读也可以写,T1可以读取到T2未提交的数据不可重复读)(幻读)(脏读)

13.5.2事务的传播机制

propagation设置事务的传播机制

传播机制解释
REQUIRED(默认)支持当前事务,当前事务不存在,创建一个新的事务(常用于增删改,因情况而定)
SUPPORTS支持当前事务,若当前事务不存在,则不使用事务(常用于查询,因情况而定)
REQUIRES_NEW创建一个新的事务,若当前事务存在,则挂起当前事务
NOT_SUPPORTED无事务执行,若当前事务存在,则挂起
NEVER无事务执行,若当前事务存在,则抛出异常
MANDATORY支持当前事务,若当前事务不存在,则抛出异常
NESTED嵌套事务,若当前事务存在,则嵌套在事务中执行,若不存在和REQUIRED是一样的

13.5.3SpringAOP事管理配置-xml配置

<!--1.将spring中提供的事务管理切面类注入到spring容器中-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="druidDataSource"></property>
</bean>
<!--2.通过SpringJDBC提供的tx标签,声明事务管理策略-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
    <!--事务管理策略-->
    <tx:attributes>
        <!--配置事务的隔离级别和传播机制-->
        <!--REPEATABLE_READ:隔离级别为重复读-->
        <!--REQUIRED:没有事务新建一个事务 ,若有事务 则加入-->
        <tx:method name="insert*" isolation="READ_COMMITTED" propagation="REQUIRED"/>
        <tx:method name="delete*" isolation="READ_COMMITTED" propagation="REQUIRED"/>
        <tx:method name="update*" isolation="READ_COMMITTED" propagation="REQUIRED"/>
        <tx:method name="query*" isolation="READ_COMMITTED" propagation="SUPPORTS"/>
    </tx:attributes>
</tx:advice>
<!--3.将事务管理策略以AOP配置的形式应用于DAO操作方法-->
<!--每个dao方法只做一个操作 业务逻辑在service包中编写-->
<aop:config>
    <aop:pointcut id="crud" expression="execution(* com.qfedu.service.impl.*.*(..))"/>
    <!--应用策略在对应的方法之上-->
    <aop:advisor advice-ref="txAdvice" pointcut-ref="crud"></aop:advisor>
</aop:config>

13.5.3SpringAOP事管理配置-注解配置

  • 在applicationContext.xml中配置事务管理,声明使用注解的方式进行事务配置
<!--1.将spring中提供的事务管理切面类注入到spring容器中-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="druidDataSource"></property>
</bean>
<!--2.声明使用注解完成事务配置-->
<tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>
  • 在需要Spring进行事务管理的方法之上添加@Transactional注解即可
//可以在注解内设置隔离级别和传播机制
@Transactional(isolation = Isolation.REPEATABLE_READ,propagation = Propagation.REQUIRED)
public void updateBook() {
    //更新用户的余额

    //更新库存
}

14事务管理案例

买书管理

所需表

用户表:t_user(u_id,u_name,u_blance)

图书表:t_book(b_id,b_name,b_price,b_store)

买书的过程(默认买一本)

public boolean buyBook(b_id,u_id);//业务:书是什么用户是谁

事务

正常的事务

用户的余额更新

仓库的库存更新

事务回滚

余额不足

库存不足

数据表的创建

CREATE TABLE `t_book` (
  `b_id` int(11) NOT NULL,
  `b_name` varchar(50) DEFAULT NULL,
  `b_price` double DEFAULT NULL,
  `b_store` int(11) DEFAULT NULL,
  PRIMARY KEY (`b_id`)
)
CREATE TABLE `t_user` (
  `u_id` int(11) NOT NULL,
  `u_name` varchar(10) DEFAULT NULL,
  `u_blance` double DEFAULT NULL,
  PRIMARY KEY (`u_id`)
)

实体类的创建

public class Book {

    private int bId;
    private String bName;
    private double bPrice;
    private int bStore;
}
public class User {
    private int uId;
    private String uName;
    private double uBlance;
}

接口的创建

public interface UserDao {
    //根据用户id查询余额
    double queryById(int uId);

    //根据id和价格(书的价格)修改余额
    void updateUser(@Param("uId") int uId, @Param("price") double price);
}
public interface BookDao {

    //根据编号查询价格
    double queryById(int bId);
    
    //查询库存
    int queryStore(int bId);

    //根据书的编号改变库存-1
    void updateStroe( int bId);
}

Mapper的创建

<mapper namespace="com.qfedu.dao.UserDao">
    <update id="updateUser">
        update t_user set u_blance = u_blance - #{price} where u_id = #{uId}
    </update>
    <select id="queryById" resultType="java.lang.Double">
        select u_blance from t_user where u_id = #{uId}
    </select>
</mapper>
<mapper namespace="com.qfedu.dao.BookDao">
    <update id="updateStroe">
        update t_book set b_store = b_store - 1 where b_id = #{bId}
    </update>
    <select id="queryById" resultType="java.lang.Double">
        select b_price from t_book where b_id = #{bId}
    </select>
    <select id="queryStore" resultType="java.lang.Integer">
        select b_store from t_book where b_id = #{bId}
    </select>
</mapper>

service的创建

package com.qfedu.service.impl;

import com.qfedu.dao.BookDao;
import com.qfedu.dao.UserDao;
import com.qfedu.exception.BlanceException;
import com.qfedu.exception.StoreException;
import com.qfedu.service.BuyBookService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

//将service添加到容器中
@Service
public class BuyBookServiceImpl implements BuyBookService {

    @Autowired
    private BookDao bookDao;
    @Autowired
    private UserDao userDao;

    /**
     *
     * @param uId 用户编号
     * @param bId 图书编号
     */
    @Override
    @Transactional
    public void buyBook(int uId, int bId) {
        //1.通过用户的id查询余额
        double uBlance = userDao.queryById(uId);
        //2.通过图书的id查询价格
        double bPrice = bookDao.queryById(bId);
        //3.通过图书的id查询库存
        int store = bookDao.queryStore(bId);
        //4.做一系列限定 库存不足 余额不足
        //余额限定
        if(uBlance < bPrice){
            throw new BlanceException("余额不足");
        }
        //5.更新余额
        userDao.updateUser(uId,bPrice);
        //库存不足
        if(store <= 0){
            throw new StoreException("库存不足");
        }
        //6.更新库存
        bookDao.updateStroe(bId);
    }
}

测试

@Test
public void test04(){
    ClassPathXmlApplicationContext cax = new ClassPathXmlApplicationContext("applicationContext.xml");
    BuyBookService buyBookService  = (BuyBookService) cax.getBean("buyBookServiceImpl");
    buyBookService.buyBook(1,1);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值