资讯专栏INFORMATION COLUMN

从动态代理到SpringAop以及AspectJ风格

msup / 767人阅读

摘要:具体的动态代理运行原理这里暂不展开,网上有很多相关的内容,比如这篇翻译过来就是面向方面切面编程。所以切面可以理解为和的集合。

1.静态代理

在提及动态代理前先说明一下静态代理模式,静态代理模式是一种很常见的通用设计模式,实现也很简单,uml类图如下:

如上图所示,代理类ProxyImpl和委托类都实现了同一个接口ObjectInterface,代理类和委托类是关联关系。
举个栗子,现在有一个发送短信消息的类SmsMessagePush,实现了MessagePush

public interface MessagePush {
    public void push(String receiver, String msg);
}
public class SmsMessagePush implements MessagePush {
    @Override
    public void push(String receiver, String msg) {
        //do push
    }
}

一般情况下,用户直接调用SmsMessage.push()即可,为什么要用代理呢?一般有两种情况
1:用户无法直接访问目标对象(委托对象)或者是不想让用户直接访问目标对象,这时候proxy对象就承担了一种类似跳板机或者防火墙的角色,代替用户访问目标对象或者对访问者的权限进行筛选。
2:对委托对象的功能增强,比如上面的例子,在发送短信前添加对手机号码进行校验之类的功能。

public class SmsMessagePushProxy implements MessagePush {

    private SmsMessagePush smsMessagePush;

    public SmsMessagePushProxy(SmsMessagePush smsMessagePush) {
        this.smsMessagePush = smsMessagePush;
    }
    @Override
    public void push(String mobile, String msg) {
        if (!checkMobile(mobile)){
            return;
        }
        smsMessagePush.push(mobile, msg);
    }

    private Boolean checkMobile(String mobile) {
        //do check
    }
}
public class App {
    public static void main() {
        SmsMessagePushProxy smsMessagePushProxy = new SmsMessagePushProxy(new SmsMessagePush());
        smsMessagePushProxy.push("10086", "老子明天不上班");
    }
}

上面的代理SmsMessagePushProxy在调用push方法前会对手机号码进行过滤。代理类作为中介提供了对委托资源的访问,但是代理和委托对象本质上是一样的,都实现了同一个接口,所以当接口变化时代理类就需要对应的修改。而且没增加一个委托类就需要增加一个对应的代理类,管理起来十分不方便且难以维护。为了解决这种情况,便引入了jdk动态代理。

2.动态代理

静态代理是在编码阶段就写好的,而动态代理是在程序运行时通过类反射动态创建的。java的动态代理分为jdk动态代理和cglib动态代理,二者使用上最大的区别是jdk动态代理是面向接口的,cglib动态代理是面向对象的。即jdk proxy的委托对象必须实现了某个接口。这里暂不讨论cglib的实现,只讲下jdk。

动态代理主要涉及的类放在java.lang.reflect包下面(由此可见是基于反射实现的),主要涉及两个类:调用处理器java.lang.reflect.InvocationHandle和主类java.lang.reflect.Proxy
其中InvocationHandler是一个接口,只定义了一个方法

 public Object invoke(Object proxy, Method method, Object[] args)

从参数名就可以猜测,invoke方法是用来执行委托对象的具体方法的
Proxy类有很多静态方法,最常用的一个方法是newProxyInstance,该方法会根据传入的参数创建一个代理对象

 @CallerSensitive
    public static Object newProxyInstance(ClassLoader loader,
                                          Class[] interfaces,
                                          InvocationHandler h)

该方法接受三个参数,第一个参数指定要使用的类加载器,第二个参数是委托对象实现的一组接口。第三个参数指定使用哪个调用处理器。
第二个参数我开始还纠结为什么是个接口数组,难道一个代理实例可以代理多个实现了不同接口的委托对象?在invoke方法中也区分不了当前要执行的是哪个对象啊,而且这样两个接口有相同名称的方法会产生冲突啊。。。后来发现大多数情况下是因为委托对象实现了多个接口。。。
下面举个栗子:
假设有一个类PushServiceImpl实现了SmsPushInterface和MailPushInterface两个接口

public class PushServiceImpl implements SmsPushInterface, MailPushInterface {
    @Override
    public void pushEmail(String address, String msg) {
        System.out.println("push a email to " + address + ",message is " + msg);
    }

    @Override
    public void pushSms(String mobile, String msg) {
        System.out.println("push a sms to " + mobile + ",message is " + msg);
    }
}

