上两节提到Spring的装配bean还有高级装配,这一节就是Spring的另一个核心内容-AOP
AOP的基本概念
AOP称为面向切面编程,在程序开发中主要用来解决一些系统层面上的问题,比如日志,事务,权限等通用的辅助模块,在系统的很多模块都要添加。
日志,事务,权限等就是切面,使用这种面向切面编程时,我们仍然可以在一个地方声明通用功能(切面),然后声明这个功能在何时通过何种方式使用,而无需修改应用这个切面的模块。这样做的好处是我们的关注点可以集中在模块,而不是分散的通用功能,其次,模块的代码因此会变得整洁。
AOP的组成
(1)Aspect(切面):通常是一个类,里面可以定义切入点和通知
(2)JointPoint(连接点):程序执行过程中明确的点,一般是方法的调用
(3)Advice(通知):AOP在特定的切入点上执行的增强处理,有before,after,afterReturning,afterThrowing,around
(4)Pointcut(切入点):就是带有通知的连接点,在程序中主要体现为书写切入点表达式
(5)AOP代理:AOP框架创建的对象,代理就是目标对象的加强。Spring中的AOP代理可以使JDK动态代理,也可以是CGLIB代理,前者基于接口,后者基于子类
例如:
@Aspect
@Component
public class Audience {
@Pointcut("execution(* com.hsb.beans.Perform.show(..))")
public void performance() {
}
@Before("performance()")
public void beforeShow() {
System.out.println("message from beforeShow");
}
Audience就是一个切面
@Pointcut("execution(* com.hsb.beans.Perform.show(..))")
是切点
beforeShow()是连接点
@Before(“performance()”) 就是通知
下面是两种常用的aop方法:
1.在XML中声明切面
- a.编写一个原始类
package com.hsb.beans;
import org.springframework.stereotype.Repository;
@Repository
public class Perform {
public void show(){
System.out.println("message from Perform.show()");
}
}
- b.编写一个切面
package com.hsb.aop;
import org.aspectj.lang.ProceedingJoinPoint;
public class Audience {
public void beforeShow(){
System.out.println("message from beforeShow");
}
public void afterShow(){
System.out.println("message from afterShow");
}
public void around(ProceedingJoinPoint joinpoint){
System.out.println("message from Start around");
long start = System.currentTimeMillis();
try {
joinpoint.proceed();
} catch (Throwable e) {
e.printStackTrace();
}
long end = System.currentTimeMillis();
System.out.println("message from End around total : "+(end-start)+" ms");
}
}
代码中的joinpoint.proceed(),这句话就是实际调用切点方法,也就是本例中的show()。如果切面类中定义了around通知,通知一定要加上这句话,否则要切点方法不会被调用!
- c.配置XML
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx" xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd"
default-lazy-init="true">
<context:component-scan base-package="com.hsb.beans" />
<bean id="audience" class="com.hsb.aop.Audience" />
<aop:config>
<aop:aspect ref="audience">
<aop:pointcut expression="execution(* com.hsb.beans.Perform.show(..))"
id="performance" />
<aop:before method="beforeShow" pointcut-ref="performance" />
<aop:after method="afterShow" pointcut-ref="performance" />
<aop:around method="around" pointcut-ref="performance" />
</aop:aspect>
</aop:config>
</beans>
首先将audience声明为一个bean,
<aop:aspect ref="audience">
定义切面。
<aop:pointcut>
定义切点。
<aop:before>
等定义通知。
这样就实现了在xml中实现aop,原理是,一个代理类封装了需要切面的这个模块,并拦截了被通知方法的调用,再把调用方法转发给真正的bean。
当拦截到方法调用时,先执行切面逻辑,再调用目标方法。
2.注解切面
- a.编写一个原始类
package com.hsb.beans;
import org.springframework.stereotype.Repository;
@Repository
public class Perform {
public void show(){
System.out.println("message from Perform.show()");
}
}
- b.编写一个切面
package com.hsb.aop;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class Audience {
@Pointcut("execution(* com.hsb.beans.Perform.show(..))")
public void performance() {
}
@Before("performance()")
public void beforeShow() {
System.out.println("message from beforeShow");
}
@After("performance()")
public void afterShow() {
System.out.println("message from afterShow");
}
@Around("performance()")
public void around(ProceedingJoinPoint joinpoint) {
System.out.println("message from Start around");
long start = System.currentTimeMillis();
try {
joinpoint.proceed();
} catch (Throwable e) {
e.printStackTrace();
}
long end = System.currentTimeMillis();
System.out.println("message from End around total : " + (end - start)
+ " ms");
}
}
@Aspect将此类声明为切面类,@Component将此类声明为bean放到spring容器中,@Pointcut将下面的performance声明为一个切点,并将Perform中的show()方法进行了关联。execution(* com.hsb.beans.Perform.show(..))的意思是,执行此方法时,忽略返回值,参数类型和个数忽略。还可以更加简写,用于匹配合适的方法,譬如对Perform类的全部方法进行匹配就是com.hsb.beans.Perform.*。
- c.配置xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx" xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd"
default-lazy-init="true">
<context:component-scan base-package="com.hsb.*" />
<aop:aspectj-autoproxy proxy-target-class="true"/>
</beans>
可以看见此处就没有使用<aop:config />
声明,而是使用<aop:aspectj-autoproxy proxy-target-class="true" />
这句话的意思是自动在spring上下文中创建一个AnnotationAwareAspectJAutoProxyCreator类,它会自动代理一些bean,这些bean的方法需要与使用@Aspect注解的bean中所定义的切点相匹配,而这些切点又是使用@Pointcut注解定义出来的。proxy-target-class=”true”的意思是使用基于类的代理使用cglib库,如果为false则使用jdk自带的基于接口的代理
如上所示,完全是一样的效果。可以看出使用注解减少了很多工作,不容易出错,各个组件间的耦合度降低了。试想,如果一个大型项目中有很多切面,切点,如果全部去xml中配置,将会是一项极为艰苦的工作,但是如果使用注解就可以做很少的工作完成这一切。