ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

通过Aspectj进行静态织入

2022-04-24 23:02:09  阅读:304  来源: 互联网

标签:静态 void 代理 public class 织入 AOP AspectJ Aspectj


一、AspectJ和Spring AOP的区别

在spring框架中有一个主要的功能就是AOP,AOP(Aspect OrientedProgramming, 面向切面/方面编程) 旨在从业务逻辑中分离出来横切逻辑【eg:性能监控、日志记录、权限控制等】,提高模块化,即通过AOP解决代码耦合问题,让职责更加单一。

Spring AOP

1、基于动态代理来实现,默认如果使用接口的,用JDK提供的动态代理实现,如果是方法则使用CGLIB实现

2、Spring AOP需要依赖IOC容器来管理,并且只能作用于Spring容器,使用纯Java代码实现

3、在性能上,由于Spring AOP是基于动态代理来实现的,在容器启动时需要生成代理实例,在方法调用上也会增加栈的深度,使得Spring AOP的性能不如AspectJ的那么好

AspectJ

AspectJ来自于Eclipse基金会,AspectJ属于静态织入,通过修改代码来实现,有如下几个织入的时机:
​* 1、编译期织入(Compile-time weaving): 如类 A 使用 AspectJ 添加了一个属性,类 B 引用了它,这个场景就需要编译期的时候就进行织入,否则没法编译类 B。

​* 2、编译后织入(Post-compile weaving): 也就是已经生成了 .class 文件,或已经打成 jar 包了,这种情况我们需要增强处理的话,就要用到编译后织入。

  • ​3、类加载后织入(Load-time weaving): 指的是在加载类的时候进行织入,要实现这个时期的织入,有几种常见的方法。

** 3.1 自定义类加载器来干这个,这个应该是最容易想到的办法,在被织入类加载到 JVM 前去对它进行加载,这样就可以在加载的时候定义行为了。

** 3.2 在 JVM 启动的时候指定 AspectJ 提供的 agent:-javaagent:xxx/xxx/aspectjweaver.jar。

AspectJ可以做Spring AOP干不了的事情,它是AOP编程的完全解决方案,Spring AOP则致力于解决企业级开发中最普遍的AOP(方法织入)。而不是成为像AspectJ一样的AOP方案。
因为AspectJ在实际运行之前就完成了织入,所以说它生成的类是没有额外运行时开销的

对比总结
下表总结了 Spring AOP 和 AspectJ 之间的关键区别:

二、AOP无法拦截的情况

2.1  AoP无法拦截内部方法调用

首先自定义一个注解,用于标识切点:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface TestCheckPoint {
}

然后定义一个切面类:

@Component
@Aspect
public class TestAspect {
    @Before("@annotation(tp)")
    public void beforMethod(JoinPoint joinPoint, TestCheckPoint tp) {
        System.out.println("Before method...");
    }

    @After("@annotation(tp)")
    public void afterMethod(JoinPoint joinPoint, TestCheckPoint tp) {
    }
}

Controller

@RestController
public class TestController {
    @Autowired
    private HelloServiceImpl helloService;

    @RequestMapping("/say")
    public void sayHello() {
        helloService.sayHello();
    }
}

HelloServiceImpl

@Service
public class HelloServiceImpl implements HelloService {
    /**
     * 切面测试
     */
    @TestCheckPoint
    @Override
    public void sayHello() {
        System.out.println("Say hello world");
    }
}

启动springboot,可以看到Aop正常生效。

但是将切面进行修改:

@RestController
public class TestController {
    @Autowired
    private HelloServiceImpl helloService;

    @RequestMapping("/say")
    public void sayHello() {
        helloService.sayHelloWithoutAsp();
    }
}

@Service
public class HelloServiceImpl implements HelloService {
    /**
     * 切面测试
     */
    @TestCheckPoint
    @Override
    public void sayHello() {
        System.out.println("Say hello world");
    }
    /**
     * 测试本类中对切面的调用
     */
    public void sayHelloWithoutAsp() {
        this.sayHello();
    }
}

AoP没有生效:

通过idea进行debug可以看到:

helloService通过cglib进行动态代理。

但是this并没有实现动态代理

到这里我们分析一下整个流程:

首先controller层调用helloServiceImpl的sayHelloWithoutAsp方法,然后sayHelloWithoutAsp方法内部再调用被自定义注解所标识的sayHello方法。过程很简单,那为什么两种方式会产生不一样的结果呢?

