Spring使用指南 ~ 5、Spring AOP 使用简介

Spring AOP 使用简介

一、通知(Advice)

公共使用类 Agent.java

package com.luo.spring.guides.aop.simple.domain;

public class Agent {
    public void speak() {
        System.out.println("Bond");
    }
}

1、前置通知

package com.luo.spring.guides.aop.simple.beforeadvice;

import org.springframework.aop.MethodBeforeAdvice;

import java.lang.reflect.Method;

public class SimpleBeforeAdvice implements MethodBeforeAdvice {
    @Override
    public void before(Method method, Object[] args, Object target)
            throws Throwable {
        System.out.println("Before method: " + method);
    }
}

测试

package com.luo.spring.guides.aop.simple.beforeadvice;

import com.luo.spring.guides.aop.simple.domain.Agent;
import org.springframework.aop.framework.ProxyFactory;

/**
 * @author : archer
 * @date : Created in 2022/12/12 14:55
 * @description :
 */
public class Main {

    public static void main(String... args) {
        Agent target = new Agent();

        ProxyFactory pf = new ProxyFactory();
        pf.addAdvice(new SimpleBeforeAdvice());
        pf.setTarget(target);

        Agent proxy = (Agent) pf.getProxy();

        System.out.println("");
        proxy.speak();
    }
}

输出

Before method: public void com.luo.spring.guides.aop.simple.domain.Agent.speak()
Bond

2、后置返回通知

package com.luo.spring.guides.aop.simple.afterreturningadvice;

import org.springframework.aop.AfterReturningAdvice;

import java.lang.reflect.Method;

public class SimpleAfterReturningAdvice implements AfterReturningAdvice {

    @Override
    public void afterReturning(Object returnValue, Method method, 
             Object[] args, Object target) throws Throwable {
        System.out.println("After return:" + method.getName());
    }
}

测试

package com.luo.spring.guides.aop.simple.afterreturningadvice;

import com.luo.spring.guides.aop.simple.domain.Agent;
import org.springframework.aop.framework.ProxyFactory;

/**
 * @author : archer
 * @date : Created in 2022/12/12 14:55
 * @description :
 */
public class Main {

    public static void main(String... args) {
        Agent target = new Agent();

        ProxyFactory pf = new ProxyFactory();
        pf.addAdvice(new SimpleAfterReturningAdvice());
        pf.setTarget(target);

        Agent proxy = (Agent) pf.getProxy();
        proxy.speak();
    }
}

输出

Bond
After return:speak

3、环绕通知

package com.luo.spring.guides.aop.simple.aroundadvice;

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;

public class SimpleAroundAdvice implements MethodInterceptor {
    public Object invoke(MethodInvocation invocation) throws Throwable {
        System.out.println("before");

        Object proceed = invocation.proceed();

        System.out.println("after");

        return proceed;
    }
}

测试

package com.luo.spring.guides.aop.simple.aroundadvice;

import com.luo.spring.guides.aop.simple.domain.Agent;
import org.springframework.aop.framework.ProxyFactory;

/**
 * @author : archer
 * @date : Created in 2022/12/12 14:55
 * @description :
 */
public class Main {

    public static void main(String... args) {
        Agent target = new Agent();

        ProxyFactory pf = new ProxyFactory();
        pf.addAdvice(new SimpleAroundAdvice());
        pf.setTarget(target);

        Agent proxy = (Agent) pf.getProxy();

        proxy.speak();
    }
}

输出

before
Bond
after

4、异常通知

  • 如果异常类型相同,Spring 优先使用参数多个那个
package com.luo.spring.guides.aop.simple.domain;

public class ErrorBean {
    public void errorMethod() throws Exception {
        throw new Exception("Generic Exception");
    }

    public void otherErrorMethod() throws IllegalArgumentException {
        throw new IllegalArgumentException("IllegalArgument Exception");
    }
}
package com.luo.spring.guides.aop.simple.throwsadvice;

import org.springframework.aop.ThrowsAdvice;

import java.lang.reflect.Method;

public class SimpleThrowsAdvice implements ThrowsAdvice {


    public void afterThrowing(Exception ex) throws Throwable {
        System.out.println("***");
        System.out.println("Generic Exception Capture");
        System.out.println("Caught: " + ex.getClass().getName());
        System.out.println("***\n");
    }

    //如果异常类型相同,Spring 优先使用参数多个那个
    public void afterThrowing(Method method, Object[] args, Object target, IllegalArgumentException ex) throws Throwable {
        System.out.println("***");
        System.out.println("IllegalArgumentException Capture");
        System.out.println("Caught: " + ex.getClass().getName());
        System.out.println("Method: " + method.getName());
        System.out.println("***\n");
    }
}

测试

package com.luo.spring.guides.aop.simple.throwsadvice;

import com.luo.spring.guides.aop.simple.domain.ErrorBean;
import org.springframework.aop.framework.ProxyFactory;

/**
 * @author : archer
 * @date : Created in 2022/12/12 15:23
 * @description :
 */
public class Main {
    public static void main(String... args) throws Exception {
        ErrorBean errorBean = new ErrorBean();

        ProxyFactory pf = new ProxyFactory();
        pf.setTarget(errorBean);
        pf.addAdvice(new SimpleThrowsAdvice());

        ErrorBean proxy = (ErrorBean) pf.getProxy();

        try {
            proxy.errorMethod();
        } catch (Exception ignored) {

        }

        try {
            proxy.otherErrorMethod();
        } catch (Exception ignored) {

        }
    }
}

输出

******
Generic Exception Capture
Caught: java.lang.Exception
******

