Spring基础-AOP编程

本文深入探讨了Spring的AOP(面向切面编程)概念,包括其本质——动态代理,以及JDK和CGLIB两种动态代理的实现方式。AOP主要步骤涉及原始对象、额外功能、切入点和切面组装。通过@Aspect和@Around注解实现切面编程,简化了代码。此外,文章还讨论了在service方法互相调用时如何确保AOP生效,并介绍了如何配置Spring以选择JDK或CGLIB代理。最后,总结了AOP编程的关键步骤和应用场景。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1、AOP编程的概念

概念:AOP面向切面编程,等同于spring代理开发。面向切面编程是以切面为基本单位的开发,通过切面相互调用。切面 = 切入点 + 额外功能

本质就是Spring动态代理,通过代理类为原始类增强方法

好处:方便对原始类进行扩展

2、AOP编程开发步骤

实际上就是动态代理的过程:

  • 原始对象
  • 额外方法
  • 切入点
  • 组装切面

3、切面的解释

切面 = 额外功能 + 切入点

4、AOP底层原理

4.1、核心问题

  • AOP是如何创建代理对象?

动态字节码技术

  • Spring工厂如何加工创建代理对象?

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

4.2、动态代理类的创建

作为spring,实现动态代理有两种方式

  • 第一种是通过原生的jdk的方式来帮助我们实现动态代理;
  • 第二种是第三方的框架cglib来实现的

4.3、JDK动态代理

我们使用JDK为我们提供的代理方式来创建代理对象,使用Proxy.newProxyInstance()方法,我们来分析下这个方法的三个参数的意义:

  • ClassLoader:这是一个类加载器,我们直接采用userService的即可。java中的每一个类都需要通过类加载器来加载class字节码文件,来生成对象
  • interfaces:接口,我们需要传递一个原始方法实现的接口
  • InvocationHandler:额外的功能,我们的原始方法需要增加哪些额外功能,这个接口的实现类是由我们程序员自己来编写的

实现InvocationHandler接口后,我们需要实现invoke方法,

我们再来看下这个InvocationHandler接口中的三个参数的意义:

  • proxy:代理类
  • method:原始方法
  • args:调用方法的参数

现在我们已经有了需要调用的方法名,方法参数,现在只需要在具有原始类,我么就可以创建对象进行增强了。刚好我们在上面创建了一个原始类对象,这样就可以使用我们的JDK代理来帮助我们完成任务了

    public void testJDKProxy(){

        //原始类
        final UserService userService = new UserServiceImpl();

        InvocationHandler invocationHandler = new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println("--------JDKProxy---------");
                Object ref = method.invoke(userService, args);
                return ref;
            }
        };

        UserService proxyUserService = (UserService) Proxy.newProxyInstance(userService.getClass().getClassLoader(), userService.getClass().getInterfaces(), invocationHandler);
        boolean flag = proxyUserService.login("zhangsan", "1223", "18");


    }

运行结果: 

4.4、CGLIB代理

前面我们演示了JDK代理,那么我们的cglib和jdk代理的区别是啥?

jdk代理还需要一个接口

jdk还需要一个实现了接口的原始类

在通过Proxy.newProxyInstance()创建出代理对象

而在现实开发中,很多时候,我们的原始类并没有实现任何接口,那么我们应该如何操作呢?

于是cglib就出现了,他是通过继承来实现的,继承父类的方法,来迷惑调用者,来达到增强方法的目的。

cglib动态代理原理:父子的继承创建代理对象,原始类作为父类,代理类作为子类,这样既可以保证方法的一致,同时代理类还可以提供新的实现(额外功能 + 原始方法)

public class UserServiceNoInterface {
    
