组合模式,是众多的结构性模式中的一种,想比较之前的桥接模式以及适配器模式,在理解上会有稍稍的难度。
从组合模式的介绍上来看的话,其实还是不甚了解,以前学过的数据结构中的树的概念到还能够想起来一些。
从网上看到的一些关于这个模式的解释,也都是一些没有说到重要地方的解释,看起来一大堆的东西,基本上没有讲清楚。既然如此麻烦,索性直接自己来通过代码来理解还好点。
所谓的树型结构和树枝等等概念看起来也比较麻烦。但是从代码上还是了解到了一些东西。所以就根据自己的所了解的程度进行类图的绘制。
从上面可以知道,作为树枝节点的部分虽然也是继承根节点的,但是在使用上是属于组合方式提供给树枝节点使用。
相当于提供这样一种情况,就是当一些对象进行组合使用和单独的继承都是我需要的情况下,才会采用这种模式。例如,在现在的娱乐公司里面,往往会推出一些单独的歌手,自然也会推出一些组合的歌手。而这样的模式自然就是属于组合模式了。虽然每个歌手不管是单独的艺人还是组合中的任何一个,都是属于歌手中的一个子类。但是,当公司进行调用的时候,往往这两种情况是不同的,因为就需要推出一些组合和一些单独演唱的歌手了。
从上面的例子,再举一个实际的代码示例吧。
#pragma once #include"root.h" //单飞歌手 class leaf:publicroot { public: leaf(void); ~leaf(void); public: voidSinger(); }; #include "root.h" #include <vector> class compoment:publicroot { public: compoment(void); ~compoment(void); public: voidSinger(); private: std::vector<root*> psinger; };
程序的运行结果如下。
这里需要值得注意一个问题,在树枝节点的上往往会有叶子节点,而叶子节点的调用方式正是以组合方式调用的。由于调用的方式正是以多个叶子节点一并调用的方式进行的,所以在调用叶子节点的时候,往往要做一些特殊处理的。
而且,这个调用方式在其他的模式中也是暂时没有看到的,是这个模式的一种特殊的地方,整个组合模式而言,最需要特别注意的地方就是这里了。
在上述的类图中,树枝节点对于根节点的调用就是一种组合方式,在实际操作中,就是通过成员变量的方式进行批量的操作。
之前使用SSH三大经典框架的时候,写了一个简单的统计Action每个方法执行时间的功能类,代码如下:
import javax.servlet.http.HttpServletRequest; import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.time.StopWatch; import org.apache.struts.actions.DispatchAction; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Service; import org.springframework.aop.framework.ReflectiveMethodInvocation; /** * 统计方法执行时间的拦截器,采用Spring AOP方式实现. * * @author Kanine */ @Service("runTimeHandler") public class RunTimeHandler implements MethodInterceptor { private static Logger logger = LoggerFactory.getLogger("code.coolbaby"); @SuppressWarnings("unchecked") public Object invoke(MethodInvocation methodInvocation) throws Throwable { Object[] args = methodInvocation.getArguments(); String method = methodInvocation.getMethod().getName(); String action = methodInvocation.getMethod().getDeclaringClass().getName(); /** * 由于Spring使用了Cglib代理,导致不能直接取得目标类名,需作此转换 */ if (methodInvocation instanceof ReflectiveMethodInvocation) { Object proxy = ((ReflectiveMethodInvocation) methodInvocation).getProxy(); action = StringUtils.substringBefore(proxy.toString(), "@"); /** * 如使用了DispatchAction,将不能直接取得目标方法,需作此处理 */ if (proxy instanceof DispatchAction) { for (Object arg : args) { if (arg instanceof HttpServletRequest) method = ((HttpServletRequest) arg).getParameter("method"); } } } /** * 方法参数类型,转换成简单类型 */ Class[] params = methodInvocation.getMethod().getParameterTypes(); String[] simpleParams = new String[params.length]; for (int i = 0; i < params.length; i++) { simpleParams[i] = params[i].getSimpleName(); } String simpleMethod = method + "(" + StringUtils.join(simpleParams, ",") + ")"; logger.info("{} 开始执行[{}]方法", action, method); StopWatch clock = new StopWatch(); clock.start(); Object result = methodInvocation.proceed(); clock.stop(); logger.info("执行[{}]方法共消耗{}毫秒", simpleMethod, clock.getTime()); return result; } }在applicationcontext.xml加入以下配置:
<context:component-scan base-package="code.coolbaby"/> <aop:config> <aop:advisor advice-ref="runTimeHandler" pointcut="execution(* code.coolbaby.core.BaseDispatchAction.*(..))"/> </aop:config>就可以正确地以AOP的方式完成原本比较繁琐的功能了。
最近把框架升级到SS2H,顺便把Spring AOP实现由原来的Schema方式改为AspectJ方式,代码如下:
import org.apache.commons.lang.time.StopWatch; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; /** * 统计方法执行时间的工具类,采用Spring AOP方式实现. * * @author Kanine */ @Aspect @Component public class RunTimeHandler { private static Logger logger = LoggerFactory.getLogger("code.coolbaby"); @Pointcut("execution(public String *()) && !execution(public String toString())" + " && target(code.coolbaby.core.CRUDActionSupport)") void timer() { } @Around("timer()") public Object time(ProceedingJoinPoint joinPoint) throws Throwable { String clazz = joinPoint.getTarget().getClass().getSimpleName(); String method = joinPoint.getSignature().getName(); StopWatch clock = new StopWatch(); clock.start(); Object result = joinPoint.proceed(); clock.stop(); String[] params = new String[] { clazz, method, clock.getTime() + "" }; logger.info("[{}]执行[{}]方法共消耗[{}]毫秒", params); return result; } }struts.xml内容如下:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE struts PUBLIC "-//Apache Software Foundation//DTD Struts Configuration 2.1//EN" "http://struts.apache.org/dtds/struts-2.1.dtd"> <struts> <constant name="struts.convention.default.parent.package" value="crud-default" /> <constant name="struts.convention.package.locators" value="web" /> <constant name="struts.convention.result.path" value="/" /> <!-- 用于CRUD Action的parent package --> <package name="crud-default" extends="convention-default"> <!-- 基于paramsPrepareParamsStack, 增加store interceptor保证actionMessage在redirect后不会丢失 --> <interceptors> <interceptor-stack name="crudStack"> <interceptor-ref name="store"> <param name="operationMode">AUTOMATIC</param> </interceptor-ref> <interceptor-ref name="paramsPrepareParamsStack" /> </interceptor-stack> </interceptors> <default-interceptor-ref name="crudStack" /> </package> <!-- 使用Convention插件,实现约定大于配置的零配置文件风格. 特殊的Result路径在Action类中使用@Result设定. --> </struts>在applicationcontext.xml加入以下配置:
<context:component-scan base-package="code.coolbaby"/> <aop:aspectj-autoproxy proxy-target-class="true"/>理论上讲,AOP的功能应该可以正确实现了,实际则不然,以UserAction举例说明,
package code.coolbaby.basal.web.security; //限于篇幅,省略import语句 /** * 用户管理Action. * * 使用Struts2 convention-plugin Annotation定义Action参数. * * @author Kanine */ @SuppressWarnings("serial") public class UserAction extends CRUDActionSupport<User> { @Autowired private UserManager userManager; private User entity; private Long id; private Page<User> page = new Page<User>(5);//每页5条记录 public User getModel() { return entity; } @Override protected void prepareModel() throws Exception { if (id != null) { entity = userManager.get(id); } else { entity = new User(); } } public void setId(Long id) { this.id = id; } public Page<User> getPage() { return page; } @Override public String list() throws Exception { HttpServletRequest request = Struts2Utils.getRequest(); List<PropertyFilter> filters = HibernateWebUtils.buildPropertyFilters(request); page = userManager.search(page, filters); return SUCCESS; } //限于篇幅,省略其他的代码 }
测试的结果是,userManager注入失败,在执行list()方法的时候报错,NullPointer!
接下来反复Debug后,发现个很奇怪的现象,在AOP执行的RunTimeHandler内部,Debug视图中methodInvocation的proxy的userManager属性是正确地注入的,而其target中的userManager却为null,当跳转到list()时,userManager亦为null,这是怎么回事呢?!
变换了几种测试方法,发现如果是对service层的EntityManager(里面有使用了@Autowired的entityDAO)切面,不会出现NPE,Debug视图中proxy的entityDAO为null而target中的entityDAO正确注入;如果去掉AOP,UserAction运行正常,不会发生userManager注入失败的情况;但是该AOP在Struts1的环境下却执行正确,也没有发生注入失败的问题!
尝试了几种解决方案后,发现如果加入userManager的setter方法,即便不加@Autowired也不会有NPE,功能运转正常,但是理论上置于field上的@Autowired已经无需sette
这个log4j的配置不正确或未配置的时候都会出现这个错误,最后在网上找到了一个方法,现在做个笔记
,多是说把ContextLoaderListener改为SpringContextServlet,但我这样改了没用。后来在一个英文网站上看到一个遇到同样问题的帖子,他是这样改的:
<context-param>
<param-name>log4jConfigLocation</param-name>
<param-value>/WEB-INF/config/log4j.properties</param-value>
</context-param>
······
<!-- 定义LOG4J监听器 -->
<listener>
<listener-class>
org.springframework.web.util.Log4jConfigListener
</listener-class>
</listener>
这样改了问题就解决了,不用再修改ContextLoaderListener。
错误信息 : log4j:WARN No appenders could be
found for logger (org.springframework.web.context.ContextLoader).
log4j:WARN Please initialize the log4j system properly.
解决方法 : 在WEB-INF/classes/路径下加上文件 log4j.properties 其参考内容如下
# Set root logger level to error
log4j.rootLogger=INFO, Console, File
###### Console appender definition #######
# All outputs currently set to be a ConsoleAppender.
log4j.appender.Console=org.apache.log4j.ConsoleAppender
log4j.appender.Console.layout=org.apache.log4j.PatternLayout
log4j.appender.Console.layout.ConversionPattern=%d{ABSOLUTE} %-5p [%c{3}] %m%n
#log4j.appender.Console.layout.ConversionPattern=%d{ABSOLUTE} %-5p [%c] %m%n
###### File appender definition #######
log4j.appender.File=org.apache.log4j.DailyRollingFileAppender
log4j.appender.File.File=spring.log
log4j.appender.File.Append=false
log4j.appender.File.layout=org.apache.log4j.PatternLayout
log4j.appender.File.layout.ConversionPattern=%d{ABSOLUTE} %-5p [%c] %m%n
看到了这个朋友的解决方法。链接http://blog.sina.com.cn/s/blog_6a6b141001012ia3.html