学习Spring----AOP+手动实现Spring底层机制+JdbcTemplate+声明式事务

学习Spring----AOP

动态代理–精致小案列

需求说明:
1, 有一个交通接口Vehicle, 有一个run(), 下面有两个实现类Car 和 Ship
2. 当运行Car对象的run() 和 Ship对象的run()时,输出内容如下:

交通工具开始运行了...
大轮船在水上 running...
交通工具停止运行了...
============================
交通工具开始运行了...
小汽车在公路 running...
交通工具停止运行了...

原始解决方案

public interface Vehicle {
    public void run();
}
public class Car implements Vehicle{

    @Override
    public void run() {
        System.out.println("交通工具开始运行了...");
        System.out.println("小汽车在公路 running...");
        System.out.println("交通工具停止运行了...");
    }
}
public class Ship implements Vehicle{
    @Override
    public void run() {
        System.out.println("交通工具开始运行了...");
        System.out.println("大轮船在水上 running...");
        System.out.println("交通工具停止运行了...");
    }
}
@Test
    public void testProexy() {
        Vehicle vehicle = new Ship();
        vehicle.run();
    }

动态代理(利用反射)解决方案:

public interface Vehicle {
    public void run();
    public void fly(int height);
}
public class Car implements Vehicle{

    @Override
    public void run() {
        //System.out.println("交通工具开始运行了...");
        System.out.println("小汽车在公路 running...");
        //System.out.println("交通工具停止运行了...");
    }

    @Override
    public void fly(int height) {
        System.out.println("飞机飞行的高度为 " + height);
    }
}
public class Ship implements Vehicle{
    @Override
    public void run() {
        //System.out.println("交通工具开始运行了...");
        System.out.println("大轮船在水上 running...");
        //System.out.println("交通工具停止运行了...");
    }

    @Override
    public void fly(int height) {
        System.out.println("飞机飞行的高度为 " + height);
    }
}
package com.zzti.spring.proxy_;

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

/**
 * 该类可以返回一个代理对象
 */
public class VehicleProxyProvider {
    //定义一个属性,target_vehicle表示真正要执行的对象
    private Vehicle target_vehicle;

    //构造器
    public VehicleProxyProvider(Vehicle target_vehicle){
        this.target_vehicle = target_vehicle;
    }

    //编写一个方法,可以返回一个代理对象, 该代理对象可以通过反射机制调用到被代理对象的方法
    public Vehicle getProxy() {
        //得到类加载器
        ClassLoader classLoader = target_vehicle.getClass().getClassLoader();
        //得到要代理的对象/被执行对象 的接口信息. 底层是通过接口来完成调用
        Class<?>[] interfaces = target_vehicle.getClass().getInterfaces();
        //创建InvocationHandler对象
        //因为InvocationHandler是接口, 所以我们可以通过匿名对象的方式来创建该对象
        /**
         * public interface InvocationHandler {
         *  public Object invoke (Object proxy, Method method, Object[]args)
         *         throws Throwable;
         *}
         * invoke()是将来执行我们的target_vehicle的方法时,会调用到
         */
        InvocationHandler invocationHandler = new InvocationHandler() {
            /**
             * invoke()是将来执行我们的target_vehicle的方法时,会调用到
             * @param proxy 表示代理对象
             * @param method 通过代理对象调用方法时的哪个方法 代理对象.run()
             * @param args 表示调用 代理对象.run(xx) 传入的参数
             * @return 表示 代理对象.run(xx) 执行后的结果
             * @throws Throwable
             */
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println("交通工具开始运行了...");
                /**
                 * method是? ===>
                 * target_vehicle是? ===> Car对象/Ship对象
                 * args 是 null
                 */
                Object result = method.invoke(target_vehicle, args);
                System.out.println("交通工具停止运行了...");
                return result;
            }
        };

        /**
         *  public static Object newProxyInstance(ClassLoader loader,
         *                                           Class<?>[] interfaces,
         *                                           InvocationHandler h)
         * Proxy.newProxyInstance可以返回一个代理对象
         * ClassLoader loader 类的加载器
         * Class<?>[] interfaces 就是奖励啊要代理的对象的接口信息
         *  InvocationHandler h 调用处理器/对象 有一个非常重要的方法invoke
         */
        Vehicle proxy = (Vehicle)Proxy.newProxyInstance(classLoader, interfaces, invocationHandler);
        return proxy;
    }
}
@Test
public void testProexyRun() {
    Vehicle vehicle = new Car();

    //创建VehicleProxyProvider对象, 并且传入要代理的对象
    VehicleProxyProvider vehicleProxyProvider = new VehicleProxyProvider(vehicle);

    //获取代理对象, 该对象可以代理执行方法
    Vehicle proxy = vehicleProxyProvider.getProxy();
    System.out.println("proxy的编译类型是 Vehicle");
    //proxy的运行类型是= class com.sun.proxy.$Proxy2
    System.out.println("proxy的运行类型是= " + proxy.getClass());
    proxy.run();
    //proxy.fly(200);
}
=============================================
proxy的编译类型是 Vehicle
proxy的运行类型是= class com.sun.proxy.$Proxy2
交通工具开始运行了...
小汽车在公路 running...
交通工具停止运行了...
============================================
proxy的编译类型是 Vehicle
proxy的运行类型是= class com.sun.proxy.$Proxy2
交通工具开始运行了...
飞机飞行的高度为 200
交通工具停止运行了...

动态代理的 动态是怎么体现的?

  1. proxy的运行类型是 class com.sun.proxy.$Proxy2 该类型被转成Vehicle,因此可以调用Vehicle的接口方法
  2. 当执行run()时会调用,根据java的动态代理绑定机制,这时直接调用Car的run()是 通过proxy对象的invocationHandler的invoke(),invoke()使用反射机制来调用run()[这个run() 也可以是Vehicle接口下面的其他方法],这时可以在调用run()前,进行前置和后置处理
  3. 也就是说proxy的target_vehicle运行类型只要是实现了Vehicle接口,就可以调用不同的方法,是动态的,变化的。底层就是反射完成的。

动态代理深入(处理日志)

土方法;

public interface SmartAnimal {
    public float getSum(float i,float j);
    public float getSub(float i,float j);
}
public class SmartDog implements SmartAnimal {

    @Override
    public float getSum(float i, float j) {
        System.out.println("日志-方法名-getSum()开始-参数: " + i + "," + j);
        float result = i + j;
        System.out.println("方法内部打印: result= " + result);
        System.out.println("日志-方法名-getSum()结束-结果: result= " + result);
        return result;
    }

    @Override
    public float getSub(float i, float j) {
        System.out.println("日志-方法名-getSub()开始-参数: " + i + "," + j);
        float result = i - j;
        System.out.println("方法内部打印: result= " + result);
        System.out.println("日志-方法名-getSub()结束-结果: result= " + result);
        return result;
    }
}
 @Test
    public void smartAnimalTest() {
        SmartDog smartDog = new SmartDog();
        smartDog.getSum(10.50f,20.50f);
        smartDog.getSub(10,8);
    }

使用动态代理

    //在调用目标方法之前打印日志
    public void before(Object proxy, Method method, Object[] args){
        System.out.println("before~日志-方法名:" + method.getName() +"--方法开始--参数: " + Arrays.asList(args));
    }

    //在调用目标方法之后打印日志
    public void after(Method method, Object result){
        System.out.println("after~日志-方法名:" + method.getName() +"--方法正常结束--result= " + result);
    }
public SmartAnimal getProxy() {
        //得到类加载器
        ClassLoader classLoader = target_smartAnimal.getClass().getClassLoader();
        //得到要代理的对象/被执行对象 的接口信息. 底层是通过接口来完成调用
        Class<?>[] interfaces = target_smartAnimal.getClass().getInterfaces();

        InvocationHandler invocationHandler = new InvocationHandler() {

            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                Object result = null;
                try {
                    before(proxy, method, args);//切入到目标方法前
                    result = method.invoke(target_smartAnimal, args);
                    after(method, result);//切入到目标方法后
                }catch (Exception e){
                    e.printStackTrace();
                    System.out.println("日志-方法名:" + method.getName() +"--方法抛出异常--异常类型: " + e.getClass().getName());
                }finally {
                    System.out.println("日志-方法名:" + method.getName() +"--方法最终结束");
                }
                return result;
            }
        };

        //将上面的classLoader,interfaces,invocationHandler构建成一个Vehicle的代理对象

        SmartAnimal proxy = (SmartAnimal)Proxy.newProxyInstance(classLoader, interfaces, invocationHandler);
        return proxy;
    }
 @Test
    public void smartAnimalTestByProexy() {
        SmartAnimal smartAnimal = new SmartDog();
        MyProxyProvider myProxyProvider = new MyProxyProvider(smartAnimal);
        SmartAnimal proxy = myProxyProvider.getProxy();
        proxy.getSum(10.50f,20.50f);
        proxy.getSub(1,0.50f);
    }

这种方法耦合度太高,开发简易的AOP切入类

public class AOP {
    //在调用目标方法之前打印日志
    public static void before(Object proxy, Method method, Object[] args){
        System.out.println("AOP~日志-方法名:" + method.getName() +"--方法开始--参数: " + Arrays.asList(args));
    }

    //在调用目标方法之后打印日志
    public static void after(Method method, Object result){
        System.out.println("AOP~日志-方法名:" + method.getName() +"--方法正常结束--result= " + result);
    }
}
public SmartAnimal getProxy() {
        //得到类加载器
        ClassLoader classLoader = target_smartAnimal.getClass().getClassLoader();
        //得到要代理的对象/被执行对象 的接口信息. 底层是通过接口来完成调用
        Class<?>[] interfaces = target_smartAnimal.getClass().getInterfaces();

        InvocationHandler invocationHandler = new InvocationHandler() {

            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                Object result = null;
                try {
                    AOP.before(proxy, method, args);//切入到目标方法前
                    result = method.invoke(target_smartAnimal, args);
                    AOP.after(method, result);//切入到目标方法后
                }catch (Exception e){
                    e.printStackTrace();
                    System.out.println("日志-方法名:" + method.getName() +"--方法抛出异常--异常类型: " + e.getClass().getName());
                }finally {
                    System.out.println("日志-方法名:" + method.getName() +"--方法最终结束");
                }
                return result;
            }
        };

        //将上面的classLoader,interfaces,invocationHandler构建成一个Vehicle的代理对象

        SmartAnimal proxy = (SmartAnimal)Proxy.newProxyInstance(classLoader, interfaces, invocationHandler);
        return proxy;
    }

引出AOP

  1. 土方法不够灵活,复用性差还是一种硬编码(没有注解和反射支撑)。
  2. 从而引出AOP,底层是ASPECTJ
  3. AOP的全称(aspect oriented programming),面向切面编程
    在这里插入图片描述
    在这里插入图片描述

两张图说明AOP的相关概念

AOP的实现方式

  1. 基于动态代理的方式[内置aop实现]
  2. 使用框架aspectj来实现

快速入门:
需要引入核心的aspect包
在切面类中声明通知方法

  1. 前置通知: @Before
  2. 返回通知: @AfterReturning
  3. 异常通知: @AfterThrowing
  4. 后置通知: @After
  5. 环绕通知: @Around
try {
	//前置通知
	System.out.println("日志-方法名:" + method.getName() +"--方法开始--参数: " + Arrays.asList(args));
	result = method.invoke(target_smartAnimal, args);
	//返回通知
	System.out.println("日志-方法名:" + method.getName() +"--方法正常结束--result= " + result);
}catch (Exception e){
	e.printStackTrace();
	//异常通知
	System.out.println("日志-方法名:" + method.getName() +"--方法抛出异常--异常类型: " + e.getClass().getName());
}finally {
	//后置通知
	System.out.println("日志-方法名:" + method.getName() +"--方法最终结束");
}
//环绕通知可以将四个通知合并管理

测试几种通知的案列:

public interface SmartAnimal {
    public float getSum(float i,float j);
    public float getSub(float i,float j);
}
@Component
public class SmartDog implements SmartAnimal {

    @Override
    public float getSum(float i, float j) {
        float result = i + j;
        //int res = 10 / 0;//测试异常通知
        System.out.println("方法内部打印: result= " + result);
        return result;
    }

    @Override
    public float getSub(float i, float j) {
        float result = i - j;
        System.out.println("方法内部打印: result= " + result);
        return result;
    }
}
package com.zzti.spring.aop.aspectj;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
import java.util.Arrays;

/**
 * 该类就是管理切面编程
 */

