AOP语言:通知(advice)、切点(pointcut)、连接点(join point)。
通知(advice):切面的工作被称为通知;
1. 前置通知(Before):在目标方法调用前调用通知功能;
2. 后置通知(After):在目标方法调用后调用通知功能;
3. 返回通知(After-returning):在目标方法成功执行够调用;
4. 异常通知(After-throwing):在目标方法抛出异常后调用;
5. 环绕通知(Around):在目标方法前后调用。
连接点(join point):应用执行过程中能够插入切面的一个点,这个点可以是调用方法、抛出异常、甚至修改字段时。
切点(pointcut):匹配通知所要织入的一个或多个连接点。
切面(Aspect):通知和切点的结合。
引入(Introduction):允许我们向现有的类添加方法和属性。
织入(Weaving):把切面应用到目标对象并创建新的代理对象的过程。
在目标对象的生命周期有多个连接点可以进行织入:
1. 编译期:切面在目标编译时被织入。这种方式需要特殊的编译器,AspectJ织入编译器就是以这种方式织入切面的。
2. 类加载期:切面在目标被加载到JVM时织入。需要特殊的类加载器,可以在目标类被引入应用之前增强该目标类的字节码。AspectJ5的加载时织入(load-time-weaving LTW)就支持以这种方式织入切面。
3. 运行期:切面在应用运行的某个时刻被织入。在织入切面时,AOP容器会为目标对象动态创建一个代理对象,SpringAOP就是以这种方式织入切面的。
Spring对AOP的支持
Spring提供了4种类型的AOP支持:
1. 基于代理的SpringAOP;
2. 纯POJO切面;
3. @AspectJ注解切面;
4. 注入式AspectJ切面(试用Spring各个版本)。
前三种都是Spring AOP的变体,Spring AOP构建在动态代理基础之上,因此,Spring对AOP的支持局限于方法拦截。
借助Spring的aop命名空间,我们可以将纯POJO转换为切面。
如果你的AOP超出了简单的方法调用(如构造器和属性拦截),那么需要考虑使用AspectJ来实现切面。
Spring通知是Java编写的。
Spring在运行时通知对象。
Spring只支持方法级别的连接点。
Spring AOP的AspectJ切点,最重要一点就是Spring仅支持AspectJ切点指示器的一个字子集。Spring AOP是基于代理的,而某些切点表达式是与基于代理的AOP无关的。
Spring借助AspectJ切点表达式语言定义Spring切面:
arg():限制连接点匹配参数为指定类型的执行方法;
@args():限制连接点匹配参数由指定注解标注的执行方法;
execution():用于匹配是连接点的执行方法;
this():限制连接点匹配AOP代理的bean引用为指定类型的类;
target:限制连接匹配目标对象为指定类型的类;
@target:限制连接点匹配特定的执行对象,这些对象对应的类上要有指定类型的注解;
within():限制连接点匹配指定的类型;
@within():限制连接点匹配指定注解所标志的类型(当使用Spring AOP时,方法定义在由指定注解所标注的类里)
@annotation:限定匹配带有指定注解的连接点。
在Spring中使用其它AspectJ指示器时,当抛出异常。
当我们查看以上所展示的指示器,只有execution是实际执行匹配的,其它都是用来限制匹配的。
例子:
在描述切点时,可以使用“and”代替“&&”,“or”和“not”分别代替“||”“!”。
在切点中选择bean:
execution(* concert.Performance.perform())and bean('woodstock')
在这里我们希望在执行perform()方法时应用通知,但限定bean的ID。
使用注解创建切面:
使用注解创建切面是AspectJ5引入的重要特性,AspectJ5之前,编写AspectJ切面需要学习一种Java语言的扩展。
定义切面:
package com.lcf.spring.aop;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
@Aspect
public class Audience {
@Pointcut("execution(** com.lcf.spring.aop.PerformanceImp.perform(..))")
public void performance(){};
// @Before("performance()")
// public void silenceCellPhones(){
// System.out.println("silencing cell phone");
// }
// @Before("performance()")
// public void takeSeat(){
// System.out.println("take seat");
// }
// @AfterReturning("performance()")
// public void applause(){
// System.out.println("CLAP CLAP CLAP!");
// }
// @AfterThrowing("performance()")
// public void demanrefund(){
// System.out.println("demanding a refund!");
// }
//
/**
* 定义环绕通知
* ProceedingJoinPoint 参数必须存在,在通知中通过它调用别通知的方法,可以重复调用,可以不调用,从而阻塞被通知方法的调用。
*/@Around("performance()")public void watchPerformance(ProceedingJoinPoint jp){try {System.out.println("silencing cell phone");System.out.println("take seat");jp.proceed();System.out.println("CLAP CLAP CLAP!");} catch (Throwable e) {System.out.println("demanding a refund!");}}
@AspectJ表明该类不仅是一个POJO,还是一个切面。
该类也可以声明成一个bean:
@Bean
public Audience audience(){
return new Audience();
}
仅仅到此为止是无法开启注解的,所以需要在JavaConfig中或者XML中配置代理。
package com.lcf.spring.aop;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@Configuration
@EnableAspectJAutoProxy
@ComponentScan
public class AopConfig {
@Bean
public Audience audience(){
return new Audience();
}
@Bean
public Performance performance(){
return new PerformanceImp();
}
@Bean
public Teacher Teacher(){
return new Teacher();
}
@Bean
public Student Student(){
return new Student();
}
}
}@Beanpublic Student Student(){return new Student();}}
或者在XML中:
<!-- 开启AspectJ自动代理 -->
<aop:aspectj-autoproxy/>
不管你是使用JavaConfig还是XML,AspectJ自动代理都会为使用@AspectJ注解的的bean创建一个代理,这个代理会围绕着所有该切面的切点所匹配的bean。
Spring的AspectJ自动代理仅仅使用@AspectJ作为创建切面的向导,切面依然是基于代理的。这意味着Spring AOP仅限于方法级别的代理。如果想利用所有的AspectJ能力,我必须运行时使用AspectJ并且不依赖Spring来创建基于代理的切面。
处理被通知方法中的参数
args(trackNumber)限定符:它表明palyTrack()方法的int参数也会传递到通知中去。参数名称应与切点中的参数相匹配。
package com.lcf.spring.aop;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
@Aspect
public class Teacher {
@Pointcut("execution(* com.lcf.spring.aop.Student.goal(int)) && args(score)")
public void goal(int score){};
// @After("goal(score)")
// public void say(int score){
// if(score<60){
// System.out.println("该学生未及格!");
// }else{
// System.out.println("该学生及格了!");
// }
// }
@Around("goal(score)")
public void round(ProceedingJoinPoint pjp,int score){
try {
System.out.println("宣布分数");
pjp.proceed();
System.out.println("学生获取的分数为:"+score);
} catch (Throwable e) {
e.printStackTrace();
}
}
}
package com.lcf.spring.aop;
public class Student {
private int score;
public void goal(int score){
System.out.println("....");
this.score=score;
}
}
我试验了一下,pjp.proceed()方法似乎无法更改参数,这里如果想要更改int参数,不知道应该怎么办?
通过注解引入新的功能:
Java是静态语言,所以只能通过代理模式来动态的为对象增加新的能力。
动态增加新的功能:
package com.lcf.spring.aop;
public interface Encoreable {
void performEnocre();
}
package com.lcf.spring.aop;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.DeclareParents;
@Aspect
public class EnocreableIntroducer {
@DeclareParents(value="com.lcf.spring.aop.Performance+",defaultImpl=DefaultEnocreable.class)
private static Encoreable encoreable;
}
package com.lcf.spring.aop;
public class DefaultEnocreable implements Encoreable{
@Override
public void performEnocre() {
System.out.println("通过AOP引入新的方法!");
}
}
@DeclareParents注解分为三部分:
1. value属性指定了那种类型的bean需要引入该接口(+表示所有子类);
2. defaultImpl指定了为引入该功能提供实现的类;
3. @DeclareParents注解标注的静态属性指明了要引入的接口。
最后将该切面声明为一个bean:
@Bean
public EnocreableIntroducer enocreableIntroducer(){
return new EnocreableIntroducer();
}
测试:
@Autowired
private Performance per;
@Test
public void defaultEnocreable(){
Encoreable encoreable = ((Encoreable)per);
encoreable.performEnocre();
}
结果证明为Performance类增加了新的功能。
但是面向切面的注解有一个明显的劣势,你必须能够为通知类添加注解。所以必要的时候,需要在XML中声明:
在XML中声明切面:
Spring的AOP配置元素能够以非侵入式的方式声明切面:
<aop:advisor>:定义AOP通知器;
<aop:after>:定义AOP后置通知(不管通知的方法是否执行成功);
<aop:after-returning>:定义AOP返回通知;
<aop:after-throwing>:定义AOP异常通知;
<aop:around>:定义AOP环绕通知;
<aop:aspect>:定义一个切面。
<aop:aspectj-autoproxy>:启用@AspectJ注解驱动的切面;
<aop:before>:定义一个AOP前置通知;
<aop:config>:顶层的AOP配置元素。大多数的<aop:*>元素必须包含在其内;
<aop:declare-parents>:以透明的方式为被通知的对象引入新的接口;
<aop:pointcut>:定义一个切点。
通过XML配置切面
声明前置和后置通知:
<!-- 声明切面 -->
<aop:config>
<aop:aspect ref="audience">
<aop:before pointcut="execution(** com.lcf.spring.aop.Performance.perform(..))" method="silenceCellPhones"/>
<aop:before pointcut="execution(** com.lcf.spring.aop.Performance.perform(..))" method="takeSeats"/>
<aop:after-returning pointcut="execution(** com.lcf.spring.aop.Performance.perform(..))" method="applause"/>
<aop:after-throwing pointcut="execution(** com.lcf.spring.aop.Performance.perform(..))" method="demandRefund"/>
</aop:aspect>
</aop:config>
<!-- 定义切点使用 -->
<aop:config>
<aop:aspect ref="audience">
<aop:pointcut id="performance" expression="execution(** com.lcf.spring.aop.Performance.perform(..))"/>
<aop:before pointcut-ref="performance" method="silenceCellPhones"/>
<aop:before pointcut-ref="performance" method="takeSeats"/>
<aop:after-returning pointcut-ref="performance" method="applause"/>
<aop:after-throwing pointcut-ref="performance" method="demandRefund"/>
</aop:aspect>
</aop:config>
如果切点被多个切面引用,可以将其定义在<aop:config>元素内。
声明环绕通知:
<!-- 声明环绕通知 -->
<aop:config>
<aop:aspect ref="audience">
<aop:pointcut id="performance" expression="execution(** com.lcf.spring.aop.Performance.perform(..))"/>
<aop:around pointcut-ref="performance" method="watchPerformance"/>
</aop:aspect>
</aop:config>
为通知传递参数:
<!-- 为通知传递参数 -->
<bean id="teacher" class="com.lcf.spring.aop.Teacher" />
<bean id="teacher" class="com.lcf.spring.aop.Teacher">
<property name="score" value="100"/>
</bean>
<aop:config>
<aop:aspect ref="teacher">
<aop:pointcut id="goal" expression="execution(** com.lcf.spring.aop.Student.goal(int)) and args(score)"/>
<aop:around pointcut-ref="goal" method="say"/>
</aop:aspect>
</aop:config>
表达式与JavaConfig中定义切面几乎一模一样,区别只是使用了“and”代替了“&&”(因为在XML中,“&”会被解析为实体的开始)。
通过切面引入新的功能:
<!-- 通过切面引入新的功能 -->
<aop:config>
<aop:aspect>
<aop:declare-parents types-matching="com.lcf.spring.aop.Performance"
implement-interface="com.lcf.spring.aop.Encoreable" default-impl="com.lcf.spring.aop.DefaultEncoreable"/>
</aop:aspect>
</aop:config>
<!-- 可以使用定义Spring bean的方式,映入默认实现的类 -->
<bean id="encoreableDelegate" class="com.lcf.spring.aop.DefaultEncoreable"/>
<aop:config>
<aop:aspect>
<aop:declare-parents types-matching="com.lcf.spring.aop.Performance"
implement-interface="com.lcf.spring.aop.Encoreable" delegate-ref="encoreableDelegate"/>
</aop:aspect>
</aop:config>
delegate-ref属性引用一个Spring bean作为引入的委托,区别在于,这样可以被引入、通知或者使用其他的Spring配置。
注入AspectJ切面
虽然Spring AOP可以满足许多应用的切面需求,但是与AspectJ相比,功能仍旧显得弱,AspectJ提供了许多Spring AOP所不能支持的许多类型的切点。
此处没有实现,我想在maven中创建aspect类似乎无法实现,希望有懂得大佬给我解释一下!!谢谢