美文网首页
Spring AOP 初识

Spring AOP 初识

作者: 耐得千事烦 | 来源:发表于2019-08-13 18:08 被阅读0次
  • 什么是AOP

AOP,顾名思义面向切面编程。要实现的是在我们原来写的代码的基础上,进行一定的包装,如在方法执行前、方法返回后、方法抛出异常后等地方进行一定的拦截处理或者叫增强处理。可以把AOP当成是OOP(面向对象)的一种有效补充。

  • Spring AOP与AspectJ的区别
  1. SpringAOP是基于动态代理模式来实现的。而动态代理的实现也是依靠俩种方式,当有接口的时候使用的是JDK提供的动态代理实现,没有接口的时候使用的是CGLIB字节码增强技术来实现
  2. SpringAOP只能作用于被SpringIOC容器管理的bean上,如果对象不被SpringIOC容器管理则SpringAOP也无能为力了。
  3. SpringAOP需要在容器启动的时候创建目标的代理对象,而且在方法调用上也增加了方法栈的深度,所以性能上比AspectJ差一点。
  1. AspectJ与SpringAOP不同的是,它是静态织入(通过修改代码来实现),它的织入时机可以是:
    Compile-time weaving:编译期织入,如类 A 使用 AspectJ 添加了一个属性,类 B 引用了它,这个场景就需要编译期的时候就进行织入,否则没法编译类 B。
    Post-compile weaving:也就是已经生成了 .class 文件,或已经打成 jar 包了,这种情况我们需要增强处理的话,就要用到编译后织入。
    Load-time weaving:指的是在加载类的时候进行织入,要实现这个时期的织入,有几种常见的方法。1、自定义类加载器来干这个,这个应该是最容易想到的办法,在被织入类加载到 JVM 前去对它进行加载,这样就可以在加载的时候定义行为了。2、在 JVM 启动的时候指定 AspectJ 提供的 agent:-javaagent:xxx/xxx/aspectjweaver.jar。
  2. 它是 AOP 编程的完全解决方案。Spring AOP 致力于解决的是企业级开发中最普遍的 AOP 需求(方法织入),而不是力求成为一个像 AspectJ 一样的 AOP 编程完全解决方案。
  3. 因为 AspectJ 在实际代码运行前完成了织入,所以大家会说它生成的类是没有额外运行时开销的。
  • 关键性的相关术语
    Advice:增强,主要是用来定义横切面代码(切面逻辑)
    Joinpoint:连接点,程序运行中的一些时间点, 例如一个方法的执行, 或者是一个异常的处理。在 Spring AOP 中, join point 总是方法的执行点, 即只有方法连接点。
    Pointcut:切点,我觉得它是一段关于连接点的描述信息(描述了哪些连接点会被切面织入)
    Aspect:切面,其实就是增强和切点的结合体
    Advisor:通知器,与切面类似(也是由增强和切点组合的),只是说使用场景不太一样,切面一般我们用来做日志、缓存处理等,而通知器可以用来做事务处理(Spring事务就是这样的),而且俩者在实现的方式上也有细微的不同。(主要就是advisor的话它的增强必须实现Advice接口,而aspect的话普通的bean用作增强就可以了)

使用Spring AOP

  • spring 1.2 基于接口的配置:最早的 Spring AOP 是完全基于几个接口的
  • Spring 2.0 schema-based 配置:Spring 2.0 以后使用 XML 的方式来配置,使用 命名空间 <aop />
  • Spring 2.0 @AspectJ 配置:使用注解的方式来配置,这种方式感觉是最方便的,还有,这里虽然叫做 @AspectJ,但是这个和 AspectJ 其实没啥关系。
spring 1.2 基于接口的配置

先定义俩个接口UserService和OrderService以及相关的实现类:

public interface UserService {

    User createUser(String firstName, String lastName, int age);

    User queryUser();
}

public class UserServiceImpl implements UserService {

    private static User user = null;

    public User createUser(String firstName, String lastName, int age) {
        user = new User();
        user.setFirstName(firstName);
        user.setLastName(lastName);
        user.setAge(age);
        return user;
    }

    public User queryUser() {
        return user;
    }
}

public interface OrderService {

    Order createOrder(String username, String product);

    Order queryOrder(String username);
}

public class OrderServiceImpl implements OrderService {

    private static Order order = null;

    public Order createOrder(String username, String product) {
        order = new Order();
        order.setUsername(username);
        order.setProduct(product);
        return order;
    }