    public boolean login(String loginName, String password, String age){
        System.out.println("UserServiceNoInterface.login");
        return true;
    }
}
    @Test
    public void testCGLIBProxy(){
        final UserServiceNoInterface serviceNoInterface = new UserServiceNoInterface();
        Enhancer enhancer = new Enhancer();
        //设置加载类
        enhancer.setClassLoader(UserServiceNoInterface.class.getClassLoader());
        //设置父类
        enhancer.setSuperclass(UserServiceNoInterface.class);
        //设置增强方法
        MethodInterceptor methodInterceptor = new MethodInterceptor() {
            @Override
            public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                System.out.println("-----------CGLIBProxy--------------");
                Object invoke = method.invoke(serviceNoInterface, objects);
                return invoke;
            }
        };
        enhancer.setCallback(methodInterceptor);
        //创建代理对象
        UserServiceNoInterface userServiceNoInterface = (UserServiceNoInterface)enhancer.create();
        userServiceNoInterface.login("zhangsan", "123", "16");
    }

测试效果 

JDK代理:基于接口实现

CGLIB代理:基于继承实现

5、回顾BeanPostProcessor

在Spring中,我们是通过bean标签中的id来创建对象。那么Spring提供的BeanPostProcessor的方式,来对创建的对象进行增强加工。然后将增强后的对象返回。我们就来通过代理来看看BeanPostProcessor是如何来实现这个事情的

创建接口

public interface UserService {

    public void register(User user);

    public boolean login(String loginName, String password);

    public boolean login(String loginName, String password, String age);
}

原始类

public class UserBeanPostProcessor implements UserService {
    @Override
    public void register(User user) {

    }

    @Override
    public boolean login(String loginName, String password) {
        return false;
    }

    @Override
    public boolean login(String loginName, String password, String age) {
        System.out.println("--------UserBeanPostProcessor.login--------");
        return false;
    }
}

定义一个BeanPostProcessor。我们主要是模拟spring给我们返回增强对象的过程。

public class ProxyBeanPostProcessor implements BeanPostProcessor {
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }

    public Object postProcessAfterInitialization(final Object bean, String beanName) throws BeansException {
        InvocationHandler invocationHandler = new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println("---------ProxyBeanPostProcessor.postProcessAfterInitialization---------");
                Object invoke = method.invoke(bean, args);
                return invoke;
            }
        };
        UserService userService = (UserService) Proxy.newProxyInstance(ProxyBeanPostProcessor.class.getClassLoader(), bean.getClass().getInterfaces(), invocationHandler);
        return userService;
    }
}

配置文件

    <bean id="beanPostProcessor" class="com.wx.service.impl.UserBeanPostProcessor"></bean>
    <bean id="proxyBeanPostProcessor" class="com.wx.proxy.ProxyBeanPostProcessor"></bean>

测试结果

6、AOP编程(注解版)

我们AOP的开发,有以下几个步骤:

  1. 创建原始类
  2. 额外功能
  3. 切入点
  4. 组装切面

接下来我们按照这个步骤来进行操作

创建原始类:

public interface UserService {

    public void register(User user);

    public boolean login(String loginName, String password);

    public boolean login(String loginName, String password, String age);
}
public class UserAnnotationAop implements UserService {

    @Override
    public void register(User user) {
        System.out.println("UserAnnotationAop.register");
    }

    @Override
    public boolean login(String loginName, String password) {
        System.out.println("UserAnnotationAop.login");
        return false;
    }

    @Override
    public boolean login(String loginName, String password, String age) {
        System.out.println("UserAnnotationAop.loginNew");
        return false;
    }
}

我们的2,3,4步其实就是在构造切面类

我们的切面,其实就是有额外功能加上切入点组装成的切面: 额外功能 + 切入点 = 切面

Spring为我们提供了一个注解,@Aspect这个注解就会告诉Spring这是一个切面类

@Aspect
public class MyAspect {
}

那么我们现在有了切面,还需要额外功能。我们需要在额外方法上加上@Around注解,方法名可以随便取,但是返回值必须是Object。

以前的额外方法,需要实现MethodInterceptor接口,实现public Object invoke(MethodInvocation methodInvocation)方法,现在我们的参数是ProceedingJoinPoint。额外功能有了,只差我们的切入点了,切入点就是@Around("execution(* login(..))"),这样我们的就完成了AOP编程的基本步骤。