******
IllegalArgumentException Capture
Caught: java.lang.IllegalArgumentException
Method: otherErrorMethod
******

二、切入点(Pointcut)

切入点:可以看成判断是否触发通知代理类的条件规则

通用接口

package com.luo.spring.guides.aop.pointcut.statics.domain;

/**
 * Created by iuliana.cosmina on 4/2/17.
 */
public interface Singer {
	void sing();
}

1、静态切入点

  • 静态切入点只能把被代理类的的一些静态信息(如类名称,方法名称等),作为判断条件

被代理类

package com.luo.spring.guides.aop.pointcut.statics.domain;


/**
 * Created by iuliana.cosmina on 4/2/17.
 */
public class GoodGuitarist implements Singer {

	@Override public void sing() {
		System.out.println("Who says I can't be free \n" +
				"From all of the things that I used to be");
	}
}
package com.luo.spring.guides.aop.pointcut.statics.domain;


/**
 * Created by iuliana.cosmina on 4/2/17.
 */
public class GreatGuitarist implements Singer {

	@Override public void sing() {
		System.out.println("I shot the sheriff, \n" +
				"But I did not shoot the deputy");
	}
}

通知类

package com.luo.spring.guides.aop.pointcut.statics.advice;

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;

public class SimpleAdvice implements MethodInterceptor {
    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        System.out.println(">> Invoking " + invocation.getMethod().getName());
        Object retVal = invocation.proceed();
        System.out.println(">> Done\n");
        return retVal;
    }
}

切入点

  • 有很多判断条件,具体参考可重写父类方法
package com.luo.spring.guides.aop.pointcut.statics;

import com.luo.spring.guides.aop.pointcut.statics.domain.GoodGuitarist;
import org.springframework.aop.support.StaticMethodMatcherPointcut;

import java.lang.reflect.Method;
//有很多判断条件,具体参考可重写父类方法
public class SimpleStaticPointcut extends StaticMethodMatcherPointcut {
    @Override
    public boolean matches(Method method, Class<?> cls) {
        return ("sing".equals(method.getName())) && cls == GoodGuitarist.class;
    }

//    @Override
//    public ClassFilter getClassFilter() {
//        ClassFilter classFilter = cls -> (cls == GoodGuitarist.class);
//        return classFilter;
//    }
}

测试

package com.luo.spring.guides.aop.pointcut.statics;

import com.luo.spring.guides.aop.pointcut.statics.advice.SimpleAdvice;
import com.luo.spring.guides.aop.pointcut.statics.domain.GoodGuitarist;
import com.luo.spring.guides.aop.pointcut.statics.domain.GreatGuitarist;
import com.luo.spring.guides.aop.pointcut.statics.domain.Singer;
import org.aopalliance.aop.Advice;
import org.springframework.aop.Advisor;
import org.springframework.aop.Pointcut;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.aop.support.DefaultPointcutAdvisor;

public class Main {
    public static void main(String... args) {
        GoodGuitarist goodGuitarist = new GoodGuitarist();
        GreatGuitarist greatGuitarist = new GreatGuitarist();

        Singer proxyOne;
        Singer proxyTwo;

        Pointcut pc = new SimpleStaticPointcut();
        Advice advice = new SimpleAdvice();
        Advisor advisor = new DefaultPointcutAdvisor(pc, advice);

        ProxyFactory pf = new ProxyFactory();
        pf.addAdvisor(advisor);
        pf.setTarget(goodGuitarist);
        proxyOne = (Singer)pf.getProxy();

        pf = new ProxyFactory();
        pf.addAdvisor(advisor);
        pf.setTarget(greatGuitarist);
        proxyTwo = (Singer)pf.getProxy();

        proxyOne.sing();
        proxyTwo.sing();
    }
}

输出

>> Invoking sing
Who says I can’t be free
From all of the things that I used to be
>> Done

I shot the sheriff,
But I did not shoot the deputy

2、动态切入点

  • 动态切入点相对于静态切入点,增加了对参数内容的条件判断
  • 增加了灵活性,降低了性能

被代理类

package com.luo.spring.guides.aop.pointcut.dyanmic;

public class SampleBean {
    public void foo(int x) {
        System.out.println("Invoked foo() with: " + x);
    }

    public void bar() {
        System.out.println("Invoked bar()");
    }
}

通知类

package com.luo.spring.guides.aop.pointcut.advice;

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;

public class SimpleAdvice implements MethodInterceptor {
    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        System.out.println(">> Invoking " + invocation.getMethod().getName());
        Object retVal = invocation.proceed();
        System.out.println(">> Done\n");
        return retVal;
    }
}

切入点

package com.luo.spring.guides.aop.pointcut.dyanmic;

import org.springframework.aop.ClassFilter;
import org.springframework.aop.support.DynamicMethodMatcherPointcut;

import java.lang.reflect.Method;

public class SimpleDynamicPointcut extends DynamicMethodMatcherPointcut {
    @Override
    public boolean matches(Method method, Class<?> cls) {
        System.out.println("Static check for " + method.getName());
        return ("foo".equals(method.getName()));
    }

    @Override
    public boolean matches(Method method, Class<?> cls, Object... args) {
        System.out.println("Dynamic check for " + method.getName());

        int x = ((Integer) args[0]).intValue();

        return (x != 100);
    }

    @Override
    public ClassFilter getClassFilter() {
        return cls -> (cls == SampleBean.class);
    }
}

测试

package com.luo.spring.guides.aop.pointcut.dyanmic;

import com.luo.spring.guides.aop.pointcut.advice.SimpleAdvice;
import org.springframework.aop.Advisor;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.aop.support.DefaultPointcutAdvisor;