    public Order queryOrder(String username) {
        return order;
    }
}

接下来定义俩个advice(增强):

public class LogArgsAdvice implements MethodBeforeAdvice {

    // 方法调用前输出 调用方法名字和参数
    public void before(Method method, Object[] args, Object target) throws Throwable {
        System.out.println("准备执行方法: "+ method.getName()+", 参数列表: " + Arrays.toString(args));
    }
}

public class LogResultAdvice implements AfterReturningAdvice {
    // 方法调用后输出结果
    public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
        System.out.println("方法返回: " + returnValue);
    }
}

OK,现在开始进行xml的配置工作:

<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 https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">

    <!-- 实现的俩个增强纳入到IOC容器管理里-->
    <bean id="logArgsAdvice" class="com.suxin.aop.advice.LogArgsAdvice" />
    <bean id="logResultAdvice" class="com.suxin.aop.advice.LogResultAdvice" />

    <!-- 俩个接口的实现类纳入到IOC容器的管理里-->
    <bean id="userServiceImpl" class="com.suxin.aop.service.impl.UserServiceImpl" />
    <bean id="orderServiceImpl" class="com.suxin.aop.service.impl.OrderServiceImpl" />

    <!-- 配置UserService的代理-->
    <bean id="userServiceProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
        <!-- 代理接口 -->
        <property name="proxyInterfaces">
            <list>
                <value>com.suxin.aop.service.UserService</value>
            </list>
        </property>
        <!-- 代理的具体实现-->
        <property name="target" ref="userServiceImpl" />
        <!-- 配置拦截器 这里可以配置advice、advisor、interceptor-->
        <property name="interceptorNames">
            <list>
                <value>logArgsAdvice</value>
                <value>logResultAdvice</value>
            </list>
        </property>
    </bean>
    <!-- OrderService的代理配置与上面是一致的-->
    <bean id="orderServiceProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
        <!-- 代理接口 -->
        <property name="proxyInterfaces">
            <list>
                <value>com.suxin.aop.service.OrderService</value>
            </list>
        </property>
        <!-- 代理的具体实现-->
        <property name="target" ref="orderServiceImpl" />
        <!-- 配置拦截器 这里可以配置advice、advisor、interceptor-->
        <property name="interceptorNames">
            <list>
                <value>logArgsAdvice</value>
                <value>logResultAdvice</value>
            </list>
        </property>
    </bean>

</beans>

执行一下main方法:

public class App {

    public static void main(String[] args) {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("aops.xml");
        UserService userService = (UserService)context.getBean("userServiceProxy");
        userService.createUser("Tom","Paul",35);
        userService.queryUser();
    }
}

看一下控制台打印

准备执行方法: createUser, 参数列表: [Tom, Paul, 35]
方法返回: com.suxin.aop.model.User@229d10bd
准备执行方法: queryUser, 参数列表: []
方法返回: com.suxin.aop.model.User@229d10bd

可以看到针对UserService的接口方法都实现了前后拦截,并且输出了拦截的代码逻辑。
虽然功能实现了,但是还有很多值得完善的地方,我们来一一道明,并且给出解决方案。

缺点:

  1. 接口的所有方法都被拦截了,我们可能只是想对特定的接口方法进行拦截。
    解决方案:在上面配置userServiceProxy的时候,会对里面的interceptorNames属性进行配置,它不仅可以配置advice增强,还可以配置advisor。而advisor可以指定需要对哪些方法进行拦截,然后交由内部的adivce来完成拦截后的切面逻辑工作。
    advisor有好几个实现类,我们选择用NameMatchMethodPointcutAdvisor
    见字知意,它需要我们给它提供方法名字,这样符合该配置的方法才会做拦截。
    直接看xml配置(只看修改的地方了):
    <!-- 定义一个advisor-->
    <bean id="logCreateAdvisor" class="org.springframework.aop.support.NameMatchMethodPointcutAdvisor">
        <!-- 内部定义一个advice增强-->
        <property name="advice" ref="logArgsAdvice" />
        <!-- 定义拦截的方法名称 凡是匹配的方法都会被拦截-->
        <property name="mappedNames" value="createUser,createOrder" />
    </bean>

    <!-- 配置UserService的代理-->
    <bean id="userServiceProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
        <!-- 代理接口 -->
        <property name="proxyInterfaces">
            <list>
                <value>com.suxin.aop.service.UserService</value>
            </list>
        </property>
        <!-- 代理的具体实现-->
        <property name="target" ref="userServiceImpl" />
        <!-- 配置拦截器 这里可以配置advice、advisor、interceptor-->
        <property name="interceptorNames">
            <list>
                <value>logCreateAdvisor</value>
            </list>
        </property>
    </bean>