@Aspect //表示该类是一个切面类(底层切面编程的支撑[动态代理+反射+动态绑定])
@Component //表示需要加入到ioc容器
public class SmartAnimalAspect {

    /**
     * 1. @Before 表示在我们的目标对象执行方法前执行
     * 2. value = "execution(public float com.zzti.spring.aop.aspectj.SmartDog.getSum(float,float))"
     * 表示指定切入到哪个类的哪个方法 形式是: 访问修饰符 返回类型 全类名.方法名(形参列表)
     * 3. showBeginLog方法 可以理解成: 一个切入方法,这个方法名可以随意指定
     * 4. JoinPoint joinPoint在底层执行时,由Aspectj切面框架,会给切入方法传入joinPoint对象,通过该方法, 可以获取到相关信息
     * 5. @Before(value = "execution(public float com.zzti.spring.aop.aspectj.SmartDog.*(float,float))") 这样写的话,SmartDog()下面所有的方法,都会切入到目标方法中
     * @param joinPoint
     */
    @Before(value = "execution(public float com.zzti.spring.aop.aspectj.SmartDog.*(float,float))")
    public  void showBeginLog(JoinPoint joinPoint){
        System.out.println("======前置通知======");
        Signature signature = joinPoint.getSignature();
        System.out.println("前置通知~日志-方法名:" + signature.getName() +"--方法开始--参数: " + Arrays.asList(joinPoint.getArgs()));
    }

    /**
     * AfterReturning 返回通知, 把showSuccessEndLog方法切入到目标对象方法正常执行完毕后的地方
     * 如果我们希望把目标方法执行的结果,返回给切入方法,需要在加上returning属性, 同时切入方法增加Object res
     * 要注意returning = "res" 和 Object res 的 res名字要一致
     * @param joinPoint
     * @param res
     */
    @AfterReturning(value = "execution(public float com.zzti.spring.aop.aspectj.SmartDog.getSum(float,float))",returning = "res")
    public  void showSuccessEndLog(JoinPoint joinPoint, Object res){
        System.out.println("======返回通知======");
        Signature signature = joinPoint.getSignature();
        System.out.println("返回通知~日志-方法名:" + signature.getName() +"--方法正常结束--返回的结果是: " + res);
    }

    /**
     * AfterThrowing 异常通知, 把showExceptionLog方法切入到目标对象方法发生异常的catch{}
     * @param joinPoint
     * @param throwable
     */
    @AfterThrowing(value = "execution(public float com.zzti.spring.aop.aspectj.SmartDog.getSum(float,float))",throwing = "throwable")
    public  void showExceptionLog(JoinPoint joinPoint, Throwable throwable){
        System.out.println("======异常通知======");
        Signature signature = joinPoint.getSignature();
        System.out.println("异常通知~日志-方法名:" + signature.getName() +"--方法异常信息--返回的结果是: " + throwable);
    }

    /**
     * After 后置通知, 把showFinallyEndLog方法切入到目标对象方法执行后(不管是否发生异常,都要执行finally{})
     * @param joinPoint
     */
    @After(value = "execution(public float com.zzti.spring.aop.aspectj.SmartDog.getSum(float,float))")
    public  void showFinallyEndLog(JoinPoint joinPoint){
        System.out.println("======后置通知======");
        Signature signature = joinPoint.getSignature();
        System.out.println("后置通知~日志-方法名:" + signature.getName());
    }
}
@Test
public void smartDogTestByProxy(){
   ApplicationContext ioc = new ClassPathXmlApplicationContext("beans06.xml");
   //需要通过接口类型来获取到注入的SmartDog对象--就是代理对象
    SmartAnimal smartAnimal = ioc.getBean(SmartAnimal.class);
    smartAnimal.getSum(10,2);
    System.out.println("===================");
    smartAnimal.getSub(10,5);
}
==================测试结果========================
======前置通知======
前置通知~日志-方法名:getSum--方法开始--参数: [10.0, 2.0]
方法内部打印: result= 12.0
======返回通知======
返回通知~日志-方法名:getSum--方法正常结束--返回的结果是: 12.0
======后置通知======
后置通知~日志-方法名:getSum
===================
======前置通知======
前置通知~日志-方法名:getSub--方法开始--参数: [10.0, 5.0]
方法内部打印: result= 5.0

细节说明:

  1. 关于切面类方法命名虽然可以随意命名,但是自己可以规范一下, 比如: showBeginLog()、showSuccessEndLog()、showExceptionLog()、showFinallyEndLog()
  2. 切入表达式的更多配置,比如使用模糊配置: @Before(value = “execution(public float com.zzti.spring.aop.aspectj.SmartDog.*(float,float))”)
  3. 表示所有访问权限,所有包的下所有类的所有方法,都会被执行该前置通知方法@Before(value = “execution(* .(…))”)
  4. 当spring容器开启了
    <aop:aspectj-autoproxy />,我们获取注入的对象,需要以接口的类型获取,因为你注入的对象.getClass(),已经是代理类型了。同时也可以通过id获取,但是也要转成接口类型。

课后作业:

public interface UsbInterface {
    public void work();
}
@Component
public class Phone implements UsbInterface{
    @Override
    public void work() {
        System.out.println("Phone work() 执行了~~~");
    }
}
@Component
public class Camera implements UsbInterface{
    @Override
    public void work() {
        System.out.println("Camera work() 执行了~~~");
    }
}
package com.zzti.spring.aop.aspectj.homework;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;


@Aspect
@Component
public class UsbAspect {

    @Before(value = "execution(public void com.zzti.spring.aop.aspectj.homework.Phone.work()) || execution(public void com.zzti.spring.aop.aspectj.homework.Camera.work())")
    public  void showBeginLog(JoinPoint joinPoint){
        System.out.println("======前置通知======");
        Signature signature = joinPoint.getSignature();
        System.out.println("前置通知~日志-方法名:" + signature.getName());
    }

    //切入表达式也可以指向接口的方法, 这时切入表达式会对实现接口的类/对象生效
    //比如下面的对UsbInterface切入, 那么对实现类Phone 和 Camera 对象都作用了
    @After(value = "execution(public void com.zzti.spring.aop.aspectj.homework.UsbInterface.work())")
    public  void showFinallyEndLog(JoinPoint joinPoint){
        System.out.println("======后置通知======");
        Signature signature = joinPoint.getSignature();
        System.out.println("后置通知~日志-方法名:" + signature.getName());
    }
}
package com.zzti.spring.aop.aspectj.homework;


import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class UsbAspectTest {

    @Test
    public void testUsbAspect(){
        ApplicationContext ioc = new ClassPathXmlApplicationContext("beans06.xml");

        //需要通过接口类型来获取到注入的UsbInterface对象--就是代理对象
        //这样会报错,因为UsbInterface下面有两个bean(phone,camera), 不知道使用哪个
        //UsbInterface usbInterface = ioc.getBean(UsbInterface.class);

        //方式1
        //UsbInterface usbInterface = ioc.getBean("phone",UsbInterface.class);
        //UsbInterface usbInterface2 = ioc.getBean("camera",UsbInterface.class);
        //usbInterface.work();
        //usbInterface2.work();

        //方式2
        UsbInterface phone = (UsbInterface) ioc.getBean("phone");
        UsbInterface camera = (UsbInterface) ioc.getBean("camera");
        phone.work();
        System.out.println("=========分隔符==========");
        camera.work();
    }
}
===============测试结果================
======前置通知======
前置通知~日志-方法名:work
Phone work() 执行了~~~
======后置通知======
后置通知~日志-方法名:work
=========分隔符==========
======前置通知======
前置通知~日志-方法名:work
Camera work() 执行了~~~
======后置通知======
后置通知~日志-方法名:work

切入表达式

  • 切入点表达式作用:
    通过表达式的方式定位一个或多个具体的连接点

  • 语法细节
    a. 切入点表达式的语法格式

    execution([权限修饰符] [返回值类型] [全类名/简单类名] [方法名] ([参数列表]))
    举例说明: execution(* com.zzti.spring…aop.aspectj.SmartDog.*(…))
    第一个 * 代表 任意修饰符及任意返回值
    第二个 * 代表 任一方法
    . . 匹配任意数量、任意类型的参数
    若目标类、接口与切面类在同一个包中可以省略。

    b. 列举几个小列子

    1.execution( public * com.zzti.spring…aop.aspectj.SmartDog.(…)) ==> SmartDog中的所有公共方法
    2.execution( public double com.zzti.spring…aop.aspectj.SmartDog.
    (…)) ==> SmartDog中返回double类型数值的方法
    3.execution( public double com.zzti.spring…aop.aspectj.SmartDog.*(double, . .)) ==> SmartDog中第一个参数为double类型的方法,后面的. .匹配任意数量、任意类型的参数

    c. 切入点表达式重用(为了统一管理切入点表达式)

@Aspect //表示该类是一个切面类(底层切面编程的支撑[动态代理+反射+动态绑定])
@Component //表示需要加入到ioc容器
public class SmartAnimalAspect {

    //定义一个切入点, 在后面使用时可以直接使用,提高了复用性
    @Pointcut(value = "execution(public float com.zzti.spring.aop.aspectj.SmartDog.*(float,float))")
    public void myPointcut(){
    }
    
    //使用定义好的切入点
    @Before(value = "myPointcut()")
    //@Before(value = "execution(public float com.zzti.spring.aop.aspectj.SmartDog.*(float,float))")
    public  void showBeginLog(JoinPoint joinPoint){
        System.out.println("======前置通知======");
        Signature signature = joinPoint.getSignature();
        System.out.println("前置通知~日志-方法名:" + signature.getName() +"--方法开始--参数: " + Arrays.asList(joinPoint.getArgs()));
    }
    
     @AfterReturning(value = "myPointcut()",returning ="res")
    //@AfterReturning(value = "execution(public float com.zzti.spring.aop.aspectj.SmartDog.getSum(float,float))",returning = "res")
    public  void showSuccessEndLog(JoinPoint joinPoint, Object res){
        System.out.println("======返回通知======");
        Signature signature = joinPoint.getSignature();
        System.out.println("返回通知~日志-方法名:" + signature.getName() +"--方法正常结束--返回的结果是: " + res);
    }
    
    @AfterThrowing(value ="myPointcut()",throwing = "throwable")
    //@AfterThrowing(value = "execution(public float com.zzti.spring.aop.aspectj.SmartDog.getSum(float,float))",throwing = "throwable")
    public  void showExceptionLog(JoinPoint joinPoint, Throwable throwable){
        System.out.println("======异常通知======");
        Signature signature = joinPoint.getSignature();
        System.out.println("异常通知~日志-方法名:" + signature.getName() +"--方法异常信息--返回的结果是: " + throwable);
    }
    
    @After(value = "myPointcut()")
    //@After(value = "execution(public float com.zzti.spring.aop.aspectj.SmartDog.getSum(float,float))")
    public  void showFinallyEndLog(JoinPoint joinPoint){
        System.out.println("======后置通知======");
        Signature signature = joinPoint.getSignature();
        System.out.println("后置通知~日志-方法名:" + signature.getName());
    }
}
  • 注意事项与细节

  • 切入表达式也可以指向类的方法,这时切入表达式会对该类/对象生效;

@Before(value = "execution(public void com.zzti.spring.aop.aspectj.homework.Phone.work()) || execution(public void com.zzti.spring.aop.aspectj.homework.Camera.work())")
public  void showBeginLog(JoinPoint joinPoint){
    System.out.println("======前置通知======");
    Signature signature = joinPoint.getSignature();
    System.out.println("前置通知~日志-方法名:" + signature.getName());
}
  • 切入表达式也可以指向接口的方法,这时切入表达式会对实现了接口的类/对象生效;
//切入表达式也可以指向接口的方法, 这时切入表达式会对实现接口的类/对象生效
//比如下面的对UsbInterface切入, 那么对实现类Phone 和 Camera 对象都作用了
@After(value = "execution(public void com.zzti.spring.aop.aspectj.homework.UsbInterface.work())")
public  void showFinallyEndLog(JoinPoint joinPoint){
    System.out.println("======后置通知======");
    Signature signature = joinPoint.getSignature();
    System.out.println("后置通知~日志-方法名:" + signature.getName());
}
  • 切入表达式也可以对没有实现接口的类,进行切入.(这种方式是通过CGlib动态代理的)
