java

位置:IT落伍者 >> java >> 浏览文章

Spring AOP 详解


发布日期:2018年01月23日
 
Spring AOP 详解

此前对于AOP的使用仅限于声明式事务除此之外在实际开发中也没有遇到过与之相关的问题最近项目中遇到了以下几点需求仔细思考之后觉得采用AOP来解决一方面是为了以更加灵活的方式来解决问题另一方面是借此机会深入学习SpringAOP相关的内容本文是权当本人的自己AOP学习笔记以下需求不用AOP肯定也能解决至于是否牵强附会仁者见仁智者见智

对部分函数的调用进行日志记录用于观察特定问题在运行过程中的函数调用情况

监控部分重要函数若抛出指定的异常需要以短信或邮件方式通知相关人员

金控部分重要函数的执行时间

事实上以上需求没有AOP也能搞定只是在实现过程中比较郁闷摆了

需要打印日志的函数分散在各个包中只能找到所有的函数体手动添加日志然而这些日志都是临时的待问题解决之后应该需要清除打印日志的代码只能再次手动清除^_^!

类似的情况需要捕获异常的地方太多如果手动添加时想到很可能明天又要手动清除只能再汗OK该需求相对比较固定属于长期监控的范畴并不需求临时添加后再清除然而客户某天要求把其中%的异常改为短信提醒剩下的%改用邮件提醒改之两天后客户抱怨短信太多全部改成邮件提醒…

该需求通常用于监控某些函数的执行时间用以判断系统执行慢的瓶颈所在瓶颈被解决之后烦恼同情况

终于下定决心采用AOP来解决!代码如下

切面类TestAspect

[java]

package comspringaop;

/**

* 切面

*

*/

