Java框架--Spring(轻量级容器框架)(从入门到底层-AOP)

目录

一.AOP

 1.动态代理

 1.1动态代理入门

1.2上手操作

2.AOP基本介绍

2.1啥子是AOP

2.2 AOP 实现方式

3.AOP编程快速入门

3.1基本说明

3.2快速入门实例

3.3AOP-切入表达式

3.4AOP-JoinPoint

3.5AOP-返回通知获取结果

3.6AOP-异常通知中获取异常

3.7AOP-环绕通知【了解】

3.8AOP-切入点表达式重用

3.9AOP-切面优先级问题

3.10AOP-基于 XML 配置 AOP

4.手动实现spring底层机制【初始化 IOC 容器+依赖注入+BeanPostProcessor 机制+AOP】

4.1简单分析 AOP 和 BeanPostProcessor 关系

4.2spring整体框架分析

4.3实现扫描包,得到bean的class对象

4.4扫描将 bean 信息封装到 BeanDefinition 对象, 并放入到 Map

4.5初始化 bean 单例池,并完成 getBean 方法 , createBean 方法

4.6完成依赖注入

4.7实现自己的 bean 后置处理器

4.8AOP 机制实现


切面编程本质就是对多个对象进行操作

一.AOP

 1.动态代理

 1.1动态代理入门

在不改变原有代码的情况下上进行对象功能增强 使用代理对象代替原来的对象完成功能 进而达到拓展功能的目的

1. Vehicle( 交通工具接口 , 有一个 run 方法 ), 下面有两个实现类 Car Ship
2. 当运行 Car 对象 的 run 方法和 Ship 对象的 run 方法时,输入如下内容 , 注意观察前后
有统一的输出

 我们需要实现上面的输出,就需要简单的重写就可以完成,但是在其中有交通工具开始运行和交通工具停止运行这个输出属于一样的,这样输出会造成代码冗余,像我们这么懒得人也不会去吃这个亏,所以便有了动态代理。

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

/**
 * @author 海绵hong
 * @version 1.0
 * VehicleProxyProvider 该类可以返回一个代理对象.
 */
public class VehicleProxyProvider {

    //定义一个属性
    //target_vehicle 表示真正要执行的对象
    //该对象实现了Vehicle接口
    private Vehicle target_vehicle;

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

    //编写一个方法,可以返回一个代理对象, 该代理对象可以通过反射机制调用到被代理对象的方法
    //解读
    //1. 这个方法非常重要, 理解有一定难度
    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 o 表示代理对象
             * @param method 就是通过代理对象调用方法时,的哪个方法 代理对象.run()
             * @param args : 表示调用 代理对象.run(xx) 传入的参数
             * @return 表示 代理对象.run(xx) 执行后的结果.
             * @throws Throwable
             */
            @Override
            public Object invoke(Object o, Method method, Object[] args)
                    throws Throwable {

                System.out.println("交通工具开始运行了....");
                //这里是我们的反射基础 => OOP
                //method 是?: public abstract void com.hspedu.spring.proxy2.Vehicle.run()
                //target_vehicle 是? 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)

          解读
          1. Proxy.newProxyInstance() 可以返回一个代理对象
          2. ClassLoader loader: 类的加载器.
          3. Class<?>[] interfaces 就是将来要代理的对象的接口信息
          4. InvocationHandler h 调用处理器/对象 有一个非常重要的方法invoke
         */
        Vehicle proxy =
                (Vehicle)Proxy.newProxyInstance(classLoader, interfaces, invocationHandler);

        return proxy;
    }
}
public class TestVehicle {

    @Test
    public void run() {
        //OOP基础=>java基础
        Vehicle vehicle = new Ship();
        //动态绑定
        vehicle.run();
    }

    @Test
    public void proxyRun() {
        //创建Ship对象
        Vehicle vehicle = new Car();

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

        //获取代理对象, 该对象可以代理执行方法
        //解读
        //1. porxy 编译类型 Vehicle
        //2. 运行类型 是代理类型 class com.sun.proxy.$Proxy9

        Vehicle proxy = vehicleProxyProvider.getProxy();

        System.out.println("proxy的编译类型是 Vehicle");
        System.out.println("proxy的运行类型是 " + proxy.getClass());
        //下面就要给大家解读/debug怎么 执行到 代理对象的 public Object invoke(Object o, Method method, Object[] args)
        //梳理完毕. proxy的编译类型是 Vehicle, 运行类型是 class com.sun.proxy.$Proxy9
        //所以当执行run方法时,会执行到 代理对象的invoke
        //如何体现动态 [1. 被代理的对象 2. 方法]
        //proxy.run();
        String result = proxy.fly(10000);
        System.out.println("result=" + result);
    }
}