public class Main {
    public static void main(String... args) {
        SampleBean target = new SampleBean();

        Advisor advisor = new DefaultPointcutAdvisor(
            new SimpleDynamicPointcut(), new SimpleAdvice());

        ProxyFactory pf = new ProxyFactory();
        pf.setTarget(target);
        pf.addAdvisor(advisor);
        SampleBean proxy = (SampleBean)pf.getProxy();

        proxy.foo(1);
        proxy.foo(10);
        proxy.foo(100);

        proxy.bar();
        proxy.bar();
        proxy.bar();
    }
}

输出

Static check for foo
Static check for bar
Static check for toString
Static check for clone
Static check for foo
Dynamic check for foo
>> Invoking foo
Invoked foo() with: 1
>> Done

Dynamic check for foo
>> Invoking foo
Invoked foo() with: 10
>> Done

Dynamic check for foo
Invoked foo() with: 100
Static check for bar
Invoked bar()
Invoked bar()
Invoked bar()

3、常用的使用技巧

通用类

package com.luo.spring.guides.aop.pointcut.namematching;

/**
 * Created by iuliana.cosmina on 4/2/17.
 */
public class Guitar {
	private String brand =" Martin";

	public String play(){
		return "G C G C Am D7";
	}

	public String getBrand() {
		return brand;
	}

	public void setBrand(String brand) {
		this.brand = brand;
	}
}
1)、使用简单名称匹配

被代理类及其相关类

package com.luo.spring.guides.aop.pointcut.namematching;

import com.luo.spring.guides.aop.pointcut.Singer;

/**
 * Created by iuliana.cosmina on 4/2/17.
 */
public class GrammyGuitarist implements Singer {

	@Override public void sing() {
		System.out.println("sing: Gravity is working against me\n" +
				"And gravity wants to bring me down");
	}

	public void sing(Guitar guitar) {
		System.out.println("play: " + guitar.play());
	}

	public void rest(){
		System.out.println("zzz");
	}

	public void talk(){
		System.out.println("talk");
	}
}

a、使用 NameMatchMethodPointcut

测试类

package com.luo.spring.guides.aop.pointcut.namematching;

import com.luo.spring.guides.aop.pointcut.advice.SimpleAdvice;
import org.springframework.aop.Advisor;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.aop.support.DefaultPointcutAdvisor;
import org.springframework.aop.support.NameMatchMethodPointcut;

/**
 * @author : archer
 * @date : Created in 2022/12/13 11:01
 * @description :
 */
public class NamePointcutMain {

    public static void main(String... args) {
        GrammyGuitarist johnMayer = new GrammyGuitarist();

        NameMatchMethodPointcut pc = new NameMatchMethodPointcut();
        pc.addMethodName("sing");
        pc.addMethodName("rest");

        Advisor advisor = new DefaultPointcutAdvisor(pc, new SimpleAdvice());
        ProxyFactory pf = new ProxyFactory();
        pf.setTarget(johnMayer);
        pf.addAdvisor(advisor);

        GrammyGuitarist proxy = (GrammyGuitarist) pf.getProxy();
        proxy.sing();
        proxy.sing(new Guitar());
        proxy.rest();
        proxy.talk();
    }
}

输出

>> Invoking sing
sing: Gravity is working against me
And gravity wants to bring me down
>> Done

>> Invoking sing
play: G C G C Am D7
>> Done

>> Invoking rest
zzz
>> Done

talk

b、使用 NameMatchMethodPointcutAdvisor

测试

package com.luo.spring.guides.aop.pointcut.namematching;

import com.luo.spring.guides.aop.pointcut.advice.SimpleAdvice;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.aop.support.NameMatchMethodPointcutAdvisor;

/**
 * Created by iuliana.cosmina on 4/2/17.
 */
public class NamePointcutUsingAdvisorMain {
	public static void main(String... args) {
		GrammyGuitarist johnMayer = new GrammyGuitarist();

		NameMatchMethodPointcutAdvisor advisor = new NameMatchMethodPointcutAdvisor(new SimpleAdvice());
		advisor.setMappedNames("sing", "rest");		

		ProxyFactory pf = new ProxyFactory();
		pf.setTarget(johnMayer);
		pf.addAdvisor(advisor);

		GrammyGuitarist proxy = (GrammyGuitarist) pf.getProxy();
		proxy.sing();
		proxy.sing(new Guitar());
		proxy.rest();
		proxy.talk();
	}
}

输出

>> Invoking sing
sing: Gravity is working against me
And gravity wants to bring me down
>> Done

>> Invoking sing
play: G C G C Am D7
>> Done

>> Invoking rest
zzz
>> Done

talk

2)、使用正则表达式创建切入点
  • 代理所有包含 sing 单词的方法

被代理类

package com.luo.spring.guides.aop.pointcut.regex;


import com.luo.spring.guides.aop.pointcut.Singer;

/**
 * Created by iuliana.cosmina on 4/2/17.
 */
public class Guitarist implements Singer {

	@Override public void sing() {
		System.out.println("Just keep me where the light is");
	}

	public void sing2() {
		System.out.println("Oh gravity, stay the hell away from me");
	}

	public void rest() {
		System.out.println("zzz");
	}

}

测试类

package com.luo.spring.guides.aop.pointcut.regex;

import com.luo.spring.guides.aop.pointcut.advice.SimpleAdvice;
import org.springframework.aop.Advisor;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.aop.support.DefaultPointcutAdvisor;
import org.springframework.aop.support.JdkRegexpMethodPointcut;