public interface SmsPushInterface {
    public void pushSms(String mobile, String msg);
}

public interface MailPushInterface {
    public void pushEmail(String address, String msg);
}

现在想要对PushServiceImpl对象进行代理,定义一个getProxy方法,方法接收一个委托对象作为参数,返回的类型是Object,实际上返回的是一个代理对象,该代理对象实现了SmsPushInterface和MailPushInterface两个接口。代理对象通过Proxy.newProxyInstance()方法创建。

public class ProxyTest {

    public Object getProxy(Object target) throws Exception {
        InvocationHandler handler = new PushHandler(target);

        return Proxy.newProxyInstance(
                ProxyTest.class.getClassLoader(),
                target.getClass().getInterfaces(),
                handler
        );
    }

    @Test
    public void proxyTest() throws Exception {
        SmsPushInterface smsPushService = (SmsPushInterface) getProxy(new PushServiceImpl());
        smsPushService.pushSms("10086", "这一切都是命运石之门的选择");

        MailPushInterface mailPushService = (MailPushInterface) getProxy(new PushServiceImpl());
        mailPushService.pushEmail("31415926@qq.com", "都是时臣的错");
    }
}

由于获取代理类返回的是Object类型,在实际使用时要根据调用的方法转换成对应的接口类型,注意这里不能转成PushServiceImpl即实例对象的类型,因为返回的代理类是Proxy的子类,实现了这两个接口而已。
在调用代理类的方法时,实际上是执行处理器在运行,如下所示,我们编写一个PushHandler实现InvocationHandler接口并覆写invoke方法:

public class PushHandler implements InvocationHandler {
    private Object proxied;

    PushHandler(Object proxied) {
        this.proxied = proxied;
    }
    
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if ("pushSms".equals(method.getName())) {
            String mobile = args[0].toString();
            String msg = args[1].toString();
            if (!checkMobile(mobile)) {
                throw new Exception("mobile invalid");
            }
        }

        if ("pushEmail".equals(method.getName())) {
            String address = args[0].toString();
            String msg = args[1].toString();
            if (!checkMail(address)) {
                throw new Exception("mail address invalid");
            }
        }

        return method.invoke(proxied, args);
    }

    private Boolean checkMobile(String mobile) {
        // check mobile valid
        return true;
    }

    private Boolean checkMail(String mailAddress) {
        // check mail valid
        return true;
    }
}

PushHandler在构造方法中接受一个委托对象实例,最后实际执行的就是这个对象的方法。在执行getProxy返回的代理对象的方法时会调用PushHandler的invoke方法,其中proxy参数就是当前的代理对象(java.lang.reflect.Proxy的子类),method是当前执行方法,args是参数数组。在这个例子里我们根据方法名来做对应的检查之后通过反射方法method.invoke()执行。
具体的动态代理运行原理这里暂不展开,网上有很多相关的内容,比如这篇

http://blog.jobbole.com/104433

3.AOP

aop(Aspect Oriented Programming) 翻译过来就是面向方面/切面编程。关于aop的定义有许多,这里引用一个可能不是特别准确但很容易理解的解释:(出处www.zhihu.com/question/24863332/answer/253016908)

AOP是对OOP的一种补充。

面向对象(OOP)引入了继承、多态、封装,将系统的业务功能按照模块划分,每个模块用一个或多个类来表示。

而对于一些系统功能,无法使用OOP的思想来实现它们。这些系统功能往往穿插在业务功能的各处,和业务代码耦合在一起;而且系统功能往往会被重复使用,这就导致了模块不利于复用,这就是使用OOP实现系统功能的弊端。

AOP即为面向切面编程,它把系统需求按照功能分门归类,把它们封装在一个个切面中,然后再指定这些系统功能往业务功能中织入的规则。最后由第三方机构根据你指定的织入规则,将系统功能整合到业务功能中。

aop是一种思想而不是一种技术。所以说,如果抛开spring,我上面写的动态代理甚至静态代理的例子也可以算是一种aop。
spring中的aop实现分为两种,基于动态代理的aop和基于AspectJ的aop,这里不得不吐槽国内的各种文章,根本没搞清二者的区别,或者打着spring aop的标题然后开始讲aspectJ的使用,你抄我我抄他,越抄越混乱。

什么是AspectJ?