我的发散:代理对象就是我们需要去动态变化的对象,而不需要变化的对象(方法,属性)我们直接写入对应的位置,通过反射我们可以获取到代理对象。再将代理对象的方法和属性存入一个数组之中,当该动态对象被调用的时候,就直接调用到动态对象的原类之中,当然这样动态对象的属性和方法也就可以调用了。所以这样写就可以在一个方法中new那个动态对象,就可以实现多个输出,解决代码冗余。

        当我们要去使用定义的方法的时候(改变run方法为flay方法)就可以直接在获取代理对象的代理的方法中点明。

1.2上手操作

1. 有一个 SmartAnimal 接口,可以完成简单的加减法 , 要求在执行 getSum() getSub()
时,输出执行前,执行过程,执行后的日志输出,请思考如何实现 .
                        日志-方法名-getSum-参数 10.0 2.0
                        方法内部打印 result = 12.0
                        日志-方法名-getSum- 结果 result= 12.0
                        ==========================
                        日志-方法名-getSub- 参数 10.0 2.0
                        方法内部打印 result = 8.0
                        日志-方法名-getSub- 结果 result= 8.0
1.2.1接口类
package com.hong.spring.proxy;

/**
 * Created with IntelliJ IDEA.
 *
 * @Author: 海绵hong
 * @Date: 2022/10/11/15:37
 * @Description:
 */
public interface SmartAnimal {
    //求和
    float getSum(float i, float j);
    //求差
    float getSub(float i, float j);

}

1.2.2方法调用类

package com.hong.spring.proxy;

/**
 * Created with IntelliJ IDEA.
 *
 * @Author: 海绵hong
 * @Date: 2022/10/11/16:39
 * @Description:
 */
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;
    }
}

1.2.3最关键的返回动态对象类

package com.hong.spring.proxy;

import java.lang.reflect.Array;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;

/**
 * Created with IntelliJ IDEA.
 *
 * @Author: 海绵hong
 * @Date: 2022/10/11/16:51
 * @Description: 可以返回一个动态代理对象, 可以执行SmartDog对象的方法
 */
public class MyProxyProvider {

    //定义我们要执行的目标对象
    private SmartAnimal target_obj;

    //构造器

    public MyProxyProvider(SmartAnimal target_obj) {
        this.target_obj = target_obj;
    }

    //方法:可以返回代理对象,该道理对象可以执行目标对象
    public SmartAnimal getProxy(){
        //1. 先到的类加载器/对象
        ClassLoader classLoader = target_obj.getClass().getClassLoader();

        //2.得到要执行的目标对象的接口信息
        Class<?>[] interfaces = target_obj.getClass().getInterfaces();

        //3. 创建InvocationHandler
        InvocationHandler invocationHandler = new InvocationHandler(){
            @Override
            public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
                Object result = null;
                try {
                System.out.println("日志-方法名-"+method.getName()+"-参数 "
                        + Arrays.asList(objects));//这里从AOP看,就是一个横切关注点--前置通知

                //使用反射调用方法
                 result = method.invoke(target_obj, objects);


                System.out.println("日志-方法名-"+method.getName()+"-参数 "
                        + result);//从AOP看,也是一个横切关注点-返回通知
                } catch (Exception e) {
                    e.printStackTrace();
                    //如果反射执行方法时,出现异常,就会进入到catch{}
                    System.out.println("方法执行异常-日志-方法名-" + method.getName()
                            + "-异常类型=" + e.getClass().getName());//从AOP看, 也是一个横切关注点-异常通知
                } finally {//不管你是否出现异常,最终都会执行到finally{}
                    //从AOP的角度看, 也是一个横切关注点-最终通知
                    System.out.println("方法最终结束-日志-方法名-" + method.getName());
                }

                return null;
            }
        };
        //创建代理对象-已经不再是之前的那种猫呀,狗呀之类的对象了,这已经是一个动态代理对象
        SmartAnimal proxy =
                (SmartAnimal) Proxy.newProxyInstance(classLoader, interfaces, invocationHandler);
        return proxy;

    }
}

1.2.4调用类

package com.hong.spring.proxy;