public class Main {
    public static void main(String... args) {
        Guitarist johnMayer = new Guitarist();

        JdkRegexpMethodPointcut pc = new JdkRegexpMethodPointcut();
        //代理所有包含 sing 单词的方法
        pc.setPattern(".*sing.*");
        Advisor advisor = new DefaultPointcutAdvisor(pc, new SimpleAdvice());

        ProxyFactory pf = new ProxyFactory();
        pf.setTarget(johnMayer);
        pf.addAdvisor(advisor);
        Guitarist proxy = (Guitarist) pf.getProxy();

        proxy.sing();
        proxy.sing2();
        proxy.rest();
    }
}

输出

>> Invoking sing
Just keep me where the light is
>> Done

>> Invoking sing2
Oh gravity, stay the hell away from me
>> Done

zzz

3)、使用 AspectJ 切入点表达式
a、编程式
package com.luo.spring.guides.aop.pointcut.aspectj;

import com.luo.spring.guides.aop.pointcut.advice.SimpleAdvice;
import com.luo.spring.guides.aop.pointcut.regex.Guitarist;
import org.springframework.aop.Advisor;
import org.springframework.aop.aspectj.AspectJExpressionPointcut;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.aop.support.DefaultPointcutAdvisor;

public class Main {
    public static void main(String... args) {
        Guitarist guitarist = new Guitarist();

        AspectJExpressionPointcut pc = new AspectJExpressionPointcut();
        //代理所有以 sing 单词开头的的方法,参数和返回值都任意
        pc.setExpression("execution(* sing*(..))");
        Advisor advisor = new DefaultPointcutAdvisor(pc, new SimpleAdvice());

        ProxyFactory pf = new ProxyFactory();
        pf.setTarget(guitarist);
        pf.addAdvisor(advisor);
        Guitarist proxy = (Guitarist) pf.getProxy();

        proxy.sing();
        proxy.sing2();
        proxy.rest();
    }
}

输出

>> Invoking sing
Just keep me where the light is
>> Done

>> Invoking sing2
Oh gravity, stay the hell away from me
>> Done

zzz

b、注解式

配置类

package com.luo.spring.guides.aop.pointcut.aspectj.annotation.config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

/**
 * Created by iuliana.cosmina on 4/9/17.
 */
@Configuration
@ComponentScan(basePackages = {"com.luo.spring.guides.aop.pointcut.aspectj.annotation"})
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class AppConfig {}

切入点和通知

package com.luo.spring.guides.aop.pointcut.aspectj.annotation.advise;

import com.luo.spring.guides.aop.pointcut.domain.Guitar;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

@Component
@Aspect
public class AnnotatedAdvice {
    
    //&& args(value) 给通知注解方法传参
    @Pointcut("execution(* com.luo.spring.guides.aop.pointcut.aspectj.annotation..sing*(com.luo.spring.guides.aop.pointcut.domain.Guitar)) && args(value)")
    public void singExecution(Guitar value) {
    }

    @Pointcut("bean(john*)")
    public void isJohn() {
    }

    //@Pointcut 中有 && args(value) 才能加入参数(Guitar value)
    @Before("singExecution(value) && isJohn()")
    public void simpleBeforeAdvice(JoinPoint joinPoint, Guitar value) {
        if(value.getBrand().equals("Gibson")) {
        System.out.println("Executing: " + 
            joinPoint.getSignature().getDeclaringTypeName() + " "
            + joinPoint.getSignature().getName() + " argument: " + value.getBrand());
        }
    }

    @Around("singExecution(value) && isJohn()")
    public Object simpleAroundAdvice(ProceedingJoinPoint pjp, Guitar value) throws Throwable {
        System.out.println("Before execution: " +      
            pjp.getSignature().getDeclaringTypeName() + " "
            + pjp.getSignature().getName()
            + " argument: " + value.getBrand());

        Object retVal = pjp.proceed();

        System.out.println("After execution: " +   
            pjp.getSignature().getDeclaringTypeName() + " "
            + pjp.getSignature().getName()
            + " argument: " + value.getBrand());

        return retVal;
    }
}

被代理相关类

package com.luo.spring.guides.aop.pointcut.aspectj.annotation;

import com.luo.spring.guides.aop.pointcut.Singer;
import com.luo.spring.guides.aop.pointcut.domain.Guitar;
import org.springframework.stereotype.Component;

/**
 * Created by iuliana.cosmina on 4/9/17.
 */
@Component("johnMayer")
public class GrammyGuitarist implements Singer {

	@Override public void sing() {
		System.out.println("sing: Gravity is working against me\n" +
				"And gravity wants to bring me down");
	}

	public void sing(Guitar guitar) {
		System.out.println("play: " + guitar.play());
	}

	public void rest(){
		System.out.println("zzz");
	}

	public void talk(){
		System.out.println("talk");
	}
}
package com.luo.spring.guides.aop.pointcut.aspectj.annotation;

import com.luo.spring.guides.aop.pointcut.domain.Guitar;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;

/**
 * Created by iuliana.cosmina on 4/9/17.
 */
@Component("documentarist")
public class NewDocumentarist {

	protected GrammyGuitarist guitarist;

	public void execute() {
		guitarist.sing();
		Guitar guitar = new Guitar();
		guitar.setBrand("Gibson");
		guitarist.sing(guitar);
		guitarist.talk();
	}

	@Autowired
	@Qualifier("johnMayer")
	public void setGuitarist(GrammyGuitarist guitarist) {
		this.guitarist = guitarist;
	}
}

测试

package com.luo.spring.guides.aop.pointcut.aspectj.annotation;

