Java Web Application 自架构 五 AOP概念的引入
? ? ? 各位朋友,新年快乐。
? ? ? 上一篇里,笔者将log4j2用自己的方法配置到了Spring中,用地还算不错,不过有些牵强附会,Spring应该会开发出很好的log4j2整合方案,敬请期待。然后我们现在的课题重点,在于如何让log的加入不再在写新方法时被忽略,于是想到了AOP,只要配置好切入点,每个方法里都会去调用切面中需要执行的语句。这一篇里,笔者就将AOP引入,来完成一些像这样的代码重用的需求。
? ? ? 其实AOP早就充斥在我们的Application中的,只是没有主动去使用过它而已。比如JavaEE的Filter, 再像Spring中,到处都是AOP,它的看家王牌IoC,就是用AOP来实现的,现在这样说有点儿勉强,等一下自己用了就会有深刻的体会。大家在以往带有Spring的开发过程中Debug时应该有发现$Proxy 这样的实例对象,笔者当时只是想到 代理模式这么一个概念,其实它就是AOP所编译出来的一种实例。另外,应该时刻提醒自己AOP不是什么框架的,它是一种编程思想,与OOP是一个级别,不只在Java中可以有,其它的编程语言也可以有。在网上多做一些Research,就会有体会。
? ? ? 例如我们即将要做的Java的AOP编程,要用到一个类库,叫做aspectj,在它的aspectjrt.jar中,是有自己的编译器的,很容易就联系到上文所提及的$Proxy实例,就是这个编译器编译出的一种实例对象。如同做javacc一样,自定义的编译器所编译出的东西,可以是一种新的实例概念。
? ? ? 你可能早就不耐烦了我上面的啰嗦,在其它地方开始Research到底如何将AOP加入,进而发现aspectj 的官网是down的,那么所需的类库aspectjrt.jar, aspectjweaver.jar等到哪下?OK,去Eclipse吧,如果你需要在Eclipse里整合它的插件,可以搜AJDT,找到它的update Site后在Eclipse里用它的Install New Software 功能。 这里方便下大家:
Eclipse AJDT Update Site:?http://download.eclipse.org/tools/ajdt/42/update
(Eclipse Juno用,3.8与4.2, 若是3.7或3.6的,只需将其中42改成37或36)
如果喜欢不借助IDE完成,到
http://www.eclipse.org/downloads/download.php?file=/tools/aspectj/aspectj-1.7.1.jar
里去下载吧。截止目前,2013年元旦,它的最新版本是1.7.1,如果需要更新的版本那就是这里了:http://www.eclipse.org/aspectj/downloads.php#stable_release
?
? ? ? 你会发现下载下来的jar文件是个可执行的jar包,双击它来进行安装。当然前提是你有JRE安装在系统中。按照弹出的窗口中的提示进行安装,完成后会有需要更改环境变量的tips,如下图:
? ? ? 我们的目的是取得所需jar包,所以你也可以不安装,直接用解压缩包的工具将那个可执行jar中lib下的所有jar取出来。或说在安装好后的aspectj的安装路径下的lib中取得。将aspectjrt.jar和aspectjweaver.jar放到我们Web App的WEB-INF/lib下,并确保以下jars也同样在lib中:
aopalliance.jar
cglib-x.x.x.jar (笔者的是2.2.2,好囧,真二)
org.springframework.aop-x.x.x.RELEASE.jar(3.1.2的)
?
? ? ? 之后打开Spring 3.2.x的AOP教程文档
http://static.springsource.org/spring/docs/3.2.x/spring-framework-reference/html/aop.html,当然,网络上其他高手的博文也是可以的;不过还是建议读官方文档。最重要的是它比较全面,以后在其他程序上需要其他用法,都可以在官方文档里找到。
? ? ? 接下来的注解式配置就是从Spring的教程文档里才有看到的,不然笔者又会用自己的野方法自己去做一个@Bean配置return出一个配置好的类实例来。文档中有写了三种启动SpringAOP的配置方法,这里所需要的就是第一种在@Configuration注解的ApplicationContext类上,再加一个注解@EnableAspecjAutoProxy.之后,在com.xxxxx.wemodel.util下新建一个LogAspect类,该类需要用@Component注解为Spring的Bean,然后用@Aspect注解为切面。这样我们就可以开始实现之前的想法,让Log可以简单地写在切面上,不再之后有新加其它方法时被遗忘。
? ? ? 继续看文档,第一个概念 @Pointcut,切入点,通常就是指执行某特定方法时。即
?
?
?
? ? ? 也就是说在执行方法有返回值时,和执行方法过程中有错误抛出时,去执行某些方法。而且可以将返回值与抛出的异常传到要执行的方法中来。只需要将参数名配置到相应注解的属性中,然后在执行方法的参数中写出即可。
?
? ? ?为了让日志的记录更像样,我们最起码需要知道 是在执行哪个类的哪个方法时,去写这个日志,以及用哪个类名命名的logger,换句话说,我们需要具体的切入点的信息。那就用JoinPoint类的参数作为第一个参数传到方法里来。别说没提醒你:在文档的Access to the current JoinPoint一小节里有写,它的getArgs()是用来获取切入方法的参数,getTarget()是获取切入方法的类对象,getThis()是获取切入方法的类对象的代理对象,即完成切面操作的$Proxy对象,getSignature()是获取切入方法的描述。
?
? ? ? 然后我们还需要logContext, 比较简单,因为LogAspect本身就是一个Spring的Bean,将logContext注入进来是很简单的事。这样,就形成了如下的代码:
?
? ? ? 直接Junit方式运行之前写的DAO的测试类PersistencePinText.java, 成功看到了log。不过,发现切入点太少了,只能定义一个包及其一层子包下的所有类的所有方法,需要改进一下吧。@Pointcut注解的方法就代表切入点的意思,只需要多加几个即可。形成以下格式:
private Session session;public void setSession(Session session) {this.session = session; }@Overridepublic void createPersistingEntity(Object persistingObj) throws Exception {try{session.save(persistingObj);}catch(HibernateException he){throw new Exception("Save Pojo failed, because of "+he.getMessage());}}?
? ? ? 这一闹,不得了,笔者有了意外收获,体会到了Spring依赖注入是怎么做到的。
上述代码的执行过程是:调用HibernatePersistencePin的createPersistingEntity()时,会先调用CommonStatement.prepare(ProceedingJoinPoint pjp)方法,直到执行其中的object=pjp.proceed(); 该句就是去执行createPersistingEntity()的意思。当然,正确写法应该是object=pjp.proceed(pjp.getArgs());
在这个过程中,HibernatePersistencePin中的seesion本身是没有被实例化的,可是在CommonStatement.prepare中,pjp.proceed()之前,我们准备了session并用setSession的方法为它注入了session,也就是所谓的setter注入。无论Session是什么方式的,HibernatePersistencePin无需再关心关不关Session,一切都交给了CommonStatement了。
? ? ? OK,测试了一下,成功。AOP,不错的编程思想。
?