import org.testng.annotations.Test;

/**
 * Created with IntelliJ IDEA.
 *
 * @Author: 海绵hong
 * @Date: 2022/10/11/16:47
 * @Description:
 */
public class AopText {
    @Test
    public void smartDogTest(){
        SmartAnimal smartAnima = new SmartDog();

        MyProxyProvider myProxyProvider =
                new MyProxyProvider(smartAnima);

        //我们返回了代理对象
        SmartAnimal proxy =
                myProxyProvider.getProxy();

        proxy.getSum(10, 2);
        System.out.println("====================");
        proxy.getSub(10, 2);
    }

}

这样编写的代码虽然已经具有较高的代码复用性,但是还是不够灵活,并且有许多方面还是不够灵活(写死了:代理对象只能是莫格接口的实现对象),还是一种硬编码(没有注解和反射支撑)

现在我们就可以展示该段操作了,AOP闪亮登场

2.AOP基本介绍

2.1啥子是AOP

         AOP,一般称为 面向切面编程,作为面向对象的一种补充,用于将那些与业务无关,但却对多个对象产生影响的公共行为和逻辑,抽取并封装为一个可重用的模块,这个模块被命名为“切面”(Aspect),切面将那些与业务无关,却被业务模块共同调用的逻辑提取、并封装起来,减少了系统中的重复代码,降低了模块间的耦合度,同时提高了系统的可维护性。可用于权限认证、日志、事务处理等。
用图说话

 

2.2 AOP 实现方式

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

3.AOP编程快速入门

3.1基本说明

在切面类中声明通知方法
1) 前置通知: @Before
2) 返回通知: @AfterReturning
3) 异常通知: @AfterThrowing
4) 后置通知: @After
5) 环绕通知: @Around

3.2快速入门实例

我们使用 aop 编程的方式,来实现手写的动态代理案例效果,就以上一个案例为例来讲解
( 如图 )

首先接口是不需要动的,还是上面的接口,但是在SmartDog类需要引入注解(因为这是spring框架不是oop)

@Component//使用@Component 当spring容器启动时,将我们的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;
    }
}

紧接着我们就书写最重要的切面类(可以将方法使用注解的方式加入需要的类中的方法)

这里加一下我的一些难理解点:

1.之前动态对象获取的的形参会封装在JoinPoint这个接口里面,使用方法或者形参直接调用即可

2.这个类的底层还是动态代理,但是在方法调用和传递参数的时候使用了注解也就是spring的技术

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

    /**
     * 海绵思维发散
     * 1. @Before 表示前置通知:即在我们的目标对象执行方法前执行
     * 2. value = "execution(public float com.hspedu.spring.aop.aspectj.SmartDog.getSum(float, float)
     * 指定切入到哪个类的哪个方法  形式是: 访问修饰符 返回类型 全类名.方法名(形参列表)
     * 3.f1()方法可以理解成就是一个切入方法, 这个方法名是可以程序员指定  比如:showBeginLog
     * 4. JoinPoint joinPoint 在底层执行时,由AspectJ切面框架, 会给该切入方法传入 joinPoint对象
     * , 通过该方法,程序员可以获取到 相关信息
     *
     * @param joinPoint
     */

        //希望将f1方法切入到SmartDog-getSum前执行-前置通知
    @Before(value = "execution(public float com.hong.spring.aspectj.SmartDog.getSum(float, float))")
    public  void f1(JoinPoint joinPoint){
        //通过连接点对象joinPoint 可以获取方法签名
        Signature signature = joinPoint.getSignature();

        System.out.println("SmartAnimalAspect-切面类showBeginLog()[使用的myPointCut()]-方法执行前-日志-方法名-" + signature.getName() + "-参数 "
                + Arrays.asList(joinPoint.getArgs()));
    }
    //返回通知:即把showSuccessEndLog方法切入到目标对象方法正常执行完毕后的地方
    //老韩解读
    //1. 如果我们希望把目标方法执行的结果,返回给切入方法
    //2. 可以再 @AfterReturning 增加属性 , 比如 returning = "res"
    //3. 同时在切入方法增加 Object res
    //4. 注意: returning = "res" 和 Object res 的 res名字一致
    //@AfterReturning(value = "execution(public float com.hspedu.spring.aop.aspectj.SmartDog.getSum(float, float))", returning = "res")
    @AfterReturning(value =  "execution(public float com.hong.spring.aspectj.SmartDog.getSum(float, float))",returning = "res")
    public void showSuccessEndLog(JoinPoint joinPoint, Object res) {
        Signature signature = joinPoint.getSignature();
        System.out.println("SmartAnimalAspect-切面类showSuccessEndLog()-方法执行正常结束-日志-方法名-" + signature.getName() + " 返回的结果是=" + res);
    }


    //异常通知:即把showExceptionLog方法切入到目标对象方法执行发生异常的的catch{}
    //@AfterThrowing(value = "execution(public float com.hspedu.spring.aop.aspectj.SmartDog.getSum(float, float))", throwing = "throwable")
    @AfterThrowing(value = "execution(public float com.hong.spring.aspectj.SmartDog.getSum(float, float))", throwing = "throwable")
    public void showExceptionLog(JoinPoint joinPoint, Throwable throwable) {
        Signature signature = joinPoint.getSignature();
        System.out.println("SmartAnimalAspect-切面类showExceptionLog()-方法执行异常-日志-方法名-" + signature.getName() + " 异常信息=" + throwable);
    }

    //最终通知:即把showFinallyEndLog方法切入到目标方法执行后(不管是否发生异常,都要执行 finally{})
    //@After(value = "execution(public float com.hspedu.spring.aop.aspectj.SmartDog.getSum(float, float))")
    @After(value = "execution(public float com.hong.spring.aspectj.SmartDog.getSum(float, float))")
    public void showFinallyEndLog(JoinPoint joinPoint) {
        Signature signature = joinPoint.getSignature();
        System.out.println("SmartAnimalAspect-切面类showFinallyEndLog()-方法最终执行完毕-日志-方法名-" + signature.getName());
    }

}