在网上一搜一大片所谓AspectJ的用法,其实都是AspectJ的“切面语法”,只是AspectJ框架的冰山一角,AspectJ是完全独立于Spring存在的一个Eclipse发起的项目,官方关于AspectJ的描述是:

Eclipse AspectJ is a seamless aspect-oriented extension to the Java™ programming language. It is Java platform compatible easy to learn and use.

是的AspectJ甚至可以说是一门独立的语言,我们常看到的在spring中用的@Aspect注解只不过是Spring2.0以后使用了AspectJ的风格而已本质上还是Spring的原生实现,关于这点Spring的手册中有提及:

@AspectJ使用了Java 5的注解,可以将切面声明为普通的Java类。@AspectJ样式在AspectJ 5发布的AspectJ project部分中被引入。Spring 2.0使用了和AspectJ 5一样的注解,并使用AspectJ来做切入点解析和匹配。但是,AOP在运行时仍旧是纯的Spring AOP,并不依赖于AspectJ的编译器或者织入器(weaver)。

so 我们常用的org.aspectj.lang.annotation包下的AspectJ相关注解只是使用了AspectJ的样式,至于全套的AspectJ以及织入器,那完全是另一套独立的东西。

名词解释

在看aop实现前,先解释几个基本名词,对就是网上一搜一大片那些

advice
advice,常被翻译成”增强“或者”通知“,实际上advice就是在切面中执行的额外操作,拿上面动态代理的例子来说在PushHandler::invoke()方法中,对手机号码以及邮箱地址的检查就是两个advice。在很多aop框架中advice是以拦截器的形式存在的,advice又常分为前置型advice和后置型advice,像手机号码检查这种就属于前置的advice,返回结果记录日志就属于后置advice

join point
连接点很容易理解,先看下spring手册上的定义

a· point during the execution of a program, such as the execution of a method or the handling of an exception. In Spring AOP, a join point always represents a method execution.

join point 就是程序运行时的一个特征点,比如方法的执行或者异常的抛出。可以把join point理解为一个触发条件。

point cut
point cut大部分情况下被翻译为’切入点‘。很多人经常搞不清楚join point 和 point cut的区别,实际上二者完全不是一个维度的概念,如果说join point是名词 point cut就是谓词。pointcut是一个规则,指定了哪些切入点会被切入。
比如:在test.network.message包下所有类的push()方法执行前,对入参做校验 其中push()就是一个join point , 在xx前,对入参进行验证是一个advice,而”在test.network.message包下所有类的push()方法“就是一个point cut。
做个比喻的话,一个插排,每个插孔都是一个join point,而电视插在哪,电脑插在哪,整个一个布线规则就是一个point cut
aspect
在test.network.message包下所有类的push()方法执行前,对入参做校验 整个这个行为就是一个切面了。所以切面可以理解为point cut和advice的集合。在使用AspectJ样式时,被@Aspect注解标注的类就是一个切面。

为防止翻译不统一造成的误解,下面对名词的使用直接使用英文原文

Spring AOP

spring aop常被人诟病复杂,难用。但又有多少新司机真正的用过而不是上来就被@Aspectj那一套洗脑了,问你为什么spring aop难用,AspectJ的优势在哪又有多少人能答上来。
spring aop相关的内容基本都在org.springframework.aop.framework包下,spring aop是通过代理工厂实现的,主要涉及的类图如下:

两个常用的aop工厂类ProxyFactoryBean和ProxyFactoryBean继承自ProxyCreatorSupport,proxyCreator是代理类的基类,并拥有一个实现了AopProxyFactory接口的属性DefaultAopProxyFactory,ProxyFactory中的getProxy方法实际上最后是调用的是AopProxy接口中的getProxy方法,而实现了AopProxy接口的对象(JdkDynamicAopProxy或CglibAopProxy)是由DefaultAopProxyFactory创建的。这么说可能会有点绕,下面用一张时序图来解释(图是我偷的,懒得画了,出处:https://www.jianshu.com/p/500...)

DefaultAopProxyFactory在创建aop代理时会判断委托类是否实现了某个接口,是的话创建JdkDynamicAopProxy,否则的话创建ObjenesisCglibAopProxy,代码如下:

public class DefaultAopProxyFactory implements AopProxyFactory, Serializable {

    @Override
    public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
        if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
            Class targetClass = config.getTargetClass();
            if (targetClass == null) {
                throw new AopConfigException("TargetSource cannot determine target class: " +
                        "Either an interface or a target is required for proxy creation.");
            }
            if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
                return new JdkDynamicAopProxy(config);
            }
            return new ObjenesisCglibAopProxy(config);
        }
        else {
            return new JdkDynamicAopProxy(config);
        }
    }

