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
>> DoneI 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
>> DoneDynamic check for foo
>> Invoking foo
Invoked foo() with: 10
>> DoneDynamic 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
>> Donetalk
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
>> Donetalk
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
>> Donezzz
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
>> Donezzz
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
>> Donezzz
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
zzzTest 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
zzzTest 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