当然我们在spring框架中使用注解是需要这beans.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: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">

    <context:component-scan base-package="com.hong.spring.aspectj"/>
    <!--        开启基于注解的aop功能-->
    <aop:aspectj-autoproxy/>
</beans>

最后让我们调用一下但是要注意通过接口类型获取到的是动态对象

public class AopAspectjTest {
    @Test
    public void smartDogTestByProxy() {
        ApplicationContext ioc =
                new ClassPathXmlApplicationContext("beans01.xml");

        //这里我们需要通过接口类型来获取到注入的SmartDog对象-就是代理对象
        SmartAnimal bean = ioc.getBean(SmartAnimal.class);
        bean.getSum(10, 2);
        System.out.println(bean.getClass());


    }
}
细节说明
1. 关于切面类方法命名可以自己规范一下 , 比如 showBeginLog() . showSuccessEndLog()
showExceptionLog(), showFinallyEndLog()
2. 切入表达式的更多配置,比如使用模糊配置
@Before(value="execution(* com.hspedu.aop.proxy.SmartDog.*(..))")
3. 表示所有访问权限,所有包的下所有有类的所方法,都会被执行该前置通知方法
@Before(value="execution(* *.*(..))")
4. spring 容器开启了 <!-- 开启基于注解的 AOP 功能 --> < aop :aspectj-autoproxy /> , 我们获
取注入的对象 , 需要以接口的类型来获取 , 因为你注入的对象 .getClass() 已经是代理类型
!
5. spring 容器开启了 <!-- 开启基于注解的 AOP 功能 --> < aop :aspectj-autoproxy /> , 我们获
取注入的对象 , 也可以通过 id 来获取 , 但是也要转成接口类型 .
6.当有一个接口被多个类所引用的时候我们就可以直接使用类名开头字母小写的方式获取bean对象
SmartAnimal bean = ioc.getBean("smartDog");

所以现在这个bean是一个接口类型的动态对象,但是他很明确的指向了SmartDog这个类李米娜的方法。

3.3AOP-切入表达式

注意事项和细节
1. 切入表达式也可以指向类的方法 , 这时切入表达式会对该类 / 对象生效
2. 切入表达式也可以指向接口的方法 , 这时切入表达式会对实现了接口的类 / 对象生效
3. 切入表达式也可以对没有实现接口的类,进行切入【 举例说明
class Car {
public void run() {
System.out.println("car run");
}
}
 

3.4AOP-JoinPoint

