Spring AOP

欢迎光临我的博客[http://poetize.cn],前端使用Vue2,聊天室使用Vue3,后台使用Spring Boot

AOP简介

AOP:
    面向切面,作为面向对象的一种补充,
    用于将那些与业务无关,但却对多个对象产生影响的公共行为和逻辑,抽取并封装为一个可重用的模块,
    这个模块被命名为“切面”(Aspect),
    减少系统中的重复代码,降低了模块间的耦合度,同时提高了系统的可维护性。
    可用于权限认证、日志、事务处理。


AOP的关键单元是切面:
    AOP可以使用简单可插拔的配置,在实际逻辑执行之前、之后或周围动态添加横切关注点。
    如果使用XML来使用切面,要添加或删除关注点,不用重新编译完整的源代码,而仅仅需要修改配置文件就可以了。

AOP主要的的实现技术主要有:
    AspectJ:
        底层技术是静态代理,
        通过编译,生成一个新的代理类,该代理类增强了业务类,这是在编译时增强,编译时增强的性能更好。

    Spring AOP:
        动态代理,在运行期间对业务方法进行增强,不会生成新类。


Spring AOP是面向切面编程:
    是面向对象编程的一种补充,
    使用动态代理实现,在内存中临时为目标对象生成一个AOP对象,这个对象包含目标对象的所有方法,在特定的切点做了增强处理,并回调原来的方法。

Spring AOP的动态代理主要有两种方式实现:
    JDK动态代理和cglib动态代理。

    JDK动态代理:
        通过反射来接收被代理的类,但是被代理的类必须实现接口,动态创建一个符合某一接口的的实例。核心是InvocationHandler和Proxy类。

    CGLIB动态代理:
        CGLIB是一个代码生成的类库,可以在运行时动态生成某个类的子类,并覆盖其中特定方法并添加增强代码,从而实现AOP。
        所以,CGLIB是通过继承的方式做的动态代理。

JDK代理

public class ProxyFactory {
    /**
     * 获取代理对象代理对象
     */
    public static Object getProxyObject(final Object realObject, final Object aspectObject) {
        final Class<?> realObjectClass = realObject.getClass();
        final Class<?> aspectObjectClass = aspectObject.getClass();
        return Proxy.newProxyInstance(
                realObjectClass.getClassLoader(),
                realObjectClass.getInterfaces(),
                new InvocationHandler() {
                    /**
                     * 模拟简单的@Before日志注解
                     */
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        /**
                         * 加载切入点信息. 这里的方法Logger被硬编码了, 后期可以根据注解来解决
                         */
                        Method pointCutMethod = aspectObjectClass.getMethod("Logger", new Class[]{});
                        ...
                        /**
                         * 执行原始对象的方法
                         */
                        return method.invoke(realObject, args);
                    }
                }
        );
    }
}


public class ProxyDemo {
    public static void main(String[] args){
        Target proxyObject = (Target) ProxyFactory.getProxyObject(new TargetImpl(), new Aspect());    //Target是接口
        proxyObject.method1();
        proxyObject.method2();
    }
}

连接点(Joint Point),切入点(Point cut),织入(weaving),增强(Advice),切面(Aspect)

连接点:
    一个连接点代表一个目标方法。

切入点:
    一个匹配连接点的断言或者表达式
    execution(* *.*(..))

织入:
    将切面与目标类连接起来以创建通知对象(adviced object)的过程。
    可以在编译时(比如使用 AspectJ 编译器)、加载时或者运行时完成。

    对方法的增强叫做 Weaving(织入),而对类的增强叫做 Introduction(引入)

增强:
    织入到目标类连接点上的一段程序代码

切面:
    切面由切点和增强组成

基于代理的AOP实现

切面类:

public class BeforeAdvice implements MethodBeforeAdvice {
    @Override
    public void before(Method arg0, Object[] arg1, Object arg2) throws Throwable {
        System.out.println("前置通知");
        /**
         * arg0 切点的方法
         * arg1 切点的方法形参
         * arg3 切点所在的对象
        */
        ...
    }
}

public class AroundAdvice implements MethodInterceptor{
    @Override
    public Object invoke(MethodInvocation arg0) throws Throwable {
        System.out.println("环绕通知前");
        Object proceed = arg0.proceed();
        System.out.println("环绕通知后");
        return proceed;
    }
}


配置:

    <!-- 注册代理目标类 -->
    <bean id="service" class="service.ServiceImpl"></bean>

    <!-- 注册前置通知 -->
    <bean id="beforeAdvice" class="advice.BeforeAdvice"></bean>

    <!-- 注册后置通知 -->
    <bean id="aroundAdvice" class="advice.AroundAdvice"></bean>

    <!-- 注册代理工厂Bean -->
    <bean id="proxyFactoryBean" class="org.springframework.aop.framework.ProxyFactoryBean">
        <!-- 配置目标类的接口 -->
        <property name="interfaces">
            <list>
                <value>service.Service</value>
            </list>
        </property>
        <!-- 配置目标类-->
        <property name="targetName" value="service"></property>
        
        <!-- 配置拦截时执行的通知 -->
        <property name="interceptorNames">
            <array>
                <value>beforeAdvice</value>
                <value>aroundAdvice</value>
            </array>
        </property>

        <!-- 指定使用的代理,proxyTargetClass是问是否代理类,JDK动态代理是代理接口,CGLIB代理是代理类。false即不是代理类,即使用JDK动态代理 -->
        <!-- 默认就是false,JDK动态代理。此句配置可缺省 -->
        <property name="proxyTargetClass" value="false" />
        <!-- 返回的代理类实例是否是单实例,默认为true,单实例。此句配置可缺省 -->
        <property name="singleton" value="true" />
    </bean>