下面看一个使用proxyFactory实现aop的例子

public class SpringAopTest {
    @Test
    public void proxy() {
        PushServiceImpl pushService = new PushServiceImpl();
        //创建工厂
        ProxyFactory proxyFactory = new ProxyFactory(pushService);
        //添加advice
        proxyFactory.addAdvice(new SmsPushBeforeAdvice());
        proxyFactory.addAdvice(new SmsPushAfterAdvice());
        //获取代理
        SmsPushInterface proxy = (SmsPushInterface) proxyFactory.getProxy();
        proxy.pushSms("10086", "EL PSY CONGROO");
    }
}
public class SmsPushBeforeAdvice implements MethodBeforeAdvice {

    @Override
    public void before(Method method, Object[] args, Object target) throws Throwable {
        if ("pushSms".equals(method.getName())) {
            String mobile = args[0].toString();
            String msg = args[1].toString();
            if (!checkMobile(mobile)) {
                throw new Exception("mobile invalid");
            }
        }


    }

    private Boolean checkMobile(String mobile) {
        //do mobile check
        System.out.println("check mobile valid");
        return true;
    }

}
public class SmsPushAfterAdvice implements AfterReturningAdvice {
    @Override
    public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
        StringBuffer logData = new StringBuffer();
        logData.append("get method return : method:" + method.getName());
        logData.append("  message : "" + args[1] + ""was sent");
        if (returnValue == null) {
            logData.append("  and return value is void");
        }

        addLog(logData.toString());
    }

    private void addLog(String logMsg) {
        //do log
        System.out.println("get log info: " + logMsg);
    }
}

下面是测试运行时的输出:

check mobile valid
push a sms to 10086,message is EL PSY CONGROO
get log info: get method return : method:pushSms  message : "EL PSY CONGROO"was sent  and return value is void

可以看到,使用proxyFactory实现aop和上面的动态代理的例子十分相似 ,spring aop中的advice就等同于我们在动态代理中InvocationHandler中的增强操作。
常用的advice有四种:
前置advice:在方法执行前触发的advice,实现了 org.springframework.aop.MethodBeforeAdvice接口
后置advice: 在方法返回时触发的advice,实现了org.springframework.aop.AfterReturningAdvice接口
异常advice:在抛出异常后触发,实现了org.springframework.aop.ThrowsAdviceArround
环绕advice:自定义触发时机,实现和InvokeHandler类似,实现了org.aopaliance.intercept.MethodInterceptor

advisor:
上面的例子,添加的advice在实际运行时会包装为Advisor对象,advisor包含了advice和pointcut,可以理解为一个切面(aspect),下面是AdvisedSupport类的addAdvice的方法实现,可以看到在执行addAdvice方法时会封装为DefaultPointcutAdvisor实例

public void addAdvice(int pos, Advice advice) throws AopConfigException {
        Assert.notNull(advice, "Advice must not be null");
        if (advice instanceof IntroductionInfo) {
            // We don"t need an IntroductionAdvisor for this kind of introduction:
            // It"s fully self-describing.
            addAdvisor(pos, new DefaultIntroductionAdvisor(advice, (IntroductionInfo) advice));
        }
        else if (advice instanceof DynamicIntroductionAdvice) {
            // We need an IntroductionAdvisor for this kind of introduction.
            throw new AopConfigException("DynamicIntroductionAdvice may only be added as part of IntroductionAdvisor");
        }
        else {
            addAdvisor(pos, new DefaultPointcutAdvisor(advice));
        }
    }

在DefaultPointcutAdvisor中,pointCut被默认设置为Poincut.TRUE,此时会匹配被代理对象的所有方法

public class DefaultPointcutAdvisor extends AbstractGenericPointcutAdvisor implements Serializable {

    private Pointcut pointcut = Pointcut.TRUE;

除了直接设置advice以外,我们还可以设置advisor,并指定对应的pointcut,这样就可以指定哪些方法会被切入,pointcut和advisor有许多包装类型,都在org.springframework.aop.support包下,这里使用最常用的正则pointcut:JdkRegexpMethodPointcut举个栗子

