Spring5学习随笔-AOP底层实现(JDK、CGlib)、实现切面(@Aspect)

学习视频:【孙哥说Spring5:从设计模式到基本应用到应用级底层分析,一次深入浅出的Spring全探索。学不会Spring?只因你未遇见孙哥】

第四章、AOP编程

1.AOP概念

AOP(Aspect Oriented Programing)面向切面编程 = Spring动态代理开发
以切面为基本单位的程序开发,通过切面间的彼此协同,相互调用,完成程序的构建
切面 = 切入点 + 额外功能

OOP(Object Oriented Programing)面向对象编程 Java
以对象为基本单位的程序开发,通过对象间的彼此协同,相互调用,完成程序的构建

POP(Producer Oriented Programing)面向过程(方法、函数)编程 C
以过程为基本单位的程序开发,通过过程间的彼此协同,相互调用,完成程序的构建

AOP的概念:本质就是Srping的动态代理开发,通过代理类为原始类增加额外功能。好处:利于原始类的维护
注意:AOP编程不可能取代OOP,OOP编程有意补充。

2.AOP编程的开发步骤

  1. 原始对象
  2. 额外功能(MethodInterceptor)
  3. 切入点
  4. 组装切面(额外功能+切入点)

3.切面的名词解释

切面 = 切入点 + 额外功能

从几何学出发
面 = 点 + 相同的性质

第五章、AOP的底层实现原理

1.核心问题

  1. AOP如何创建动态代理类(动态字节码技术)

  2. Spring工厂如何加工创建代理对象

    通过原始对象的id值,获得的是代理对象

2.动态代理类的创建

2.1 JDK的动态代理

  • Proxy.newProxyInstance方法参数详解

public class TestJDKProxy {
    public static void main(String[] args) {
        //1 创建原始对象
        UserService userService = new UserServiceImpl();

        //2 JDK创建动态代理
        /*
        参数详解:
            ClassLoader:借用类加载器,哪个都行,TestJDKProxy UserServiceImpl....
            interfaces:原始对象所实现的接口
            handler:实现invoke方法(额外方法加到原始方法中)
        注意.JDK8.x前
                创建原始对象要加 final
         */
        InvocationHandler handler = new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println("-----proxy log-------");
                // 1.原始方法运行  原始对象和参数
                Object ret = method.invoke(userService, args);
                return ret;
            }
        };
        UserService userServiceProxy = (UserService) Proxy.newProxyInstance(TestJDKProxy.class.getClassLoader(), userService.getClass().getInterfaces(), handler);
        userServiceProxy.login("suns", "123456");
        userServiceProxy.register(new User());
    }
}

2.2 CGlib的动态代理

CGlib创建动态代理的原理:父子继承关系创建代理对象,原始类作为父类,代理类作为子类,这样既保证2者方法一致,同时在代理类中提供新的实现(额外功能+原始方法)

package com.baizhi.cglib;
import com.baizhi.proxy.User;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;

public class TestCglib {
    public static void main(String[] args) {
        // 1.创建原始对象
        UserService userService = new UserService();
        /*
            2.通过cglib方式创建动态代理对象
              JDK:Proxy.newProxyInstance(classloader,interface,invocationhandler)

              Enhancer,setClassLoader()
              Enhancer.setSuperClass()
              Enhancer.setCallback(); --->MethodInterceptor(cglib)
              Enhancer.create() ---> 代理
         */
        Enhancer enhancer = new Enhancer();
        enhancer.setClassLoader(TestCglib.class.getClassLoader());
        enhancer.setSuperclass(userService.getClass());

        MethodInterceptor interceptor = new MethodInterceptor() {
            // 等同于 InvocationHandler ---- invoke
            @Override
            public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
                System.out.println("----cglib log-----");
                Object ret = method.invoke(userService, args);
                return ret;
            }
        };
        enhancer.setCallback(interceptor);
        UserService userServiceProxy = (UserService) enhancer.create();
        userServiceProxy.login("suns","12346");
        userServiceProxy.register(new User());
    }
}
  • 总结(如果有接口就可以使用JDK动态代理,没有使用Cglib继承父类)
    1. JDK动态代理 Proxy.newProxyInstance() 通过接口创建代理的实现类
    2. Cglib动态代理 Enhancer 通过继承父类创建的代理类

3.Spring工厂如何加工代理对象

  • 思路分析

  • 编码