Spring + AspectJ 基于XML的开发

切面类:

public class Aspect {

    public void before() {
        System.out.println("前置通知");
    }

    public void afterReturn(Object returnVal) {
        System.out.println("后置通知-->返回值:" + returnVal);
    }

    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("环绕通知前");
        Object object = joinPoint.proceed();
        System.out.println("环绕通知后");
        return object;
    }

    public void afterThrowing(Throwable throwable) {
        System.out.println("异常通知:" + throwable.getMessage());
    }

    public void after() {
        System.out.println("最终通知");
    }
}


配置文件:

<!-- 定义目标对象 -->
<bean name="productDao" class="dao.ProductDaoImpl"/>

<!-- 定义切面 -->
<bean name="aspect" class="aspect.Aspect"/>

<!-- 配置AOP 切面 -->
<aop:config>
    <!-- 定义切点函数 -->
    <aop:pointcut id="addPointcut" expression="execution(* dao.ProductDao.add(..))"/>
    <!-- 定义其他切点函数 -->
    <aop:pointcut id="delPointcut" expression="execution(* dao.ProductDao.delete(..))"/>

    <!-- 定义通知:order定义优先级,值越小优先级越大 -->
    <aop:aspect ref="aspect" order="0">
        <!-- 定义通知
        method 指定通知方法名,必须与Aspect中的相同
        pointcut 指定切点函数 -->
        <aop:before method="before" pointcut-ref="addPointcut"/>

        <!-- 后置通知:returning="returnVal" 定义返回值,必须与类中声明的名称一样-->
        <aop:after-returning method="afterReturn" pointcut-ref="addPointcut" returning="returnVal"/>

        <!-- 环绕通知 -->
        <aop:around method="around" pointcut-ref="delPointcut"/>

        <!--异常通知:throwing="throwable"指定异常通知错误信息变量,必须与类中声明的名称一样-->
        <aop:after-throwing method="afterThrowing" pointcut-ref="delPointcut" throwing="throwable"/>

        <!-- method : 通知的方法(最终通知)
            pointcut-ref : 通知应用到的切点方法 -->
        <aop:after method="after" pointcut-ref="delPointcut"/>
    </aop:aspect>
</aop:config>

Spring + AspectJ 全注解配置

<aop:aspectj-autoproxy proxy-target-class="true"/>:
    启动@AspectJ支持。
    proxy-target-class="true" 属性,它的默认值是 false:默认只能代理接口(使用 JDK 动态代理),当为 true 时,才能代理目标类(使用 CGLib 动态代理)。

<!-- 定义目标对象 -->
<bean id="targer" class="Targer" />
<!-- 定义aspect类 -->
<!-- <bean name="aspect" class="Aspect"/> -->


@Pointcut("@within(com.spring.annotation.MarkerAnnotation)")    //匹配使用了MarkerAnnotation注解的类(注意是类)
@Pointcut("within(dao.*)")    //匹配dao包所有类中的所有方法


/**
 * @Component: 把该放入Spring IOC容器中
 * @Aspect: 声明该类为一个切面(需要先放入Spring IOC容器中)
 */
@Component
@Aspect
public class Aspect {

    @Pointcut("execution(* service.Targer.*(..))")
    private void pc(){}

    /**
     * 前置通知: 在目标方法执行之前执行前置通知
     */
    @Before("pc()")
    public void beforeAdvice(JoinPoint joinPoint){
        //获取方法名
        String methodName = joinPoint.getSignature().getName();
        //通过JoinPoint 获取切点方法的参数列表集合
        Object[] args = joinPoint.getArgs();
        //获取切点所在的目标对象
        Object target = joinPoint.getTarget();
        //获取切点的方法的对象
        Signature signature = joinPoint.getSignature();
        MethodSignature ms = (MethodSignature)signature;
        Method method = ms.getMethod();
    }

    /**
     * 后置通知: 在目标方法执行之后执行后置通知(无论是否出现异常)。
     */
    @After("pc()")
    public void afterAdvice(JoinPoint joinPoint){
        //获取方法名
        String methodName = joinPoint.getSignature().getName();
    }

    /**
     * 返回通知: 连接点正常结束之后执行(未抛出异常)
     */
    @AfterReturning(value = "execution(* service.Targer.*(..))", returning = "result")
    public void returnAdvice(JoinPoint joinPoint, Object result){
        //获取方法名
        String methodName = joinPoint.getSignature().getName();
    }

    /**
     * 异常通知: 在目标方法抛出异常后执行
     */
    @AfterThrowing(value = "pc()", throwing= "ex")
    public void returnAdvice(JoinPoint joinPoint, Exception ex){
        //获取方法名
        String methodName = joinPoint.getSignature().getName();
    }

    /**
     * 环绕通知
     */
    @Around("pc()")
    public Object around(ProceedingJoinPoint pjp){
        Object[] args = pjp.getArgs();
        try {
            System.out.println("前置通知");
            Object proceed = pjp.proceed(args);
            System.out.println("后置通知");
            return proceed;
        }catch (Throwable e){
            System.out.println("异常通知");
            throw new RuntimeException(e);
        }finally {
            System.out.println("最终通知");
        }
    }
}

使用场景

  1. Authentication 权限
  2. Caching 缓存
  3. Context passing 内容传递
  4. Error handling 错误处理
  5. Lazy loading 懒加载
  6. Debuggin 调试
  7. logging,tracing,profiling,monitoring 记录跟踪 优化 校准
  8. Performance optimization 性能优化
  9. Persistence 持久化
  10. Resource pooling 资源池
  11. Synchronization 同步
  12. Transactions 事务
posted @ 2019-09-21 21:03  LittleDonkey  阅读(245)  评论(0编辑  收藏  举报