 public void pointTest() {
        SmsPushInterface pushService = new PushServiceImpl();
        //创建工厂
        ProxyFactory proxyFactory = new ProxyFactory(pushService);
        //创建advisor并添加advice
        DefaultPointcutAdvisor beforeAdvisor = new DefaultPointcutAdvisor(new SmsPushBeforeAdvice());
        DefaultPointcutAdvisor afterAdvisor = new DefaultPointcutAdvisor(new SmsPushAfterAdvice());
        //创建正则方法pointcut
        JdkRegexpMethodPointcut pointcut = new JdkRegexpMethodPointcut();
        //设置正则规则
        pointcut.setPattern(PushServiceImpl.class.getName() + ".push.*");
        beforeAdvisor.setPointcut(pointcut);
        afterAdvisor.setPointcut(pointcut);
        //设置advisor
        proxyFactory.addAdvisor(beforeAdvisor);
        proxyFactory.addAdvisor(afterAdvisor);
        //获取代理
        SmsPushInterface proxy = (SmsPushInterface) proxyFactory.getProxy();
        proxy.pushSms("10086", "EL PSY CONGROO");
    }

可以看到这次我们没有直接使用addAdvice而是创建了两个advisor,并创建了一个pointcut,设置正则pattern匹配所有以push开头的方法。
这里有一个坑,我开始在设置pattern时是直接写成 setPattern("push.*")的,发现没有匹配,后来发现AbstractRegexpMethodPointcut的matches方法里是这样写的

public boolean matches(Method method, Class targetClass) {
        return ((targetClass != null && matchesPattern(ClassUtils.getQualifiedMethodName(method, targetClass))) ||
                matchesPattern(ClassUtils.getQualifiedMethodName(method)));
    }

ClassUtils.getQualifiedMethodName方法会将method名称拼上类名,即要匹配的方法名实际上是 spring.aop.PushServiceImpl.pushSms

在使用时我们会发现这样手动设置pointcut和advisor十分麻烦,所以spring aop提供了一些包装好的advisor,比如上面的这个正则的例子我们就可以直接使用org.springframework.aop.supportRegexpMethodPointcutAdvisor,还有一些更方便的包装advisor比如下面这个例子直接使用了NameMatchMethodPointcutAdvisor设置匹配的方法名

 @Test
    public void proxy() {
        PushServiceImpl pushService = new PushServiceImpl();
        //创建工厂
        ProxyFactory proxyFactory = new ProxyFactory(pushService);
        //创建方法名称匹配advisor并添加advice
        NameMatchMethodPointcutAdvisor beforeAdvisor = new NameMatchMethodPointcutAdvisor(new SmsPushBeforeAdvice());
        NameMatchMethodPointcutAdvisor afterAdvisor = new NameMatchMethodPointcutAdvisor(new SmsPushAfterAdvice());
        //设置匹配的方法名
        beforeAdvisor.setMappedName("pushSms");
        afterAdvisor.setMappedName("pushSms");
        //设置advisor
        proxyFactory.addAdvisor(beforeAdvisor);
        proxyFactory.addAdvisor(afterAdvisor);
        //获取代理
        SmsPushInterface proxy = (SmsPushInterface) proxyFactory.getProxy();
        proxy.pushSms("10086", "EL PSY CONGROO");
    }

以上就是SpringAop的基本使用方法,通过上面的例子可以看出springAop的确存在一些问题,最明显的就是切面不够独立,对业务代码的侵入性很强,声明Aspect需要以过程的形式显示声明(虽然ProxyFactoryBean可以将切面部分封装为bean,但是我看到xml是在是想吐)。而且advice和pointcut的结合灵活性较差,实际使用时还需要自己写一些轮子。spring也认识到了这些问题并在spring2.0之后推出了AspectJ样式的Aop

AspectJ样式AOP

再次强调一下这里讲的AspectJ样式的aop只是使用了AspectJ的一些语法特性,底层依旧是SpringAop实现的
首先 使用aspectJ样式的aop需要一些额外配置

springboot
如果你使用的是springboot,可以通过在主类使用@EnableAspectJAutoProxy注解来开启,另外如果你使用了@EnableAutoConfiguration会默认开启。如果想关闭aop可以配置设置spring.aop.auto = falsespring.aop.proxy-target-class可以指定使用jdk代理还是cglib代理,默认是jdk(false:jdk,true:cglib)

spring
普通的spring框架可以通过设置来开启,设置为true使用cglib

前面springAop已经将aop的概念说的很清楚这里就不扯淡了直接上个例子:
@Aspect注解将当前类标记为一个切面,@Component将PushAspect注册为Bean
@Pointcut注解将一个void方法标记为一个pointcut,execution、within、args等被称为pointcut designators(切点标志符)简称为PCD,每个PCD都有对应的表达式,比如最常用的execution,下面的例子第一个表示修饰符,即public还是private之类。后面紧接着是包名+类名+方法名,spring.aop.PushServiceImpl.* 表示匹配 spring.aop包下PushServiceImpl类的所有方法。最后是参数标识(String,Stirng)表示参数是两个String类型的方法,若要匹配所有参数类型,可以使用(..)表示

@Aspect
@Component
public class PushAspect {
    @Pointcut(value = "execution(* spring.aop.PushServiceImpl.pushSms(String,String))")
    public void pushSmsPointcut() {
    }

