组合模式,是众多的结构性模式中的一种,想比较之前的桥接模式以及适配器模式,在理解上会有稍稍的难度。
从组合模式的介绍上来看的话,其实还是不甚了解,以前学过的数据结构中的树的概念到还能够想起来一些。
从网上看到的一些关于这个模式的解释,也都是一些没有说到重要地方的解释,看起来一大堆的东西,基本上没有讲清楚。既然如此麻烦,索性直接自己来通过代码来理解还好点。
所谓的树型结构和树枝等等概念看起来也比较麻烦。但是从代码上还是了解到了一些东西。所以就根据自己的所了解的程度进行类图的绘制。
从上面可以知道,作为树枝节点的部分虽然也是继承根节点的,但是在使用上是属于组合方式提供给树枝节点使用。
相当于提供这样一种情况,就是当一些对象进行组合使用和单独的继承都是我需要的情况下,才会采用这种模式。例如,在现在的娱乐公司里面,往往会推出一些单独的歌手,自然也会推出一些组合的歌手。而这样的模式自然就是属于组合模式了。虽然每个歌手不管是单独的艺人还是组合中的任何一个,都是属于歌手中的一个子类。但是,当公司进行调用的时候,往往这两种情况是不同的,因为就需要推出一些组合和一些单独演唱的歌手了。
从上面的例子,再举一个实际的代码示例吧。
#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