import com.luo.spring.guides.aop.pointcut.aspectj.annotation.config.AppConfig;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.support.GenericApplicationContext;

/**
 * @author : archer
 * @date : Created in 2022/12/13 14:38
 * @description :
 */
public class Main {

    public static void main(String[] args) {
        GenericApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);

        NewDocumentarist documentarist = ctx.getBean("documentarist", NewDocumentarist.class);
        documentarist.execute();

        ctx.close();
    }
}

输出

sing: Gravity is working against me
And gravity wants to bring me down
Before execution: com.luo.spring.guides.aop.pointcut.aspectj.annotation.GrammyGuitarist sing argument: Gibson
Executing: com.luo.spring.guides.aop.pointcut.aspectj.annotation.GrammyGuitarist sing argument: Gibson
play: G C G C Am D7
After execution: com.luo.spring.guides.aop.pointcut.aspectj.annotation.GrammyGuitarist sing argument: Gibson
talk

4)、使用 AnnotationMatchingPointcut
  • @AdviceRequired 注解标识的方法会被代理增强
package com.luo.spring.guides.aop.pointcut.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface AdviceRequired {
}
package com.luo.spring.guides.aop.pointcut.annotation;

import com.luo.spring.guides.aop.pointcut.Singer;
import com.luo.spring.guides.aop.pointcut.domain.Guitar;

/**
 * Created by iuliana.cosmina on 4/2/17.
 */
public class Guitarist implements Singer {

	@Override public void sing() {
		System.out.println("Dream of ways to throw it all away");
	}

	@AdviceRequired
	public void sing(Guitar guitar) {
		System.out.println("play: " + guitar.play());
	}

	public void rest(){
		System.out.println("zzz");
	}

}

测试

package com.luo.spring.guides.aop.pointcut.annotation;

import com.luo.spring.guides.aop.pointcut.advice.SimpleAdvice;
import com.luo.spring.guides.aop.pointcut.domain.Guitar;
import org.springframework.aop.Advisor;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.aop.support.DefaultPointcutAdvisor;
import org.springframework.aop.support.annotation.AnnotationMatchingPointcut;

public class Main {
    public static void main(String... args) {
        Guitarist johnMayer = new Guitarist();

        //用 `@AdviceRequired` 注解标识的方法会被代理增强
        AnnotationMatchingPointcut pc = AnnotationMatchingPointcut
            .forMethodAnnotation(AdviceRequired.class);
        Advisor advisor = new DefaultPointcutAdvisor(pc, new SimpleAdvice());

        ProxyFactory pf = new ProxyFactory();
        pf.setTarget(johnMayer);
        pf.addAdvisor(advisor);
        Guitarist proxy = (Guitarist) pf.getProxy();

        proxy.sing(new Guitar());
        proxy.rest();
    }
}

输出

>> Invoking sing
play: G C G C Am D7
>> Done

zzz

4、切入点的高级使用

1)、使用控制流切入点
  • 可以设置只能某一特别方法才触发通知
package com.apress.prospring5.ch5;

public class TestBean {
    public void foo() {
        System.out.println("foo()");
    }
}

测试

package com.luo.spring.guides.aop.pointcut.cflow;

import com.luo.spring.guides.aop.simple.beforeadvice.SimpleBeforeAdvice;
import org.springframework.aop.Advisor;
import org.springframework.aop.Pointcut;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.aop.support.ControlFlowPointcut;
import org.springframework.aop.support.DefaultPointcutAdvisor;

public class Main {
    public static void main(String... args) {
        Main ex = new Main();
        ex.run();
    }

    public void run() {
        TestBean target = new TestBean();

        //方法名为 test 才触发通知
        Pointcut pc = new ControlFlowPointcut(Main.class, "test");
        Advisor advisor = new DefaultPointcutAdvisor(pc, 
            new SimpleBeforeAdvice());

        ProxyFactory pf = new ProxyFactory();
        pf.setTarget(target);
        pf.addAdvisor(advisor);

        TestBean proxy = (TestBean) pf.getProxy();
 
        System.out.println("Trying normal invoke");
        proxy.foo();
        System.out.println("Trying under ControlFlowDemo.test()");
        test(proxy);
    }

    private void test(TestBean bean) {
        bean.foo();
    }
}

输出

Trying normal invoke
foo()
Trying under ControlFlowDemo.test()
Before method: public void com.luo.spring.guides.aop.pointcut.cflow.TestBean.foo()
foo()

2)、使用组合切入点

被代理类

package com.luo.spring.guides.aop.pointcut.namematching;

import com.luo.spring.guides.aop.pointcut.Singer;
import com.luo.spring.guides.aop.pointcut.domain.Guitar;

/**
 * Created by iuliana.cosmina on 4/2/17.
 */
public class GrammyGuitarist implements Singer {

	@Override public void sing() {
		System.out.println("sing: Gravity is working against me\n" +
				"And gravity wants to bring me down");
	}

	public void sing(Guitar guitar) {
		System.out.println("play: " + guitar.play());
	}

	public void rest(){
		System.out.println("zzz");
	}

	public void talk(){
		System.out.println("talk");
	}
}

通知类

package com.luo.spring.guides.aop.simple.beforeadvice;

import org.springframework.aop.MethodBeforeAdvice;

import java.lang.reflect.Method;

public class SimpleBeforeAdvice implements MethodBeforeAdvice {
    @Override
    public void before(Method method, Object[] args, Object target)
            throws Throwable {
        System.out.println("Before method: " + method);
    }
}

切入点

package com.luo.spring.guides.aop.pointcut.composable;

import org.springframework.aop.support.StaticMethodMatcher;