    @Pointcut("execution(* spring.aop.PushServiceImpl.*(..))")
    public void pushMethodPointcut() {
    }

    @Before("pushSmsPointcut() && args(mobile,msg)")
    public void checkMobile(String mobile, String msg) throws Exception {
        if (!checkMobile(mobile)) {
            throw new Exception("mobile invalid");
        }
    }

    private Boolean checkMobile(String mobile) {
        //do mobile check
        System.out.println("check mobile valid");
        return true;
    }

    @AfterReturning(pointcut = "pushMethodPointcut()", returning = "returnValue")
    public void writeLog(JoinPoint joinPoint, Object returnValue) {
        StringBuffer logData = new StringBuffer();
        logData.append("get method return : method:" + joinPoint.getSignature().getName());
        logData.append("  message : "" + joinPoint.getArgs()[1].toString() + ""was sent");
        if (returnValue == null) {
            logData.append("  and return value is void");
        }

        addLog(logData.toString());
    }

    private void addLog(String logMsg) {
        //do log
        System.out.println("get log info: " + logMsg);
    }
}

@Before、@AfterReturn、@Around等注解标识了一个advice方法,advice和pointcut相关联,像上面这个例子前置advice checkMobile()就是作用于被pushSmsPointcut标识的方法上,pointcut可以显示绑定在value或pointcut参数上,或者不写参数默认第一表达式就是pointcut。也可以不写pointcut,在advice表达式内直接写PCD,但是不建议这样做

关于怎么向advice body里传递参数,先看下手册里的描述:

To make argument values available to the advice body, you can use the binding form of args. If a parameter name is used in place of a type name in an args expression, then the value of the corresponding argument will be passed as the parameter value when the advice is invoked

所以想要传递参数在advcie中使用args表达式即可,注意这里要在args里写参数名,而不是写参数类型。写参数类型的话是另一种用法,一般写在PCD中,作为joinpoint匹配时参数类型的筛选。另外传递的参数中还有一个特别的参数类型JoinPoint. jointpoint包含了很多实用的反射方法,必须获取当前的代理类,获取参数列表等等,joinpoint可以作为第一个参数传入advice且不用再参数列表里指定。这里注意使用around advice时传入的是ProceedingJoinPoint类型的jointpoint

execution表达式中的参数和args
在designator表达式中,有两种方式可以限定方法的参数,一个是通过execution表达式的最后一个参数,另一个是通过args标识符,二者有什么区别?

args(java.lang.String)

execution(* *(java.lang.String))

区别是args表达式标识的是在运行时传入的参数是String类型,而execution表达式表示的是方法签名在定义的时候是String。
Aspectj风格还有很多designators(with,@annotation等),以及advice类型,这里不再赘述,可以参考spring的手册。虽然是英文的但写的很详细

手册:https://docs.spring.io/spring...

下面是委托对象的执行(实际上不应该再叫他委托对象了,因为这里已经将aop剥离出来了,用户感知不到代理的过程)。可以看到和IOC结合起来十分方便,切面和业务代码没有任何耦合。这里如果把注入的SmsPushInterface换成SmsBean named "pushServiceImpl" is expected to be of type "spring.aop.PushServiceImpl" but was actually of type "com.sun.proxy.$Proxy61".可以看出底层的实现还是使用的动态代理,如果这里想声明Impl类型可以把代理改成cglib.

@RunWith(SpringRunner.class)
@SpringBootTest(classes = Application.class)
public class SpringAopTest {
    @Resource
    SmsPushInterface pushService;