通过 JoinPoint 可以获取到调用方法的签名
    public void beforeMethod(JoinPoint joinPoint) {
        joinPoint.getSignature().getName(); // 获取目标方法名
        joinPoint.getSignature().getDeclaringType().getSimpleName(); // 获取目标方法所属类的简单类名
        joinPoint.getSignature().getDeclaringTypeName(); // 获取目标方法所属类的类名
        joinPoint.getSignature().getModifiers(); // 获取目标方法声明类型(public、private、protected)
        Object[] args = joinPoint.getArgs(); // 获取传入目标方法的参数,返回一个数组
        joinPoint.getTarget(); // 获取被代理的对象
        joinPoint.getThis(); // 获取代理对象自己
    }

3.5AOP-返回通知获取结果

@AfterReturning(value =  "execution(public float com.hong.spring.aspectj.SmartDog.getSum(float, float))",returning = "res")
public void showSuccessEndLog(JoinPoint joinPoint, Object res) {
    Signature signature = joinPoint.getSignature();
    System.out.println("SmartAnimalAspect-切面类showSuccessEndLog()-方法执行正常结束-日志-方法名-" + signature.getName() + " 返回的结果是=" + res);
}
海绵发散
    //1. 如果我们希望把目标方法执行的结果,返回给切入方法
    //2. 可以再 @AfterReturning 增加属性 , 比如 returning = "res"
    //3. 同时在切入方法增加 Object res
    //4. 注意: returning = "res" 和 Object res 的 res名字一致
    //@AfterReturning(value = "execution(public float com.hspedu.spring.aop.aspectj.SmartDog.getSum(float, float))", returning = "res")

3.6AOP-异常通知中获取异常

@AfterThrowing(value = "execution(public float com.hong.spring.aspectj.SmartDog.getSum(float, float))", throwing = "throwable")
public void showExceptionLog(JoinPoint joinPoint, Throwable throwable) {
    Signature signature = joinPoint.getSignature();
    System.out.println("SmartAnimalAspect-切面类showExceptionLog()-方法执行异常-日志-方法名-" + signature.getName() + " 异常信息=" + throwable);
}

如果你在执行value中路径所指的方法出现异常,那么就会把这个异常存储到throwing所指的变量之中。和返回通知获取结果的res概念相同。都可以输出。

3.7AOP-环绕通知【了解】

环绕通知可以完成其它四个通知要做的事情、
//@Aspect //表示是一个切面类[底层切面编程的支撑(动态代理+反射+动态绑定...)]
//@Component //会注入SmartAnimalAspect2到容器
public class SmartAnimalAspect2 {

    //演示环绕通知的使用-了解
    //1. @Around: 表示这是一个环绕通知[完成其它四个通知的功能]
    //2. value = "execution(public float com.hspedu.spring.aop.aspectj.SmartDog.getSum(float, float)) 切入点表达式
    //3. doAround 表示要切入的方法 - 调用结构 try-catch-finally
    @Around(value = "execution(public float com.hspedu.spring.aop.aspectj.SmartDog.getSum(float, float))")
    public Object doAround(ProceedingJoinPoint joinPoint) {
        Object result = null;
        String methodName = joinPoint.getSignature().getName();
        try {
            //1.相当于前置通知完成的事情
            Object[] args = joinPoint.getArgs();
            List<Object> argList = Arrays.asList(args);
            System.out.println("AOP环绕通知[-前置通知]" + methodName + "方法开始了--参数有:" + argList);
            //在环绕通知中一定要调用joinPoint.proceed()来执行目标方法
            result = joinPoint.proceed();
            //2.相当于返回通知完成的事情
            System.out.println("AOP环绕通知[-返回通知]" + methodName + "方法结束了--结果是:" + result);
        } catch (Throwable throwable) {
            //3.相当于异常通知完成的事情
            System.out.println("AOP环绕通知[-异常通知]" + methodName + "方法抛异常了--异常对象:" + throwable);
        } finally {
            //4.相当于最终通知完成的事情
            System.out.println("AOP环绕通知[-后置通知]" + methodName + "方法最终结束了...");
        }
        return result;
    }
}

如果我们将切面类定义两次就会发生调用方法的时候,切面类执行两次(海绵理解:因为切面类底层是反射,所以没有存储在内存空间,自然没有key值相同的结果)

