Spring之Spring AOP与AspectJ

在日常开发中,我们经常用到切面,比如在springboot搭建的服务里,经常使用日志切面来记录客户端的访问日志。

我用一个小的demo演示

项目结构如下

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.yyu</groupId>
    <artifactId>myproject</artifactId>
    <version>0.0.1-SNAPSHOT</version>

    <!-- Inherit defaults from Spring Boot -->
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.9.RELEASE</version>
    </parent>

    <!-- Add typical dependencies for a web application -->
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>
    </dependencies>

    <!-- Package as an executable jar -->
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

OperationLog

package com.yyu.aop;

import java.lang.annotation.*;

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface OperationLog {
    String desc() default "";
}

OperationLogAop

package com.yyu.aop;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;

@Aspect
@Component
public class OperationLogAop {

    @Pointcut("@annotation(com.yyu.aop.OperationLog)")
    public void pointCut() {
    }

    @Before("pointCut()")
    public void before(JoinPoint joinPoint) {
        System.out.println("before===" + getDesc(joinPoint));
    }

    @After("pointCut()")
    public void after(JoinPoint joinPoint) {
        System.out.println("after===" + getDesc(joinPoint));
    }

    @Around("pointCut()")
    public void around(ProceedingJoinPoint point) throws Throwable {
        System.out.println("around before===" + getDesc(point));
        point.proceed();
        System.out.println("around after===" + getDesc(point));
    }

    @AfterReturning("pointCut()")
    public void afterReturning(JoinPoint joinPoint) {
        System.out.println("afterReturning===" + getDesc(joinPoint));
    }

    @AfterThrowing("pointCut()")
    public void afterThrow(JoinPoint joinPoint) {
        System.out.println("afterThrow===" + getDesc(joinPoint));
    }

    private String getDesc(JoinPoint joinPoint) {
        MethodSignature sign = (MethodSignature) joinPoint.getSignature();
        Method method = sign.getMethod();
        Class<?> declareClass = joinPoint.getTarget().getClass();
        OperationLog annotation = method.getAnnotation(OperationLog.class);
        return annotation.desc();
    }
}

启动类

package com.yyu;

import com.yyu.aop.OperationLog;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@SpringBootApplication
@RestController
public class YyuApp {

    @GetMapping(value = "/aop")
    @OperationLog(desc = "测试AOP")
    public String testAop() {
        System.out.println("执行testAop方法");
        return "ok";
    }

    public static void main(String[] args) {
        SpringApplication.run(YyuApp.class, args);
    }
}

运行后访问http://localhost:8080/aop

控制台输出如下

在这个示例中,通过aop我们可以实现对日志的集中管理,提高了工作效率和代码的整洁性,这也正是我们经常提到的面向切面编程,可以说面向切面加强了OOP。

AspectJ与Spring AOP

AspectJ和Spring AOP都实现了AOP技术,但是很多人经常混淆Spring AOP和AspectJ,AspectJ其实并不属于Spring,它是Eclipse发起的一个项目,AspectJ是一套关于AOP的完整体系,是一门独立的语言,他有自己关于AOP独立的实现。

Aspectj提供了非常完善的AOP能力,几乎能在java class的任何时刻使用织入功能。

  1. 编译时织入
    利用ajc编译器替代javac编译器,直接将源文件(java或者aspect文件)编译成class文件并将切面织入进代码。

  2. 编译后织入
    利用ajc编译器向javac编译期编译后的class文件或jar文件织入切面代码。

  3. 加载时织入
    不使用ajc编译器,利用aspectjweaver.jar工具,使用java agent代理在类加载期将切面织入进代码。
    (aspectjweaver.jar中,存在着@Before,@After等注解,并且有他们织入的实现(ASM动态代理))

ajc编译器,是一种能够识别aspect语法的编译器,它是采用java语言编写的,由于javac并不能识别aspect语法,便有了ajc编译器,注意ajc编译器也可编译java文件。

而Spring AOP却完全只会使用Cglib或者JDK动态代理,在类加载时通过动态代理织入。

为了避免依赖于特定的像acj这样的编译器,Spring AOP也可以说放弃了静态织入,转向采用动态代理技术的实现原理来构建Spring AOP的内部机制(动态织入)。

Spring AOP借用了AspectJ的概念,可以拿aspectjweaver中的一些功能,来为Spring AOP服务。例如切点、连接点、切点表达式、前置通知、后置通知等等,并且仿照AspectJ实现了自己的AOP功能。

综上,我们可以说Spring AOP只能在运行(加载)时织入,而AspectJ在编译时和运行(加载)时均可。

JDK动态代理与Cglib

二者主要区别是JDK动态代理只能对实现了接口的类生成代理。Cglib则对类有没有实现接口都可实现代理,主要是对指定的类生成一个子类,覆盖其中的方法,因为是继承,所以该类或方法最好不要声明成final。

Spring如何选择两种代理模式的?Spring AOP会在实际使用中,根据所代理的类灵活切换为JDK动态代理或Cglib。

1、如果目标对象实现了接口,则默认采用JDK动态代理;

2、如果目标对象没有实现接口,则使用Cglib代理;

3、如果目标对象实现了接口,但强制使用了Cglib,则使用Cglib进行代理

 

以下用示例来说明

实现了接口的UserServiceImpl和没有实现接口的DeptService

public interface UserService {
    void addUser(String user);
}

public class UserServiceImpl implements UserService {
    @Override
    public void addUser(String user) {
        System.out.println("添加了用户:" + user);
    }
}

public class DeptService {
    public void addDept(String dept) {
        System.out.println("添加了部门:" + dept);
    }
}

  • JDK动态代理
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class JdkProxyFactory implements InvocationHandler {
    private Object target;

    public JdkProxyFactory(Object target) {
        super();
        this.target = target;
    }

    public Object createProxy() {
        ClassLoader classLoader = target.getClass().getClassLoader();
        Class<?>[] interfaces = target.getClass().getInterfaces();
        Object newProxyInstance = Proxy.newProxyInstance(classLoader, interfaces, this);
        return newProxyInstance;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("执行代理方法前......");
        Object invoke = method.invoke(target, args);
        System.out.println("这是代理方法后......");
        return invoke;
    }

    public static void main(String[] args) {
        UserServiceImpl userService = new UserServiceImpl();
        JdkProxyFactory factory = new JdkProxyFactory(userService);
        UserService proxy = (UserService) factory.createProxy();
        proxy.addUser("yyu");
    }
}

执行结果

  • Cglib
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 CglibProxyFactory implements MethodInterceptor {
    private Object target;

    public CglibProxyFactory(Object target) {
        super();
        this.target = target;
    }

    public Object createProxy() {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(target.getClass());
        enhancer.setCallback(this);
        return enhancer.create();
    }

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("执行代理方法前......");
        Object invoke = method.invoke(target, objects);
        System.out.println("这是代理方法后......");
        return invoke;
    }

    public static void main(String[] args) {
        DeptService deptService = new DeptService();
        CglibProxyFactory cglibProxyFactory = new CglibProxyFactory(deptService);
        DeptService proxy = (DeptService) cglibProxyFactory.createProxy();
        proxy.addDept("hik");
    }
}

执行结果

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值