1AOP简介
通知(Advice):通知定义了切面是什么以及何时使用。除了要描述切面要完成的工作,通知还解决了何时执行这个工作的问题。
连接点(Joinpoint):连接点是在程序执行过程中能够插入切面的一个点。这个店可以是方法被调用时、异常被抛出时、甚至字段被编辑时。切面代码可以通过这些点插入到程序的一般流程之中,从而添加新的行为。
切入点(Poincut):切入点可以缩小切面通知的连接点的范围。如果说通知定义了切面的“什么”和“何时”,那么切入点就定义了“何地”。
切面(Aspect):切面是通知和切入点的组合。
引入(Introduction):“引入”允许我们向现有的类添加新方法或者属性。
目标(Target):被通知的对象。
代理(Proxy):是向目标对象应用通知之后被创建的对象。
织入(Weaving):是把切面应用到目标对象来创建新的代理对象的过程。编译时、类加载时、运行时。
主流的Spring:
AspectJ\JBoss AOP\Spring AOP
Spring对AOP的支持:
经典的基于代理的AOP(各版本Spring);
@AspectJ注解驱动的切面。(仅Spring2.0);
纯POJO切面(仅Spring2.0);
注入式AspectJ切面(各版本Spring)。
Spring通知是用Java编写的:
Spring创建的全部通知都是用标准的Java编写的。
Spring在运行时通知对象:
Spring利用代理类包裹切面,从而把他们注入到Spring管理的Bean里。
Spring只支持方法连接点:
由于Spring是基于动态代理的,他只支持方法连接点。
2创建典型的Spring切面
Spring AOP的通知类型:
Before(前):org.springframework.aop.MethodBeforeAdvice
After-returning(返回后):org.springframework.aop.AfterReturningAdvice
After-throwing(抛出后):org.springframework.aop.ThrowsAdvice
Around(包围):org.aopalliance.intercept.MethodInterceptor
Introduction(引入):org.springframework.aop.IntroductionInterceptor
除了Around之外,这些接口都属于Spring。
demo:
通知调用的方法类
-
package cn.partner4java.springidol;
-
-
-
-
-
-
-
-
public class Audience
{
-
public void takeSeats(){
-
System.out.println("The
audience is taking their seats.");
-
}
-
-
public void appluad(){
-
System.out.println("CLAP
CLAP!");
-
}
-
-
public void demandRefund(){
-
System.out.println("Boo!
We want our moeny back!");
-
}
-
-
}
定义两种实现方式的通知:
方式1:利用前置、后置、异常通知
-
package cn.partner4java.springidol;
-
-
import java.lang.reflect.Method;
-
-
import org.springframework.aop.AfterReturningAdvice;
-
import org.springframework.aop.MethodBeforeAdvice;
-
import org.springframework.aop.ThrowsAdvice;
-
-
-
-
-
-
-
-
-
public class AudienceAdvice implements MethodBeforeAdvice,
-
AfterReturningAdvice,
ThrowsAdvice {
-
private Audience
audience;
-
-
public void setAudience(Audience
audience) {
-
this.audience
= audience;
-
}
-
-
public void before(Method
method, Object[] args, Object target)
-
throws Throwable
{
-
audience.takeSeats();
-
}
-
-
public void afterReturning(Object
returnValue, Method method,
-
Object[]
args, Object target) throws Throwable
{
-
audience.appluad();
-
}
-
-
-
-
public void afterThrowing(Throwable
throwable){
-
audience.demandRefund();
-
}
-
-
}
方式2:环绕通知
-
package cn.partner4java.springidol;
-
-
import org.aopalliance.intercept.MethodInterceptor;
-
import org.aopalliance.intercept.MethodInvocation;
-
-
-
-
-
-
-
public class AudienceAroundAdvice implements MethodInterceptor
{
-
private Audience
audience;
-
-
public void setAudience(Audience
audience) {
-
this.audience
= audience;
-
}
-
-
public Object
invoke(MethodInvocation invocation) throws Throwable
{
-
try {
-
audience.takeSeats();
-
Object
returnValue = invocation.proceed();
-
audience.appluad();
-
-
return returnValue;
-
} catch (Exception
e) {
-
audience.demandRefund();
-
throw e;
-
}
-
}
-
-
}
定义切点和通知者:
声明正则表达式切点、联合切点与通知者:
-
<?xml
version="1.0" encoding="UTF-8"?>
-
<beans
xmlns="http://www./schema/beans"
-
xmlns:xsi="http://www./2001/XMLSchema-instance"
-
xmlns:aop="http://www./schema/aop"
-
xsi:schemaLocation="
-
http:
-
http:
-
http:
-
http:
-
-
<bean
id="audience" class="cn.partner4java.springidol.Audience"/>
-
-
<!--
通知(Advice):通知定义了切面是什么以及何时使用 -->
-
<bean
id="advice1" class="cn.partner4java.springidol.AudienceAdvice">
-
<property
name="audience" ref="audience"></property>
-
</bean>
-
<bean
id="advice2" class="cn.partner4java.springidol.AudienceAroundAdvice">
-
<property
name="audience" ref="audience"></property>
-
</bean>
-
-
-
<!--
切入点(Poincut):切入点可以缩小切面通知的连接点的范围 -->
-
<bean
id="performancePointcut1" class="org.springframework.aop.support.JdkRegexpMethodPointcut">
-
<property
name="pattern" value=".*perform"></property>
-
</bean>
-
<!--
定义AspectJ方式切点 -->
-
<bean
id="performancePointcut2" class="org.springframework.aop.aspectj.AspectJExpressionPointcut">
-
<property
name="expression" value="execution(*
Performer+.perform(..))"></property>
-
</bean>
-
-
-
<!--
切面(Aspect):切面是通知和切入点的组合 (通知者)-->
-
<bean
id="audienceAdvisor1" class="org.springframework.aop.support.DefaultPointcutAdvisor">
-
<property
name="advice" ref="advice1"></property>
-
<property
name="pointcut" ref="performancePointcut1"></property>
-
</bean>
-
<bean
id="audienceAdvisor2" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
-
<property
name="advice" ref="advice1"></property>
-
<property
name="pattern" value=".*perform"></property>
-
</bean>
-
-
<bean
id="performer" class="cn.partner4java.springidol.PerformerBean"></bean>
-
-
</beans>
定义AspectJ切点
正则表达式虽然可以作为切点定义语言来使用,但他并不是针对切点而创建的,其主要用途换是文本解析。与之相比,从AspectJ里定义切点的方式就可以看出AspectJ的切点语言是一种真正的切点表达式语言。
使用ProxyFactoryBean:
代理(Proxy):是向目标对象应用通知之后被创建的对象
利用ProxyFactoryBean代理被切面的对象。
-
<?xml
version="1.0" encoding="UTF-8"?>
-
<beans
xmlns="http://www./schema/beans"
-
xmlns:xsi="http://www./2001/XMLSchema-instance"
-
xmlns:aop="http://www./schema/aop"
-
xsi:schemaLocation="
-
http:
-
http:
-
http:
-
http:
-
-
<bean
id="audience" class="cn.partner4java.springidol.Audience"/>
-
-
<!--
通知(Advice):通知定义了切面是什么以及何时使用 -->
-
<bean
id="advice1" class="cn.partner4java.springidol.AudienceAdvice">
-
<property
name="audience" ref="audience"></property>
-
</bean>
-
<bean
id="advice2" class="cn.partner4java.springidol.AudienceAroundAdvice">
-
<property
name="audience" ref="audience"></property>
-
</bean>
-
-
-
<!--
切入点(Poincut):切入点可以缩小切面通知的连接点的范围 -->
-
<bean
id="performancePointcut1" class="org.springframework.aop.support.JdkRegexpMethodPointcut">
-
<property
name="pattern" value=".*perform"></property>
-
</bean>
-
<!--
定义AspectJ方式切点 -->
-
<bean
id="performancePointcut2" class="org.springframework.aop.aspectj.AspectJExpressionPointcut">
-
<property
name="expression" value="execution(*
Performer+.perform(..))"></property>
-
</bean>
-
-
-
<!--
切面(Aspect):切面是通知和切入点的组合 (通知者)-->
-
<bean
id="audienceAdvisor1" class="org.springframework.aop.support.DefaultPointcutAdvisor">
-
<property
name="advice" ref="advice1"></property>
-
<property
name="pointcut" ref="performancePointcut1"></property>
-
</bean>
-
<bean
id="audienceAdvisor2" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
-
<property
name="advice" ref="advice1"></property>
-
<property
name="pattern" value=".*perform"></property>
-
</bean>
-
-
-
<!--
使用ProxyFactoryBean -->
-
<bean
id="performer" class="cn.partner4java.springidol.PerformerBean"></bean>
-
-
<bean
id="duke" class="org.springframework.aop.framework.ProxyFactoryBean">
-
<property
name="target" ref="performer"></property>
-
<property
name="interceptorNames" value="audienceAdvisor1"></property>
-
<property
name="proxyInterfaces" value="cn.partner4java.springidol.Performer"></property>
-
</bean>
-
-
</beans>
调用:
-
package cn.partner4java.springidol;
-
-
import org.springframework.context.ApplicationContext;
-
import org.springframework.context.support.ClassPathXmlApplicationContext;
-
-
public class HelloWorld
{
-
-
public static void main(String[]
args) {
-
ApplicationContext
ctx = new ClassPathXmlApplicationContext(
-
"/META-INF/spring/springido.xml");
-
-
Performer
performer = (Performer) ctx.getBean("duke");
-
performer.perform();
-
}
-
-
}
3自动代理
@AspectJ注解
@AspectJ跟AspectJ没有关系。他是Spring用来解析连接点和通知的一组java 5注解。
demo1:
-
package cn.partner4java.spring.aspectj;
-
-
import org.aspectj.lang.ProceedingJoinPoint;
-
import org.aspectj.lang.annotation.Around;
-
import org.aspectj.lang.annotation.Aspect;
-
-
-
-
-
-
-
-
@Aspect
-
public class LoggingAspect
{
-
-
@Around("execution(*
cn.partner4java.spring.aspectj.*.*(..))")
-
public Object
log(ProceedingJoinPoint joinPoint) throws Throwable
{
-
System.out.println("begin");
-
Object
ret = joinPoint.proceed();
-
System.out.println("after");
-
return ret;
-
}
-
}
-
package cn.partner4java.spring.aspectj;
-
-
-
-
-
-
-
public class TestBean
{
-
public void work()
{
-
System.out.println("work");
-
}
-
-
public void stop()
{
-
System.out.println("stop");
-
}
-
}
-
<?xml
version="1.0" encoding="UTF-8"?>
-
<beans
xmlns="http://www./schema/beans"
-
xmlns:xsi="http://www./2001/XMLSchema-instance"
-
xmlns:aop="http://www./schema/aop"
-
xsi:schemaLocation="
-
http:
-
http:
-
http:
-
http:
-
-
<bean
id="test" class="cn.partner4java.spring.aspectj.TestBean"/>
-
<bean class="cn.partner4java.spring.aspectj.LoggingAspect">
-
</bean>
-
<aop:aspectj-autoproxy
/>
-
-
</beans>
-
package cn.partner4java.spring.aspectj;
-
-
import org.springframework.context.ApplicationContext;
-
import org.springframework.context.support.ClassPathXmlApplicationContext;
-
-
public class LoggingAspectDemo
{
-
-
public static void main(String[]
args) {
-
ApplicationContext
ac = new ClassPathXmlApplicationContext(
-
"/META-INF/spring/ataspectjdemo1-context.xml"
-
);
-
TestBean
testBean = (TestBean) ac.getBean("test");
-
testBean.work();
-
testBean.stop();
-
-
-
-
-
-
-
-
}
-
-
}
@AspectJ方面详解
切入点声明表达式可以引入别人的表达式如:
@Pointcut("execution(* com.apress.prospring2.ch06.simple.TestBean.*(..))")
private void testBeanExecution() { }
@Around("testBeanExecution()")
public Object log(ProceedingJoinPoint pjp) throws Throwable {
或者
@Around("SystemPointcuts.testBeanExecution()")
public Object log(ProceedingJoinPoint pjp) throws Throwable {
切入点表达式:
execution:匹配方法执行连接点。我们可以指定包、类或者方法名,以及方法的可见性、返回值和参数类型。这是应用的最为广泛的切入点表达式。
within:匹配那些在已声明的类型中执行的连接点。
this:通过用bean引用的类型跟指定的类型做对比来匹配连接点。
args:通过比较方法的参数类型跟指定的参数类型来匹配连接点。
@target:通过检查调用目标对象是否具有特定注解来匹配连接点。
@args:跟args类似,不过@args检查的是方法参数的注解而不是他们的类型。
@within:跟within相似,这个表达式匹配那些带有特定注解的类中执行的连接点。
@annotation:通过检查讲被调用的方法上的注解是否为指定的注解来匹配连接点。
bean:通过比较bean的ID来匹配连接点,我们也可以在bean名模式中使用通配符。
我们可以使用||(或)和&&(与)组合切入点表达式。!(非)取否。
探讨切入点表达式:
1、execution表达式
如:
* com.partner4java..*.*(..)
第一个*表示任意返回类型,..*表示包下的所有类,*(..)表示任意方法和任意参数.
2、within表达式
within(com..TestBean):匹配从TestBean的方法中产生的调用
within(com..*):匹配在com包及其子包的任意类中对任意方法调用的执行过程的切入点。
3、this表达式
this表达式和within表达有些类似,但是不能使用..或者*等通配符。
this(class-name)
4、target表达式
和this完全一样。
5、args表达式
args(type-pattern?(,type-pattern)*)
可以使用..通配符
execution(* SimpleBean.*(String,String))
args(Integer,..,String)
6、@target表达式
4定义纯粹的POJO切面
Spring开发组意识到使用ProxyFactoryBean有些欠优雅,所以致力于提供一种更好的切面声明方式。Spring 2.0里新的XML配置元素就体现出了这种努力。
Spring 2.0的AOP配置元素:
<aop:advisor>:定义一个AOP通知者
<aop:after>:定义一个AOP后通知(不考虑被通知的方法是否成功返回)
<aop:after-returning>:定义一个AOP返回后通知
<aop:after-throwing>:定义一个AOP抛出后通知
<aop:around>:定义一个AOP包围通知
<aop:aspect>:定义一个切面
<aop:before>:顶一个AOP前置通知
<aop:config>:顶级AOP元素。大多数<aop:*>元素必须包含在
<aop:pointcut>:定义一个切点
demo:
使用上面的元素来定义一个完整的切面,其中包含通知者和切入点,不同的是,我们不需要自己声明代理(Proxy)就完成了织入(Weaving)。
-
<?xml
version="1.0" encoding="UTF-8"?>
-
<beans
xmlns="http://www./schema/beans"
-
xmlns:xsi="http://www./2001/XMLSchema-instance"
-
xmlns:aop="http://www./schema/aop"
-
xsi:schemaLocation="
-
http:
-
http:
-
http:
-
http:
-
-
<bean
id="audience" class="cn.partner4java.springidol.Audience"/>
-
-
<bean
id="performer" class="cn.partner4java.springidol.PerformerBean"></bean>
-
-
<aop:config>
-
<aop:aspect
ref="audience">
-
<aop:pointcut
expression="execution(*
*.perform*(..))" id="performance"/>
-
<aop:before
method="takeSeats" pointcut-ref="performance"/>
-
<aop:after-returning
method="appluad" pointcut-ref="performance"/>
-
<aop:after-throwing
method="demandRefund" pointcut-ref="performance"/>
-
</aop:aspect>
-
</aop:config>
-
</beans>
调用:
-
ApplicationContext
ctx = new ClassPathXmlApplicationContext(
-
"/META-INF/spring/spring-pojo1.xml");
-
-
Performer
performer = (Performer) ctx.getBean("performer");
-
performer.perform();
5注入AspectJ切面
虽然Spring AOP对于大多数切面程序来说就足够了,但是与AspectJ相比,他只能算是一个功能较弱的AOP解决方案。AspectJ提供了Spring AOP不可能实现的多种切点类型。
(如果想更深入的了解AspectJ,可以阅读Raminvas Ladded的AspectJ in Action(Manning,2003))
demo:
创建第一个AspectJ方面。
Spring通过添加aspectOf()方法增加对AspectJ的支持,不需要你额外的管理容器。
创建一个aj格式的文件,也就是AspectJ的切面:
-
package cn.partner4java.aspectj;
-
-
public aspect
StockServiceAspect {
-
private String
suffix;
-
private String
prefix;
-
-
public void setPrefix(String
prefix) {
-
this.prefix
= prefix;
-
}
-
-
public void setSuffix(String
suffix) {
-
this.suffix
= suffix;
-
}
-
-
pointcut
doServiceCall() :
-
execution(*
cn.partner4java.aspectj.*.*(..));
-
-
before()
: doServiceCall() {
-
System.out.println(this.prefix);
-
}
-
-
after()
: doServiceCall() {
-
System.out.println(this.suffix);
-
}
-
}
配置文件:额外添加factory-method="aspectOf"知名是AspectJ文件
-
<?xml
version="1.0" encoding="UTF-8"?>
-
<beans
xmlns="http://www./schema/beans"
-
xmlns:xsi="http://www./2001/XMLSchema-instance"
-
xsi:schemaLocation="
-
http:
-
http:
-
-
<bean
id="stockService" class="cn.partner4java.aspectj.DefaultStockService"/>
-
<!--
额外添加factory-method="aspectOf"知名是AspectJ文件
-->
-
<bean class="cn.partner4java.aspectj.StockServiceAspect"
-
factory-method="aspectOf">
-
<property
name="prefix" value="Before
call"/>
-
<property
name="suffix" value="After
call"/>
-
</bean>
-
-
</beans>
调用:为了让示例程序正常运行,我们必须使用AspectJ编译器(需要像使用JDK那样安装,配置环境变量等)
-
package cn.partner4java.aspectj;
-
-
import org.springframework.context.ApplicationContext;
-
import org.springframework.context.support.ClassPathXmlApplicationContext;
-
-
-
public class HelloWorld
{
-
-
public static void main(String[]
args) {
-
-
ApplicationContext
ac = new ClassPathXmlApplicationContext(
-
"/META-INF/spring/aspectjdemo1-context.xml"
-
);
-
StockService
stockService = (StockService) ac.getBean("stockService");
-
System.out.println(stockService.getStockLevel("ABC"));
-
}
-
-
}
AspectJ还可以指定“加载时织入”:
使用spring-agent.jar作为JVM代理来替代<aop:aspectj-autoproxy/>,并使用上下文命名空间来初始化加载时织入。
除了ApplicationContext的XML配置文件之外,还需要创建META-INF/aop.xml文件。这个aop.xml文件是一个标准的AspectJ组件。他告诉AspectJ注入器在加载时织入哪些类。
Spring加入<context:load-time-weaver>,可以配置aspectj-weaving,设置on或者off设置开启或关闭织入。
|