- 浏览: 15136 次
- 性别:
- 来自: 大连
最新评论
各位朋友,新年快乐。
上一篇里,笔者将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,切入点,通常就是指执行某特定方法时。即
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern) throws-pattern?)
带?是可有可无的成分,笔者这里需要的是"execution(* com.xxxxx.webmodel.pin..*.*(..))"
就是说:在执行com.xxxxx.webmodel.pin包下以及一层子包下(第一个..)的所有类(第二个*)的所有方法(第三个*)的时候,无论该方法返回值是什么(第一个*,),参数有多少是什么(第二个..)。 文档中还有许多例子来帮助读者们理解切入点的概念。有需要的同志们可以细读文档。“execution”应该是aspectj的,还有许多 Spring自定义,如within,target等等。
之后的其它概念@Before,@After, @AfterReturning,@AfterThrowing属于同一类型,注解在方法上,意思是说:在切入点的这个时刻去执行所注解的方法。
比如
@Before("execution(* com.xxxxx.webmodel.pin..*.*(..))") public void logMethodStart(){}
它的意思就是 在执行所有pin包及一层子包下的所有类的所有方法之前,去调用logMethodStart()这个方法。依此类推@After自不用多说。再来看@AfterReturning与@AfterThrowing,格式如下:
@AfterReturning(value=" execution(* com.xxxxx.webmodel.pin..*.*(..))",returning="returnedObj") public void logMethodSuccess(Object returnedObj){} @AfterThrowing(value=" execution(* com.xxxxx.webmodel.pin..*.*(..))",throwing="ex") public void logMethodFailed(Exception ex){}
也就是说在执行方法有返回值时,和执行方法过程中有错误抛出时,去执行某些方法。而且可以将返回值与抛出的异常传到要执行的方法中来。只需要将参数名配置到相应注解的属性中,然后在执行方法的参数中写出即可。
为了让日志的记录更像样,我们最起码需要知道 是在执行哪个类的哪个方法时,去写这个日志,以及用哪个类名命名的logger,换句话说,我们需要具体的切入点的信息。那就用JoinPoint类的参数作为第一个参数传到方法里来。别说没提醒你:在文档的Access to the current JoinPoint一小节里有写,它的getArgs()是用来获取切入方法的参数,getTarget()是获取切入方法的类对象,getThis()是获取切入方法的类对象的代理对象,即完成切面操作的$Proxy对象,getSignature()是获取切入方法的描述。
然后我们还需要logContext, 比较简单,因为LogAspect本身就是一个Spring的Bean,将logContext注入进来是很简单的事。这样,就形成了如下的代码:
package com.xxxxx.webmodel.util; import javax.annotation.Resource; import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.core.LoggerContext; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.Signature; import org.aspectj.lang.annotation.After; import org.aspectj.lang.annotation.AfterReturning; import org.aspectj.lang.annotation.AfterThrowing; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.Pointcut; import org.springframework.stereotype.Component; @Component @Aspect public class LogAspect { private LoggerContext logCtx; @Resource public void setLogCtx(LoggerContext logCtx) { this.logCtx = logCtx; } @Before("execution(* com.xxxxx.webmodel.pin..*.*(..))") public void logMethodStart(JoinPoint joinPoint){ // Object[] objs = joinPoint.getArgs(); Signature sign = joinPoint.getSignature(); Object target =joinPoint.getTarget(); // Object thisT =joinPoint.getThis(); Logger logger =logCtx.getLogger(target.getClass().getName()); logger.info("Start to execute method " + sign); } @After("execution(* com.xxxxx.webmodel.pin..*.*(..))") public void logMethodEnd(JoinPoint joinPoint){ Signature sign = joinPoint.getSignature(); Object target =joinPoint.getTarget(); Logger logger =logCtx.getLogger(target.getClass().getName()); logger.info("End to execute method "+ sign); } @AfterReturning(value=" execution(* com.xxxxx.webmodel.pin..*.*(..))",returning="returnedObj") public void logMethodSuccess(JoinPoint joinPoint,Object returnedObj){ Signature sign = joinPoint.getSignature(); Object target =joinPoint.getTarget(); Logger logger =logCtx.getLogger(target.getClass().getName()); logger.info("Successfully executed method "+ sign + (returnedObj==null?".":". returned object is "+returnedObj)); } @AfterThrowing(value="executeMethodsInPin()",throwing="ex") public void logMethodFailed(JoinPoint joinPoint,Exception ex){ Signature sign = joinPoint.getSignature(); Object target =joinPoint.getTarget(); Logger logger =logCtx.getLogger(target.getClass().getName()); logger.error("Failed to execute method "+ sign + ", Cause by "+ex.getMessage()); } }
直接Junit方式运行之前写的DAO的测试类PersistencePinText.java, 成功看到了log。不过,发现切入点太少了,只能定义一个包及其一层子包下的所有类的所有方法,需要改进一下吧。@Pointcut注解的方法就代表切入点的意思,只需要多加几个即可。形成以下格式:
@Pointcut("execution(* com.gxino.webmodel.pin..*.*(..))") private void executeMethodsInPin(){} @Pointcut("execution(* com.gxino.webmodel.hub..*.*(..))") private void executeMethodsInHub(){}
那么@Before里的值怎么写呢? 一个还行,切入点多了,怎么办?看文档。
有了,文档里有许多@Before(“anyMethod()&& args(account)”)的形式。也就是说里面是可以把多个切入点加进去的。
尝试1:
@Before("executeMethodsInPin() && executeMethodsInHub()") public void logMethodStart(JoinPoint joinPoint){}
发现没有log了,难道,里面是条件表达试?
尝试2:
@Before("executeMethodsInPin() || executeMethodsInHub()") public void logMethodStart(JoinPoint joinPoint){}
Bingo,成功。
到这里笔者已然是颇有成就感了,那就一举歼灭吧,前面的DAO层里的每个方法重用的代码好多,每个方法只有下面的一个Try块体内的第一句话是不同的,其余都相同。完全可以把相同部分放到一个方法里来调用。可是相同的部分是分布在那句不同的代码上下两部分的,而且有一个上下两部分都要用到的变量,之前能想到的解决方案只能是把共享的那个变量从方法内抽出来成为类的成员,然后把上下两部分别写成方法。但是,问题是共享的那个变量需要在方法体内被锁住,因为DAO是单例的,不锁的话,方法并行执行就会出问题,可是锁住又让效率降低。
实际上是我为难自己:Hibernate的Session有时是getCurrentSession()的,有时是openSession()的,然后就需要一个boolean值needCloseSession来告诉执行完操作需不需要关闭session
这个可以用aspectj中的@Around来实现,仔细读下文档中的@Around的使用提示,@Around是用来共享 执行一个方法前后 的状态的,最好不要在可以简单用@Before和@After来完成的Case中去用@Around. 上面的case需要在执行session具体方法的前后共享一个变量needCloseSession.符合要求,那就来试一下吧:
直接在HibernatePersistencePin.java的文件里append以下代码:
@Component @Aspect class CommonStatement { private SessionFactory sessionFactory; @Resource public void setSessionFactory(SessionFactory sessionFactory){ this.sessionFactory = sessionFactory; } @Around("execution (* com.gxino.webmodel.pin.impl.HibernatePersistencePin.*(..))") public Object prepare(ProceedingJoinPoint pjp)throws Throwable{ Object target =pjp.getTarget(); //HibernatePersistencePin targetObj = (HibernatePersistencePin)proxy; boolean needCloseSession = false; boolean needThrowOut= false; try { Session session =null; try{ session = sessionFactory.getCurrentSession(); if(session ==null)throw new HibernateException(""); } catch(HibernateException he){ session = sessionFactory.openSession(); needCloseSession = true; } Method setSession =target.getClass().getMethod("setSession", Session.class); setSession.invoke(target, session); Object object=null; try{ object =pjp.proceed(); } catch(Throwable t){ needThrowOut= true; throw t; } if(needCloseSession)session.close(); return object; } catch (Throwable t) { if(needThrowOut)throw t; else {t.printStackTrace(); return null;} } } }
然后在HibernatePersistencePin类中将seesionFactory类成员改成session, 然后精简每一个方法。如下:
private Session session; public void setSession(Session session) { this.session = session; } @Override public 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,不错的编程思想。
相关推荐
在本文中,我们将深入浅出地探讨Java Web开发的核心概念、关键技术和应用场景。 首先,Java Web开发的基础是Java语言本身。Java以其“一次编写,到处运行”的特性,成为跨平台开发的理想选择。其面向对象的特性、...
Java Web项目的完整案例是开发人员学习和理解Java在Web应用中的实际运用的重要资源。这个案例涵盖了从基础到高级的各种概念,特别强调了Spring Boot框架的使用。Spring Boot简化了Java Web应用的初始设置和配置,...
【标题】"21天学通java+web开发_第4到21天的资料"是一个涵盖Java Web开发核心概念的教程系列,主要讲解了从第4天到第21天的学习内容。这个系列可能包括逐步进阶的课程,帮助初学者在21天内掌握Java编程以及Web应用...
在Java开发环境中,配置是至关重要的步骤,特别是在使用集成开发环境(IDE)如IBM Rational Application Developer (RAD)时。以下将详细讲解如何配置基于JDK 1.6、Tomcat 6.0.16和RAD 7.5的开发环境,并介绍如何安装...
SpringMVC则是Spring框架的一部分,专门用于处理Web请求,它与Spring的其他组件无缝集成,简化了MVC(Model-View-Controller)架构的实现。Mybatis作为持久层框架,允许开发者通过简单的XML或注解配置,将SQL与Java...
本章将介绍如何使用MVC架构设计Web应用。 - Struts是基于MVC的Java Web框架,它提供了一种组织代码的方式,使开发者能更高效地创建Web应用。你将学习Struts的配置、Action类以及表单验证。 5. **Chapter 9 - JDBC...
DWZ(Design for Web Application)是一个基于jQuery的前端UI框架,提供了大量的UI组件,如表格、下拉菜单、对话框等,帮助快速构建交互式的Web应用。在Shop系统中,DWZ可以用于美化页面,增强用户体验,比如商品...
11. 开发工具和IDE: Eclipse, IBM Rational Application Developer(RAD),JBoss, Tomcat, Jetty等都是流行的Java开发工具和Web服务器。这些工具和服务器简化了Java开发和部署流程。 12. 数据库技术: 文档提到了...
Java和SSH(Struts、Spring、Hibernate)是Java Web开发中的三大框架,它们共同构建了企业级应用的基础架构。在这些技术中,注解(Annotation)的使用日益普及,极大地简化了配置工作,增强了代码的可读性和维护性。...
在IT行业中,Spring Boot是一个非常流行的Java框架,它简化了Spring应用的开发过程,提供了自动配置功能,并且能够快速构建可运行的应用程序。本项目主要讲述了如何将Spring Boot与MyBatis、Druid和PageHelper集成,...
常见的Java Web框架如Spring、Struts、Hibernate等,它们包含了大量的jar文件,用于提供各种服务,如MVC架构、持久化操作、AOP(面向切面编程)等。这些框架的jar文件通常会包含在`jar.zip`这样的压缩包中,便于...
Struts是MVC(Model-View-Controller)架构的一个经典实现,学习Struts的基本概念、ActionForm、Action和业务逻辑类的关系,以及如何在Struts和JSP间传递数据。掌握Struts的处理流程和异常处理机制,了解Action...
11. **Spring框架**:Spring是一个全面的Java企业级应用开发框架,提供了依赖注入、AOP(面向切面编程)、数据访问和Web开发等功能。 12. **微服务架构**:Java的轻量级容器,如Spring Boot,以及服务发现工具如...
Spring的核心特性包括依赖注入(DI)和面向切面编程(AOP),并且它还支持事务管理、JDBC抽象、数据访问/对象关系映射(ORM)、Web应用程序开发等多个方面。 3. **MyBatis**:MyBatis是一个优秀的持久层框架,它...
Spring Boot是Java开发中的一个流行框架,用于简化Spring应用程序的初始搭建以及开发过程。在"springboot-03-web.zip"这个压缩包中,我们聚焦的是Spring Boot与Web开发相关的知识。Spring Boot的核心特性之一是自动...
英文版的Java文档通常是官方的Java API(Application Programming Interface)文档,它是学习和理解Java类库的重要资源。API文档详尽地列出了Java平台的各种类、接口、方法和常量,帮助开发者了解如何使用这些组件来...
6. **Java Blend**:随着Web Services等新技术的出现,Java EE逐渐与其他技术融合,形成了更加灵活和强大的开发框架。 7. **超越Java EE**:尽管Java EE在企业级开发中占据主导地位,但也有越来越多的开发者开始探索...
在构建过程中,这些库会被打包到最终的WAR(Web Application Archive)或EAR(Enterprise Application Archive)文件中,以便在Web服务器或应用服务器上部署运行。 总结一下,SSH1和SS2都是为了简化Java Web开发的...
2. **AOP(Aspect-Oriented Programming)**:切面编程的概念,包括通知类型、切入点表达式和代理模式。 3. **Spring注解**:@Autowired、@Component、@Service、@Repository、@Controller等注解的使用。 4. **...
9. **RESTful服务**:使用JAX-RS(Java API for RESTful Web Services)标准,可以轻松创建符合REST原则的Web服务,便于与其他系统集成。 10. **安全性**:JavaEE提供了一套全面的安全机制,包括角色基