import java.lang.reflect.Method;

/**
 * @author : archer
 * @date : Created in 2022/12/13 17:09
 * @description :
 */
public class SingMethodMatcher  extends StaticMethodMatcher {
    @Override
    public boolean matches(Method method, Class<?> cls) {
        return (method.getName().startsWith("si"));
    }
}
package com.luo.spring.guides.aop.pointcut.composable;

import org.springframework.aop.support.StaticMethodMatcher;

import java.lang.reflect.Method;

/**
 * @author : archer
 * @date : Created in 2022/12/13 17:09
 * @description :
 */
public class TalkMethodMatcher extends StaticMethodMatcher {
    @Override
    public boolean matches(Method method, Class<?> cls) {
        return "talk".equals(method.getName());
    }
}
package com.luo.spring.guides.aop.pointcut.composable;

import org.springframework.aop.support.StaticMethodMatcher;

import java.lang.reflect.Method;

/**
 * @author : archer
 * @date : Created in 2022/12/13 17:09
 * @description :
 */
public class RestMethodMatcher extends StaticMethodMatcher {
    @Override
    public boolean matches(Method method, Class<?> cls) {
        return (method.getName().endsWith("st"));
    }
}

测试

package com.luo.spring.guides.aop.pointcut.composable;

import com.luo.spring.guides.aop.pointcut.domain.Guitar;
import com.luo.spring.guides.aop.pointcut.namematching.GrammyGuitarist;
import com.luo.spring.guides.aop.simple.beforeadvice.SimpleBeforeAdvice;
import org.springframework.aop.Advisor;
import org.springframework.aop.ClassFilter;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.aop.support.ComposablePointcut;
import org.springframework.aop.support.DefaultPointcutAdvisor;
import org.springframework.aop.support.StaticMethodMatcher;

import java.lang.reflect.Method;

public class Main {
    public static void main(String... args) {
        GrammyGuitarist johnMayer = new GrammyGuitarist();

        ComposablePointcut pc = new ComposablePointcut(ClassFilter.TRUE, 
            new SingMethodMatcher());

        System.out.println("Test 1 >> ");
        GrammyGuitarist proxy = getProxy(pc, johnMayer);
        testInvoke(proxy);
        System.out.println();

        System.out.println("Test 2 >> ");
        //与前面取并集
        pc.union(new TalkMethodMatcher());
        proxy = getProxy(pc, johnMayer);
        testInvoke(proxy);
        System.out.println();

        System.out.println("Test 3 >> ");
        //与前面取交集
        pc.intersection(new RestMethodMatcher());
        proxy = getProxy(pc, johnMayer);
        testInvoke(proxy);
    }

    private static GrammyGuitarist getProxy(ComposablePointcut pc,
            GrammyGuitarist target) {
        Advisor advisor = new DefaultPointcutAdvisor(pc, 
            new SimpleBeforeAdvice());

        ProxyFactory pf = new ProxyFactory();
        pf.setTarget(target);
        pf.addAdvisor(advisor);
        return (GrammyGuitarist) pf.getProxy();
    }

    private static void testInvoke(GrammyGuitarist proxy) {
        proxy.sing();
        proxy.sing(new Guitar());
        proxy.talk();
        proxy.rest();
    }
}

输出

Test 1 >>
Before method: public void com.luo.spring.guides.aop.pointcut.namematching.GrammyGuitarist.sing()
sing: Gravity is working against me
And gravity wants to bring me down
Before method: public void com.luo.spring.guides.aop.pointcut.namematching.GrammyGuitarist.sing(com.luo.spring.guides.aop.pointcut.domain.Guitar)
play: G C G C Am D7
talk
zzz

Test 2 >>
Before method: public void com.luo.spring.guides.aop.pointcut.namematching.GrammyGuitarist.sing()
sing: Gravity is working against me
And gravity wants to bring me down
Before method: public void com.luo.spring.guides.aop.pointcut.namematching.GrammyGuitarist.sing(com.luo.spring.guides.aop.pointcut.domain.Guitar)
play: G C G C Am D7
Before method: public void com.luo.spring.guides.aop.pointcut.namematching.GrammyGuitarist.talk()
talk
zzz

Test 3 >>
sing: Gravity is working against me
And gravity wants to bring me down
play: G C G C Am D7
talk
zzz

三、动态代理

目前动态代理实现有 JDK 动态代理和 CGLIB 动态代理两种方案,两种方案的详情介绍,请读者自行阅读资料,这里就不介绍了。

这里是只对两者的性能做下简单的对比。

被代理类相关

package com.luo.spring.guides.aop.pointcut.dyanmicproxy;

public interface SimpleBean {
    //被代理增强的通知方法,即被通知方法
    void advised();
    //不被被代理增强的通知方法,即未被通知方法
    void unadvised();
}
package com.apress.prospring5.ch5;

public class DefaultSimpleBean implements SimpleBean {
    private long dummy = 0;

    //被代理增强的通知方法,即被通知方法
    @Override
    public void advised() {
        dummy = System.currentTimeMillis();
    }

    //不被被代理增强的通知方法,即未被通知方法
    @Override
    public void unadvised() {
        dummy = System.currentTimeMillis();
    }
}

切入点

package com.luo.spring.guides.aop.pointcut.dyanmicproxy;

import org.springframework.aop.support.StaticMethodMatcherPointcut;

import java.lang.reflect.Method;

public class TestPointcut extends StaticMethodMatcherPointcut {
    @Override
    public boolean matches(Method method, Class cls) {
        return ("advise".equals(method.getName()));
    } 
}

通知类

package com.luo.spring.guides.aop.pointcut.dyanmicproxy;

