AOP:面向切面编程
AOP核心概念:
连接点:应用在执行期间明确定义的一个点,(包括方法调用、方法调用本身、类初始化和对象实例化)
通知:连接点执行的代码就是通知,他是由类中的方法定义的
切入点:用于定义何时执行通知的连接点集合
切面:封装在类中的通知和切入点的组合
织入:在适当的位置将切面插入到应用程序代码中的过程
目标对象:执行流由AOP进程修改的对象被称为目标对象
引入:通过引入其它方法或字段来修改对象结果的过程
AOP类型
静态AOP:通过修改应用程序的实际字节码并根据需要更改和扩展应用程序代码来实现静态AOP实现中的织入过程
动态AOP:织入过程在运行时动态执行
Hello Word示例
public class Agent {
public void speak() {
System.out.print("Bond");
}
}
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
/**
* MethodInterceptor是一个标准的AOP Alliance接口,用于实现方法调用连接点的环绕通知
* MethodInvocation对象表示正在被通知的方法调用,通过此对象,可以控制方法调用何时进行
*/
public class AgentDecorator implements MethodInterceptor {
public Object invoke(MethodInvocation invocation) throws Throwable {
System.out.print("James ");
Object retVal = invocation.proceed();
System.out.println("!");
return retVal;
}
}
import org.springframework.aop.framework.ProxyFactory;
public class AgentAOPDemo {
public static void main(String... args) {
Agent target = new Agent();
ProxyFactory pf = new ProxyFactory();
//通过调用addAdvice()将AgentDecorator通知传递给ProxyFfactory,并通过调用setTarget()指定织入目标
pf.addAdvice(new AgentDecorator());
pf.setTarget(target);
Agent proxy = (Agent) pf.getProxy();
target.speak();
System.out.println("");
proxy.speak();
}
}
输出:
Bond
James Bond!
Spring AOP架构
Spring AOP的核心架构基于代理。
在运行时,Spring会分析ApplicationContext中的bean定义的横切关注点,并动态生成代理bean(封装了底层目标bean)。此时,不会直接调用bean,而是将调用者注入代理bean,然后代理bean分析运行条件(即连接点、切入点或通知)并相应的织入适当的通知。
Spring中的连接点:方法调用
Spring中的切面:切面由实现了Advisor接口的类的实例表示,两个子接口:PointcutAdvisor(所有)和IntroductionAdvisor(引言)
ProxyFactory类:ProxyFactory类控制Spring AOP中的织入和代理创建过程。
Spring支持的6种通知
MethodBeforeAdvice | 前置通知,可以在连接点执行之前完成自定义处理 |
AfterReturningAdvice | 后置返回通知,在连接点的方法调用完成执行并返回一个值后执行后置返回通知 |
AfterAdvice | 仅当被通知方法正常完成时才执行后置通知 |
MethodInterceptor | 环绕通知,允许在方法调用之前和之后执行,并且可以控制允许进行方法调用的点 |
ThrowsAdvice | 异常通知,在方法调用返回后执行,但只有在该调用抛出异常时才执行 |
IntroductionInterceptor | 引入通知,可以指定由引入通知引入的方法的实现 |
前置通知示例:
public interface Singer {
void sing();
}
public class Guitarist implements Singer {
private String lyric="You're gonna live forever in me";
@Override
public void sing(){
System.out.println(lyric);
}
}
import java.lang.reflect.Method;
import org.springframework.aop.MethodBeforeAdvice;
import org.springframework.aop.framework.ProxyFactory;
public class SimpleBeforeAdvice implements MethodBeforeAdvice {
public static void main(String... args) {
Guitarist johnMayer = new Guitarist();
ProxyFactory pf = new ProxyFactory();
pf.addAdvice(new SimpleBeforeAdvice());
pf.setTarget(johnMayer);
Guitarist proxy = (Guitarist) pf.getProxy();
proxy.sing();
}
@Override
public void before(Method method, Object[] args, Object target)
throws Throwable {
System.out.println("Before '" + method.getName() + "', tune guitar.");
}
}
输出:
创建后置返回通知示例:
import java.lang.reflect.Method;
import org.springframework.aop.AfterReturningAdvice;
import org.springframework.aop.framework.ProxyFactory;
public class SimpleAfterReturningAdvice implements AfterReturningAdvice {
public static void main(String... args) {
Guitarist target = new Guitarist();
ProxyFactory pf = new ProxyFactory();
pf.addAdvice(new SimpleAfterReturningAdvice());
pf.setTarget(target);
Guitarist proxy = (Guitarist) pf.getProxy();
proxy.sing();
}
@Override
public void afterReturning(Object returnValue, Method method,
Object[] args, Object target) throws Throwable {
System.out.println("After '" + method.getName()+ "' put down guitar.");
}
}
创建环绕通知示例:
public class WorkerBean {
public void doSomeWork(int noOfTimes) {
for(int x = 0; x < noOfTimes; x++) {
work();
}
}
private void work() {
System.out.print("");
}
}
import java.lang.reflect.Method;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.util.StopWatch;
/**
* 环绕通知类似于前置通知和后置通知功能的组合,区别在于:可以修改返回值,还可以阻止方法执行
*/
public class ProfilingInterceptor implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
StopWatch sw = new StopWatch();
sw.start(invocation.getMethod().getName());
Object returnValue = invocation.proceed();
sw.stop();
dumpInfo(invocation, sw.getTotalTimeMillis());
return returnValue;
}
private void dumpInfo(MethodInvocation invocation, long ms) {
Method m = invocation.getMethod();
Object target = invocation.getThis();
Object[] args = invocation.getArguments();
System.out.println("Executed method: " + m.getName());
System.out.println("On object of type: " +
target.getClass().getName());
System.out.println("With arguments:");
for (int x = 0; x < args.length; x++) {
System.out.print(" > " + args[x]);
}
System.out.print("\n");
System.out.println("Took: " + ms + " ms");
}
}
import org.springframework.aop.framework.ProxyFactory;
public class ProfilingDemo {
public static void main(String... args) {
WorkerBean bean = getWorkerBean();
bean.doSomeWork(10000000);
}
private static WorkerBean getWorkerBean() {
WorkerBean target = new WorkerBean();
ProxyFactory factory = new ProxyFactory();
factory.setTarget(target);
factory.addAdvice(new ProfilingInterceptor());
return (WorkerBean)factory.getProxy();
}
}
创建异常通知示例:
public class ErrorBean {
public void errorProneMethod() throws Exception {
throw new Exception("Generic Exception");
}
public void otherErrorProneMethod() throws IllegalArgumentException {
throw new IllegalArgumentException("IllegalArgument Exception");
}
}
import java.lang.reflect.Method;
import org.springframework.aop.ThrowsAdvice;
import org.springframework.aop.framework.ProxyFactory;
public class SimpleThrowsAdvice implements ThrowsAdvice {
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.errorProneMethod();
} catch (Exception ignored) {
}
try {
proxy.otherErrorProneMethod();
} catch (Exception ignored) {
}
}
//Spring在异常通知种寻找的第一种方法是一个或多个被称为afterThrowing()的公共方法
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");
}
//若是Exception类型声明相同,其中一个方法一个参数,另一个方法四个参数,Spring会调用带四个参数的afterThrowing()方法
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");
}
}
在Spring种使用顾问和切入点
我们将通知和目标之间的这种耦合称为目标关联性,当通知具有较强或没有目标关联性时,应该使用切入点。
Pointcut接口
public interface Pointcut {
ClassFilter getClassFilter();
MethodMatcher getMethodMatcher();
}
public interface ClassFilter {
//检查Pointcut接口是否适用于该方法类
boolean matches(Class<?> clazz);
}
public interface MethodMatcher {
//如果切入点适用于类,matches()方法返回true,否则返回false
boolean matches(Method m,Class<?> targetClass);
//决定是静态的MethodMatcher还是动态的MethodMatcher
boolean isRuntime();
boolean matches(Method m, Class<?> targetClass, Object[] args);
}
可用的切入点实现
AnnotationMatchingPointcut | 此实现在类或方法上查找特定的Java注解 |
AspectJExpressionPointcut | 此实现使用AspectJ织入器以AspectJ语法评估切入点表达式 |
ComposablePointcut | ComposablePointcut使用诸如union()和intersection()等操作组合两个或更多切入点 |
ControlFlowPointcut | 匹配另一个方法的控制流中的所有方法,即任何作为另一个方法的结果而直接或间接调用的方法 |
DynamicMethodMatcherPointcut | 旨在作为构建动态切入点的基类 |
JdkRegexpMethodPointcut | 允许使用JDK1.4正则表达式支持定义切入点 |
NameMatchMethodPointcut | 对方法名称列表执行简单匹配 |
StaticMethodMatcherPointcut | 用作构建静态切入点的基础 |
使用DefaultPointcutAdvisor
这是一个简单的PointcutAdvisor,用于将单个Pointcut于单个Advice相关联
使用StaticMethodMatcherPointcut创建静态切入点
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");
}
}
public class GreatGuitarist implements Singer {
@Override public void sing() {
System.out.println("I shot the sheriff, \n" +
"But I did not shoot the deputy");
}
}
import java.lang.reflect.Method;
import org.springframework.aop.ClassFilter;
import org.springframework.aop.support.StaticMethodMatcherPointcut;
public class SimpleStaticPointcut extends StaticMethodMatcherPointcut {
@Override
public boolean matches(Method method, Class<?> cls) {
return ("sing".equals(method.getName()));
}
@Override
public ClassFilter getClassFilter() {
return cls -> (cls == GoodGuitarist.class);
}
}
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 StaticPointcutDemo {
public static void main(String... args) {
GoodGuitarist johnMayer = new GoodGuitarist();
GreatGuitarist ericClapton = 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(johnMayer);
proxyOne = (Singer)pf.getProxy();
pf = new ProxyFactory();
pf.addAdvisor(advisor);
pf.setTarget(ericClapton);
proxyTwo = (Singer)pf.getProxy();
proxyOne.sing();
proxyTwo.sing();
}
}
使用DynamicMethodMatcherPointcut创建动态切入点
public class SampleBean {
public void foo(int x) {
System.out.println("Invoked foo() with: " + x);
}
public void bar() {
System.out.println("Invoked bar()");
}
}
import java.lang.reflect.Method;
import org.springframework.aop.ClassFilter;
import org.springframework.aop.support.DynamicMethodMatcherPointcut;
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);
}
}
import org.springframework.aop.Advisor;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.aop.support.DefaultPointcutAdvisor;
public class DynamicPointcutDemo {
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();
}
}
使用简单名称匹配
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;
}
}
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");
}
}
import com.apress.prospring5.ch2.common.Guitar;
import org.springframework.aop.Advisor;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.aop.support.DefaultPointcutAdvisor;
import org.springframework.aop.support.NameMatchMethodPointcut;
public class NamePointcutDemo {
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();
}
}
用正则表达式创建切入点
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");
}
}
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 RegexpPointcutDemo {
public static void main(String... args) {
Guitarist johnMayer = new Guitarist();
JdkRegexpMethodPointcut pc = new JdkRegexpMethodPointcut();
//设置正则表达式
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();
}
}
使用AspectJ切入点表达式创建切入点
项目中引入aspectjrt.jar和aspectjweaver.jar两个包。
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 AspectjexpPointcutDemo {
public static void main(String... args) {
Guitarist johnMayer = new Guitarist();
AspectJExpressionPointcut pc = new AspectJExpressionPointcut();
//设置aspectj表达式
pc.setExpression("execution(* 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();
}
}
创建注解匹配切入点
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 {
}
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");
}
}
import com.apress.prospring5.ch2.common.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 AnnotationPointcutDemo {
public static void main(String... args) {
Guitarist johnMayer = new Guitarist();
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();
}
}
便捷的Advisor实现
import org.springframework.aop.Advisor;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.aop.support.NameMatchMethodPointcut;
import org.springframework.aop.support.NameMatchMethodPointcutAdvisor;
public class NamePointcutUsingAdvisor {
public static void main(String... args) {
GrammyGuitarist johnMayer = new GrammyGuitarist();
NameMatchMethodPointcutAdvisor advisor = new NameMatchMethodPointcutAdvisor(new SimpleAdvice());
advisor.setMappedNames("sing");
advisor.setMappedNames("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();
}
}
了解代理
在Spring中有两种代理:使用JDK Proxy类创建的JDK代理以及使用CGLIB Enhancer类创建的基于CGLIB代理。
JDK动态代理:
JDK代理式Spring中最基本的代理类型。JDK代理只能生成接口的代理,而不能生成类的代理。当使用JDK代理时,所有方法调用都会被JVM拦截并路由到代理的invoke()方法,然后由invoke方法确定是否通知有关方法,如果通知,则通过反射调用通知链,然后调用方法本身。通过setIinterfaces()指定要代理的接口列表,从而指示ProxyFactory使用JDK代理。
使用CGLIB代理
CGLIB会为每个类动态生成新类的字节码,并尽可能重用自己生成的类
当首次创建CGLIB代理时,CGLIB会询问Spring如何处理每个方法。这意味着每次调用JDK代理商的invoke()时所执行的许多策略在CGLIB代理来说只会执行一次。如果想要在代理接口时使用CGLIB代理,必须使用setOptimize()方法将ProxyFactory中的Optimize标志设置为true。
参考:《Spirng5高级编程》