Velocity可配置消息模版封装

编程

3.实现:

3.1.涉及到的包:

<dependency>

<groupId>org.apache.velocity</groupId>

<artifactId>velocity</artifactId>

<version>1.7</version>

<exclusions>

<exclusion>

<groupId>commons-collections</groupId>

<artifactId>commons-collections</artifactId>

</exclusion>

</exclusions>

</dependency>

3.2.取模版数据并重写ResourceLoader

@Service

public class VelocityTemplateServiceImpl implements IVelocityTemplateService {

private static final Logger logger = LoggerFactory.getLogger(VelocityTemplateServiceImpl.class);

...

/**

* 模版实体

*/

private static NotifyTemplateDto notifyTemplateDto;

/**

* 获取消息内容,这里涉及邮件和钉钉两种消息格式

*

* @param scene 场景

* @param attrMap 属性map

* @return 消息实体

*/

@Override

public NotifyTemplateDto fetchContent(String scene, Map<String, Object> attrMap) {

notifyTemplateDto = new NotifyTemplateDto();

if (StringUtils.isBlank(scene)) {

return notifyTemplateDto;

}

//通过场景查出场景下配置的模版取最新

...

notifyTemplateDto = selectNewestOne();

//整合邮件模版内容

String mailContent = mergeContent(CustomMailTemplateLoader.class.getName(), TemplateTypeEnums.MAIL.name(), attrMap);

notifyTemplateDto.setMailTemplate(mailContent);

//整合钉钉模版内容

String ddContent = mergeContent(CustomDDTemplateLoader.class.getName(), TemplateTypeEnums.DD.name(), attrMap);

notifyTemplateDto.setDDTemplate(ddContent);

return notifyTemplateDto;

}

/**

* 整合消息内容

*

* @param resourceLoaderClass 自定义资源加载类

* @param templateType 模版类型(邮件/钉钉)

* @param attrMap 参数map

* @return 消息内容

*/

private String mergeContent(String resourceLoaderClass, String templateType, Map<String, Object> attrMap) {

Properties p = new Properties();

//自定义模版资源加载器

p.setProperty(Velocity.RESOURCE_LOADER, "CustomResourceLoader");

//注意这里会创建resourceLoaderClass的实例

p.setProperty("CustomResourceLoader.resource.loader.class", resourceLoaderClass);

p.setProperty(Velocity.INPUT_ENCODING, "utf-8");

p.setProperty(Velocity.OUTPUT_ENCODING, "utf-8");

VelocityEngine ve = new VelocityEngine();

//初始化模版引擎

ve.init(p);

Template template = ve.getTemplate(templateType, "UTF-8");

VelocityContext context = new VelocityContext();

//动态参数处理

for (Map.Entry<String, Object> entry : attrMap.entrySet()) {

context.put(entry.getKey(), entry.getValue());

}

StringWriter writer = new StringWriter();

//模版内容整合

template.merge(context, writer);

return writer.toString();

}

/**

* 重写资源加载方法

*

* @param

* @return

*/

public static class CustomMailTemplateLoader extends ResourceLoader {

@Override

public void init(ExtendedProperties configuration) {

}

@Override

public InputStream getResourceStream(String source) throws ResourceNotFoundException {

try {

return new ByteArrayInputStream(notifyTemplateDto.getMailTemplate().getBytes("utf-8"));

} catch (UnsupportedEncodingException e) {

throw new RuntimeException(e);

}

}

@Override

public boolean isSourceModified(Resource resource) {

return false;

}

@Override

public long getLastModified(Resource resource) {

return 0;

}

}

/**

* 重写资源加载方法

*

* @param

* @return

*/

public static class CustomDDTemplateLoader extends ResourceLoader {

@Override

public void init(ExtendedProperties configuration) {

}

@Override

public InputStream getResourceStream(String source) throws ResourceNotFoundException {

try {

return new ByteArrayInputStream(notifyTemplateDto.getDDTemplate().getBytes("utf-8"));

} catch (UnsupportedEncodingException e) {

throw new RuntimeException(e);

}

}

@Override

public boolean isSourceModified(Resource resource) {

return false;

}

@Override

public long getLastModified(Resource resource) {

return 0;

}

}

}

解读:在外部类中持有一个静态NotifyTemplateDto对象,用于存放从数据库中查询到的模版内容,这个实体中包含各种格式的消息模版(邮件、钉钉etc...),而查询数据库的操作放在外部类的一个统一方法中。然后根据消息格式的不同,定义多个静态内部类CustomXXXTemplateLoader继承ResourceLoader重写其getResourceStream的方法,实现就很简单直接从NotifyTemplateDto对象里取相应格式模版返回即可。

为什么使用内部类?

也可以不使用内部类。将查询库的逻辑写在重写的getResourceStream方法中,只不过这样的话多种消息类型就要写多个查询的逻辑,比较冗余。使用内部类是为了能够将查库逻辑抽离出来,结果放在一个全局变量中,这样getResourceStream方法只需要从全局变量中取值。

为什么内部类得是静态?

在初始化模版引擎时会生成自定义资源加载器的实例,而普通内部类的实例生成需要外部类的实例引用,而这时并没有外部类的实例引用,而静态内部类通过外部类的类名就可初始化。当没有声明为static时,模版引擎初始化会报如下错误:

org.apache.velocity.exception.VelocityException: Problem instantiating the template loader: com.xxx.xxx.xxx.service.impl.tools.VelocityTemplateServiceImpl$CustomMailTemplateLoader.
Look at your properties file and make sure the
name of the template loader is correct.
    at org.apache.velocity.runtime.resource.loader.ResourceLoaderFactory.getLoader(ResourceLoaderFactory.java:60)
    at org.apache.velocity.runtime.resource.ResourceManagerImpl.initialize(ResourceManagerImpl.java:130)
    at org.apache.velocity.runtime.RuntimeInstance.initializeResourceManager(RuntimeInstance.java:730)
    at org.apache.velocity.runtime.RuntimeInstance.init(RuntimeInstance.java:263)
    at org.apache.velocity.runtime.RuntimeInstance.init(RuntimeInstance.java:646)
    at org.apache.velocity.app.VelocityEngine.init(VelocityEngine.java:116)
   ...
Caused by: java.lang.InstantiationException: com.xxx.xxx.xxx.service.impl.tools.VelocityTemplateServiceImpl$CustomMailTemplateLoader
    at java.lang.Class.newInstance(Class.java:427)
    at org.apache.velocity.util.ClassUtils.getNewInstance(ClassUtils.java:105)
    at org.apache.velocity.runtime.resource.loader.ResourceLoaderFactory.getLoader(ResourceLoaderFactory.java:46)
    ... 64 common frames omitted
Caused by: java.lang.NoSuchMethodException: com.xxx.xxx.xxx.service.impl.tools.VelocityTemplateServiceImpl$CustomMailTemplateLoader.<init>()
    at java.lang.Class.getConstructor0(Class.java:3082)
    at java.lang.Class.newInstance(Class.java:412)
    ... 66 common frames omitted

当然了,既然想要被静态内部类访问,全局的NotifyTemplateDto对象就也得声明为静态。

3.3.自定义注解

@Retention(RetentionPolicy.RUNTIME)

@Target({ElementType.METHOD, ElementType.TYPE})

public @interface NeedNotify {

/**

* 消息通知场景

*/

String scene() default "";

}

需要发送消息的方法使用注解标注:

    /**

* 需要发送消息的场景返回结果为消息模版所需的填充参数

*

* @param

* @return

*/

@NeedNotify(scene = "apply_success")//指定消息场景

@Override

public Map<String, Object> handleBusiness(BusinessParamsDto dto) {

...//业务逻辑处理

//消息所需的动态参数

Map<String, Object> notifyMap = Maps.newHashMap();

notifyMap.put(TemplateAttrEnums.url.name(), EnvUtils.pageUrl);

//如果动态参数较多可封装为一个实体传入,模版中通过 $obj.属性名 进行配置

notifyMap.put(TemplateAttrEnums.obj.name(), dto);

notifyMap.put(TemplateAttrEnums.title.name(), "测试");

//放入动态的收件人

notifyMap.put("recipients", "xxx");

return notifyMap;

}

@NeedNotify注解需要指定了消息场景

如果项目中的动态参数比较统一,比如项目首页地址等,可以提炼出一个属性的枚举,在配置模版时从这个枚举中选择属性,这样模版的配置和动态参数的绑定比较容易统一

3.4.设置切面

统一在业务方法执行完成之后发送消息

@Aspect

@Component

public class NotificationAspect {

private static final Logger logger = LoggerFactory.getLogger(NotificationAspect.class);

private static final String DEFAULT_TITLE = "消息通知";

@Autowired

private IVelocityTemplateService velocityTemplateService;

/**

* 统一消息通知

*

* @param

* @return

*/

@AfterReturning(pointcut = "execution(* com.xxx.xxx.xxx.service.impl.*.*(..)) && @annotation(needNotify)", returning = "result")

public void after(JoinPoint joinPoint, NeedNotify needNotify, Object result) {

try {

Map<String, Object> resultMap = (Map<String, Object>) result;

if (resultMap.get(TemplateAttrEnums.obj.name()) == null) {

//目标方法的入参可用于模版填充

resultMap.put(TemplateAttrEnums.obj.name(), joinPoint.getArgs());

}

//根据注解中的指定的场景获取到消息模版

NotifyTemplateDto notifyTemplateDto = velocityTemplateService.fetchContent(needNotify.scene(), resultMap);

//模版配置校验

if (notifyTemplateDto == null || (StringUtils.isBlank(notifyTemplateDto.getRecipients()) && resultMap.get(TemplateAttrEnums.recipients.name()) == null) || (StringUtils.isBlank(notifyTemplateDto.getMailTemplate()) && StringUtils.isBlank(notifyTemplateDto.getDDTemplate()))) {

logger.warn("场景:{} 下消息模版配置异常", needNotify.scene());

return;

}

...

if (StringUtils.isNotBlank(notifyTemplateDto.getMailTemplate())) {

sedMail(...);//具体发送邮件消息的封装实现

}

if (StringUtils.isNotBlank(notifyTemplateDto.getDDTemplate())) {

sedDD(...);//具体发送钉钉消息的封装实现

}

} catch (Exception e) {

logger.error("消息通知异常:", e);

}

}

@AfterReturning注解的切面在目标方法后执行,且能获取到目标方法的返回值。其属性pointcut定义了切面起作用的范围,returning定义返回的接收名,可用该定义取到方法的返回值。方法中第一个参数刚开始用成了ProceedJoinPoint结果报错,注意ProceedJoinPoint只能用于around切面。

测试成功,over。

以上是 Velocity可配置消息模版封装 的全部内容, 来源链接: utcz.com/z/515115.html

回到顶部