    @Test
    public void aspectjTest() throws Exception {
        pushService.pushSms("10086", "EL PSY CONGROO");
    }

上面的例子都是在IOC环境中自动加载的,如果脱离这个环境想在过程中执行怎么办?spring提供了一个org.springframework.aop.aspectj.annotation.AspectJProxyFactory 代理工厂,和ProxyFactory一样继承ProxyCreatorSupport,通过该工厂可以像proxyFactory一样直接显示进行代理

 @Test
    public void testAspectJProxyFactory() {
        PushServiceImpl pushService = new PushServiceImpl();
        AspectJProxyFactory proxyFactory = new AspectJProxyFactory(pushService);
        proxyFactory.addAspect(PushAspect.class);
        SmsPushInterface proxy = (SmsPushInterface) proxyFactory.getProxy();
        proxy.pushSms("10086", "EL PSY CONGROO");
    }

从下面的源码可以看出,这里addAspect实际上还是把@Aspect解析成Advisor来处理

public void addAspect(Class aspectClass) {
    String aspectName = aspectClass.getName();
    AspectMetadata am = createAspectMetadata(aspectClass, aspectName);
    MetadataAwareAspectInstanceFactory instanceFactory = createAspectInstanceFactory(am, aspectClass, aspectName);
    addAdvisorsFromAspectInstanceFactory(instanceFactory);
}

由此可见,AspectJ样式的方便不只是体现在提供了一些方便的注解以及PCD,更体现在和Spring IOC的完美结合
关于@Aspect的解析原理,是在没时间写了,以后有时间再补吧。
写的比较匆忙如有问题欢迎指正

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/69735.html

相关文章

  • SpringAOP面向切面详解(带实例)

    摘要:了解的相关术语通知通知定义了切面是什么,以及何时使用。描述了切面要完成的工作和何时需要执行这个工作。就是用来配置切面设置代理模式。 了解AOP的相关术语 1.通知(Advice): 通知定义了切面是什么,以及何时使用。描述了切面要完成的工作和何时需要执行这个工作。 2.连接点(Joinpoint): 程序能够应用通知的一个时机,这些时机就是连接点,例如方法被调用时、异常被抛出时等等。 ...

    马忠志 评论0 收藏0
  • Spring AOP就是这么简单啦

    摘要:是一种特殊的增强切面切面由切点和增强通知组成,它既包括了横切逻辑的定义也包括了连接点的定义。实际上,一个的实现被拆分到多个类中在中声明切面我们知道注解很方便,但是,要想使用注解的方式使用就必须要有源码因为我们要 前言 只有光头才能变强 上一篇已经讲解了Spring IOC知识点一网打尽!,这篇主要是讲解Spring的AOP模块~ 之前我已经写过一篇关于AOP的文章了,那篇把比较重要的知...

    Jacendfeng 评论0 收藏0
  • 慕课网_《Spring入门篇》学习总结

    摘要:入门篇学习总结时间年月日星期三说明本文部分内容均来自慕课网。主要的功能是日志记录,性能统计,安全控制,事务处理,异常处理等等。 《Spring入门篇》学习总结 时间:2017年1月18日星期三说明:本文部分内容均来自慕课网。@慕课网:http://www.imooc.com教学示例源码:https://github.com/zccodere/s...个人学习源码:https://git...

    Ververica 评论0 收藏0
  • Learn Spring - Spring AOP

    摘要:下例表示方法入参为的方法匹配该切点,并将和两个参数绑定到切面方法的入参中绑定代理对象使用或可以绑定被代理对象的实例。 1. 术语 连接点(JointPoint):代码中具有边界性质特定点;Spring仅支持方法的连接点,包含方法和方位两方面信息 切点(Pointcut):定位到某个方法 增强(Advice):织入到目标连接点上的代码 目标对象(Target):增强逻辑的目标织入类 引...

    kgbook 评论0 收藏0
  • Spring AOP零单排-织入时期源码分析

    摘要:何为简单点来定义就是切面,是一种编程范式。定义一个切面的载体定义一个切点定义一个为,并指定对应的切点一个注册配置类,启动容器,初始化时期获取对象,获取对象时期,并进行打印好了,这样我们整体的代理就已经完成。 问题:Spring AOP代理中的运行时期,是在初始化时期织入还是获取对象时期织入? 织入就是代理的过程,指目标对象进行封装转换成代理,实现了代理,就可以运用各种代理的场景模式。 ...

    honmaple 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<