3.8AOP-切入点表达式重用

 //定义一个切入点, 在后面使用时可以直接引用, 提高了复用性
    @Pointcut(value = "execution(public float com.hspedu.spring.aop.aspectj.SmartDog.getSum(float, float)))")
    public void myPointCut() {
    }
  //@Before(value = "execution(public float com.hong.spring.aop.aspectj.SmartDog.getSum(float, float))")
    //这里我们使用定义好的切入点
    @Before(value = "myPointCut()")
    public void showBeginLog(JoinPoint joinPoint) {
        //通过连接点对象joinPoint 可以获取方法签名
        Signature signature = joinPoint.getSignature();
        System.out.println("SmartAnimalAspect-切面类showBeginLog()[使用的myPointCut()]-方法执行前-日志-方法名-" + signature.getName() + "-参数 "
                + Arrays.asList(joinPoint.getArgs()));
    }

我们使用切入点表达式就可以减少代码的复用性,当定义@Pointcut之后就可以直接使用定义的方法去调用某个包下的类。就是可以理解为将这个地址封装起来然后去调用的时候就可以直接调用某方法。

3.9AOP-切面优先级问题

如果同一个方法,有多个切面在同一个切入点切入,那么执行的优先级如何控制
@order(value=n) 来控制 n 值越小,优先级越高 .

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

2. 如何理解执行顺序
        1. 类似前面学习过的 Filter 链式调用
        2. 示意图 ( 画一下 )

3.10AOP-基于 XML 配置 AOP

<?xml version="1.0" encoding="UTF-8"?>

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

    <!--使用XML配置,完成AOP编程-->


    <!--配置一个切面类对象-bean-->


    <bean id="smartAnimalAspect" class="com.hong.spring.aop.xml.SmartAnimalAspect"/>

    <!--配置一个SmartDog对象-bean-->


    <bean id="smartDog" class="com.hong.spring.aop.xml.SmartDog"/>

    <!--配置切面类, 细节一定要引入 xmlns:aop-->


    -
    <aop:config>

        <!--配置切入点-->


        <aop:pointcut id="myPointCut"
                      expression="execution(public float com.hspedu.spring.aop.xml.SmartDog.getSum(float, float)))"/>

        <!--配置切面的前置,返回, 异常, 最终通知-->


        -
        <aop:aspect order="10" ref="smartAnimalAspect">

            <!--配置前置通知-->


            <aop:before pointcut-ref="myPointCut" method="showBeginLog"/>

            <!--返回通知-->


            <aop:after-returning pointcut-ref="myPointCut" method="showSuccessEndLog" returning="res"/>

            <!--异常通知-->


            <aop:after-throwing pointcut-ref="myPointCut" method="showExceptionLog" throwing="throwable"/>

            <!--最终通知-->


            <aop:after pointcut-ref="myPointCut" method="showFinallyEndLog"/>

            <!--配置环绕通知-->


            <!--<aop:around method=""/>-->


        </aop:aspect>

    </aop:config>

</beans>

4.手动实现spring底层机制【初始化 IOC 容器+依赖注入+BeanPostProcessor 机制+AOP

记住:

       1.在maven项目中执行的是target包下的文件,所以我们的beans.xml需要放在resources包下(在maven中多数配置文件都会放在resources包下),因为src是普通的Java项目

        2.所谓单例就是所有的请求都用一个对象来处理,比如我们常用的service和dao层的对象通常都是单例的,而多例则指每个请求用一个新的对象来处理,比如action;

4.1简单分析 AOP BeanPostProcessor 关系

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

4.2spring整体框架分析

 一图胜千言

4.3实现扫描包,得到bean的class对象

知识扩展:类加载器
java 的类加载器 3 种
Bootstrap 类加载器--------------对应路径 jre/lib
Ext 类加载器--------------------对应路径 jre/lib/ext
App 类加载器-------------------对应路径 classpath

@ComponentScan用于完成组件扫描,指定spring扫描范围,通过它指定的路径,Spring会从被指定的包及其下级包扫描@Component及其子类注释的类,用于Spring容器自动装配,也就是告诉@ComponentSpring从哪里找到bean。
将普通JavaBean注入到spring容器中,Spring容器统一管理,用起来不用自己new了,相当于配置文件中的  <bean id="" class=""/>
接口:
ComponentScan指定要扫描那些路径,和beans.xml文件中的相似
<context:component-scan base-package="com.hong.spring.component" />

Component是进行一个装配准备,要是需要访问那些类就会进行扫描

类:

创建两个类:Service和Dao,进行接口的读取

HongSpringApplicationContext 类的作用类似Spring原生ioc容器,就是之前的ioc读取(动态代理和反射机制)验证是否是bean对象

4.4扫描将 bean 信息封装到 BeanDefinition 对象, 并放入到 Map