import org.springframework.aop.MethodBeforeAdvice;

import java.lang.reflect.Method;

public class NoOpBeforeAdvice implements MethodBeforeAdvice {
    @Override
    public void before(Method method, Object[] args, Object target)
        throws Throwable {
        // no-op
    }
}

测试

package com.luo.spring.guides.aop.pointcut.dyanmicproxy;

import org.springframework.aop.Advisor;
import org.springframework.aop.framework.Advised;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.aop.support.DefaultPointcutAdvisor;

public class ProxyPerfTest {
    public static void main(String... args) {
        SimpleBean target = new DefaultSimpleBean();

        Advisor advisor = new DefaultPointcutAdvisor(new TestPointcut(),
                new NoOpBeforeAdvice());

        //标准 CGLIB 代理
        runCglibTests(advisor, target);
        //冻结通知链 CGLIB 代理
        runCglibFrozenTests(advisor, target);
        //JDK 代理
        runJdkTests(advisor, target);
    }

    private static void runCglibTests(Advisor advisor, SimpleBean target) {
        ProxyFactory pf = new ProxyFactory();
        pf.setProxyTargetClass(true);
        pf.setTarget(target);
        pf.addAdvisor(advisor);

        SimpleBean proxy = (SimpleBean)pf.getProxy();
        System.out.println("Running CGLIB (Standard) Tests");
        test(proxy);
    }

    private static void runCglibFrozenTests(Advisor advisor, SimpleBean target) {
        ProxyFactory pf = new ProxyFactory();
        pf.setProxyTargetClass(true);
        pf.setTarget(target);
        pf.addAdvisor(advisor);
        //冻结通知链
        pf.setFrozen(true);

        SimpleBean proxy = (SimpleBean) pf.getProxy();
        System.out.println("Running CGLIB (Frozen) Tests");
        test(proxy);
    }

    private static void runJdkTests(Advisor advisor, SimpleBean target) {
        ProxyFactory pf = new ProxyFactory();
        pf.setTarget(target);
        pf.addAdvisor(advisor);
        pf.setInterfaces(new Class[]{SimpleBean.class});

        SimpleBean proxy = (SimpleBean)pf.getProxy();
        System.out.println("Running JDK Tests");
        test(proxy);
    }

    private static void test(SimpleBean bean) {
        long before = 0;
        long after = 0;

        System.out.println("Testing Advised Method");
        before = System.nanoTime();
        for(int x = 0; x < 500000; x++) {
            bean.advised();
        }
        after = System.nanoTime();

        System.out.println("Took " + (after - before) / 1000000 + " ms");

        System.out.println("Testing Unadvised Method");
        before = System.nanoTime();
        for(int x = 0; x < 500000; x++) {
            bean.unadvised();
        }
        after = System.nanoTime();

        System.out.println("Took " + (after - before) / 1000000 + " ms");

        System.out.println("Testing equals() Method");
        before = System.nanoTime();
        for(int x = 0; x < 500000; x++) {
            bean.equals(bean);
        }
        after = System.nanoTime();

        System.out.println("Took " + (after - before) / 1000000 + " ms");

        System.out.println("Testing hashCode() Method");
        before = System.nanoTime();
        for(int x = 0; x < 500000; x++) {
            bean.hashCode();
        }
        after = System.nanoTime();

        System.out.println("Took " + (after - before) / 1000000 + " ms");

        Advised advised = (Advised)bean;

        System.out.println("Testing Advised.getProxyTargetClass() Method");
        before = System.nanoTime();
        for(int x = 0; x < 500000; x++) {
            advised.getTargetClass();
        }
        after = System.nanoTime();

        System.out.println("Took " + (after - before) / 1000000 + " ms");

        System.out.println(">>>\n");
    }
}

输出图解

在这里插入图片描述

小结

  • 标准 CGLIB 和 JDK 动态代理,在被通知方法和未被通知方法之间的性能差异不大。但是,当使用具有冻结通知链的 CGLIB 代理时,性能存在显著差距。
  • 对于 equals() 和 hashCode() 方法,在使用 CGLIB 代理时,明显更快。
  • 对于 Advised 接口上的方法,CGLIB 冻结代理上运行的更快,原因是被通知方法早就在 intercept() 方法中进行了处理,从而避免了其他方法所需的大部分逻辑。

四、引入

引入是 Spring 中可用的 AOP 功能集的重要组成部分,通过使用引入,可以动态的向现有对象引入新功能。可以将引入视为一种特殊类型的通知。

被代理类

package com.luo.spring.guides.aop.pointcut.introduction;

/**
 * Created by iuliana.cosmina on 4/9/17.
 */
public class Contact {

	private String name;
	private String phoneNumber;
	private String email;

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public String getPhoneNumber() {
		return phoneNumber;
	}

	public void setPhoneNumber(String phoneNumber) {
		this.phoneNumber = phoneNumber;
	}

	public String getEmail() {
		return email;
	}

	public void setEmail(String email) {
		this.email = email;
	}
}

引入及其相关类

package com.luo.spring.guides.aop.pointcut.introduction;

public interface IsModified {
    boolean isModified();
}
package com.luo.spring.guides.aop.pointcut.introduction;

import org.aopalliance.intercept.MethodInvocation;
import org.springframework.aop.support.DelegatingIntroductionInterceptor;

import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

