AOP的重要概念
- 切面(Aspect):切面是方面(日志、事物、安全验证)的实现,如日志切面、权限切面、失误切面等,在实际应用中通常是存放方面实现的普通java类,要被AOP容器识别为切面,需要在配置中通过标记指定
- 通知(Advice):通知是切面的具体实现。以目标方法为参考点。它根据放置的位置不同可以分为,前置通知、后置通知、环绕 通知、最终(返回后)通知、异常通知等五种。切面类中的某个方法具体属于哪类通知,需要在配置中制定。
- 切入点(Pointcut):用以定义通知应该织入到那些连接点上。
- 目标对象(TargetObject):目标对象 指将要织入切面的对象,即那些被通知的对象。这些对象中只包含核心业务逻辑代码,所有日志、事物、安全验证等方面的功能等待AOP容器织入。
- 代理对象(ProxyObject):代理对象是将通知应用到目标对象之后,被动态创建的对象,代理对象的功能相当于目标对象中实现的核心业务逻辑功能加上方面(日志、事物、安全验证)代码实现的功能。
- 织入(Remoting):织入是将切面应用到目标对象,从而创建一个新的代理对象的过程。
SpringAOP通知(Advice)种类介绍
- 前置通知(Before advice)
:在某连接点(JoinPoint)之前执行的通知,但这个通知不能阻止连接点前的执行。ApplicationContext中在<aop:aspect>
里面使用<aop:before>
元素进行声明。 - 后通知(After advice)
:当某连接点退出的时候执行的通知(不论是正常返回还是异常退出)。ApplicationContext中在<aop:aspect>
里面使用<aop:after>
元素进行声明。 - 返回后通知(After return advice)
:在某连接点正常完成后执行的通知,不包括抛出异常的情况。ApplicationContext中在<aop:aspect
>里面使用<after-returning>
元素进行声明。 - 环绕通知(Around advice)
:包围一个连接点的通知,类似Web中Servlet规范中的Filter的doFilter方法。可以在方法的调用前后完成自定义的行为,也可以选择不执行。ApplicationContext中在<aop:aspect>
里面使用<aop:around>
元素进行声明。 - 抛出异常后通知(After throwing advice) : 在方法抛出异常退出时执行的通知。
ApplicationContext中在<aop:aspect>
里面使用<aop:after-throwing>
元素进行声明。
Advice通知类
通知方法中传入了连接点参数JoinPoint,可以通过该参数获取目标对象的类名,目标方法名和目标方法参数等信息。
package hl.aop;
import java.util.List;
import org.apache.log4j.Logger;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
public class AllLogAdvice {
private Logger logger = Logger.getLogger(AllLogAdvice.class);
public void myBeforeAdvice(JoinPoint joinpoint){
//获取被调用的类名
String targetClassName = joinpoint.getTarget().getClass().getName();
//获取被调用的方法名
String targetMethodName = joinpoint.getSignature().getName();
//日志格式字符串
String logInfoText = "前置通知:"+targetClassName+"类的"+targetMethodName+"方法开始执行";
//将日志写入配置文件中
logger.info(logInfoText);
}
public void myAfterReturnAdvice(JoinPoint joinPoint){
String targetClassName = joinPoint.getTarget().getClass().getName();
String targetMethodName = joinPoint.getSignature().getName();
String logInfoText = "返回后通知:"+targetClassName+"类的"+targetMethodName+"方法执行完毕";
logger.info(logInfoText);
}
public void myThrowingAdvice(JoinPoint joinPoint,Exception e){
String targetClassName = joinPoint.getTarget().getClass().getName();
String targetMethodName = joinPoint.getSignature().getName();
String logInfoText = "异常通知:执行"+targetClassName+"类的"+targetMethodName+"方法时发生异常";
logger.info(logInfoText);
}
/** * 目标方法若有返回值,则该环绕通知也必须要有与目标方法相同的放回值 * 否者会发生空指针异常(即:目标方法中的返回值被拦截并且不会向下传递) * @param pJoinpoint * @return * @throws Throwable */
public List myAroundAdvice(ProceedingJoinPoint pJoinpoint) throws Throwable{
long beginTime = System.currentTimeMillis();
List list = (List)pJoinpoint.proceed();
long endTime =System.currentTimeMillis();
long custTime = endTime-beginTime;
String targetMethodName = pJoinpoint.getSignature().getName();
String logInfoText = "环绕通知:"+targetMethodName+"方法调用开始时间"+beginTime+"毫秒,调用结束时间"+endTime+"毫秒,共耗时+"+custTime+"毫秒";
logger.info(logInfoText);
return list;
}
}
applicationContext.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:p="http://www.springframework.org/schema/p" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.1.xsd">
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
<!--driverClassName 该属性要加上,否则会出现no suitable Driver错误 -->
<property name="driverClassName">
<value>com.MysqL.jdbc.Driver</value>
</property>
<property name="url" value="jdbc:MysqL://localhost:3306/bank"></property>
<property name="username" value="root"></property>
<property name="password" value="123456"></property>
</bean>
<bean id="sessionFactory" class="org.springframework.orm.hibernate4.LocalSessionfactorybean">
<property name="dataSource">
<ref bean="dataSource" />
</property>
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">
org.hibernate.dialect.MysqLDialect
</prop>
</props>
</property>
<property name="mappingResources">
<list>
<value>hl/entity/Account.hbm.xml</value>
</list>
</property>
</bean>
<bean id="AccountDao" class="hl.dao.AccountDaoImpl">
<property name="sessionFactory" ref="sessionFactory"></property>
</bean>
<bean id="AccountBiz" class="hl.bean.AccountBizImpl">
<property name="accountDao" ref="AccountDao"></property>
</bean>
<bean id="searchAction" class="hl.action.SearchAction">
<property name="accountBiz" ref="AccountBiz"></property>
</bean>
<bean id="allLogAdvice" class="hl.aop.AllLogAdvice"></bean>
<aop:config>
<!-- 配置日志切面 -->
<aop:aspect id="logaop" ref="allLogAdvice">
<!-- 定义切入点,切入点采用正则表达式 (此处含义为:对hl.action中的所有方法都进行拦截) -->
<aop:pointcut expression="execution(* hl.bean..*.*(..))" id="logpointcut" />
<!-- 配置前置通知 -->
<aop:before method="myBeforeAdvice" pointcut-ref="logpointcut" />
<!-- 配置返回后通知 -->
<aop:after-returning method="myAfterReturnAdvice" pointcut-ref="logpointcut" />
<!-- 配置异常通知 -->
<aop:after-throwing method="myThrowingAdvice" pointcut-ref="logpointcut" throwing="e"/>
<!-- 配置环绕通知 -->
<aop:around method="myAroundAdvice" pointcut-ref="logpointcut" />
</aop:aspect>
</aop:config>
</beans>
<!-- 切入点表达式 通常情况下,表达式中使用”execution“就可以满足大部分的要求。表达式格式如下: 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之外,其他都是可选的。上例中,execution(* hl.bean.*.*(..))表示hl.bean包下,返回值为任意类型;方法名任意;参数不作限制的所有方法。 JoinPoint 接口提供了一系列有用的方法, 比如 getArgs() (返回方法参数)、getThis() (返回代理对象)、getTarget() (返回目标)、getSignature() (返回正在被通知的方法相关信息)和 toString() (打印出正在被通知的方法的有用信息。 -->
如果要在工程中使用AOP需要几个jar包:
1 Aop的核心包,即org.springframework.aop-xxx.jar
2 Spring的联盟包:aopalliance-1.0.jar
3 aspectJ相关的jar包:aspectjrt.jar aspectjweaver.jar
4 如果使用了动态代理,还需要添加cglib相关的jar包:cglib.zip
xml配置文件解析
Xml技术在各类框架中都有重要的作用。除了xml的配置,Spring还支持基于注解及基于Java类的Bean配置,由于不同配置方式在“质”上都是基本相同的,只是存在“形”的区别,而基于XML的配置方式是功能最强的,所以我们先以基于XML的配置方式入手,后续再讲述其他两种配置方式。
对于基于XML的配置,Spring 2.0以后使用Schema的格式,让不同类型的配置拥有了自己的命名空间,使配置文件更具扩展性。
此外,Spring基于Schema配置方案为许多领域的问题提供了简化的配置方法,配置工作因此得到了大幅简化。
我们通过一个简单的xml配置文件示例来解析一下spring配置文件中schema含义。
要了解文件头中声明的内容,需要学习一点XML Schema的知识,Schema在文档根节点中通过xmlns对文档中的命名空间进行声明。我们在上面的代码中定义了3个命名空间:
①默认命名空间:它没有空间名,用于SprinBean的定义:
②XSI命名空间:这个命名空间用于为每个文档中命名空间指定相应的Schema样式文件,是标准组织定义的标准命名空间;
③aop命名空间:这个命名空间是Spring配置AOP的命名空间,是用户自定义的命名空间。
命名空间的定义分为两个步骤:第一步指定命名空间的名称,第二步指定命名空间的Schema文档样式文件的位置,用空格或回车换行进行分隔。
在第一步中,需要指定命名空间的缩略名和全名。
xmlns:aop=”http://www.springframework.org/schema/aop”
aop为命名空间的别名,一般使用简洁易记的名称,在XML文档中的元素可通过命名空间别名加以区分。而http://www.springframework.org/schema/aop为空间的全限定名,习惯上用文档发布机构的官方网站和相关网站目录作为全限定名。这种命名方式既可以标识文档所属的机构,又可以很好地避免重名的问题。但从XML Schema语法来说,别名和全限定名都可以任意命名。 如果命名空间的别名为空,则表示该命名空间为文档默认命名空间,文档中无命名空间前缀的元素都属于默认命名空间,如等都属于①处定义的默认命名空间。 在第二步中,为每个命名空间指定了对应的Schema文档格式定义文件。 命名空间使用全限定名,每个组织机构在发布Schema文件后,都会为该Schema文件提供一个引用的URL地址,一般使用这个URL地址指定命名空间对应的Schema文件。 命名空间名称和对应的Schema文件地址之间使用空格或回车分隔,不同的命名空间之间也使用这种分隔方法。 指定命名空间的Schema文件地址有两个用途:XML解析器可以获取Schema文件并对文档进行格式合法性验证;在开发环境下,IDE可以引用Schema文件对文档编辑提供提示功能。 Spring的配置Schema文件分布在各模块类包中,如果模块拥有对应的Schema文件,则可以在模块类包中找到一个config目录,Schema文件就位于该目录中。