@Aspect
public class MyAspect {

    @Around("execution(* login(..))")
    public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        System.out.println("MyAspect.around log");
        Object proceed = proceedingJoinPoint.proceed();
        return proceed;
    }
}

添加配置文件,让Spring帮助我们管理这几个bean对象 

    <bean id="annotationAop" class="com.wx.service.impl.UserAnnotationAop"></bean>
    <bean id="myAspect" class="com.wx.proxy.MyAspect"></bean>
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>

切入点复用

如果我们的切面类中,有多个额外方法但是使用同一个切入点,那么我们的代码就是这样的,看起来重复,有没有办法优化呢?

@Aspect
public class MyAspect {

    @Around("execution(* login(..))")
    public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        System.out.println("MyAspect.around log");
        Object proceed = proceedingJoinPoint.proceed();
        return proceed;
    }
    @Around("execution(* login(..))")
    public Object around2(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        System.out.println("MyAspect.around log");
        Object proceed = proceedingJoinPoint.proceed();
        return proceed;
    }
}

 我们可以使用这个@PointCut注解来进行优化

@Aspect
public class MyAspect {

    @Around("myPointCut()")
    public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        System.out.println("MyAspect.around log");
        Object proceed = proceedingJoinPoint.proceed();
        return proceed;
    }
    @Around("myPointCut()")
    public Object around2(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        System.out.println("MyAspect.around log");
        Object proceed = proceedingJoinPoint.proceed();
        return proceed;
    }

    @Pointcut("execution(* login(..))")
    public void myPointCut(){

    }
}

测试结果

7、动态代理的两种方式使用

我们上文中提到过,Spring动态代理有jdk代理和cglib代理,我们来看看在程序中如何配置,让spring来帮助我们进行切换。spring默认使用jdk动态代理。

false代表是jdk,true代表是cglib

配置文件中修改参数

<aop:aspectj-autoproxy proxy-target-class="true"></aop:aspectj-autoproxy>

 

不适用注解开发,我们是这么修改的

    <aop:config proxy-target-class="true">
        ...
    </aop:config>

8、AOP中的小细节

随着我们的业务越来越复杂,我们经常会出现service方法中调用其他的service方法的时候,这样会不会出现问题呢?

修改切入点

    @Pointcut("execution(* *..UserAnnotationAop.*(..))")
    public void myPointCut(){

    }

修改service代码

public class UserAnnotationAop implements UserService {

    @Override
    public void register(User user) {
        System.out.println("UserAnnotationAop.register");
    }

    @Override
    public boolean login(String loginName, String password) {
        System.out.println("UserAnnotationAop.login");
        this.register(new User());
        return false;
    }

    @Override
    public boolean login(String loginName, String password, String age) {
        System.out.println("UserAnnotationAop.loginNew");
        return false;
    }
}

我们在login方法中调用register方法,那么我们额外功能能否作用于register方法呢?我们来测试看看

出现这个问题的原因是,这里的this并不是指向我们的代理对象,所以没有办法触发我们的额外功能。Spring为我们提供了一个ApplicationContextAware接口,来解决这种问题

public class UserAnnotationAop implements UserService, ApplicationContextAware {

    private ApplicationContext applicationContext;

    @Override
    public void register(User user) {
        System.out.println("UserAnnotationAop.register");
    }

    @Override
    public boolean login(String loginName, String password) {
        System.out.println("UserAnnotationAop.login");
        // this.register(new User());
        UserService annotationAop = (UserService) applicationContext.getBean("annotationAop");
        annotationAop.register(new User());
        return false;
    }

    @Override
    public boolean login(String loginName, String password, String age) {
        System.out.println("UserAnnotationAop.loginNew");
        return false;
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
}

测试

9、AOP总结

AOP编程就是4步:

  • 原始对象
  • 额外功能
  • 切入点
  • 组装切面

具体我们可以看下注解版和非注解版的对比

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值