//对象修改检测技术,可用于检测是否有值被更改过,改过就入库,否则不入库,减少与数据库的链接次数
@SuppressWarnings("all")
public class IsModifiedMixin extends DelegatingIntroductionInterceptor 
        implements IsModified {
    private boolean isModified = false;

    private Map<Method, Method> methodCache = new HashMap<>();

    @Override
    public boolean isModified() {
        return isModified;
    }

    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        if (!isModified) {
            if ((invocation.getMethod().getName().startsWith("set"))
                && (invocation.getArguments().length == 1)) {

                Method getter = getGetter(invocation.getMethod());

                if (getter != null) {
                    Object newVal = invocation.getArguments()[0];
                    Object oldVal = getter.invoke(invocation.getThis(),null);

                    if((newVal == null) && (oldVal == null)) {
                        isModified = false;
                    } else if((newVal == null) && (oldVal != null)) {
                        isModified = true;
                    } else if((newVal != null) && (oldVal == null)) {
                        isModified = true;
                    } else {
                        isModified = !newVal.equals(oldVal);
                    }
                }
            }
        }

        return super.invoke(invocation);
    }


    private Method getGetter(Method setter) {
        Method  getter = methodCache.get(setter);

        if (getter != null) {
            return getter;
        }

        String getterName = setter.getName().replaceFirst("set", "get");
        try {
            getter = setter.getDeclaringClass().getMethod(getterName, null);
            synchronized (methodCache) {
                methodCache.put(setter, getter);
            }
            return getter;
        } catch (NoSuchMethodException ex) {
            return null;
        }
    }
}
package com.luo.spring.guides.aop.pointcut.introduction;

import org.springframework.aop.support.DefaultIntroductionAdvisor;

public class IsModifiedAdvisor extends DefaultIntroductionAdvisor {
    public IsModifiedAdvisor() {
        super(new IsModifiedMixin());
    }
}

1、引入简介

下面示例是对象修改检测技术的实现。

测试

package com.luo.spring.guides.aop.pointcut.introduction;

import org.springframework.aop.IntroductionAdvisor;
import org.springframework.aop.framework.ProxyFactory;

@SuppressWarnings("all")
public class Main {
    public static void main(String... args) {
        Contact target = new Contact();
        target.setName("John Mayer");

        IntroductionAdvisor advisor = new IsModifiedAdvisor();

        ProxyFactory pf = new ProxyFactory();
        pf.setTarget(target);
        pf.addAdvisor(advisor);
        //使用cglib代理方式,默认jdk代理
        pf.setOptimize(true);

        Object proxy = pf.getProxy();

        //使用 jdk 代理时,生成的代理对象并不是对象类(Contact)的实例。
        System.out.println("Is Contact?: " + (proxy instanceof Contact));
        System.out.println("Is IsModified?: " + (proxy instanceof IsModified));

        Contact contact = (Contact) pf.getProxy();
        IsModified proxyInterface = (IsModified)proxy;
        System.out.println("Has been modified?: " + 
            proxyInterface.isModified());

        contact.setName("John Mayer");

        System.out.println("Has been modified?: " + 
            proxyInterface.isModified());

        contact.setName("Eric Clapton");

        System.out.println("Has been modified?: " + 
            proxyInterface.isModified());
    }
}

输出

Is Contact?: true
Is IsModified?: true
Has been modified?: false
Has been modified?: false
Has been modified?: true

2、使用 ProxyFactoryBean

ProxyFactoryBcan类是FactoryBean 的一个实现,它允许指定一个bean 作为目标,并且为该 bean 提供一组通知和顾问(Advisor)(这些通知和顾问最终被合并到一个 AOP 代理中)。

配置类

package com.luo.spring.guides.aop.proxyfactorybean.introduction;

import com.luo.spring.guides.aop.proxyfactorybean.introduction.domain.Contact;
import com.luo.spring.guides.aop.proxyfactorybean.introduction.domain.IsModifiedAdvisor;
import org.springframework.aop.Advisor;
import org.springframework.aop.framework.ProxyFactoryBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * Created by iuliana.cosmina on 4/9/17.
 */
@Configuration
public class AppConfig {

	@Bean
	public Contact guitarist() {
		Contact guitarist = new Contact();
		guitarist.setName("John Mayer");
		return guitarist;
	}

	@Bean
	public Advisor advisor() {
		return new IsModifiedAdvisor();
	}

	@Bean
	ProxyFactoryBean bean() {
		ProxyFactoryBean proxyFactoryBean = new ProxyFactoryBean();
		proxyFactoryBean.setTarget(guitarist());
		//cglib代理 设置是否直接代理目标类,false表示直接代理特定接口
		proxyFactoryBean.setProxyTargetClass(true);
		proxyFactoryBean.addAdvisor(advisor());
		return proxyFactoryBean;
	}
}

测试

package com.luo.spring.guides.aop.proxyfactorybean.introduction;

import com.luo.spring.guides.aop.proxyfactorybean.introduction.domain.Contact;
import com.luo.spring.guides.aop.proxyfactorybean.introduction.domain.IsModified;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.support.GenericApplicationContext;

@SuppressWarnings("all")
public class Main {
    public static void main(String... args) {
        GenericApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);

        Contact bean = (Contact) ctx.getBean("bean");
        IsModified mod = (IsModified) bean;

        System.out.println("Is Contact?: " + (bean instanceof Contact));
        System.out.println("Is IsModified?: " + (bean instanceof IsModified));

        System.out.println("Has been modified?: " + mod.isModified());
        bean.setName("John Mayer");

        System.out.println("Has been modified?: " + mod.isModified());
        bean.setName("Eric Clapton");

        System.out.println("Has been modified?: " + mod.isModified());
    }
}

输出

Is Contact?: true
Is IsModified?: true
Has been modified?: false
Has been modified?: false
Has been modified?: true

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值