/切入表达式也可以对没有实现接口的类, 进行切入
//给Car设置一个后置通知
@After(value = "execution(public void Car.run())")
public void ok1(JoinPoint joinPoint){
    System.out.println("======Car后置通知======");
    Signature signature = joinPoint.getSignature();
    System.out.println("Car后置通知~日志-方法名:" + signature.getName());
}
@Test
public void testCar() {
    ApplicationContext ioc = new ClassPathXmlApplicationContext("beans06.xml");
    Car car = ioc.getBean(Car.class);
    //car对象仍然是代理对象
    //car的类型是= class com.zzti.spring.aop.aspectj.homework.Car$$EnhancerBySpringCGLIB$$1fd8defb
    //这种没有实现接口来代理,是基于Spring的CGlib; 之前通过实现接口是通过动态代理jdk的proxy
    System.out.println("car的类型是= " + car.getClass());
    car.run();
}

两个动态代理的区别

  • JDK动态代理是面向接口的,只能增强实现类中接口中存在的方法。CGlib是面向父类的,可以增强父类的所有方法

  • JDK得到的对象是JDK代理对象实例,而CGlib得到的对象是被代理对象的子类
    参考如下链接:
    https://www.cnblogs.com/threeAgePie/p/15832586.html

AOP—JoinPoint

通过JoinPoint可以获取到调用方法的签名

joinPoint.getSignature().getName();//获取目标方法名
joinPoint.getSignature().getDeclaringType().getSimpleName();//获取目标方法所属类的简单名
joinPoint.getSignature().getDeclaringTypeName();//获取目标方法所属类的类名
joinPoint.getSignature().getModifiers();//获取目标方法声明类型(public,private,protected)      

AOP—环绕通知[了解]

环绕通知可以完成其他四个通知要做的事情

package com.zzti.spring.aop.aspectj;

import com.zzti.spring.aop.proxy3.AOP;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

import java.util.Arrays;
import java.util.List;

/**
 * 该类就是管理切面编程
 */

@Aspect //表示该类是一个切面类(底层切面编程的支撑[动态代理+反射+动态绑定])
@Component //表示需要加入到ioc容器
public class SmartAnimalAspect2 {

    //环绕通知
    @Around(value = "execution(public float com.zzti.spring.aop.aspectj.SmartDog.*(float,float))")
    public  Object doAround(ProceedingJoinPoint joinPoint){
        Object result = null;
        String methodName = joinPoint.getSignature().getName();
        try {
            //1.相当于完成前置通知完成的事情
            Object[] args = joinPoint.getArgs();
            List<Object> objectList = Arrays.asList(args);
            System.out.println("环绕通知==" + methodName + "方法开始了====参数有: " + objectList);
            //在环绕通知中一定要调用joinPoint.proceed()来执行目标方法
            result = joinPoint.proceed();
            //2.相当于返回通知完成的事情
            System.out.println("环绕通知==" + methodName + "方法结束了====结果是: " + result);
        } catch (Throwable throwable) {
            //3.相当于异常通知完成的事情
            System.out.println("环绕通知==" + methodName + "方法抛出异常了====异常对象是: " + throwable);
        } finally {
            //4.相当于后置通知完成的事情
            System.out.println("环绕通知==" + methodName + "方法抛最终结束了");
        }
        return result;
    }
}
@Test
public void smartDogTestByAround(){
    ApplicationContext ioc = new ClassPathXmlApplicationContext("beans06.xml");
    //需要通过接口类型来获取到注入的SmartDog对象--就是代理对象
    SmartAnimal smartAnimal = ioc.getBean(SmartAnimal.class);
    smartAnimal.getSum(10,2);
    System.out.println("===================");
    smartAnimal.getSub(10,5);
}
=======================测试结果============================
环绕通知==getSum方法开始了====参数有: [10.0, 2.0]
方法内部打印: result= 12.0
环绕通知==getSum方法结束了====结果是: 12.0
环绕通知==getSum方法抛最终结束了
===================
环绕通知==getSub方法开始了====参数有: [10.0, 5.0]
方法内部打印: result= 5.0
环绕通知==getSub方法结束了====结果是: 5.0
环绕通知==getSub方法抛最终结束了

AOP–切面优先级问题

如果同一个方法,有多个切面在同一个切入点切入,那么执行的优先级如何控制,
通过@Order(value=n)来控制, n值越小, 优先级越高。
在这里插入图片描述

@Order(value = 1)
@Aspect //表示该类是一个切面类(底层切面编程的支撑[动态代理+反射+动态绑定])
@Component //表示需要加入到ioc容器
public class SmartAnimalAspect {
.......
}
@Order(value = 2)
@Aspect //表示该类是一个切面类(底层切面编程的支撑[动态代理+反射+动态绑定])
@Component //表示需要加入到ioc容器
public class SmartAnimalAspect3 {
.......
}
@Test
public void smartDogTestByProxy(){
   ApplicationContext ioc = new ClassPathXmlApplicationContext("beans06.xml");
   //需要通过接口类型来获取到注入的SmartDog对象--就是代理对象
    SmartAnimal smartAnimal = ioc.getBean(SmartAnimal.class);
    smartAnimal.getSum(10,2);
    System.out.println("===================");
    smartAnimal.getSub(10,5);
}
==================测试结果===================
======前置通知======
前置通知~日志-方法名:getSum--方法开始--参数: [10.0, 2.0]
======前置通知======
SmartAnimalAspect3-前置通知~日志-方法名:getSum--方法开始--参数: [10.0, 2.0]
方法内部打印: result= 12.0
======返回通知======
SmartAnimalAspect3-返回通知~日志-方法名:getSum--方法正常结束--返回的结果是: 12.0
======后置通知======
SmartAnimalAspect3-后置通知~日志-方法名:getSum
======返回通知======
返回通知~日志-方法名:getSum--方法正常结束--返回的结果是: 12.0
======后置通知======
后置通知~日志-方法名:getSum
===================
======前置通知======
前置通知~日志-方法名:getSub--方法开始--参数: [10.0, 5.0]
======前置通知======
SmartAnimalAspect3-前置通知~日志-方法名:getSub--方法开始--参数: [10.0, 5.0]
方法内部打印: result= 5.0
======返回通知======
返回通知~日志-方法名:getSub--方法正常结束--返回的结果是: 5.0
======后置通知======
后置通知~日志-方法名:getSub

注意事项:
不能理解成,优先级越高的每个消息通知都先执行,这个和方法调用机制(Filter过滤器链式调用类似)

AOP–基于XML配置AOP

SmartAnimal 和 SmartDog(@Component注解去掉)不变

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

    <!--配置SmartAnimalAspect beans-->
    <bean id="smartAnimalAspect" class="com.zzti.spring.aop.xml.SmartAnimalAspect"/>
    <!--配置SmartDog-->
    <bean id="smartDog" class="com.zzti.spring.aop.xml.SmartDog"/>

    <aop:config>
        <!--配置切入点-->
        <aop:pointcut id="myPointCut" expression="execution(public float com.zzti.spring.aop.xml.SmartDog.*(float,float))"/>
        <aop:aspect ref="smartAnimalAspect" order="1">
            <!--配置各个通知对应的切入点-->
            <aop:before method="showBeginLog" pointcut-ref="myPointCut"/>
            <aop:after-returning method="showSuccessEndLog" pointcut-ref="myPointCut" returning="res"/>
            <aop:after-throwing method="showExceptionLog" pointcut-ref="myPointCut" throwing="throwable"/>
            <aop:after method="showFinallyEndLog" pointcut-ref="myPointCut"/>
            <!--还可以配置环绕通知-->
            <!--<aop:around method=""/>-->
        </aop:aspect>

    </aop:config>

</beans>
package com.zzti.spring.aop.xml;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import java.util.Arrays;



public class SmartAnimalAspect {

    public  void showBeginLog(JoinPoint joinPoint){
        System.out.println("======前置通知======");
        Signature signature = joinPoint.getSignature();
        System.out.println("XML-前置通知~日志-方法名:" + signature.getName() +"--方法开始--参数: " + Arrays.asList(joinPoint.getArgs()));
    }

    public  void showSuccessEndLog(JoinPoint joinPoint, Object res){
        System.out.println("======返回通知======");
        Signature signature = joinPoint.getSignature();
        System.out.println("XML-返回通知~日志-方法名:" + signature.getName() +"--方法正常结束--返回的结果是: " + res);
    }

    public  void showExceptionLog(JoinPoint joinPoint, Throwable throwable){
        System.out.println("======异常通知======");
        Signature signature = joinPoint.getSignature();
        System.out.println("XML-异常通知~日志-方法名:" + signature.getName() +"--方法异常信息--返回的结果是: " + throwable);
    }

    public  void showFinallyEndLog(JoinPoint joinPoint){
        System.out.println("======后置通知======");
        Signature signature = joinPoint.getSignature();
        System.out.println("XML-后置通知~日志-方法名:" + signature.getName());
    }
}
@Test
public void smartDogTestByXml(){
    ApplicationContext ioc = new ClassPathXmlApplicationContext("beans07.xml");
    //需要通过接口类型来获取到注入的SmartDog对象--就是代理对象
    SmartAnimal smartAnimal = ioc.getBean(SmartAnimal.class);
    smartAnimal.getSum(10,6);
    System.out.println("===================");
    smartAnimal.getSub(20,5);
}
=============测试结果======================
======前置通知======
XML-前置通知~日志-方法名:getSum--方法开始--参数: [10.0, 6.0]
方法内部打印: result= 16.0
======返回通知======
XML-返回通知~日志-方法名:getSum--方法正常结束--返回的结果是: 16.0
======后置通知======
XML-后置通知~日志-方法名:getSum
===================
======前置通知======
XML-前置通知~日志-方法名:getSub--方法开始--参数: [20.0, 5.0]
方法内部打印: result= 15.0
======返回通知======
XML-返回通知~日志-方法名:getSub--方法正常结束--返回的结果是: 15.0
======后置通知======
XML-后置通知~日志-方法名:getSub

课后作业:

<context:component-scan base-package="com.zzti.spring.aop.homework" />

<!--开启基于注解的aop功能-->
<aop:aspectj-autoproxy />
public interface Cal {
    public void cal1(int n);
    public void cal2(int n);
}
package com.zzti.spring.aop.homework;

import org.springframework.stereotype.Component;

@Component
public class MyCal implements Cal{
    @Override
    public void cal1(int n) {
        int res = 0;
        for (int i = 0; i <= n; i++) {
            res += i;
        }
        System.out.println("cal1 res= " + res);
    }

    @Override
    public void cal2(int n) {
        int res = 1;
        for (int i = 1; i <= n; i++) {
            res *= i;
        }
        System.out.println("cal2 res= " + res);
    }
}
package com.zzti.spring.aop.homework;

import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class CalAspect {

    @Pointcut(value = "execution(public void com.zzti.spring.aop.homework.MyCal.*(int))")
    public void myPointcut(){
    }

    @Before(value="myPointcut()")
    public void starCal(){
        System.out.println("开始执行计算== " + System.currentTimeMillis());
    }

    @AfterReturning(value = "myPointcut()")
    public void endCal(){
        System.out.println("结束执行计算== " + System.currentTimeMillis());
    }
}
@Test
public void testCalAspect(){
    ApplicationContext ioc = new ClassPathXmlApplicationContext("beans06.xml");
    //需要通过接口类型来获取到注入的MyCal对象--就是代理对象
    Cal cal = ioc.getBean(Cal.class);
    cal.cal1(5);
    System.out.println("=========分界线==========");
    cal.cal2(5);
}
==================测试结果======================
开始执行计算== 1657541597514
cal1 res= 15
结束执行计算== 1657541597514
=========分界线==========
开始执行计算== 1657541597514
cal2 res= 120
结束执行计算== 1657541597515

先看个案列,引出对Spring底层实现再思考

前面已经实现了,Spring XML注入bean,Spring注解方式注入bean,Spring AOP动态代理实现。
思考1: 原生Spring如何实现依赖注入和singleton、prototype
Spring如何实现ioc容器创建和初始化
Spring如何实现getBean,根据singleton 和prototype来返回bean实例?

答: 之前是用的@Service、@Repository、@Controller都是单例的,若在UserController类上加上@Scope(value=“prototype”),就是多例的,创建同一个userController就是两个对象。

思考2. 原生Spring如何实现 BeanPostProcessor(bean后置处理器)

<context:component-scan base-package="com.zzti.spring.processor"/>

<!--bean的后置处理器-->
<bean id="myBeanPostProcessor" class="com.zzti.spring.processor.MyBeanPostProcessor"/>
package com.zzti.spring.processor;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;

public class MyBeanPostProcessor implements BeanPostProcessor {