1.创建一个接口Scope 可以指定Bean的作用范围[singleton(单例), prototype(多例)]

2.创建一个类BeanDefinition 用于封装/记录Bean的信息[1. scope 2 Bean对应的Class对象, 反射可以生对应的对象]

3.在HongSpringApplicationContext中定义BeanDefinition

//定义属性BeanDefinitionMap -> 存放BeanDefinition对象
    private ConcurrentHashMap<String, BeanDefinition> beanDefinitionMap =
            new ConcurrentHashMap<>();
    //定义属性SingletonObjects -> 存放单例对象
    private ConcurrentHashMap<String, Object> singletonObjects =
            new ConcurrentHashMap<>();

4.同时在开始的时候我们使用了ioc容器判断是否装配到bean对象里面,所以在这里我们只需要在判断是beans对象之后将Bean的信息封装到BeanDefinition对象->放入到BeanDefinitionMap

5.获取Scope值(有就获取,无则默认为singleton)

6.蒋beanDefinition 对象放入到Map

beanDefinitionMap.put(beanName, beanDefinition);

4.5初始化 bean 单例池,并完成 getBean 方法 , createBean 方法

 初始化单例池(也就是如果Bean是单例的就实例化,并放入到单例池Map)

1.将BeanDefinition beanDefinition写入到createBean方法之中,封装的对象传入该方法之中。

        1.得到Bean的clazz对象

        2.使用反射得到实例

2.通过beanDefinitionMap , 初始化singletonObjects 单例池(封装成方法 遍历所有beanDefinition对象),如果是单例就放入到单例池

Enumeration<String> keys = beanDefinitionMap.keys();
        while (keys.hasMoreElements()) {
            //得到beanName
            String beanName = keys.nextElement();
            //通过beanName 得到对应的beanDefinition对象
            BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
            //判断该bean是singleton还是prototype
            if ("singleton".equalsIgnoreCase(beanDefinition.getScope())) {
                //将该bean实例放入到singletonObjects 集合
                Object bean = createBean(beanName, beanDefinition);
                singletonObjects.put(beanName, bean);
            }

3.编写方法getBean(String name)编写方法返回对容器中对象

        先获取beanDefinitionMap对象的名字

//得到beanDefinition的scope, 分别进行处理
            if ("singleton".equalsIgnoreCase(beanDefinition.getScope())) {
                //说明是单例配置, 就直接从单例池获取
                return singletonObjects.get(name);
            } else {//如果不是单例的,我就调用createBean, 反射一个对象
                return createBean(name, beanDefinition);
            }
        }

4.6完成依赖注入

只要使用@Autowired修饰了一个属性,那么我们就完成依赖注入

如何依赖注入:

        1.首先我们需要判断这个字段是否有@Autowired

        2.得到这个字段的名字

        3.通过getBean方法来获取要组装对象

        4.进行组装

//1. 遍历当前要创建的对象的所有字段
            for (Field declaredField : clazz.getDeclaredFields()) {
                //2. 判断这个字段是否有@Autowired
                if (declaredField.isAnnotationPresent(Autowired.class)) {
                    //提示一下
                    //处理@Autowired 的required ,很简单
                    //Autowired annotation = declaredField.getAnnotation(Autowired.class)
                    //annotation.required()=> 然后根据true, 是false 进行其它处理..
                    //3. 得到这个字段名字
                    String name = declaredField.getName();
                    //4. 通过getBean方法来获取要组装对象
                    Object bean = getBean(name);
                    //5. 进行组装
                    declaredField.setAccessible(true);//因为属性是pirvate, 需要暴破
                    declaredField.set(instance, bean);
                }
            }

4.7实现自己的 bean 后置处理器

容器中常用的一个方法是,根据该类是否实现了某个接口,来判断是否要执行某个业务逻辑,这几其实就是Java基础的接口编程实际运用:标记接口(没有方法,实现的价值就是判断是否实现了某个业务)

 后置处理器是针对所有bean对象的

bean对象创建是由jvm完成的,然后执行如下方法

1.执行构造器

2.执行set相关方法

3.调用bean的初始化的方法(需要配置)在spring中我们是通过init-method="init"实现的 

4.使用bean

5.当容器关闭的时候,调用bean的销毁方法(需要配置)

 可以看出来为了搞定后置处理器,我们需要给bean配置一个初始化方法

        1.判断是否执行Bean的初始化方法

                判断当前创建的bean对象是否实现了InitializingBean(nitializingBean是Spring提供的拓展性接口,InitializingBean接口为bean提供了属性初始化后的处理方法,它只有一个afterPropertiesSet方法,凡是继承该接口的类,在bean的属性初始化后都会执行该方法。)

        2.编写后置处理器接口BeanPostProcessor

