侧边栏壁纸
  • 累计撰写 98 篇文章
  • 累计创建 85 个标签
  • 累计收到 9 条评论

spring系列笔记 - 第⼗八章 基于AspectJ注解的AOP编程

bearjun
2021-07-15 / 0 评论 / 0 点赞 / 579 阅读 / 4,410 字 / 正在检测是否收录...
温馨提示:
本文最后更新于 2021-07-15,若内容或图片失效,请留言反馈。部分素材来自网络,若不小心影响到您的利益,请联系我们删除。

以前基于spring的aop动态代理开发,需要完成以下几步:

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

其中步骤2,3,4其实就是切面的开发。

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

基于Aspect的注解开发步骤如下:

  1. 创建一个类,加上@Aspect注解
  2. 创建一个方法,方法名随意,方法参数ProceedingJoinPoint joinPoint
  3. 给上述方法添加@Around注解,并添加对应的切入点表达式
  4. 在配置文件中注入,并且开启spring注解开发
/*
    1. 额外功能
        public class MyAround implements MethodInterceptor {
            public Object invoke(MethodInvocation invocation) {
                Object ret = invocation.invoke();
                return ret;
            }
        }
        <bean id="around" class="com.yusael.dynamic.Around"/>

    2. 切入点
        <aop:config>
            <aop:pointcut id="pc" expression="execution(* login(..)))"/>
            <aop:advisor advice-ref="around" pointcut-ref="pc"/>
        </aop:config>
 */
// 1. 创建一个类,加上@Aspect注解
@Aspect
public class MyAspect {
    // 3.方法添加@Around注解,并添加对应的切入点表达式
    @Around("execution(* login(..))")
    // 2. 创建一个方法,方法名随意,方法参数ProceedingJoinPoint joinPoint
    // joinPoint参数相当于invoke方法中的MethodInvocation invocation
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("---- aspect log ----");
        // 执行方法,并获取返回值
        Object ret = joinPoint.proceed();
        return ret;
    }
}
<!-- 4.在配置文件中注入,并且开启spring注解开发 -->
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
                           http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">

    <bean id="userService" class="com.bearjun.aspect.UserServiceImpl"/>
    <!-- 切面:1. 额外功能 2. 切入点啊 3. 组装切面 -->
    <bean id="around" class="com.bearjun.aspect.MyAspect"/>
    <!--告知 Spring 基于注解进行 AOP 编程-->
    <aop:aspectj-autoproxy/>

</beans>

注意细节

切入点复用

切⼊点复⽤:在切⾯类中定义⼀个函数上⾯@Pointcut注解 通过这种⽅式,定义切⼊点表达式,后 > 续更加有利于切⼊点复⽤。

@Aspect
public class MyAspect {

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

    @Around(value = "myPoincut()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("---- aspect log ----");
        Object ret = joinPoint.proceed();
        return ret;
    }
    @Around(value = "myPoincut()")
    public Object around1(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("---- aspect transaction ----");
        Object ret = joinPoint.proceed();
        return ret;
    }
}

注意:以上的代码around和around1方法共用myPoincut的切入点表达式方法

切换动态代理的创建方式(JDK、Cglib)

前面我们知道, AOP底层实现代理创建方式有2种:
1、JDK:通过 实现接口,做新的实现类 创建代理对象
2、Cglib:通过 继承父类,做新的子类 创建代理对象

思考一:默认情况下,用的是哪一种方式呢?
98384-6qra0w7uht4.png
默认情况AOP编程底层应用JDK动态代理创建方式

思考二:如果需要切换,怎么切换成Cglib呢?
基于注解的AOP开发中切换为Cglib:
在开启注解开发的标签上加上proxy-target-class="true"

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

传统的AOP开发中切换为Cglib:
在aop:config标签添加proxy-target-class="true"

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

AOP开发中的一个坑

坑!:在同⼀个业务类中,进⾏业务方法间的相互调用,只有最外层的方法,才是加入了额外功能(内部的方法,通过普通的方式调用,都调用的是原始方法)。如果想让内层的方法也调用代理对象的方法,就要实现AppicationContextAware获得⼯厂,进而获得代理对象。

public class UserServiceImpl implements UserService {
    
    @Override
    public void register(User user) {
        System.out.println("UserServiceImpl.register 业务运算 + DAO");

        // this.login("zhenyu", "123456"); // 这么写调用的是本类的 login 方法, 即原始对象的 login 方法
        UserService userService = (UserService) ctx.getBean("userService");
        userService.login("yusael", "123456");
    }

    @Override
    public boolean login(String name, String password) {
        System.out.println("UserServiceImpl.login 业务运算 + DAO");
        return true;
    }

}

例如上述的代码,在register上做切入点,对其做加入日志的增强,但是register内部调用了本类中的login方法,但最后,只是register做了日志的打印,而login方法并没有。
原因:调用register方法时,是调用了其动态代理类来完成增强的,而login是本来的方法,即使用的是原始类的方法,故不能增强。

public class UserServiceImpl implements UserService, ApplicationContextAware {
    private ApplicationContext ctx;
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        ctx = applicationContext;
    }
    @Override
    public void register(User user) {
        System.out.println("UserServiceImpl.register 业务运算 + DAO");

        // this.login("zhenyu", "123456"); // 这么写调用的是本类的 login 方法, 即原始对象的 login 方法
        // 为什么不在这里创建一个工厂获取代理对象呢?
        // Spring的工厂是重量级资源, 一个应用中应该只创建一个工厂.
        // 因此我们必须通过 ApplicationContextAware 拿到已经创建好的工厂
        UserService userService = (UserService) ctx.getBean("userService");
        userService.login("yusael", "123456");
    }

    @Override
    public boolean login(String name, String password) {
        System.out.println("UserServiceImpl.login 业务运算 + DAO");
        return true;
    }

}

通过以上方法可以很好的解决这个问题。

0

评论区