public class TestAspect {

public void doAfter(JoinPoint jp) {

Systemoutprintln(log Ending method:

+ jpgetTarget()getClass()getName() +

+ jpgetSignature()getName())

}

public Object doAround(ProceedingJoinPoint pjp) throws Throwable {

long time = SystemcurrentTimeMillis()

Object retVal = pjpproceed()

time = SystemcurrentTimeMillis() time;

Systemoutprintln(process time: + time + ms

return retVal;

}

public void doBefore(JoinPoint jp) {

Systemoutprintln(log Begining method:

+ jpgetTarget()getClass()getName() +

+ jpgetSignature()getName())

}

public void doThrowing(JoinPoint jp Throwable ex) {

Systemoutprintln(method + jpgetTarget()getClass()getName()

+ + jpgetSignature()getName() + throw exception

Systemoutprintln(exgetMessage())

}

private void sendEx(String ex) {

//TODO 发送短信或邮件提醒

}

}

package comspringaop;

/**

* 切面

*

*/

public class TestAspect {

public void doAfter(JoinPoint jp) {

Systemoutprintln(log Ending method:

+ jpgetTarget()getClass()getName() +

+ jpgetSignature()getName())

}

public Object doAround(ProceedingJoinPoint pjp) throws Throwable {

long time = SystemcurrentTimeMillis()

Object retVal = pjpproceed()

time = SystemcurrentTimeMillis() time;

Systemoutprintln(process time: + time + ms

return retVal;

}

public void doBefore(JoinPoint jp) {

Systemoutprintln(log Begining method:

+ jpgetTarget()getClass()getName() +

+ jpgetSignature()getName())

}

public void doThrowing(JoinPoint jp Throwable ex) {

Systemoutprintln(method + jpgetTarget()getClass()getName()

+ + jpgetSignature()getName() + throw exception

Systemoutprintln(exgetMessage())

}

private void sendEx(String ex) {

//TODO 发送短信或邮件提醒

}

}

[java]

package comspringservice;

/**

* 接口A

*/

public interface AService {

public void fooA(String _msg)

public void barA()

}

package comspringservice;

/**

* 接口A

*/

public interface AService {

public void fooA(String _msg)

public void barA()

} [java]

package comspringservice;

/**

*接口A的实现类

*/

public class AServiceImpl implements AService {

public void barA() {

Systemoutprintln(AServiceImplbarA()

}

public void fooA(String _msg) {

Systemoutprintln(AServiceImplfooA(msg:+_msg+

}

}

package comspringservice;

/**

*接口A的实现类

*/

public class AServiceImpl implements AService {

public void barA() {

Systemoutprintln(AServiceImplbarA()

}

public void fooA(String _msg) {

Systemoutprintln(AServiceImplfooA(msg:+_msg+

}

}

[java]

package comspringservice;

/**

* Service类B

*/

public class BServiceImpl {

public void barB(String _msg int _type) {

Systemoutprintln(BServiceImplbarB(msg:+_msg+ type:+_type+

if(_type ==

throw new IllegalArgumentException(测试异常

}

public void fooB() {

Systemoutprintln(BServiceImplfooB()

}

}

package comspringservice;

/**

* Service类B

*/

public class BServiceImpl {

public void barB(String _msg int _type) {

Systemoutprintln(BServiceImplbarB(msg:+_msg+ type:+_type+

if(_type ==

throw new IllegalArgumentException(测试异常

}

public void fooB() {

Systemoutprintln(BServiceImplfooB()

}

}

ApplicationContext

[java]

<?xml version= encoding=UTF?>

<beans xmlns=

xmlns:xsi=instance

xmlns:aop=

xsi:schemaLocation=

beansxsd

aopxsd

defaultautowire=autodetect>

<aop:config>

<aop:aspect id=TestAspect ref=aspectBean>

<!配置comspringservice包下所有类或接口的所有方法>

<aop:pointcut id=businessService

expression=execution(* comspringservice**()) />

<aop:before pointcutref=businessService method=doBefore/>

<aop:after pointcutref=businessService method=doAfter/>

<aop:around pointcutref=businessService method=doAround/>

<aop:afterthrowing pointcutref=businessService method=doThrowing throwing=ex/>

</aop:aspect>

</aop:config>

<bean id=aspectBean class=comspringaopTestAspect />

<bean id=aService class=comspringserviceAServiceImpl></bean>

<bean id=bService class=comspringserviceBServiceImpl></bean>

</beans>

<?xml version= encoding=UTF?>

<beans xmlns=

xmlns:xsi=instance

xmlns:aop=

xsi:schemaLocation=

beansxsd

aopxsd

defaultautowire=autodetect>

<aop:config>

<aop:aspect id=TestAspect ref=aspectBean>

<!配置comspringservice包下所有类或接口的所有方法>

<aop:pointcut id=businessService

expression=execution(* comspringservice**()) />

<aop:before pointcutref=businessService method=doBefore/>

<aop:after pointcutref=businessService method=doAfter/>

<aop:around pointcutref=businessService method=doAround/>

<aop:afterthrowing pointcutref=businessService method=doThrowing throwing=ex/>

</aop:aspect>

</aop:config>

<bean id=aspectBean class=comspringaopTestAspect />

<bean id=aService class=comspringserviceAServiceImpl></bean>

<bean id=bService class=comspringserviceBServiceImpl></bean>

</beans>

测试类AOPTest

[java]

public class AOPTest extends AbstractDependencyInjectionSpringContextTests {

private AService aService;

private BServiceImpl bService;

protected String[] getConfigLocations() {

String[] configs = new String[] { /applicationContextxml};

return configs;

}

/**

* 测试正常调用

*/

public void testCall()

{

Systemoutprintln(SpringTest JUnit test

aServicefooA(JUnit test fooA

aServicebarA()

bServicefooB()

bServicebarB(JUnit test barB

}

/**

* 测试AfterThrowing

*/

public void testThrow()

{

try {

bServicebarB(JUnit call barB

} catch (IllegalArgumentException e) {

}

}

public void setAService(AService service) {

aService = service;

}

public void setBService(BServiceImpl service) {

bService = service;

}

}

public class AOPTest extends AbstractDependencyInjectionSpringContextTests {

private AService aService;

private BServiceImpl bService;

protected String[] getConfigLocations() {

String[] configs = new String[] { /applicationContextxml};

return configs;

}

/**

* 测试正常调用

*/

public void testCall()

{

Systemoutprintln(SpringTest JUnit test

aServicefooA(JUnit test fooA

aServicebarA()

bServicefooB()

bServicebarB(JUnit test barB

}

/**

* 测试AfterThrowing

*/

public void testThrow()

{

try {

bServicebarB(JUnit call barB

} catch (IllegalArgumentException e) {

}

}

public void setAService(AService service) {

aService = service;

}

public void setBService(BServiceImpl service) {

bService = service;

}

}

运行结果如下

[java]

log Begining method: comspringserviceAServiceImplfooA

AServiceImplfooA(msg:JUnit test fooA)

log Ending method: comspringserviceAServiceImplfooA

process time: ms

log Begining method: comspringserviceAServiceImplbarA

AServiceImplbarA()

log Ending method: comspringserviceAServiceImplbarA

process time: ms

log Begining method: comspringserviceBServiceImplfooB

BServiceImplfooB()

log Ending method: comspringserviceBServiceImplfooB

process time: ms

log Begining method: comspringserviceBServiceImplbarB

BServiceImplbarB(msg:JUnit test barB type:

log Ending method: comspringserviceBServiceImplbarB

process time: ms

log Begining method: comspringserviceBServiceImplbarB

BServiceImplbarB(msg:JUnit call barB type:

log Ending method: comspringserviceBServiceImplbarB

method comspringserviceBServiceImplbarB throw exception

测试异常

log Begining method: comspringserviceAServiceImplfooA

AServiceImplfooA(msg:JUnit test fooA)

log Ending method: comspringserviceAServiceImplfooA

process time: ms

log Begining method: comspringserviceAServiceImplbarA

AServiceImplbarA()

log Ending method: comspringserviceAServiceImplbarA

process time: ms

log Begining method: comspringserviceBServiceImplfooB

BServiceImplfooB()

log Ending method: comspringserviceBServiceImplfooB

process time: ms

log Begining method: comspringserviceBServiceImplbarB

BServiceImplbarB(msg:JUnit test barB type:

log Ending method: comspringserviceBServiceImplbarB

process time: ms

log Begining method: comspringserviceBServiceImplbarB

BServiceImplbarB(msg:JUnit call barB type:

log Ending method: comspringserviceBServiceImplbarB

method comspringserviceBServiceImplbarB throw exception

测试异常

《Spring参考手册》中定义了以下几个AOP的重要概念结合以上代码分析如下

切面(Aspect)官方的抽象定义为一个关注点的模块化这个关注点可能会横切多个对象在本例中切面就是类TestAspect所关注的具体行为例如AServiceImplbarA()的调用就是切面TestAspect所关注的行为之一切面在ApplicationContext中<aop:aspect>来配置

连接点(Joinpoint)程序执行过程中的某一行为例如AServiceImplbarA()的调用或者BServiceImplbarB(String _msg int _type)抛出异常等行为

通知(Advice)切面对于某个连接点所产生的动作例如TestAspect中对comspringservice包下所有类的方法进行日志记录的动作就是一个Advice其中一个切面可以包含多个Advice例如TestAspect

切入点(Pointcut)匹配连接点的断言在AOP中通知和一个切入点表达式关联例如TestAspect中的所有通知所关注的连接点都由切入点表达式execution(* comspringservice**())来决定

目标对象(Target Object)被一个或者多个切面所通知的对象例如AServcieImpl和BServiceImpl当然在实际运行时Spring AOP采用代理实现实际AOP操作的是TargetObject的代理对象

AOP代理(AOP Proxy)在Spring AOP中有两种代理方式JDK动态代理和CGLIB代理默认情况下TargetObject实现了接口时则采用JDK动态代理例如AServiceImpl;反之采用CGLIB代理例如BServiceImpl强制使用CGLIB代理需要将 <aop:config> 的 proxytargetclass 属性设为true

通知(Advice)类型

前置通知(Before advice)在某连接点(JoinPoint)之前执行的通知但这个通知不能阻止连接点前的执行ApplicationContext中在<aop:aspect>里面使用<aop:before>元素进行声明例如TestAspect中的doBefore方法

后通知(After advice)当某连接点退出的时候执行的通知(不论是正常返回还是异常退出)ApplicationContext中在<aop:aspect>里面使用<aop:after>元素进行声明例如TestAspect中的doAfter方法所以AOPTest中调用BServiceImplbarB抛出异常时doAfter方法仍然执行

返回后通知(After return advice)在某连接点正常完成后执行的通知不包括抛出异常的情况ApplicationContext中在<aop:aspect>里面使用<afterreturning>元素进行声明

环绕通知(Around advice)包围一个连接点的通知类似Web中Servlet规范中的Filter的doFilter方法可以在方法的调用前后完成自定义的行为也可以选择不执行ApplicationContext中在<aop:aspect>里面使用<aop:around>元素进行声明例如TestAspect中的doAround方法

抛出异常后通知(After throwing advice) 在方法抛出异常退出时执行的通知 ApplicationContext中在<aop:aspect>里面使用<aop:afterthrowing>元素进行声明例如TestAspect中的doThrowing方法

切入点表达式

通常情况下表达式中使用execution就可以满足大部分的要求表达式格式如下

[java]

execution(modifierspattern? rettypepattern declaringtypepattern? namepattern(parampattern) throwspattern?)

execution(modifierspattern? rettypepattern declaringtypepattern? namepattern(parampattern) throwspattern?)modifierspattern:方法的操作权限

rettypepattern:返回值

declaringtypepattern:方法所在的包

namepattern:方法名

parmpattern:参数名

throwspattern:异常

其中除rettypepattern和namepattern之外其他都是可选的上例中execution(* comspringservice**())表示comspringservice包下返回值为任意类型方法名任意参数不作限制的所有方法

通知参数

可以通过args来绑定参数这样就可以在通知(Advice)中访问具体参数了例如<aop:aspect>配置如下

[java]

<aop:config>

<aop:aspect id=TestAspect ref=aspectBean>

<aop:pointcut id=businessService

expression=execution(* comspringservice**(String)) and args(msg />

<aop:after pointcutref=businessService method=doAfter/>

</aop:aspect>

</aop:config>

<aop:config>

<aop:aspect id=TestAspect ref=aspectBean>

<aop:pointcut id=businessService

expression=execution(* comspringservice**(String)) and args(msg />

<aop:after pointcutref=businessService method=doAfter/>

</aop:aspect>

</aop:config>TestAspect的doAfter方法中就可以访问msg参数但这样以来AService中的barA()和BServiceImpl中的barB()就不再是连接点因为execution(* comspringservice**(String))只配置第一个参数为String类型的方法其中doAfter方法定义如下

[java]

public void doAfter(JoinPoint jpString msg)

public void doAfter(JoinPoint jpString msg) 访问当前的连接点

任何通知(Advice)方法可以将第一个参数定义为 orgaspectjlangJoinPoint 类型JoinPoint 接口提供了一系列有用的方法 比如 getArgs()(返回方法参数)getThis()(返回代理对象)getTarget()(返回目标)getSignature()(返回正在被通知的方法相关信息)和 toString()(打印出正在被通知的方法的有用信息

               

上一篇:Hibernate中多对多关系的常见问题

下一篇:hibernate关系映射