//bean 初始化前执行的业务
Object postProcessBeforeInitialization(Object bean, String beanName);
//bean 初始化后执行的业务
Object postProcessAfterInitialization(Object bean, String beanName);

这两个方法会对所有的spring中的bean对象生效,已经是切面编程的概念了

        3.编写一个实现了BeanPostProcessor接口的后置处理器HongBeanPostProcessor。重写两个业务方法

        4.在spring容器中,仍然把HongBeanPostProcessor当作一个Bean对象,要注入到容器(使用@Component标识)

        5.让HongBeanPostProcessor成为真正的后置处理器,需要在容器中加入业务代码,还要考虑多个后置处理器对象注入到容器问题

                  那么我们就可以定义一个属性来存放后置处理器

private List<BeanPostProcessor> beanPostProcessorList =
            new ArrayList<>();
1. 为了方便,这里将后置处理器放入到一个ArrayList
2. 如果发现是一个后置处理器, 放入到 beanPostProcessorList
3. 在原生的Spring容器中, 对后置处理器还是走的getBean, createBean但是需要我们在singletonObjects 加入相应的业务逻辑
4. 因为这里我们是为了讲解后置处理去的机制,我就简化(如果不进行简化就需要在singletonObjects 放入和取出会相应多许多代码)

        6.判断当前的这个clazz有没有实现BeanPostProcessor

//说明, 这里我们不能使用 instanceof 来判断clazz是否实现了BeanPostProcessor
                            //原因: clazz不是一个实例对象,而是一个类对象/clazz, 使用isAssignableFrom
                            //小伙伴将其当做一个语法理解
                            if (BeanPostProcessor.class.isAssignableFrom(clazz)) {

                                BeanPostProcessor beanPostProcessor =
                                        (BeanPostProcessor) clazz.newInstance();
                                //放入到beanPostProcessorList
                                beanPostProcessorList.add(beanPostProcessor);
                                continue;
                            }

        7.我们在Bean的初始化方法前,调用后置处理器的before方法

for (BeanPostProcessor beanPostProcessor : beanPostProcessorList) {
                //在后置处理器的before方法,可以对容器的bean实例进行处理
                //然后返回处理后的bean实例, 相当于做一个前置处理
                Object current =
                        beanPostProcessor.postProcessBeforeInitialization(instance, beanName);
                if (current != null) {
                    instance = current;
                }
            }

        8.after方法

//我们在Bean的初始化方法后,调用后置处理器的after方法
            for (BeanPostProcessor beanPostProcessor : beanPostProcessorList) {
                //在后置处理器的after方法,可以对容器的bean实例进行处理
                //然后返回处理后的bean实例, 相当于做一个后置处理
                //原生Spring容器,比我们这个还要复杂
                Object current =
                        beanPostProcessor.postProcessAfterInitialization(instance, beanName);
                if(current != null) {
                    instance = current;
                }
            }

4.8AOP 机制实现

当我们去回头看之前的aop的时候我们会发现在aop的后置处理器的时候,我们的bean对象就已经转化为动态代理对象了。所以我们在这次写aop的底层机制的时候会在后置处理器的after中进行一些操作。

实现AOP, 返回代理对象, 即对Bean进行包装   

我们需要在后置处理器的After中判断是返回一个原生的bean还是一个代理对象 

 //1. 先死后活-> 后面我们可以通过注解就可以更加灵活
        if ("smartDog".equals(beanName)) {
            //使用Jdk的动态代理,返回返回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;
                            //假如我们进行前置通知+返回通知 处理的方法是getSum
                            //后面可以通过注解来做的更加灵活
                            if ("getSum".equals(method.getName())) {
                                SmartAnimalAspect.showBeginLog();
                                result = method.invoke(bean, args);//执行目标方法
                                //进行返回通知的处理
                                SmartAnimalAspect.showSuccessLog();
                            } else {
                                result = method.invoke(bean, args);//执行目标方法
                            }
                            return result;
                        }
                    });
            //如果bean是需要返回代理对象的, 这里就直接return proxyInstance
            return proxyInstance;
        }
        //如果不需要AOP, 返回 bean
        return bean;

  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

海绵hong

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值