    /**
     * 在bean初始化之前完成某些任务
     * @param bean 就是ioc容器返回的对象,如果这里被替换会修改,则返回的bean对象也会被修改
     * @param beanName 就是ioc容器配置的bean的名称
     * @return Object 就是返回的bean对象
     * @throws BeansException
     */
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("postProcessBeforeInitialization被调用 " + beanName + ",bean= "+ bean);
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("postProcessAfterInitialization被调用 " + beanName + ",bean= "+ bean.getClass());
        return bean;
    }
}
@Component
public class UserDao {
    public void hi(){
        System.out.println("UserDao hi()执行了~~");
    }
}
@Component
public class UserService {

    @Autowired
    private UserDao userDao;

    public void m1(){
        userDao.hi();
    }

    //通过注解指定在构造器完成后执行的方法, 即完成初始化任务
    @PostConstruct
    public void init(){
        System.out.println("初始化业务~~~");
    }
}
@Component
public class UserController {
}
@Test
 public void testBeanPostProcessor(){
     ApplicationContext ioc = new ClassPathXmlApplicationContext("beans08.xml");
     UserController userController1 = (UserController)ioc.getBean("userController");
     UserController userController2 = (UserController)ioc.getBean("userController");
     System.out.println("userController1= " + userController1);
     System.out.println("userController2= " + userController2);
     UserService userService = (UserService)ioc.getBean("userService");
     System.out.println("userService= " + userService);
     userService.m1();
     UserDao userDao = (UserDao)ioc.getBean("userDao");
     System.out.println("userDao= " + userDao);
 }
=================测试结果================================
postProcessBeforeInitialization被调用 userController,bean= com.zzti.spring.processor.UserController@14a50707
postProcessAfterInitialization被调用 userController,bean= class com.zzti.spring.processor.UserController
postProcessBeforeInitialization被调用 userDao,bean= com.zzti.spring.processor.UserDao@4d518b32
postProcessAfterInitialization被调用 userDao,bean= class com.zzti.spring.processor.UserDao
postProcessBeforeInitialization被调用 userService,bean= com.zzti.spring.processor.UserService@3ddc6915
初始化业务~~~
postProcessAfterInitialization被调用 userService,bean= class com.zzti.spring.processor.UserService
userController1= com.zzti.spring.processor.UserController@14a50707
userController2= com.zzti.spring.processor.UserController@14a50707
userService= com.zzti.spring.processor.UserService@3ddc6915
UserDao hi()执行了~~
userDao= com.zzti.spring.processor.UserDao@4d518b32

思考3. 原生Spring如何实现AOP?

思考4. 分析一下,AOP和BeanPostProcessor之间的关系

  • AOP实现Spring可以通过给一个类,加入注解 @EnableAspectJAutoProxy来指定
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import({AspectJAutoProxyRegistrar.class})
public @interface EnableAspectJAutoProxy {
    boolean proxyTargetClass() default false;

    boolean exposeProxy() default false;
}

解读:

  • AOP底层是基于BeanPostProcessor机制的
  • 即在Bean创建好后,根据是否需要Aop处理,决定返回代理对象,还是原生bean
  • 在返回代理对象时,就可以根据要代理的类和方法来返回
  • 其实这个机制并不难,本质就是在BeanPostProcessor机制 + 动态代理技术

Spring整体架构分析

在这里插入图片描述

手动实现Spring底层机制[初始化ioc容器+依赖注入+BeanPostProcessor机制+AOP]

1.实现任务阶段1----编写自己Spring容器,实现扫描包,得到bean的class对象

在这里插入图片描述
知识扩展: 类加载器
java的类加载器分为3种
Bootstrap类加载器 ===> 对应路径jre/lib
Ext类加载器 ===> 对应路径jre/lib/ext
App类加载器 ===> 对应路径classpath

"D:\Program Files\Java\jdk1.8.0_301\bin\java.exe" -ea -Didea.test.cyclic.buffer.size=1048576 "-javaagent:F:\idea\IntelliJ IDEA Community Edition 2021.2.2\lib\idea_rt.jar=51353:F:\idea\IntelliJ IDEA Community Edition 2021.2.2\bin" -Dfile.encoding=UTF-8 -classpath "F:\idea\IntelliJ IDEA Community Edition 2021.2.2\lib\idea_rt.jar;F:\idea\IntelliJ IDEA Community Edition 2021.2.2\plugins\junit\lib\junit5-rt.jar;F:\idea\IntelliJ IDEA Community Edition 2021.2.2\plugins\junit\lib\junit-rt.jar;D:\Program Files\Java\jdk1.8.0_301\jre\lib\charsets.jar;D:\Program Files\Java\jdk1.8.0_301\jre\lib\deploy.jar;D:\Program Files\Java\jdk1.8.0_301\jre\lib\ext\access-bridge-64.jar;D:\Program Files\Java\jdk1.8.0_301\jre\lib\ext\cldrdata.jar;D:\Program Files\Java\jdk1.8.0_301\jre\lib\ext\dnsns.jar;D:\Program Files\Java\jdk1.8.0_301\jre\lib\ext\jaccess.jar;D:\Program Files\Java\jdk1.8.0_301\jre\lib\ext\jfxrt.jar;D:\Program Files\Java\jdk1.8.0_301\jre\lib\ext\localedata.jar;D:\Program Files\Java\jdk1.8.0_301\jre\lib\ext\nashorn.jar;D:\Program Files\Java\jdk1.8.0_301\jre\lib\ext\sunec.jar;D:\Program Files\Java\jdk1.8.0_301\jre\lib\ext\sunjce_provider.jar;D:\Program Files\Java\jdk1.8.0_301\jre\lib\ext\sunmscapi.jar;D:\Program Files\Java\jdk1.8.0_301\jre\lib\ext\sunpkcs11.jar;D:\Program Files\Java\jdk1.8.0_301\jre\lib\ext\zipfs.jar;D:\Program Files\Java\jdk1.8.0_301\jre\lib\javaws.jar;D:\Program Files\Java\jdk1.8.0_301\jre\lib\jce.jar;D:\Program Files\Java\jdk1.8.0_301\jre\lib\jfr.jar;D:\Program Files\Java\jdk1.8.0_301\jre\lib\jfxswt.jar;D:\Program Files\Java\jdk1.8.0_301\jre\lib\jsse.jar;D:\Program Files\Java\jdk1.8.0_301\jre\lib\management-agent.jar;D:\Program Files\Java\jdk1.8.0_301\jre\lib\plugin.jar;D:\Program Files\Java\jdk1.8.0_301\jre\lib\resources.jar;D:\Program Files\Java\jdk1.8.0_301\jre\lib\rt.jar;D:\Exercise_items\hsp_spring\target\classes;D:\maven-repository\junit\junit\4.11\junit-4.11.jar;D:\maven-repository\org\hamcrest\hamcrest-core\1.3\hamcrest-core-1.3.jar;D:\Exercise_items\hsp_spring\src\main\webapp\lib\junit-4.12.jar;D:\Exercise_items\hsp_spring\src\main\webapp\lib\dom4j-1.6.1.jar;D:\Exercise_items\hsp_spring\src\main\webapp\lib\spring-aop-5.3.8.jar;D:\Exercise_items\hsp_spring\src\main\webapp\lib\hamcrest-core-1.3.jar;D:\Exercise_items\hsp_spring\src\main\webapp\lib\spring-core-5.3.8.jar;D:\Exercise_items\hsp_spring\src\main\webapp\lib\spring-beans-5.3.8.jar;D:\Exercise_items\hsp_spring\src\main\webapp\lib\hamcrest-library-1.3.jar;D:\Exercise_items\hsp_spring\src\main\webapp\lib\spring-context-5.3.8.jar;D:\Exercise_items\hsp_spring\src\main\webapp\lib\commons-logging-1.1.3.jar;D:\Exercise_items\hsp_spring\src\main\webapp\lib\spring-expression-5.3.8.jar" com.intellij.rt.junit.JUnitStarter -ideVersion5 -junit4 com.zzti.spring.aop.proxy_.TestProxy,smartAnimalTest

扫描包,得到bean的class对象,排除包下面不是bean的对象

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Component {
    //@Component可以传入属性值,默认为""
    String value() default "";
}
package com.zzti.myspring.component;
import com.zzti.myspring.annotation.Component;

/**
 * 1. 使用 @Component("monsterService")修饰
 * 2. 给该MonsterService注入到容器,设置beanName为monsterService
 * 3. 若没有设置, 默认为 类名首字母小写
 */
@Component("monsterService")
public class MonsterService {
}
package com.zzti.myspring.component;

import com.zzti.myspring.annotation.Component;

@Component("monsterDao")
public class MonsterDao {
}
package com.zzti.myspring.ioc;

import com.zzti.myspring.annotation.ComponentScan;

/**
 * 这是一个配置类,作用类似我们原生spring的beans.xml 容器配置文件
 */
@ComponentScan(value = "com.zzti.myspring.component")
public class HspMySpringConfig {
}
package com.zzti.myspring.ioc;

import com.zzti.myspring.annotation.Component;
import com.zzti.myspring.annotation.ComponentScan;
import java.io.File;
import java.net.URL;

/**
 * 这个类的作用类似, spring原生的ioc容器
 *   1. 搭建基本结构并获取的扫描包
 *   2. 获取扫描包下所有的.class文件
 *   3. 获取全类名, 反射对象, 放入容器
 */
public class HspMySpringApplicationContext {

    private Class configClass;

    public HspMySpringApplicationContext(Class configClass){
        this.configClass = configClass;

        //1. 解析配置类
        //2. 获取到配置类的 @ComponentScan(value = "com.zzti.myspring.component")
        ComponentScan componentScan = (ComponentScan)this.configClass.getDeclaredAnnotation(ComponentScan.class);
        String path = componentScan.value();
        //扫描路径path= com.zzti.myspring.component
        System.out.println("扫描路径path= " + path);

        //3. 获取扫描路径下所有的类文件
        //1) 先得到类加载器
        ClassLoader classLoader = HspMySpringApplicationContext.class.getClassLoader();
        //在获取某个包对应的url时, 要求是com/zzti/myspring/component
        //URL resource = classLoader.getResource("com/zzti/spring/component");
        //System.out.println(resource);

        //2) 将path转换成com/zzti/myspring/component形式
        path = path.replace(".", "/");

        //3) 获取到我们要加载的类路径(工作路径: file:/D:/Exercise_items/hsp_spring/target/classes/com/zzti/spring/component )

        //解决文件路径空格转变成了转义字符%20的问题
        URL resource = classLoader.getResource(path);
        System.out.println("resource= " + resource);

        File file = new File(resource.getFile());
        if (file.isDirectory()) {
            File[] files = file.listFiles();
            for (File f : files) {
                String fileAbsolutePath = f.getAbsolutePath();
                System.out.println("===============================");
                System.out.println("文件绝对路径= " + fileAbsolutePath);
                if (fileAbsolutePath.endsWith(".class")) {
                    //先得到类的完整类路径, 形式为: com.zzti,myspring.component.MonsterService
                    String className = fileAbsolutePath.substring(fileAbsolutePath.lastIndexOf("\\") + 1, fileAbsolutePath.indexOf(".class"));
                    String classFullPath = path.replace("/",".") + "." + className;
                    System.out.println("类名= " + className);
                    System.out.println("类的全路径= " + classFullPath);

                    //这里只处理.class文件
                    try {
                        Class<?> aClass = classLoader.loadClass(classFullPath);
                        if (aClass.isAnnotationPresent(Component.class)) {
                            //如果这个类有@Component注解,说明是一个bean
                            System.out.println("是一个bean= " + aClass);

                        }else {
                            //如果这个类没有@Component注解,说明不是一个bean
                            System.out.println("不是一个bean= " + aClass);
                        }
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                    System.out.println("===================================");

                }

            }
        }

    }

    public Object getBean(String name){
        return null;
    }
}
package com.zzti.myspring;

import com.zzti.myspring.ioc.HspMySpringApplicationContext;
import com.zzti.myspring.ioc.HspMySpringConfig;

public class AppMain {
    public static void main(String[] args) {
        HspMySpringApplicationContext hspMySpringApplicationContext = new HspMySpringApplicationContext(HspMySpringConfig.class);

    }
}
==============测试结果============
扫描路径path= com.zzti.myspring.component
resource= file:/D:/Exercise_items/hsp_spring/hsp_myspring/target/classes/com/zzti/myspring/component
===============================
文件绝对路径= D:\Exercise_items\hsp_spring\hsp_myspring\target\classes\com\zzti\myspring\component\MonsterDao.class
类名= MonsterDao
类的全路径= com.zzti.myspring.component.MonsterDao
是一个bean= class com.zzti.myspring.component.MonsterDao
===================================
===============================
文件绝对路径= D:\Exercise_items\hsp_spring\hsp_myspring\target\classes\com\zzti\myspring\component\MonsterService.class
类名= MonsterService
类的全路径= com.zzti.myspring.component.MonsterService
是一个bean= class com.zzti.myspring.component.MonsterService
===================================

2. 实现任务阶段2----扫描bean信息封装到BeanDefinition对象,并放入到Map

在这里插入图片描述
因为bean的作用域可能是singleton,也可能是prototype;所以Spring容器将扫描到的bean信息,保存到集合,这样当getBean() 根据实际情况来处理

//定义我们的Scope注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Scope {
    //不需要给默认值
    String value();
}
package com.zzti.myspring.ioc;

