1、概念术语
在开始之前,需要理解Spring aop 的一些基本的概念术语(总结的个人理解,并非Spring官方定义):
切面(aspect):用来切插业务方法的类。
连接点(joinpoint):是切面类和业务类的连接点,其实就是封装了业务方法的一些基本属性,作为通知的参数来解析。
通知(advice):在切面类中,声明对业务方法做额外处理的方法。
切入点(pointcut):业务类中指定的方法,作为切面切入的点。其实就是指定某个方法作为切面切的地方。
目标对象(target object):被代理对象。
AOP代理(aop proxy):代理对象。
通知:
前置通知(before advice):在切入点之前执行。
后置通知(after returning advice):在切入点执行完成后,执行通知。
环绕通知(around advice):包围切入点,调用方法前后完成自定义行为。
异常通知(after throwing advice):在切入点抛出异常后,执行通知。
Spring AOP使用了两种代理机制,一种是基于JDK的动态代理,另一种是基于CGLib的动态代理,之所以需要两种代理机制,很大程度上是因为JDK本身只提供基于接口的代理,不支持类的代理。
基于JDK的代理和基于CGLib的代理是Spring AOP的核心实现技术
2、AOP接口实现方式
利用Spring AOP接口实现AOP,主要是为了指定自定义通知来供spring AOP机制识别。主要接口:前置通知 MethodBeforeAdvice ,后置通知:AfterReturningAdvice,环绕通知:MethodInterceptor,异常通知:ThrowsAdvice 。
a、业务接口
package org.lazyzhong.dao;
public interface BossSpeak {
public void speak();
}
b、业务实现类
package org.lazyzhong.daoimpl;
import org.lazyzhong.dao.BossSpeak;
public class BossSpeakImpl implements BossSpeak{
@Override
public void speak() {
System.out.println("老板——————大家工作辛苦啦。。。我决定给大家发奖金1毛。。。");
}
}
c、前置通知
package org.lazyzhong.util;
import java.lang.reflect.Method;
import org.springframework.aop.MethodBeforeAdvice;
public class BeforeInter implements MethodBeforeAdvice {
@Override
public void before(Method arg0, Object[] arg1, Object arg2)
throws Throwable {
System.out.println("今天是个好日子啊。。。看到上个月的销售额,老板感到很开心。。。于是召集大家开会。。。");
}
}
d、后置通知
package org.lazyzhong.util;
import java.lang.reflect.Method;
import org.springframework.aop.AfterReturningAdvice;
public class AfterInter implements AfterReturningAdvice{
@Override
public void afterReturning(Object arg0, Method arg1, Object[] arg2,
Object arg3) throws Throwable {
System.out.println("会议结束。。。大家各回各的位置继续工作。。。");
}
}
e、环绕通知
package org.lazyzhong.util;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
public class AroundInter implements MethodInterceptor{
@Override
public Object invoke(MethodInvocation arg0) throws Throwable {
//speak()方法开始。。。进入环绕方法
System.out.println("老板要开始讲话了。。。各位员工找个位置坐好。。。");
arg0.proceed();
//speak()方法结束。。。进入环绕方法
System.out.println("听到这个消息。。各位员工感到灰常开心。。。于是。。啪啪啪的声音响起。。");
return null;
}
}
f、切点
package org.lazyzhong.util;
import java.lang.reflect.Method;
import org.springframework.aop.support.NameMatchMethodPointcut;
public class Pointcut extends NameMatchMethodPointcut {
@Override
public boolean matches(Method arg0, Class arg1) {
//配置单个方法
this.setMappedName("speak");
// 配置多个方法
// String[] methods = { "xxx", "yyy" };
// this.setMappedNames(methods);
return super.matches(arg0, arg1);
}
}
g、配置
<bean id="beforeAdvice" class="org.lazyzhong.util.BeforeInter" />
<bean id="afterAdvice" class="org.lazyzhong.util.AfterInter" />
<bean id="aroundAdvice" class="org.lazyzhong.util.AroundInter" />
<bean id="bossSpeak" class="org.lazyzhong.daoimpl.BossSpeakImpl" />
<bean id="pointcutBean" class="org.springframework.aop.support.NameMatchMethodPointcut">
<property name="mappedNames">
<list>
<value>speak</value>
</list>
</property>
</bean>
<!-- 指定切点匹配类 -->
<bean id="pointcut" class="org.lazyzhong.util.Pointcut" />
<bean id="matchBeforeAdvice" class="org.springframework.aop.support.DefaultPointcutAdvisor">
<property name="pointcut">
<ref bean="pointcut" /> <!-- 也可以替换成pointcutBean -->
</property>
<property name="advice">
<ref bean="beforeAdvice" />
</property>
</bean>
<bean id="bossProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="proxyInterfaces">
<value>org.lazyzhong.dao.BossSpeak</value>
</property>
<property name="target">
<ref bean="bossSpeak"/>
</property>
<property name="interceptorNames">
<list>
<value>matchBeforeAdvice</value>
<value>afterAdvice</value>
<value>aroundAdvice</value>
</list>
</property>
</bean>
h、测试类
package org.lazyzhong.test;
import org.lazyzhong.dao.BossSpeak;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MyTest1 {
public static void main(String[] args) {
ApplicationContext context=new ClassPathXmlApplicationContext("beans.xml");
BossSpeak boss=(BossSpeak)context.getBean("bossProxy");
boss.speak();
}
}
i、输出:
今天是个好日子啊。。。看到上个月的销售额,老板感到很开心。。。于是召集大家开会。。。
老板要开始讲话了。。。各位员工找个位置坐好。。。
老板——————大家工作辛苦啦。。。我决定给大家发奖金1毛。。。
听到这个消息。。各位员工感到灰常开心。。。于是。。啪啪啪的声音响起。。
会议结束。。。大家各回各的位置继续工作。。。
3、方式二——schema方式
a、业务类
package org.lazyzhong.dao;
public class EmployeeSpeak {
public void speak(){
System.out.println("员工——老板真抠门");
}
}
b、切面类:切面类中包含所有通知
package org.lazyzhong.util;
import org.aspectj.lang.JoinPoint;
public class AspectAdvice {
public void doBefore(JoinPoint jp) {
System.out.println("员工们有话要讲。。。。");
}
public void doAfter(JoinPoint jp) {
System.out.println("员工们被炒鱿鱼了。。。");
}
}
c、配置
<bean id="employee" class="org.lazyzhong.dao.EmployeeSpeak" />
<!-- 声明通知类 -->
<bean id="aspectAdvice" class="org.lazyzhong.util.AspectAdvice" />
<aop:config>
<aop:aspect id="TestAspect" ref="aspectAdvice">
<aop:pointcut id="point_cut"
expression="execution(* org.lazyzhong.dao.*.*(..))" />
<aop:before pointcut-ref="point_cut" method="doBefore"/>
<aop:after pointcut-ref="point_cut" method="doAfter"/>
</aop:aspect>
</aop:config>
d、测试类
package org.lazyzhong.test;
import org.lazyzhong.dao.EmployeeSpeak;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MyTest2 {
public static void main(String[] args) {
ApplicationContext context=new ClassPathXmlApplicationContext("beans.xml");
EmployeeSpeak employee=(EmployeeSpeak)context.getBean("employee");
employee.speak();
}
}
e、输出
员工们有话要讲。。。。
员工——老板真抠门
员工们被炒鱿鱼了。。。
切入点表达式:
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern) throws-pattern?)
modifiers-pattern:方法的操作权限
ret-type-pattern:返回值
declaring-type-pattern:方法所在的包
name-pattern:方法名
parm-pattern:参数名
throws-pattern:异常
其中,除ret-type-pattern和name-pattern之外,其他都是可选的。
Spring中的切入点分为两类:静态和动态。因为静态切入点的性能要优于动态切入点,所以优先考虑使用。Spring 为我们提供创建静态切入点很实用的类NameMatchMethodPointcut。在这个类中,我们只需要关心setMappedName和 setMappedNames方法。你可以使用具体的类名,也可以使用通配符。如:设置mappedName属性为set* 则匹配所有的set方法。Spring还提供了另通过正则表达式来创建静态切入点的实用类RegexpMethodPointcut。通过使用Perl样 式的正则表达式来定义你感兴趣的方法。当切入点需要运行时参数值来执行通知时,这时就需要使用动态切入点。静态切入点只限定于给定的方法和目标类,而不考虑方法的参数。