我们对比下这两种的区别,可以发现仅仅是调用sayHello的对象不同,先来看第二种,由于是在本类的内部方法之间进行调用,所以肯定是调用的当前类this对象,断点调试下,可以发现这个this对象并不是代理对象,而只是当前类的普通对象,因此不存在方法增强。

接下来看下第一种调用方式中的helloServiceImpl,可以发现该类在被Spring注入时已经完成代理操作,返回的是一个cglib代理对象,因此其调用的sayHello方法自然而然可以对其进行增强,简单来说呢就是调用者首先调用代理对象,然后执行一系列前置操作,接着调用目标对象的方法,最后执行一系列后置操作,完成整个业务逻辑。对比上述第二种方式的直接调用目标对象的方法,那肯定得不到正确结果。

解决本类中调用问题

那么我们只需要使得在内部方法调用时拿到当前类的代理对象,然后再用这个代理对象去执行目标方法。

将@Service 修改成


@Service
public class HelloServiceImpl implements HelloService {
    /**
     * 切面测试
     */
    @TestCheckPoint
    @Override
    public void sayHello() {
        System.out.println("Say hello world");
    }

    /**
     * 测试本类中对切面的调用
     */
    public void sayHelloWithoutAsp() {
        getHelloServiceImpl().sayHello();
    }

    /**
     * 强制获取代理对象,必须开启exposeProxy配置,否则获取不到当前代理对象
     * @return
     */
    private HelloServiceImpl getHelloServiceImpl() {
        return AopContext.currentProxy() != null ? (HelloServiceImpl) AopContext.currentProxy() : this;
    }
}

能够实现代理

2.2  Spring AoP无法代理手动new的对象问题

@Autowired
RedisTemplate redisTemplate;
@Autowired
SayService sayService;
@Autowired
TestService testService;
@Autowired
TestCache tcache;
@RestController
public class TestController {
	@RequestMapping(value = {"get"}, method = RequestMethod.GET)
    public void getTest() {
        testService.cache.test();
    }
}

@Service
public class TestService {
    public TestCache cache = new TestCache();
}


@Component
public class TestCache {
    @TestPoint
    void test() {
        System.out.println("test");
    }
}
@Slf4j
@Aspect
@Component
@Order(1)
public class TestAspect {
    @Pointcut("@annotation(com.hks.redis.controller.TestPoint)")
    public void pointCutMethod() {
    }

    @Around(value = " @annotation(annotation)")
    public Object fileVideoCacheCollector(ProceedingJoinPoint joinPoint, TestPoint annotation) throws Throwable {
        System.out.println("Before method...");
        joinPoint.proceed();
        System.out.println("After method...");
        return null;
    }
}

修改controller

public class TestController {
	@RequestMapping(value = {"get"}, method = RequestMethod.GET)
    public void getTest() {
        testService.cache.test();
		tcache.test();
    }
}

testService.cache.test();中的test()没有进行代理;

tcache.test();中的test()进行了代理。

通过idea进行debug:

可以看到testService.cache不是cglib对象

这是因为spring AoP无法代理new出来的对象,只能代理被spring容器管理的对象。

2.3  通过Aspectj实现代理

改成aspectJ做代理

  • 1、aspectJ做代理,因为AspectJ是在编译时织入,Aop是在运行时织入。

  • 2、AspectJ可以在所有域对象中应用,Aop只能应用于由 Spring 容器管理的 bean。

  • 3、AspectJ是静态代理,Aop由动态代理JDK代理、CGLib代理。

  • 4、AspectJ可以代理call和execution等,Aop只能代理点execution。(call捕获的joinpoint是签名方法的调用点,而execution捕获的则是执行点)

@Aspect
public class TestAspect {
    @Pointcut("@annotation(com.hks.redis.controller.TestPoint)")
    public void pointCutMethod() {
    }

    @Around(value = "execution(* *(..)) && @annotation(annotation)")
    public Object fileVideoCacheCollector(ProceedingJoinPoint joinPoint, TestPoint annotation) throws Throwable {
        System.out.println("Before method...");
        joinPoint.proceed();
        System.out.println("After method...");
        return null;
    }
}

标签:静态,void,代理,public,class,织入,AOP,AspectJ,Aspectj
来源: https://www.cnblogs.com/xingzhouQAQ/p/16188163.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有