/**
 * 在扫描时,将bean信息, 封装到BeanDefinition对象中
 * 然后将BeanDefinition对象,放入到spring容器集合中
 */
public class BeanDefinition {
    //bean对应的Class对象
    private Class clazz;

    //bean的作用域(scope/prototype)
    private String scope;

    public Class getClazz() {
        return clazz;
    }

    public void setClazz(Class clazz) {
        this.clazz = clazz;
    }

    public String getScope() {
        return scope;
    }

    public void setScope(String scope) {
        this.scope = scope;
    }

    @Override
    public String toString() {
        return "BeanDefinition{" +
                "clazz=" + clazz +
                ", scope='" + scope + '\'' +
                '}';
    }
}
@Component("monsterService")
@Scope("prototype")
public class MonsterService {
}
package com.zzti.myspring.ioc;

import com.zzti.myspring.annotation.Component;
import com.zzti.myspring.annotation.ComponentScan;
import com.zzti.myspring.annotation.Scope;

import java.io.File;
import java.net.URL;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 这个类的作用类似, spring原生的ioc容器
 *   1. 搭建基本结构并获取的扫描包
 *   2. 获取扫描包下所有的.class文件
 *   3. 获取全类名, 反射对象, 放入容器
 */
public class HspMySpringApplicationContext {

    private Class configClass;

    //如果这个bean是单例的,就直接放入这个单例的bean对象池
    private ConcurrentHashMap<String,Object> singletonObjects = new ConcurrentHashMap<>();

    //将bean的定义,放在这个集合
    private ConcurrentHashMap<String,BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>();


    public HspMySpringApplicationContext(Class configClass){
        //通过扫描,得到beanDefinition的map
        beanDefinitionByscan(configClass);
        System.out.println(beanDefinitionMap);
    }

    private void beanDefinitionByscan(Class configClass){
        this.configClass = configClass;

        //1. 解析配置类
        //2. 获取到配置类的 @ComponentScan(value = "com.zzti.myspring.component")
        ComponentScan componentScan = (ComponentScan)this.configClass.getDeclaredAnnotation(ComponentScan.class);
        String path = componentScan.value();
        //扫描路径path= com.zzti.myspring.component
        System.out.println("扫描路径path= " + path);

        //3. 获取扫描路径下所有的类文件
        //1) 先得到类加载器
        ClassLoader classLoader = HspMySpringApplicationContext.class.getClassLoader();
        //在获取某个包对应的url时, 要求是com/zzti/myspring/component
        //URL resource = classLoader.getResource("com/zzti/spring/component");
        //System.out.println(resource);

        //2) 将path转换成com/zzti/myspring/component形式
        path = path.replace(".", "/");

        //3) 获取到我们要加载的类路径(工作路径: file:/D:/Exercise_items/hsp_spring/target/classes/com/zzti/spring/component )

        //解决文件路径空格转变成了转义字符%20的问题
        URL resource = classLoader.getResource(path);
        System.out.println("resource= " + resource);

        File file = new File(resource.getFile());
        if (file.isDirectory()) {
            File[] files = file.listFiles();
            for (File f : files) {
                String fileAbsolutePath = f.getAbsolutePath();
                System.out.println("===============================");
                System.out.println("文件绝对路径= " + fileAbsolutePath);
                if (fileAbsolutePath.endsWith(".class")) {
                    //先得到类的完整类路径, 形式为: com.zzti,myspring.component.MonsterService
                    String className = fileAbsolutePath.substring(fileAbsolutePath.lastIndexOf("\\") + 1, fileAbsolutePath.indexOf(".class"));
                    String classFullPath = path.replace("/",".") + "." + className;
                    System.out.println("类名= " + className);
                    System.out.println("类的全路径= " + classFullPath);

                    //这里只处理.class文件
                    try {
                        Class<?> aClass = classLoader.loadClass(classFullPath);
                        if (aClass.isAnnotationPresent(Component.class)) {
                            //如果这个类有@Component注解,说明是一个bean
                            System.out.println("是一个bean= " + aClass);
                            //这里不能直接将bean实例放入singletonObjects,
                            //原因是若bean是prototype,则需要每次创建新的bean对象;
                            // 所以Spring底层是将bean信息封装到BeanDefinition对象中,便于getBean的操作
                            BeanDefinition beanDefinition = new BeanDefinition();
                            beanDefinition.setClazz(aClass);
                            //获取bean的name
                            Component componentAnnotation = aClass.getDeclaredAnnotation(Component.class);
                            String beanName = componentAnnotation.value();
                            //获取bean的scope
                            if (aClass.isAnnotationPresent(Scope.class)) {
                                //如果bean类上面有@Scope注解
                                Scope scopeAnnotation = aClass.getDeclaredAnnotation(Scope.class);
                                beanDefinition.setScope(scopeAnnotation.value());
                            }else {
                                //如果bean类上面没有@Scope注解,默认是singleton
                                beanDefinition.setScope("singleton");
                            }
                            //放入到beanDefinitionMap中
                            beanDefinitionMap.put(beanName,beanDefinition);

                        }else {
                            //如果这个类没有@Component注解,说明不是一个bean
                            System.out.println("不是一个bean= " + aClass);
                        }
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                    System.out.println("===================================");

                }

            }
        }
    }

    public Object getBean(String name){
        return null;
    }

}

测试结果如下:

扫描路径path= com.zzti.myspring.component
resource= file:/D:/Exercise_items/hsp_spring/hsp_myspring/target/classes/com/zzti/myspring/component
===============================
文件绝对路径= D:\Exercise_items\hsp_spring\hsp_myspring\target\classes\com\zzti\myspring\component\Car.class
类名= Car
类的全路径= com.zzti.myspring.component.Car
不是一个bean= class com.zzti.myspring.component.Car
===================================
===============================
文件绝对路径= D:\Exercise_items\hsp_spring\hsp_myspring\target\classes\com\zzti\myspring\component\MonsterDao.class
类名= MonsterDao
类的全路径= com.zzti.myspring.component.MonsterDao
是一个bean= class com.zzti.myspring.component.MonsterDao
===================================
===============================
文件绝对路径= D:\Exercise_items\hsp_spring\hsp_myspring\target\classes\com\zzti\myspring\component\MonsterService.class
类名= MonsterService
类的全路径= com.zzti.myspring.component.MonsterService
是一个bean= class com.zzti.myspring.component.MonsterService
===================================
{monsterService=BeanDefinition{clazz=class com.zzti.myspring.component.MonsterService, scope='prototype'}, monsterDao=BeanDefinition{clazz=class com.zzti.myspring.component.MonsterDao, scope='singleton'}}

3. 实现任务阶段3----初始化bean单例池,并完成getBean方法,createBean方法

在这里插入图片描述
初始化单例池,也就是若bean是单例的就实例化,并放入到单例池Map中。
单例池bean map集合是如何保存的 (key[beanName]–value[单例bean对象])

public HspMySpringApplicationContext(Class configClass){
     //通过扫描,得到beanDefinition的map
      beanDefinitionByscan(configClass);
      System.out.println(beanDefinitionMap);

      //通过beanDefinitionMap,初始化singletonObjects单例池
      Enumeration<String> keys = beanDefinitionMap.keys();
      while (keys.hasMoreElements()) {
          //得到beanName
          String beanName = keys.nextElement();
          //通过beanName得到beanDefinition
          BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
          if ("singleton".equalsIgnoreCase(beanDefinition.getScope())) {
              //将该bean实例放入singletonObjects
              Object bean = createBean(beanDefinition);
              singletonObjects.put(beanName,bean);
          }
      }
      System.out.println("singletonObjects 单例池= " + singletonObjects);
}
//创建bean
public Object createBean(BeanDefinition beanDefinition){
    //得到bean的类型
    Class clazz = beanDefinition.getClazz();
    Object instance = null;
    try {
        //使用反射得到实例
        instance = clazz.getDeclaredConstructor().newInstance();
        return instance;
    } catch (InstantiationException e) {
        e.printStackTrace();
    } catch (IllegalAccessException e) {
        e.printStackTrace();
    } catch (InvocationTargetException e) {
        e.printStackTrace();
    } catch (NoSuchMethodException e) {
        e.printStackTrace();
    }
    //若没有创建成功,返回null
    return null;
}
//得到bean
public Object getBean(String name){
    if (beanDefinitionMap.containsKey(name)) {
        BeanDefinition beanDefinition = beanDefinitionMap.get(name);
        //得到bean的scope,分别处理
        if ("singleton".equalsIgnoreCase(beanDefinition.getScope())) {
            //单例,直接从bean单例池中获取
            return singletonObjects.get(name);
        }else {
            //不是单例, 则没有返回新的实例
            return createBean(beanDefinition);
        }
    }else {
        throw new NullPointerException("没有该bean");
    }
}
public class AppMain {
public static void main(String[] args) {
    HspMySpringApplicationContext hspMySpringApplicationContext = new HspMySpringApplicationContext(HspMySpringConfig.class);

    //通过spring容器对象,获取bean对象
    MonsterService monsterService1 = (MonsterService)hspMySpringApplicationContext.getBean("monsterService");
    MonsterService monsterService2 = (MonsterService)hspMySpringApplicationContext.getBean("monsterService");
    //MonsterService monsterService4 = (MonsterService)hspMySpringApplicationContext.getBean("xxxService");

    System.out.println("monsterService1=  " + monsterService1);
    System.out.println("monsterService2=  " + monsterService2);
    //System.out.println("monsterService4=  " + monsterService4);//java.lang.NullPointerException: 没有该bean


    MonsterDao monsterDao = (MonsterDao)hspMySpringApplicationContext.getBean("monsterDao");
    MonsterDao monsterDao1 = (MonsterDao)hspMySpringApplicationContext.getBean("monsterDao");
    System.out.println("monsterDao= " + monsterDao);
    System.out.println("monsterDao1= " + monsterDao1);


 }
}
===============测试结果=================
{monsterService=BeanDefinition{clazz=class com.zzti.myspring.component.MonsterService, scope='prototype'}, monsterDao=BeanDefinition{clazz=class com.zzti.myspring.component.MonsterDao, scope='singleton'}}
singletonObjects 单例池= {monsterDao=com.zzti.myspring.component.MonsterDao@6f79caec}
monsterService1=  com.zzti.myspring.component.MonsterService@5d3411d
monsterService2=  com.zzti.myspring.component.MonsterService@2471cca7
monsterDao= com.zzti.myspring.component.MonsterDao@6f79caec
monsterDao1= com.zzti.myspring.component.MonsterDao@6f79caec

4. 实现任务阶段4----完成依赖注入

@Target({ElementType.METHOD,ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Autowired {
    //String required() default "true";
}
@Component("monsterDao")
public class MonsterDao {
    public void hi(){
        System.out.println("hi 我是monsterDao, select * from table....");
    }
}
@Component("monsterService")
@Scope("prototype")
public class MonsterService {
    @Autowired
    private MonsterDao monsterDao;

    public void m1(){
        monsterDao.hi();
    }
}
//创建bean
public Object createBean(BeanDefinition beanDefinition){
    //得到bean的类型
    Class clazz = beanDefinition.getClazz();
    Object instance = null;
    try {
        //使用反射得到实例
        instance = clazz.getDeclaredConstructor().newInstance();

        //完成依赖注入
        for (Field declaredField : clazz.getDeclaredFields()) {
            if (declaredField.isAnnotationPresent(Autowired.class)) {
                //如果该属性有@Autowired,就进行组装
                Object bean = getBean(declaredField.getName());
                declaredField.setAccessible(true);//因为属性是private,需要爆破
                declaredField.set(instance,bean);
            }

        }
        return instance;
    } catch (InstantiationException e) {
        e.printStackTrace();
    } catch (IllegalAccessException e) {
        e.printStackTrace();
    } catch (InvocationTargetException e) {
        e.printStackTrace();
    } catch (NoSuchMethodException e) {
        e.printStackTrace();
    }
    //若没有创建成功,返回null
    return null;
}
//测试一个依赖注入
MonsterService monsterService = (MonsterService)hspMySpringApplicationContext.getBean("monsterService");
monsterService.m1();
===================测试结果=========================
singletonObjects 单例池= {monsterDao=com.zzti.myspring.component.MonsterDao@6f79caec}
hi 我是monsterDao, select * from table....

5. 实现任务阶段5----bean后置处理器实现

/**
 * 1. 根据原生Spring定义了一个InitializingBean接口
 * 2. 该InitializingBean接口有一个方法 void afterPropertiesSet() throws Exception
 * 3. 当一个bean实现这个接口后, 就实现afterPropertiesSet(),这个方法就是初始化方法
 */
public interface InitializingBean {
    void afterPropertiesSet() throws Exception;
}
@Component("monsterService")
@Scope("prototype")
public class MonsterService implements InitializingBean{
    @Autowired
    private MonsterDao monsterDao;

    public void m1(){
        monsterDao.hi();
    }

    /**
     * afterPropertiesSet(),就是在bean的setter()执行完之后,被spring容器调用
     * @throws Exception
     */
    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("MonsterService 初始化方法被调用 程序员在这里加入初始化业务逻辑");
    }
}
//创建bean
public Object createBean(BeanDefinition beanDefinition){
   //得到bean的类型
   Class clazz = beanDefinition.getClazz();
   Object instance = null;
   try {
       //使用反射得到实例
       instance = clazz.getDeclaredConstructor().newInstance();

       //完成依赖注入
       for (Field declaredField : clazz.getDeclaredFields()) {
           if (declaredField.isAnnotationPresent(Autowired.class)) {
               //如果该属性有@Autowired,就进行组装
               Object bean = getBean(declaredField.getName());
               declaredField.setAccessible(true);//因为属性是private,需要爆破
               declaredField.set(instance,bean);
           }
       }
       System.out.println("==========创建好了==========" + instance);
       //这里调用初始化,如果bean实现了InitializingBean
       if (instance instanceof InitializingBean) {
           try {
               ((InitializingBean)instance).afterPropertiesSet();
           } catch (Exception e) {
               e.printStackTrace();
           }
       }



       return instance;
   } catch (InstantiationException e) {
       e.printStackTrace();
   } catch (IllegalAccessException e) {
       e.printStackTrace();
   } catch (InvocationTargetException e) {
       e.printStackTrace();
   } catch (NoSuchMethodException e) {
       e.printStackTrace();
   }
   //若没有创建成功,返回null
   return null;
}
============测试结果(若bean实现了InitializingBean,就会在创建好后,调用它的初始化方法)==============
singletonObjects 单例池= {=com.zzti.myspring.component.HspBeanPostProcessor@78308db1, monsterDao=com.zzti.myspring.component.MonsterDao@5451c3a8}
==========创建好了==========com.zzti.myspring.component.MonsterService@2c7b84de
MonsterService 初始化方法被调用 程序员在这里加入初始化业务逻辑
hi 我是monsterDao, select * from table....
/**
 * 参考原生Spring容器定义一个接口BeanPostProcessor
 * 这个接口有两个方法
 * 这两个方法,会对Spring容器的所有Bean生效,已经是切面编程的概念
 */
public interface BeanPostProcessor {
    /**
     * 1. postProcessBeforeInitialization在bean的初始化方法前调用
     * @param bean
     * @param beanName
     * @return
     */
    default Object postProcessBeforeInitialization(Object bean,String beanName){
        return bean;
    }

    /**
     * 1. postProcessAfterInitialization在bean的初始化方法后调用
     * @param bean
     * @param beanName
     * @return
     */
    default Object postProcessAfterInitialization(Object bean,String beanName){
        return bean;
    }

}
package com.zzti.myspring.component;

import com.zzti.myspring.annotation.Component;
import com.zzti.myspring.processor.BeanPostProcessor;

/**
 * 1. 这是我们自己的一个后置处理器
 * 2. 实现了BeanPostProcessor,我们可以重写before和after方法
 * 3. 在spring容器中,仍然把HspBeanPostProcessor当做一个bean对象, 要注入到容器中,
 * 所以要用@Component标识一下
 * 4. 我们要让HspBeanPostProcessor成为真正的后置处理器,
 * 需要在容器中加入业务代码,还要考虑多个后置处理器注入到容器的问题
 */
@Component
public class HspBeanPostProcessor implements BeanPostProcessor {
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) {
        System.out.println("后置处理器HspBeanPostProcessor Before被调用, bean类型= "
                + bean.getClass() + ",bean的名字= " + beanName);
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) {
        System.out.println("后置处理器HspBeanPostProcessor After被调用, bean类型= "
                + bean.getClass() + ",bean的名字= " + beanName);
        return bean;
    }
}
package com.zzti.myspring.ioc;
 
public class HspMySpringApplicationContext {

