1、AOP:java
是对OOP编程方式的一种补充。翻译过来为“面向切面编程”。程序员
能够理解为一个拦截器框架,可是这个拦截器会很是武断,若是它拦截一个类,那么它就会拦截这个类中的全部方法。如对一个目标列的代理,加强了目标类的全部方法。spring
两个解决办法:express
1.不优雅的作法:编程
在添加加强时,根据方法名去判断,是否添加加强,可是这样就得一直去维护这个加强类。后端
2.面向切面:框架
将加强类和拦截条件组合在一块儿,而后将这个切面配置到 ProxyFactory 中,从而生成代理。ide
2、AOP 和 切面的关系工具
1.类比于 OOP 和 对象,AOP 和 切面就是这样的一种关系。性能
2.也能够将 切面 当作是 AOP 的一个工具。
3、几个概念
切面(Advisor):是AOP中的一个术语,表示从业务逻辑中分离出来的横切逻辑,好比性能监控,日志记录,权限控制等。
这些功能均可以从核心的业务逻辑中抽离出去。能够解决代码耦合问题,职责更加单一。封装了加强和切点。
加强(Advice):加强代码的功能的类,横切到代码中。
目标:目标方法(JDK代理)或目标类(CGLIB代理)
代理:JDK代理,CGLIB代理。或是经过 ProxyFactory 类生产。
切点:经过一个条件来匹配要拦截的类,这个条件称为切点。如拦截全部带 Controller 注解的类。加强的条件。
链接点:做为加强方法的入参,能够获取到目标方法的信息。
4、归纳为一张图
5、加强
1.Weaving(织入):对方法进行加强
(1)前置加强(BeforeAdvice):在目标方法前调用。
(2)后置加强(AfterAdvice):在目标方法后调用。
(3)环绕加强(AroundAdvice):将 Before 和 After ,甚至抛出加强和返回加强合到一块儿。
(4)返回加强(AfterReturningAdvice):在方法返回结果后执行,该加强能够接收到目标方法返回结果。
(5)抛出加强(AfterThrowingAdvice):在目标方法抛出对应的类型后执行,能够接收到对应的异常信息。
2.Introduction(引入):对类进行加强
(1)引入加强(DeclareParentsAdvice):想让程序在运行的时候动态去实现某个接口,须要引入加强。
6、SpringAOP
1.编程式
(1)前置加强,须要实现:MethodBeforeAdvice 接口
加强类:
/** * @author solverpeng * @create 2016-07-27-11:07 */ public class CarBeforeAdvice implements MethodBeforeAdvice{ @Override public void before(Method method, Object[] objects, Object o) throws Throwable { System.out.println("before"); } }
测试方法:
@Test public void test03() { ProxyFactory proxyFactory = new ProxyFactory(); proxyFactory.setTarget(new Car()); proxyFactory.addAdvice(new CarBeforeAdvice()); Wheel carProxy = (Wheel)proxyFactory.getProxy(); carProxy.run(); }
(2)后置加强:实现 AfterReturningAdvice 接口
(3)环绕加强:实现 org.aopalliance.intercept.MethodInterceptor 接口,使用 Object result = methodInvocation.proceed(); 调用目标方法。在目标方法先后添加加强。
2.声明式:Spring + AspectJ
开发步骤:
(1)定义切面类,将该切面类放入到 IOC 容器中
/** * @author solverpeng * @create 2016-07-27-13:10 */ @Component public class XMLLoggingAspect { public void beforeAdvice(JoinPoint point) { System.out.println("xml aspects logging before"); } public void afterAdvice(JoinPoint point) { System.out.println("xml aspects logging after"); } public void afterReturningAdvice(JoinPoint point, Object result) { System.out.println("xml aspects logging afterReturningAdvice"); } public void afterThrowingAdvice(JoinPoint point, Exception e) { System.out.println("xml aspects logging afterThrowingAdvice"); } public Object aroundAdvice(ProceedingJoinPoint point) throws Throwable { Object result = point.proceed(); System.out.println("xml aspects logging aroundAdvice"); return result; } }
(2)Spring AOP 配置都必须定义在 <aop:config>元素内部。
(3)在 <aop:config> 中,每一个切面都须要建立一个 <aop:aspect> 元素
(4)为具体的切面实现引用后端的 bean 实例。
下面展现 前置加强、后置加强、环绕加强、返回加强、抛出加强 的一个例子:
<aop:config> <aop:aspect ref="XMLLoggingAspect"> <aop:pointcut id="carPointcut" expression="execution(void run())"/> <aop:before method="beforeAdvice" pointcut-ref="carPointcut"/> <aop:after method="afterAdvice" pointcut-ref="carPointcut"/> <aop:after-returning method="afterReturningAdvice" pointcut-ref="carPointcut" returning="result"/> <aop:after-throwing method="afterThrowingAdvice" pointcut-ref="carPointcut" throwing="e"/> <aop:around method="aroundAdvice" pointcut-ref="carPointcut"/> </aop:aspect> </aop:config>
控制台输出:
xml aspects logging before
i am a car, i can run.
xml aspects logging aroundAdvice
xml aspects logging afterReturningAdvice
xml aspects logging after
基于声明式的 Spring AspectJ 织入加强配置说明:
支持配置两个级别的公共切点表达式,一个是针对某个切面的全部方法(定义在 <aop:aspect> 节点内),另外一个是针对全部切面(定义在<aop:config>节点内)。使用 pointcut-ref 来引入切点。
下面展现 引入加强 的一个例子:
对于引入加强,只须要在配置在 <aop:aspect> 节点下就能够,不须要去切面类中添加任何属性。
<aop:config> <aop:aspect ref="XMLLoggingAspect"> <aop:declare-parents types-matching="com.nucsoft.spring.target.impl.Student" implement-interface="com.nucsoft.spring.target.Fly" default-impl="com.nucsoft.spring.target.impl.SuperMan"/> </aop:aspect> </aop:config>
测试:
@Test public void test04() { Person student = context.getBean(Student.class); System.out.println(student.say("james")); Fly fly = (Fly) student; fly.fly(); }
控制台输出:
hello,james
i am super man, i can fly.
基于声明式的 Spring AspectJ 引入加强配置说明:
(1)引入加强是类级别的,因此不存在切点表达式。
(1)利用 <aop:declare-parents> 节点在 <aop:aspect> 内部声明
(2)types-matching 属性,要加强的目标类,这里须要全类名。
(3)implement-interface 属性:动态的加强类接口。
(4)default-impl 属性:动态加强类接口的实现类。
3.注解:Spring + AspectJ
对切面类添加 @Aspect 注解,将切面类和目标类放入到 IOC 容器中,能够经过 <context:component-scan base-package=""/> 进行扫描。
添加加强方法(包括加强类型和切点表达式,以及链接点)。
在 Spring Config 文件中添加 <aop:aspectj-autoproxy proxy-target-class="true"/>, proxy-target-class属性,false 只能代理接口(JDK动态代理),true 代理类(CGLIB动态代理)
3.1 经过切点表达式(AspectJ execution)进行拦截
spring-config.xml
<context:component-scan base-package="com.nucsoft.spring"/> <aop:aspectj-autoproxy proxy-target-class="true"/>
Person 接口:
/** * Created by solverpeng on 2016/7/26. */ public interface Person { String say(String name); }
Person 实现类 Student:
/** * @author solverpeng * @create 2016-07-26-17:55 */ @Component public class Student implements Person{ @Override public String say(String name) { return "hello," + name; } }
测试:
/** * @author solverpeng * @create 2016-07-26-18:15 */ public class SpringTest { @Test public void test01() { ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml"); Student student = context.getBean(Student.class); String james = student.say("james"); System.out.println(james); } }
(1)前置加强:关键字:@Before,JoinPoint,execution 切点表达式,表达式内容支持通配符
/** * @author solverpeng * @create 2016-07-26-17:42 */ @Aspect @Component public class LoggingAspect { @Before("execution(String say(String))") public void before(JoinPoint point) { System.out.println("before"); } }
控制台输出:
before
hello,james
(2)后置加强:关键字:@After
/** * @author solverpeng * @create 2016-07-26-17:42 */ @Aspect @Component public class LoggingAspect { @After("execution(String say(String))") public void afterAdvice(JoinPoint point) { System.out.println("after..."); } }
控制台输出:
after...
hello,james
(3)环绕加强:关键字:@Around,ProceedingJoinPoint
将 Student 类还原
切面类:
/** * @author solverpeng * @create 2016-07-26-17:42 */ @Aspect @Component public class LoggingAspect { @Around("execution(String say(String))") public Object aroundAdvice(ProceedingJoinPoint point) throws Throwable { before(); Object result = point.proceed(); after(); return result; } private void before(){ System.out.println("before"); } private void after(){ System.out.println("after"); } }
控制台输出:
before
after
hello,james
注意:
<1>.加强方法的返回值为 Object 类型的,该返回值与目标方法返回值一致。
<2>.Object result = point.proceed(); 该 result 即为目标方法执行后的返回值。
<3>.在环绕通知中须要明确调用 ProceedingJoinPoint 的 proceed() 方法来执行被代理的方法. 若是忘记这样作就会致使通知被执行了, 但目标方法没有被执行
<4>.环绕通知的方法须要返回目标方法执行以后的结果, 即调用 joinPoint.proceed(); 的返回值, 不然会出现空指针异常
(4)返回加强:关键字:@AfterReturning,returning,JoinPoint
/** * @author solverpeng * @create 2016-07-26-17:42 */ @Aspect @Component public class LoggingAspect { @AfterReturning(value = "execution(String say(String))", returning = "str") public void aferRetruningAdvice(JoinPoint point, String str) { System.out.println("str:" + str); System.out.println("aferRetruningAdvice"); } }
控制台输出:
str:hello,james
aferRetruningAdvice
hello,james
(5)抛出加强:关键字:@AfterThrowing,throwing。注意:抛出的异常类型必须和切面抛出加强接收的异常类型相同或是其子类。
更改 Student 类,手动抛出一个异常:
/** * @author solverpeng * @create 2016-07-26-17:55 */ @Component public class Student implements Person{ @Override public String say(String name) { throw new RuntimeException("exception"); } }
切面类:
/** * @author solverpeng * @create 2016-07-26-17:42 */ @Aspect @Component public class LoggingAspect { @AfterThrowing(value = "execution(String say(String))", throwing = "e") public void AfterThrowingAdvice(JoinPoint point, Exception e) { String message = e.getMessage(); System.out.println(message); System.out.println("AfterThrowingAdvice"); } }
控制台输出:
exception
AfterThrowingAdvice
3.2 经过切点注解表达式(AspectJ @annotation)进行拦截
开发步骤:
(1)定义注解类
/** * Created by solverpeng on 2016/7/27. */ @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface AuthorityTag {}
(2)为切面类中加强指定注解表达式
/** * Created by solverpeng on 2016/7/27. */ @Aspect @Component public class AuthorityAspect { @Before("@annotation(com.nucsoft.spring.annotation.AuthorityTag)") public void before(JoinPoint point) { System.out.println("authority before"); } }
(3)在目标类目标方法上标注注解
/** * @author xzsw * @create 2016-07-27-9:59 */ @Component public class Car implements Wheel{ @AuthorityTag @Override public void run() { System.out.println("i am a car, i can run."); } }
各类加强的使用:
(1)前置加强
上面介绍步骤的例子就是一个前置加强。
控制台输出:
authority before
i am a car, i can run.
(2)后置加强
能够为多个加强使用同一个注解,如:
/** * Created by solverpeng on 2016/7/27. */ @Aspect @Component public class AuthorityAspect { @Before("@annotation(com.nucsoft.spring.annotation.AuthorityTag)") public void before(JoinPoint point) { System.out.println("authority before"); } @After("@annotation(com.nucsoft.spring.annotation.AuthorityTag)") public void afterAdvice(JoinPoint point) { System.out.println("authority after"); } }
控制台输出:
authority before
i am a car, i can run.
authority after
也能够为每一个加强使用不一样的注解,如:
注解:
/** * Created by solverpeng on 2016/7/27. */ @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface BeforeAuthorityTag {}
/** * Created by solverpeng on 2016/7/27. */ @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface AfterAuthorityTag {}
切面:
/** * Created by solverpeng on 2016/7/27. */ @Aspect @Component public class AuthorityAspect { @Before("@annotation(com.nucsoft.spring.annotation.BeforeAuthorityTag)") public void before(JoinPoint point) { System.out.println("authority before"); } @After("@annotation(com.nucsoft.spring.annotation.AfterAuthorityTag)") public void afterAdvice(JoinPoint point) { System.out.println("authority after"); } }
使用:
/** * @author solverpeng * @create 2016-07-27-9:59 */ @Component public class Car implements Wheel{ @BeforeAuthorityTag @AfterAuthorityTag @Override public void run() { System.out.println("i am a car, i can run."); } }
控制台输出:
authority before
i am a car, i can run.
authority after
(3)环绕加强
注解类:
/** * Created by solverpeng on 2016/7/27. */ @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface AroundAuthorityTag {}
切面加强:
@Around(value = "@annotation(com.nucsoft.spring.annotation.AroundAuthorityTag)") public Object aroundAdvice(ProceedingJoinPoint point) throws Throwable { Object result = point.proceed(); after(); System.out.println("authority aroundAdvice"); return result; } private void after() { System.out.println("after"); }
目标类:
/** * @author solverpeng * @create 2016-07-27-9:59 */ @Component public class Car implements Wheel{ @AroundAuthorityTag @Override public void run() { System.out.println("i am a car, i can run."); } }
控制台输出:
i am a car, i can run.
after
authority aroundAdvice
(4)返回加强
注解类:
/** * Created by solverpeng on 2016/7/27. */ @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface AfterReturningAuthorityTag {}
切面加强:
@AfterReturning(value = "@annotation(com.nucsoft.spring.annotation.AfterReturningAuthorityTag)", returning = "result") public void afterReturningAdvice(JoinPoint point, Object result) { System.out.println("authority afterReturning"); }
目标类:
/** * @author solverpeng * @create 2016-07-27-9:59 */ @Component public class Car implements Wheel{ @AfterReturningAuthorityTag @Override public void run() { System.out.println("i am a car, i can run."); } }
控制台输出:
i am a car, i can run.
authority afterReturning
(5)抛出加强
注解类:
/** * Created by solverpeng on 2016/7/27. */ @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface AfterThrowingAuthorityTag {}
切面加强:
@AfterThrowing(value = "@annotation(com.nucsoft.spring.annotation.AfterThrowingAuthorityTag)", throwing = "e") public void afterThrowAdvice(JoinPoint point, Exception e) { System.out.println(e.getMessage()); System.out.println("authority afterThrowing"); }
目标类:
@Component public class Car implements Wheel{ @AfterThrowingAuthorityTag @Override public void run() { System.out.println("i am a car, i can run."); throw new RuntimeException("throw a new runtimeException"); } }
控制台输出:
i am a car, i can run.
throw a new runtimeException
authority afterThrowing
java.lang.RuntimeException: throw a new runtimeException
(6)引入加强:关键字:@DeclareParents
将要引入的接口:
/**
* Created by solverpeng on 2016/7/26.
*/
public interface Fly {
void fly(); }
将要引入的接口的实现:
/**
* @author solverpeng
* @create 2016-07-26-20:55
*/
public class SuperMan implements Fly{
@Override
public void fly() { System.out.println("i am super man, i can fly."); } }
切面类:
/**
* @author solverpeng
* @create 2016-07-26-17:42
*/
@Aspect
@Component
public class LoggingAspect { @DeclareParents(value = "com.nucsoft.spring.target.impl.Student", defaultImpl = SuperMan.class) private Fly fly; }
测试类:
/**
* @author solverpeng
* @create 2016-07-26-18:15
*/
public class SpringTest {
@Test
public void test01() { ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml"); Person student = context.getBean(Student.class); String james = student.say("james"); System.out.println(james); Fly fly = (Fly) student; fly.fly(); } }
控制台输出:
hello,james
i am super man, i can fly.
说明:
<1>.在 Aspect 类中定义一个须要引入加强的接口,它也就是运行时须要动态实现的接口。
<2>.@DeclareParents 注解:
value 属性:目标类,能够是一个 AspectJ 类型的表达式,能够引入到多个类中。
defaultImpl 属性:引入接口的默认实现类。
<3>.虽然切面类中标注有@DeclareParents 注解 的属性能够是任意的,可是通常仍是将其设置为 引入加强类型。
<4>.从 ApplicationContext 中获取到的 student 对象实际上是一个代理对象,能够转型为本身静态实现的接口 Person,也能够转型为动态实现的接口 Fly,切换起来很是方便。
3.3 对比基于注解的切点表达式和注解表达式
注解表达式:更加灵活,可是相应的在开发的过程当中,须要程序员手动的去为每一个须要添加加强的方法添加对应注解。更加容易扩展。
切点表达式:能够写出通用的加强,也不须要程序员手动的去为每一个方法添加加强,可是须要切点表达式适配。
7、小的知识点
1.利用方法签名编写 AspectJ 切点表达式
execution * com.nucsoft.spring.Calculator.*(..): 匹配 Calculator 中声明的全部方法,
第一个 * 表明任意修饰符及任意返回值. 第二个 * 表明任意方法. .. 匹配任意数量的参数. 若目标类与接口与该切面在同一个包中, 能够省略包名.
execution public * Calculator.*(..): 匹配 ArithmeticCalculator 接口的全部公有方法.
execution public double Calculator.*(..): 匹配 Calculator 中返回 double 类型数值的方法
execution public double Calculator.*(double, ..): 匹配第一个参数为 double 类型的方法, .. 匹配任意数量任意类型的参数
execution public double Calculator.*(double, double): 匹配参数类型为 double, double 类型的方法.
2.能够合并切点表达式 使用 &&, ||, ! 来合并。如:
execution(void run()) || execution(void say())
3.切面优先级:
能够经过实现 Ordered 接口或利用 @Order 注解指定。
(1)实现 Ordered 接口,getOrder() 方法返回的值越小,优先级越高。
(2)使用 @Order 注解,须要出如今注解中,一样是值越小优先级越高。
4.重用切点定义
在 AspectJ 切面中, 能够经过 @Pointcut 注解将一个切入点声明成简单的方法. 切入点的方法体一般是空的, 由于将切入点定义与应用程序逻辑混在一块儿是不合理的。
切入点方法的访问控制符同时也控制着这个切入点的可见性。若是切入点要在多个切面中共用, 最好将它们集中在一个公共的类中。
在这种状况下, 它们必须被声明为 public。在引入这个切入点时, 必须将类名也包括在内。若是类没有与这个切面放在同一个包中, 还必须包含包名。
其余通知能够经过方法名称引入该切入点。
如:
@Pointcut("execution(void run())") public void LoggingPointcut(){}; @Before("LoggingPointcut()") public void before() { System.out.println("before"); }
8、总结
1.在学习 Spring AOP 以前,最好先理解了 静态代理,JDK动态代理,CGLIB动态代理。
2.明白切面和加强以及切点之间的关系
3.几个加强之间的区别
除环绕加强外,全部的链接点使用的都是 JoinPoint 类型的入参,而环绕加强使用的是 ProceedingJoinPoint。
返回加强能接受到返回值
抛出加强能接收到抛出的异常
环绕加强的返回值类型为目标方法返回值类型
4.Spring AOP 支持声明式和基于注解的方式,注解优先。
5.步骤:
1.定义切面,以及加强,编写切点表达式
2.若是切点表达式是基于注解的,还须要对目标方法添加对应的注解。