运行main方法,直接看输出:

准备执行方法: createUser, 参数列表: [Tom, Paul, 35]

可以看到只有createUser方法被拦截了,而queryUser方法则是没被拦截。所以第一个缺点的问题我们已经完善了。

  1. 根据上面的玩法,我们得为每个需要拦截的bean设置一个代理类,这样就非常的不方便了。如果有很多个bean需要,岂不是需要很多的代理类,这样的玩法也不现实。

解决方案:引入AutoProxy自动代理。也就是说当 Spring 发现一个 bean 需要被切面织入的时候,Spring 会自动生成这个 bean 的一个代理来拦截方法的执行,确保定义的切面能被执行。这里强调自动,也就是说 Spring 会自动做这件事,而不用像前面介绍的,我们需要显式地指定代理类的 bean

我们去掉原来的 ProxyFactoryBean 的配置,改为使用 BeanNameAutoProxyCreator 来配置:

    <bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
        <!-- 配置拦截器-->
        <property name="interceptorNames">
            <list>
                <value>logArgsAdvice</value>
                <value>logResultAdvice</value>
            </list>
        </property>
        <!-- 定义匹配的bean的规则-->
        <property name="beanNames" value="*ServiceImpl" />
    </bean>

修改一下原先的main方法:

public class App {

    public static void main(String[] args) {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("aops.xml");
        UserService userService = (UserService)context.getBean("userServiceImpl");
        userService.createUser("Tom","Paul",35);
        userService.queryUser();
    }
}

控制台输出:

准备执行方法: createUser, 参数列表: [Tom, Paul, 35]
方法返回: com.suxin.aop.model.User@161479c6
准备执行方法: queryUser, 参数列表: []
方法返回: com.suxin.aop.model.User@161479c6

俩个问题我们都得到了解决,现在我们来介绍一下DefaultAdvisorAutoProxyCreator
在上面的BeanNameAutoProxyCreator里通过自身匹配方法,然后交由内部配置 advice 来拦截处理;而 DefaultAdvisorAutoProxyCreator 是让 ioc 容器中的所有 advisor 来匹配方法,advisor 内部都是有 advice 的,让它们内部的 advice 来执行拦截处理。

我们需要再回头看下 Advisor 的配置,上面我们用了 NameMatchMethodPointcutAdvisor 这个类:

<bean id="logCreateAdvisor" class="org.springframework.aop.support.NameMatchMethodPointcutAdvisor">
    <property name="advice" ref="logArgsAdvice" />
    <property name="mappedNames" value="createUser,createOrder" />
</bean>

其实 Advisor 还有一个更加灵活的实现类 RegexpMethodPointcutAdvisor,它能实现正则匹配,如:

<bean id="logArgsAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
    <property name="advice" ref="logArgsAdvice" />
    <property name="pattern" value="com.suxin.aop.service.*.create.*" />
</bean>

之后,我们需要配置 DefaultAdvisorAutoProxyCreator,它的配置非常简单,直接使用下面这段配置就可以了,它就会使得所有的 Advisor 自动生效,无须其他配置。

<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator" />

控制台输出:

准备执行方法: createUser, 参数列表: [Tom, Paul, 35]
方法返回: com.suxin.aop.model.User@77167fb7

从结果可以看出,create* 方法使用了 logArgsAdvisor 进行传参输出,query* 方法使用了 logResultAdvisor 进行了返回结果输出。
到这里,Spring 1.2 的配置就要介绍完

Spring 2.0 @AspectJ 配置

注意了,@AspectJ 和 AspectJ 没多大关系,并不是说基于 AspectJ 实现的,而仅仅是使用了 AspectJ 中的概念,包括使用的注解也是直接来自于 AspectJ 的包。
先引入依赖的包吧:

    <dependency>
      <groupId>org.aspectj</groupId>
      <artifactId>aspectjweaver</artifactId>
      <version>1.8.13</version>
    </dependency>

在 @AspectJ 的配置方式中,之所以要引入 aspectjweaver 并不是因为我们需要使用 AspectJ 的处理功能,而是因为 Spring 使用了 AspectJ 提供的一些注解,实际上还是纯的 Spring AOP 代码。

开启@AspectJ注解配置的方式有俩种:
1.使用xml配置:

<!-- 开启注解方式来配置AOP-->
    <aop:aspectj-autoproxy />

2.使用@EnableAspectJAutoProxy

@Configuration
@EnableAspectJAutoProxy
public class AppConfig {

}

开启以后,所有被@Aspect注解的bean都会被Spring当成实现aop的配置类。可以理解为一个Aspect。
直接上代码:

@Aspect
public class LogArgsAspect {

    @Pointcut("execution(* com.suxin..service.*.*(..))")
    public void businessService2() {
    }

    @Before("businessService2()")
    public void logArgs(JoinPoint joinPoint) throws Throwable {
        System.out.println("方法执行前,打印入参: "+ Arrays.toString(joinPoint.getArgs()));
    }
}
@Aspect
public class LogResultAspect {

    @Pointcut("execution(* com.suxin..service.*.*(..))")
    public void businessService2() {

    }
    
    @AfterReturning(pointcut = "businessService2()",returning = "result")
    public void logResult(Object result) {
        System.out.println(result);
    }
}

继续看xml的配置:

    <!-- 俩个接口的实现类纳入到IOC容器的管理里-->
    <bean id="userServiceImpl" class="com.suxin.aop.service.impl.UserServiceImpl" />
    <bean id="orderServiceImpl" class="com.suxin.aop.service.impl.OrderServiceImpl" />

    <aop:aspectj-autoproxy />

    <bean class="com.suxin.aop.aspect.LogArgsAspect" />
    <bean class="com.suxin.aop.aspect.LogResultAspect" />

运行main方法,控制台打印:

方法执行前,打印入参: [Tom, Paul, 35]
com.suxin.aop.model.User@2f465398
方法执行前,打印入参: []
com.suxin.aop.model.User@2f465398

ok,这就是基于注解的形式来进行Spring的AOP,当然只是点到为止,还有很多玩法,大家感兴趣自行去发现。

Spring 2.0 schema-based 配置

Spring 2.0 以后提供的基于 <aop /> 命名空间的 XML 配置。这里说的 schema-based 就是指基于 aop 这个 schema。

解析 <aop /> 的源码在 org.springframework.aop.config.AopNamespaceHandler 中。

所有的配置都在 <aop:config> 下面。
<aop:aspect > 中需要指定一个 bean,和前面介绍的 LogArgsAspect 和 LogResultAspect 一样,我们知道该 bean 中我们需要写处理代码。
然后,我们写好 Aspect 代码后,将其“织入”到合适的 Pointcut 中,这就是面向切面。
直接上代码吧:

    <!-- 俩个接口的实现类纳入到IOC容器的管理里-->
    <bean id="userServiceImpl" class="com.suxin.aop.service.impl.UserServiceImpl" />
    <bean id="orderServiceImpl" class="com.suxin.aop.service.impl.OrderServiceImpl" />

    <bean id="logArgsAspect" class="com.suxin.aop.aspect.LogArgsAspect" />
    <bean id="logResultAspect" class="com.suxin.aop.aspect.LogResultAspect" />


    <aop:config>
        <!-- 全局的pointcut,可以作用在多个切面里-->
        <aop:pointcut id="logResultPointcut" expression="com.suxin.aop.aspect.LogResultAspect.businessService2()"/>
        
        <aop:aspect  ref="logArgsAspect">
            <!-- 局部的pointcut只能在这个切面里进行使用-->
            <aop:pointcut id="internalPointcut" expression="com.suxin.aop.aspect.LogArgsAspect.businessService2()"/>
            <!-- 定义拦截逻辑以及引用的切点-->
            <aop:before method="logArgs" pointcut-ref="internalPointcut"></aop:before>
        </aop:aspect>


        <aop:aspect  ref="logResultAspect">
            <aop:after-returning method="logResult" returning="result" pointcut-ref="logResultPointcut"></aop:after-returning>
        </aop:aspect>

    </aop:config>

运行main方法,控制台打印:

方法执行前,打印入参: [Tom, Paul, 35]
com.suxin.aop.model.User@31f9b85e
方法执行前,打印入参: []
com.suxin.aop.model.User@31f9b85e

其实基于 XML 的配置也是非常灵活的,这里没办法给大家演示各种搭配,大家抓住基本的 Pointcut、Advice 和 Aspect 这几个概念,就很容易配置了。

相关文章

网友评论

      本文标题:Spring AOP 初识

      本文链接:https://www.haomeiwen.com/subject/tpqpjctx.html