    //存放后置处理器,放在List中
    private List<BeanPostProcessor> beanPostProcessorList = new ArrayList<>();



    public HspMySpringApplicationContext(Class configClass){
        //通过扫描,得到beanDefinition的map
        beanDefinitionByscan(configClass);
        System.out.println(beanDefinitionMap);

        //通过beanDefinitionMap,初始化singletonObjects单例池
        Enumeration<String> keys = beanDefinitionMap.keys();
        while (keys.hasMoreElements()) {
            //得到beanName
            String beanName = keys.nextElement();
            //通过beanName得到beanDefinition
            BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
            if ("singleton".equalsIgnoreCase(beanDefinition.getScope())) {
                //将该bean实例放入singletonObjects
                Object bean = createBean(beanName,beanDefinition);
                singletonObjects.put(beanName,bean);
            }
        }
        System.out.println("singletonObjects 单例池= " + singletonObjects);
    }

    private void beanDefinitionByscan(Class configClass){
        this.configClass = configClass;

        //1. 解析配置类
        //2. 获取到配置类的 @ComponentScan(value = "com.zzti.myspring.component")
        ComponentScan componentScan = (ComponentScan)this.configClass.getDeclaredAnnotation(ComponentScan.class);
        String path = componentScan.value();
        //扫描路径path= com.zzti.myspring.component
        System.out.println("扫描路径path= " + path);

        //3. 获取扫描路径下所有的类文件
        //1) 先得到类加载器
        ClassLoader classLoader = HspMySpringApplicationContext.class.getClassLoader();
        //在获取某个包对应的url时, 要求是com/zzti/myspring/component
        //URL resource = classLoader.getResource("com/zzti/spring/component");
        //System.out.println(resource);

        //2) 将path转换成com/zzti/myspring/component形式
        path = path.replace(".", "/");

        //3) 获取到我们要加载的类路径(工作路径: file:/D:/Exercise_items/hsp_spring/target/classes/com/zzti/spring/component )

        //解决文件路径空格转变成了转义字符%20的问题
        URL resource = classLoader.getResource(path);
        System.out.println("resource= " + resource);

        File file = new File(resource.getFile());
        if (file.isDirectory()) {
            File[] files = file.listFiles();
            for (File f : files) {
                String fileAbsolutePath = f.getAbsolutePath();
                System.out.println("===============================");
                System.out.println("文件绝对路径= " + fileAbsolutePath);
                if (fileAbsolutePath.endsWith(".class")) {
                    //先得到类的完整类路径, 形式为: com.zzti,myspring.component.MonsterService
                    String className = fileAbsolutePath.substring(fileAbsolutePath.lastIndexOf("\\") + 1, fileAbsolutePath.indexOf(".class"));
                    String classFullPath = path.replace("/",".") + "." + className;
                    System.out.println("类名= " + className);
                    System.out.println("类的全路径= " + classFullPath);

                    //这里只处理.class文件
                    try {
                        Class<?> aClass = classLoader.loadClass(classFullPath);
                        if (aClass.isAnnotationPresent(Component.class)) {
                            //如果这个类有@Component注解,说明是一个bean
                            System.out.println("是一个bean= " + aClass);

                            //增加一个逻辑,如果这个clazz类型是实现了BeanPostProcessor接口,说明是一个bean处理器,特殊处理
                            //因为aClass并不是一个对象实例,不能用instanceof来判断aClass是不是BeanPostProcessor
                            if (BeanPostProcessor.class.isAssignableFrom(aClass)) {
                                //创建一个实例对象
                                BeanPostProcessor beanPostProcessor = (BeanPostProcessor)aClass.newInstance();
                                //放入到beanPostProcessorList
                                beanPostProcessorList.add(beanPostProcessor);
                                continue;
                            }





                            //这里不能直接将bean实例放入singletonObjects,
                            //原因是若bean是prototype,则需要每次创建新的bean对象;
                            // 所以Spring底层是将bean信息封装到BeanDefinition对象中,便于getBean的操作
                            BeanDefinition beanDefinition = new BeanDefinition();
                            beanDefinition.setClazz(aClass);
                            //获取bean的name
                            Component componentAnnotation = aClass.getDeclaredAnnotation(Component.class);
                            String beanName = componentAnnotation.value();
                            //获取bean的scope
                            if (aClass.isAnnotationPresent(Scope.class)) {
                                //如果bean类上面有@Scope注解
                                Scope scopeAnnotation = aClass.getDeclaredAnnotation(Scope.class);
                                beanDefinition.setScope(scopeAnnotation.value());
                            }else {
                                //如果bean类上面没有@Scope注解,默认是singleton
                                beanDefinition.setScope("singleton");
                            }
                            //放入到beanDefinitionMap中
                            beanDefinitionMap.put(beanName,beanDefinition);

                        }else {
                            //如果这个类没有@Component注解,说明不是一个bean
                            System.out.println("不是一个bean= " + aClass);
                        }
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                    System.out.println("===================================");

                }

            }
        }
    }

    //创建bean
    public Object createBean(String beanName,BeanDefinition beanDefinition){
        //得到bean的类型
        Class clazz = beanDefinition.getClazz();
        Object instance = null;
        try {
            //使用反射得到实例
            instance = clazz.getDeclaredConstructor().newInstance();

            //完成依赖注入
            for (Field declaredField : clazz.getDeclaredFields()) {
                if (declaredField.isAnnotationPresent(Autowired.class)) {
                    //如果该属性有@Autowired,就进行组装
                    Object bean = getBean(declaredField.getName());
                    declaredField.setAccessible(true);//因为属性是private,需要爆破
                    declaredField.set(instance,bean);
                }
            }
            System.out.println("==========创建好了==========" + instance);

            //这里调用初始化,如果bean实现了InitializingBean
            if (instance instanceof InitializingBean) {
                try {
                    ((InitializingBean)instance).afterPropertiesSet();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            //在bean初始化后调用所有bean处理器的postProcessAfterInitialization,在调用时,不能保证顺序
            //如果希望指定对象短些bean进行初始化后处理,可以在处理器的postProcessAfterInitialization加入相关的业务处理
            for (BeanPostProcessor beanPostProcessor : beanPostProcessorList) {
                 instance = beanPostProcessor.postProcessAfterInitialization(instance, beanName);
            }

            return instance;
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
        //若没有创建成功,返回null
        return null;
    }

    //得到bean
    public Object getBean(String name){
        if (beanDefinitionMap.containsKey(name)) {
            BeanDefinition beanDefinition = beanDefinitionMap.get(name);
            //得到bean的scope,分别处理
            if ("singleton".equalsIgnoreCase(beanDefinition.getScope())) {
                //单例,直接从bean单例池中获取
                return singletonObjects.get(name);
            }else {
                //不是单例, 则没有返回新的实例
                return createBean(name,beanDefinition);
            }
        }else {
            throw new NullPointerException("没有该bean");
        }
    }
}
=============测试结果============================
==========创建好了==========com.zzti.myspring.component.MonsterDao@78308db1
后置处理器HspBeanPostProcessor After被调用, bean类型= class com.zzti.myspring.component.MonsterDao,bean的名字= monsterDao
singletonObjects 单例池= {monsterDao=com.zzti.myspring.component.MonsterDao@78308db1}
==========创建好了==========com.zzti.myspring.component.MonsterService@5a07e868
MonsterService 初始化方法被调用 程序员在这里加入初始化业务逻辑
后置处理器HspBeanPostProcessor After被调用, bean类型= class com.zzti.myspring.component.MonsterService,bean的名字= monsterService

6. 实现任务阶段6----AOP机制实现

以下是没有使用注解的方式实现的,也可以使用注解,更简洁点。

public interface SmartAnimal {
    public float getSum(float i,float j);
    public float getSub(float i,float j);
}
@Component("smartDog")
public class SmartDog implements SmartAnimal {

    @Override
    public float getSum(float i, float j) {
        //System.out.println("日志-方法名-getSum()开始-参数: " + i + "," + j);
        float result = i + j;
        //System.out.println("方法内部打印: result= " + result);
        //System.out.println("日志-方法名-getSum()结束-结果: result= " + result);
        return result;
    }

    @Override
    public float getSub(float i, float j) {
        //System.out.println("日志-方法名-getSub()开始-参数: " + i + "," + j);
        float result = i - j;
        //System.out.println("方法内部打印: result= " + result);
        //System.out.println("日志-方法名-getSub()结束-结果: result= " + result);
        return result;
    }
}
public class SmartAnimalAspect {

    public static void showBeginLog(){
        System.out.println("======前置通知======");
    }

    public static void showSuccessEndLog(){
        System.out.println("======返回通知======");
    }
}
@Component
public class HspBeanPostProcessor implements BeanPostProcessor {
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) {
        System.out.println("后置处理器HspBeanPostProcessor Before被调用, bean类型= "
                + bean.getClass() + ",bean的名字= " + beanName);
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) {
        System.out.println("后置处理器HspBeanPostProcessor After被调用, bean类型= "
                + bean.getClass() + ",bean的名字= " + beanName);
        //根据情况返回代理对象,还是bean原生对象
        //实现AOP,返回代理对象,即对bean对象进行包装,不在返回原生的bean
        if ("smartDog".equals(beanName)) {
            //使用动态代理,代理bean,返回的代理对象
            Object proxyInstance = Proxy.newProxyInstance(HspBeanPostProcessor.class.getClassLoader(), bean.getClass().getInterfaces(), new InvocationHandler() {
                @Override
                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                    System.out.println("method= " + method.getName());
                    Object result = null;
                    if ("getSum".equals(method.getName())) {
                        SmartAnimalAspect.showBeginLog();
                        result = method.invoke(bean,args);
                        SmartAnimalAspect.showSuccessEndLog();
                    }else {
                        result = method.invoke(bean,args);
                    }
                    return result;
                }
            });
            return proxyInstance;
        }
        //不需要实现AOP,就返回原生的bean
        return bean;
    }
}
//测试AOP机制
MonsterService monsterService = (MonsterService)hspMySpringApplicationContext.getBean("monsterService");
SmartAnimal smartDog = (SmartAnimal)hspMySpringApplicationContext.getBean("smartDog");
smartDog.getSum(10,20);
System.out.println("-----------分割getSum与getSub--------------");
smartDog.getSub(10,20);
================测试结果===================
method= getSum
======前置通知======
======返回通知======
-----------分割getSum与getSub--------------
method= getSub

JdbcTemplate

Spring提供了一个操作数据库(表)功能强大的类JdbcTemplate。我们可以用ioc容器来配置一个jdbcTemplate对象,使用它来完成对数据库表的各种操作。

jdbc.properties

jdbc.user=root
jdbc.password=root
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/spring?useUnicode=true&characterEncoding=utf-8

jdbc_ioc.xml

   <!--引入外部的jdbc.properties-->
    <context:property-placeholder location="classpath:jdbc.properties"/>
    <!--配置数据源对象-DataSource-->
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <!--给数据源对象配置属性值-->
        <property name="user" value="${jdbc.user}"/>
        <property name="password" value="${jdbc.password}"/>
        <property name="driverClass" value="${jdbc.driver}"/>
        <property name="jdbcUrl" value="${jdbc.url}"/>
    </bean>

    <!--配置JdbcTemplate对象-->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <!--给jdbcTemplate对象配置dataSource-->
        <property name="dataSource" ref="dataSource"/>

    </bean>
@Test
public void testDataSourceByJdbcTemplate(){
    ApplicationContext ioc = new ClassPathXmlApplicationContext("jdbc_ioc.xml");
    DataSource dataSource = ioc.getBean(DataSource.class);
    Connection connection = null;
    try {
        connection = dataSource.getConnection();
    } catch (SQLException e) {
        e.printStackTrace();
    }
    //获取到connection= com.mchange.v2.c3p0.impl.NewProxyConnection@63a65a25
    System.out.println("获取到connection= " + connection);

    System.out.println("ok~~");
}

添加一个monster

@Test
public void addDataByJdbcTemplate() {
    ApplicationContext ioc = new ClassPathXmlApplicationContext("jdbc_ioc.xml");
    JdbcTemplate jdbcTemplate = ioc.getBean(JdbcTemplate.class);

    //添加一个monster----添加方式1
    //String sql = "insert into monster values(400,'红孩儿','三味真火')";
    //jdbcTemplate.execute(sql);
    //System.out.println("插入成功~~");

    //添加一个monster----添加方式2--推荐使用
    String sql = "insert into monster values(?,?,?)";
    int affected = jdbcTemplate.update(sql, 600, "红孩儿2", "三味真火2");
    System.out.println("add ok~~ affected= " + affected);//affected表示执行后表受影响的记录数

    //更新一个monster的skill
    String sql2 = "update monster set skill=? where id=?";
    int affected2 = jdbcTemplate.update(sql2, "美人计", 300);
    System.out.println("update ok  affected= " + affected2);
}

批量添加

//批量添加
 @Test
 public void addBatchDataByJdbcTemplate() {
     ApplicationContext ioc = new ClassPathXmlApplicationContext("jdbc_ioc.xml");
     JdbcTemplate jdbcTemplate = ioc.getBean(JdbcTemplate.class);
     String sql = "insert into monster values(?,?,?)";
     List<Object[]> param_list = new ArrayList<Object[]>();
     param_list.add(new Object[]{400,"白蛇精","吃人"});
     param_list.add(new Object[]{500,"青蛇精","吃小孩"});
     jdbcTemplate.batchUpdate(sql,param_list);
     System.out.println("bathData add ok~~~");
 }

查询id=100的monster,并封装到Monster

//查询id=100的monster,并封装到Monster
@Test
public void selectDataByJdbcTemplate() {
    ApplicationContext ioc = new ClassPathXmlApplicationContext("jdbc_ioc.xml");
    JdbcTemplate jdbcTemplate = ioc.getBean(JdbcTemplate.class);

    //String sql = "select id,name,skill from monster where id=?";

    String sql = "select id as monsterId,name as monsterName,skill from monster where id=?";
    //RowMapper是一个接口,可以将查询的结果,封装到指定的Monster对象中
    RowMapper<Monster> rowMapper = new BeanPropertyRowMapper<>(Monster.class);
    Monster monster = jdbcTemplate.queryForObject(sql, rowMapper, 100);
    //monster= Monster{monsterId=null, monsterName='null', skill='吐火'}
    /**
     * 为什么查询出来的monsterId=null, monsterName='null'?
     * 因为sql中的字段要和实体类中自己写的字段要对应上
     */
    System.out.println("monster= " + monster);
}

查询id>=100的monster,并封装到Monster

//查询id>=100的monster,并封装到Monster
@Test
public void selectMulDataByJdbcTemplate() {
    ApplicationContext ioc = new ClassPathXmlApplicationContext("jdbc_ioc.xml");
    JdbcTemplate jdbcTemplate = ioc.getBean(JdbcTemplate.class);

    String sql = "select id as monsterId,name as monsterName,skill from monster where id>=?";
    RowMapper<Monster> rowMapper = new BeanPropertyRowMapper<>(Monster.class);
    List<Monster> monsterList = jdbcTemplate.query(sql, rowMapper, 200);
    for (Monster monster : monsterList) {
        System.out.println("id>=?的monster= " + monster);
    }
}

查询返回结果只有一行一列的值

//查询返回结果只有一行一列的值
@Test
public void selectScalarDataByJdbcTemplate() {
    ApplicationContext ioc = new ClassPathXmlApplicationContext("jdbc_ioc.xml");
    JdbcTemplate jdbcTemplate = ioc.getBean(JdbcTemplate.class);

    String sql = "select name as monsterName from monster where id=500";
    String monsterName = jdbcTemplate.queryForObject(sql, String.class);
    System.out.println("monsterName= " + monsterName);
}

使用map传入具名参数完成操作

<!--配置JdbcTemplate对象-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
    <!--给jdbcTemplate对象配置dataSource-->
    <property name="dataSource" ref="dataSource"/>

</bean>
//使用map传入具名参数完成操作
@Test
public void testDataByNamedParameterJdbcTemplate() {
    ApplicationContext ioc = new ClassPathXmlApplicationContext("jdbc_ioc.xml");
    NamedParameterJdbcTemplate namedParameterJdbcTemplate = ioc.getBean(NamedParameterJdbcTemplate.class);
    String sql = "insert into monster values(:id,:name,:skill)";
    Map<String, Object> map = new HashMap<>();
    map.put("id",600);
    map.put("name","螃蟹精");
    map.put("skill","钳子大法无敌");
    namedParameterJdbcTemplate.update(sql,map);
    System.out.println("namedParameterJdbcTemplate add ok~~");

}

使用SqlParameterSource来封装具名参数

//使用SqlParameterSource来封装具名参数,
@Test
public void testDataBySqlParameterSource() {
    ApplicationContext ioc = new ClassPathXmlApplicationContext("jdbc_ioc.xml");
    NamedParameterJdbcTemplate namedParameterJdbcTemplate = ioc.getBean(NamedParameterJdbcTemplate.class);
    String sql = "insert into monster values(:monsterId,:monsterName,:skill)";
    Monster monster = new Monster(900, "狐狸精", "狐媚之术");
    BeanPropertySqlParameterSource source = new BeanPropertySqlParameterSource(monster);
    namedParameterJdbcTemplate.update(sql,source);
    System.out.println("SqlParameterSource add ok~~");
}

Dao对象中使用jdbcTemplate,完成对数据的操作

<!--配置自动扫描包-->
<context:component-scan base-package="com.zzti.spring.jdbc.dao"/>
@Repository
public class MonsterDao {
    @Autowired
    private JdbcTemplate jdbcTemplate;

    public void save(Monster monster){
        String sql = "insert into monster values(?,?,?)";
        jdbcTemplate.update(sql,monster.getMonsterId(),monster.getMonsterName(),monster.getSkill());
    }
}
//Dao对象中使用jdbcTemplate,完成对数据的操作
@Test
public void testDataByDao() {
    ApplicationContext ioc = new ClassPathXmlApplicationContext("jdbc_ioc.xml");
    MonsterDao monsterDao = ioc.getBean(MonsterDao.class);
    Monster monster = new Monster(800, "大虾精", "吃虾米");
    monsterDao.save(monster);
    System.out.println("testDataByDao add ok~~");
}

四. 声明式事务

  1. 传统方式(容易理解,但代码冗余,效率低,不利于扩展)
Connection connection = JdbcUtils.getConnection();
try{
// 1.先设置事务不要自动提交
connection.setAutoCommint(false);
//2.进行各种crud
//多个表的修改, 添加, 删除
//3. 提交
connection.commit();
}catch(Exception e){
//4. 回滚
connection rollback();
}
  1. 声明式事务
    需求说明–用户购买商品
    需求分析:
  • 通过商品id获取价格 购买商品(某人购买商品,
  • 修改用户的余额)
  • 修改库存存量
  • 事务处理
Connection connection = JdbcUtils.getConnection();
try{
// 1.先设置事务不要自动提交
connection.setAutoCommint(false);
//2.进行各种crud
//多个表的修改, 添加, 删除
select from 商品表 ===> 获取价格
修改用户余额
修改库存量
//3. 提交
connection.commit();
}catch(Exception e){
//4. 回滚
connection rollback();
}

Spring的声明式事务处理,可以将上面三个步骤分别写成一个方法,然后统一管理[底层使用AOP(动态代理+动态绑定+反射+注解)]

1. 先完成对GoodsDao

创建三张表account(id,name,money)、goods(id,name,price)、amount(id,num)
tx_ioc.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:tx="http://www.springframework.org/schema/tx"
       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
        http://www.springframework.org/schema/tx
        http://www.springframework.org/schema/tx/spring-tx.xsd">

    <!--配置自动扫描包-->
    <context:component-scan base-package="com.zzti.spring.tx.dao"/>

    <!--开启基于注解的声明式事务功能-->
    <tx:annotation-driven transaction-manager="dataSourceTransactionManager"/>

    <!--引入外部的jdbc.properties-->
    <context:property-placeholder location="classpath:jdbc.properties"/>
    <!--配置数据源对象-DataSource-->
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <!--给数据源对象配置属性值-->
        <property name="user" value="${jdbc.user}"/>
        <property name="password" value="${jdbc.password}"/>
        <property name="driverClass" value="${jdbc.driver}"/>
        <property name="jdbcUrl" value="${jdbc.url}"/>
    </bean>

    <!--配置JdbcTemplate对象-->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <!--给jdbcTemplate对象配置dataSource-->
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <!--配置事务管理器-->
    <bean id="dataSourceTransactionManager"
          class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>


</beans>

GoodsDao.java

package com.zzti.spring.tx.dao;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;

@Repository
public class GoodsDao {
    @Autowired
    private JdbcTemplate jdbcTemplate;

    //根据id查询价格
    /**
     *
     * @param id 商品id
     * @return
     */
    public Float queryPriceById(Integer id){
        String sql = "select price from goods where id=?";
        Float price = jdbcTemplate.queryForObject(sql, Float.class, id);
        return price;
    }

    //修改用户余额
    /**
     *
     * @param id 用户id
     * @param money 用户余额
     */
    public void updateBalance(Integer id,Float money){
        String sql = "update account set money=money-? where id=?";
        jdbcTemplate.update(sql,money,id);
    }

    //修改库存量
    /**
     *
     * @param id 商品id
     * @param amount 商品库存
     */
    public void updateAmount(Integer id, int amount){
        String sql = "update amount set num=num-? where id=?";
        jdbcTemplate.update(sql,amount,id);
    }
}
//完成对GoodsDao的测试

    //1. 查询商品价格
    @Test
    public void queryPriceByIdTest(){
       ApplicationContext ioc = new ClassPathXmlApplicationContext("tx_ioc.xml");
       GoodsDao bean = ioc.getBean(GoodsDao.class);
       System.out.println("=======查询价格======");
       Float priceById = bean.queryPriceById(1);
       System.out.println("商品的价格是= " + priceById);
    }

    //2. 修改用户余额(购买商品后)
    @Test
    public void updateBalanceTest(){
        ApplicationContext ioc = new ClassPathXmlApplicationContext("tx_ioc.xml");
        GoodsDao bean = ioc.getBean(GoodsDao.class);
        System.out.println("=======修改用户余额(购买商品后)======");
        bean.updateBalance(1,100f);
        System.out.println("修改余额成功~~");
    }

    //3. 修改商品的库存量
    @Test
    public void updateAmountTest(){
        ApplicationContext ioc = new ClassPathXmlApplicationContext("tx_ioc.xml");
        GoodsDao bean = ioc.getBean(GoodsDao.class);
        System.out.println("=======修改商品的库存量======");
        bean.updateAmount(1,30);
        System.out.println("修改商品的库存量 成功~~");
    }

2. 验证不使用事务就会出现数据不一致现象

正常情况下: 购买完商品,余额和库存量都会减少
GoodsService.java

/**
 * 验证不使用事务就会出现数据不一致现象
 */
@Service
public class GoodsService {

    @Autowired
    private GoodsDao goodsDao;

    public void buyGoods(int user_id,int goods_id,int num){
        //得到商品价格
        Float goods_price = goodsDao.queryPriceById(goods_id);
        //减去价格,得到余额
        goodsDao.updateBalance(user_id,goods_price * num);

        //模拟一个异常,就会发现数据不一致问题
        //int i = 10/0;

        //更新库存
        goodsDao.updateAmount(goods_id,num);
    }
}
@Test
public void buyGoodsTest(){
    ApplicationContext ioc = new ClassPathXmlApplicationContext("tx_ioc.xml");
    GoodsService bean = ioc.getBean(GoodsService.class);
    bean.buyGoods(1,1,1);
    System.out.println("购买商品成功,没有加入事务~~");
}

非正常情况下:

  1. 故意将updateAmount中的SQL语句写错,会出现余额会继续减少,但是库存量不会减少的情况。
 public void updateAmount(Integer goods_id, int goods_num){
        String sql = "updatex goods_amount set goods_num=goods_num-? where goods_id=?";
        jdbcTemplate.update(sql,goods_num,goods_id);
    }
  1. 为了解决这种情况,我们使用声明式事务
<!--配置事务管理器(一定要配置数据源属性,指定对那个数据源进行事务管理控制)-->
<bean id="dataSourceTransactionManager"
      class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"/>
</bean>
<!--开启基于注解的声明式事务功能-->
<tx:annotation-driven transaction-manager="dataSourceTransactionManager"/>

在GoodsService.java中增加一个方法

/**
 *  加入声明式事务注解, 默认是传播属性: REQUIRED
 *  1. 标识方法中的, 对数据库的操作作为一个事务管理,
 *  也是AOP机制,底层是动态代理来调用buyGoodsByTx
 *  2.在执行buyGoodsByTx()前, 先调用事务管理器的doBegin(),再调用buyGoodsByTx()
 *  若没有发生异常,则调用事务管理器的doCommit()
 *  若发生异常,就调用管理管理器的doRollback()
 * @param user_id
 * @param goods_id
 * @param num
 */
@Transactional
public void buyGoodsByTx(int user_id,int goods_id,int num){
    Float goods_price = goodsDao.queryPriceById(goods_id);
    goodsDao.updateBalance(user_id,goods_price * num);
    goodsDao.updateAmount(goods_id,num);
}
//加入声明式事务
@Test
public void buyGoodsTestByTx(){
    ApplicationContext ioc = new ClassPathXmlApplicationContext("tx_ioc.xml");
    GoodsService bean = ioc.getBean(GoodsService.class);
    bean.buyGoodsByTx(1,2,1);
    System.out.println("~~购买商品成功~~");
}

这样就会包异常说SQL语句写错,但是余额和库存量都不会减少了。

五. 事务的传播机制

  1. 当有几个事务处理并存时,如何控制?
  2. 比如用户去购买两次商品(使用不同的方法), 每个方法都是一个事务,该如何控制?
    主要分析REQUIREDREQUIRED_NEW这两种事务,处理事务的策略如下:
@Transactional(本身就是一个事务) 
public void multiTxTest(){
    ApplicationContext ioc = new ClassPathXmlApplicationContext("tx_ioc.xml");
    GoodsService goodsService = ioc.getBean(GoodsService.class);
    goodsService.buyGoods(1,1,1);(一个事务)
    goodsService.buyGoods2(2,2,2);(又一个事务)
}

如果设置为REQUIRED_NEW,buyGoods2()错误,不会影响到 buyGoods(),反之亦然。即它们的事务是独立的。
如果设置为REQUIRED,buyGoods2() 和 buyGoods() 是一个整体,只要有方法的事务错误,那么两个方法都不会执行。

应用实例:
在GoodsDao.java中加入下面的方法,其中updateBalance2的SQL语句故意写错

public Float queryPriceById2(Integer goods_id){
     String sql = "select price from goods where goods_id=?";
     Float price = jdbcTemplate.queryForObject(sql, Float.class, goods_id);
     return price;
 }


 public void updateBalance2(Integer account_id,Float money){
     String sql = "updatex account set money=money-? where account_id=?";
     jdbcTemplate.update(sql,money,account_id);
 }


 public void updateAmount2(Integer goods_id, int goods_num){
     String sql = "update goods_amount set goods_num=goods_num-? where goods_id=?";
     jdbcTemplate.update(sql,goods_num,goods_id);
 }

GoodsService.java

@Transactional
public void buyGoodsByTx(int user_id,int goods_id,int num){
    Float goods_price = goodsDao.queryPriceById(goods_id);
    goodsDao.updateBalance(user_id,goods_price * num);
    goodsDao.updateAmount(goods_id,num);
}

//加入声明式事务注解, 默认是传播属性: REQUIRED
@Transactional
public void buyGoodsByTx02(int user_id,int goods_id,int num){
    Float goods_price = goodsDao.queryPriceById2(goods_id);
    goodsDao.updateBalance2(user_id,goods_price * num);
    goodsDao.updateAmount2(goods_id,num);
}

MultiplyTxService.java

@Service
public class MultiplyTxService {
    @Autowired
    private GoodsService goodsService;

    @Transactional
    public void multiTxTest(){
        goodsService.buyGoodsByTx(2,2,2);
        goodsService.buyGoodsByTx02(1,1,1);
    }
}
//测试事务的传播机制
@Test
public void BuyGoodsByMulTxTest(){
    ApplicationContext ioc = new ClassPathXmlApplicationContext("tx_ioc.xml");
    MultiplyTxService bean = ioc.getBean(MultiplyTxService.class);
    bean.multiTxTest();
    System.out.println("-------ok------");
}

因为@Transactional 默认是传播属性: REQUIRED,所以余额和库存量,都不会变化。

将传播机制修改为REQUIRED_NEW

@Transactional(propagation = Propagation.REQUIRES_NEW)
public void buyGoodsByTx(int user_id,int goods_id,int num){
    Float goods_price = goodsDao.queryPriceById(goods_id);
    goodsDao.updateBalance(user_id,goods_price * num);
    goodsDao.updateAmount(goods_id,num);
}

//加入声明式事务注解, 默认是传播属性: REQUIRED
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void buyGoodsByTx02(int user_id,int goods_id,int num){
    Float goods_price = goodsDao.queryPriceById2(goods_id);
    goodsDao.updateBalance2(user_id,goods_price * num);
    goodsDao.updateAmount2(goods_id,num);
}

因为REQUIRED_NEW的事务机制是相互独立,所以buyGoodsByTx()中的事务会生效,buyGoodsByTx2()中的事务不会生效。

六. 事务的隔离级别

在这里插入图片描述

隔离级别说明

  1. 默认的隔离级别,就是mysql数据库默认的隔离级别,一般为Repeatable Read(可重读)
  2. 看源码可知 Isolation.DEFAULT是: use the default isolation level of the underying datastore
  3. 查看数据库默认的隔离级别 select @@global.tx_isolation

隔离级别设置和测试

  1. Repeatable Read(可重读)
//隔离级别,一般为Repeatable Read(可重读)
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void buyGoodsByTxISOLATION(int user_id,int goods_id,int num){
    Float goods_price = goodsDao.queryPriceById(goods_id);
    System.out.println("第一次读取的价格= " + goods_price);
    //在此处下一个断点,执行到这里时,在SQLyog中修改对应商品的价格,然后看下第二次读取到的价格情况
    //update spring.goods set price=100 where goods_id=1;在SQLyog中,默认是自动提交
    goods_price = goodsDao.queryPriceById(goods_id);
    System.out.println("第二次读取的价格= " + goods_price);
}

两次读取到的价格不会受到SQLyog修改影响

  1. Read Committed(读取提交内容)
@Transactional(propagation = Propagation.REQUIRES_NEW,isolation = Isolation.READ_COMMITTED)
public void buyGoodsByTxISOLATION(int user_id,int goods_id,int num){
    Float goods_price = goodsDao.queryPriceById(goods_id);
    System.out.println("第一次读取的价格= " + goods_price);
      //在此处下一个断点,执行到这里时,在SQLyog中修改对应商品的价格,然后看下第二次读取到的价格情况
    //update spring.goods set price=200 where goods_id=1;在SQLyog中,默认是自动提交
    goods_price = goodsDao.queryPriceById(goods_id);
    System.out.println("第二次读取的价格= " + goods_price);
}

两次读取到的价格会受到SQLyog修改影响

七. 事务的超时回滚

  1. 如果一个事务执行的时间超过某个时间限制,就让该事务回滚
  2. 可以通过设置事务超时回滚实现 @Transactional(propagation = Propagation.REQUIRES_NEW,isolation = Isolation.READ_COMMITTED,timeout = 2)
 //超时回滚
@Transactional(timeout = 2)
public void buyGoodsByTxTimeout(int user_id,int goods_id,int num){
    Float goods_price = goodsDao.queryPriceById(goods_id);
    goodsDao.updateBalance(user_id,goods_price * num);
    System.out.println("========超时 start=======");
    try {
        Thread.sleep(4000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    System.out.println("========超时 end=======");
    goodsDao.updateAmount(goods_id,num);
}

会抛出异常,事务回滚,原来的操作将会撤销,Transaction timed out: deadline was Sat Jul 16 15:44:41 CST 2022

 //测试超时回滚
@Test
public void BuyGoodsByTxTimeoutTest(){
    ApplicationContext ioc = new ClassPathXmlApplicationContext("tx_ioc.xml");
    GoodsService bean = ioc.getBean(GoodsService.class);
    bean.buyGoodsByTxTimeout(1,1,1);
    System.out.println("-------ok------");
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值