public class ProxyBeanPostProcessor implements BeanPostProcessor {
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }
    @Override
    /*
        Proxy.newProxyInstance(); 基于接口创建代理
     */
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        InvocationHandler handler = new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println("---------- new Log ------------");
                Object ret = method.invoke(bean, args);
                return ret;
            }
        };
        return Proxy.newProxyInstance(ProxyBeanPostProcessor.class.getClassLoader(),bean.getClass().getInterfaces(),handler);
    }
}
<bean id="userService" class="com.baizhi.factory.UserServiceImpl"/>
<!--
    1.实现BeanPostProcessor 进行加工
    2.配置文件中对BeanPostProcessor进行配置
-->
<bean id="proxyBeanPostProcessor" class="com.baizhi.factory.ProxyBeanPostProcessor"/>

第六章、基于注解的AOP编程

1.基于注解的AOP编程的开发步骤

  1. 原始对象
  2. 额外功能
  3. 切入点
  4. 组装切面

最大的区别是在2、3、4步,因为是由切面类定义额外功能(@Around)

定义了切入点@Around(”execution( login(..)))”*

@Aspect
/*
    构建切面所需的2个点
    1.额外功能
        public class MyArround implements MethodInterceptor{
            public Object invoke(MethodInvocation invocation){
                Object ret = invocation.proceed();
                return ret;
            }
        }
    2.切入点
        <aop:config
            <aop:pointcut id="" expression="execution(*login(..))"/>
 */
public class MyAspect {
    @Around("execution(* login(..))") // 1. 切入点
    public Object arround(ProceedingJoinPoint joinPoint) throws Throwable {  // 2.额外功能 等同于 上面的 invoke
        System.out.println("-----aspect log-------");
        Object ret = joinPoint.proceed();

        return ret;
    }
}
<!-- 1. 原始对象-->
<bean id="userService" class="com.baizhi.aspect.UserServiceImpl"/>
<!--
   切面:
      1.额外功能
      2.切入点
      3.组装切面
-->
<!-- 2. 切面-->
<bean id="arround" class="com.baizhi.aspect.MyAspect"/>
<!-- 告知Spring基于注解进行AOP编程-->
<aop:aspectj-autoproxy/>

2.细节

2.1 切入点复用

在切面类中定义一个函数 上面@Pointcut注解 通过这种方式,定义切入点表达式,后续更加有利于切入点复用。

@Aspect
public class MyAspect {
    @Pointcut("execution(* login(..))") // 复用切入点,解耦合
    public void myPointcut(){}

    @Around("myPointcut()") // 1. 切入点
    public Object arround(ProceedingJoinPoint joinPoint) throws Throwable {  // 2.额外功能 等同于 上面的 invoke
        System.out.println("-----aspect log-------");
        Object ret = joinPoint.proceed();

        return ret;
    }

    @Around("myPointcut()") // 1. 切入点
    public Object arround1(ProceedingJoinPoint joinPoint) throws Throwable {  // 2.额外功能 等同于 上面的 invoke
        System.out.println("-----aspect tx-------");
        Object ret = joinPoint.proceed();

        return ret;
    }
}

2.2动态代理的创建方式

AOP底层实现 2种代理创建方式

  1. JDK 通过实现接口 做新的实现类方式 创建代理对象
  2. Cglib通过继承父类 做新的子类 创建代理对象

默认情况 AOP编程 底层应用JDK动态代理创建方式

如果切换Cglib方式

1. 基于注解AOP开发
<aop:aspectj-autoproxy proxy-target-class="true"/>
2.传统的AOP开发
<aop:config proxy-target-class="true">
</aop>

第七章、AOP开发中的一个坑

坑:在同一个业务类中,进行业务方法间的相互调用,只有最外层的方法,才是加入了额外功能(内部的方法,通过this的方式调用,获得的是原始对象),如果想让内层的this.方法也调用代理对象的方法,就需要实现ApplicationContextAware接口获取。

@Service
public class UserServiceImpl implements UserService, ApplicationContextAware {
    private ApplicationContext ctx;
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.ctx = applicationContext;
    }
    @Log
    @Override
    public void register(User user) {

        System.out.println("UserServiceImpl.register 注册成功");
//        throw new RuntimeException("测试异常");
        /*
            调用的是原始对象的login方法 ---》核心功能,没有额外功能
            设计目的:代理对象的login方法 ---》 额外功能+核心功能
            所以应该调取代理对象的login方法,要获得工厂,而工厂通过ApplicationContextAware获得
         */
        UserService userService = (UserService) ctx.getBean("userService");
        userService.login("suns", "123456");
    }

    @Override
    public boolean login(String name, String password) {
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("UserServiceImpl.login登录成功");
        return true;
    }
}

第八章、AOP阶段知识总结

下一章:Spring5学习随笔-整合MyBatis(持久层)、连接池、Mapper文件

posted @ 2023-11-19 15:55  扬眉剑出鞘  阅读(113)  评论(0编辑  收藏  举报