Spring容器装饰者模式应用之实现业务类与服务类自由组合的解决方式
在不论什么一个项目中都不可或缺的存在两种bean,一种是实现系统核心功能的bean,我们称之为业务类,第二种是与系统核心业务无关但同一时候又提供十分重要服务bean,我们称之为服务类。业务类的bean依据每一个系统自身核心功能的不同能够有随意多个,可是服务类的种类在各个系统之间的差异却并非非常大。在系统中经经常使用到的服务有下面几种。权限服务,日志服务。缓存服务,事务服务以及预警服务等。在整个系统的不断进化过程中。服务类与业务类的关系也不断的发生着变化,由当初垂直模式变为横切模式,这也是编程思想不断演化过程。服务类与业务类本来就不应耦合在一起,否则不但会造成大量的代码冗余同一时候也难以对服务进行可控性变更。
那么怎样解决依据不同业务类自身要求为其提供不同服务的问题呢?spring aop给出了完美解决 方案。Spring解决这个难题的核心思想是将服务类与业务类统统交由spring容器管理。依据不同业务类的不同要求动态配置服务类。也即动态联编服务类与业务类实现两者的自由组合。至于业务类与服务类之间的关系演变能够用下图简单呈现一下:
这张图是最原始的业务-服务关系图,看到这连想都不敢想了,相同的服务反复出如今N多个业务中,想想也是醉了,假设哪天服务内容改变除了跳楼预计也没有别的选择了,还好Spring AOP的出现将这个让人头疼的问题。使用Spring 注解方式将切面类横切到各个服务类中就能够一绝解决代码冗余。难于维护的问题
watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center" alt="">
本图在代码中的提现例如以下:
package com.test.util; @Aspect
public class AuthorityService { @Autowired
private LogManager logManager; @Before("execution(* com.test.web.*.*(..))")
public void logAll(JoinPoint point) throws Throwable {
System.out.println("======authority-before======");
} @After("execution(* com.test.web.*.*(..))")
public void after() {
System.out.println("=========authority-after=========");
} // 方法运行的前后调用
@Around("execution(* com.test.web.*.*(..))")
public Object around(ProceedingJoinPoint point) throws Throwable { System.out.println("======authority-around開始之前before======");
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder
.getRequestAttributes()).getRequest(); // 获得具体时间
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
// Calendar ca = Calendar.getInstance();
// String operDate = df.format(ca.getTime()); Log sysLog = new Log();
// 開始时间
sysLog.setStartTime(df.format(new Date()));
// 获取ip地址
String ip = TCPIPUtil.getIpAddr(request); String loginName;
String name; String methodRemark = getMthodRemark(point);
String methodName = point.getSignature().getName();
String packages = point.getThis().getClass().getName();
if (packages.indexOf("$$EnhancerByCGLIB$$") > -1) { // 假设是CGLIB动态生成的类
try {
packages = packages.substring(0, packages.indexOf("$$"));
} catch (Exception ex) {
ex.printStackTrace();
}
} Object object;
try {
// method_param = point.getArgs(); //获取方法參数
// String param=(String) point.proceed(point.getArgs());
object = point.proceed();
} catch (Exception e) {
// 异常处理记录日志..log.error(e);
throw e;
} sysLog.setIp(ip);
sysLog.setClazz(packages);
sysLog.setMethod(methodName);
sysLog.setMessage(methodRemark);
// 结束时间
sysLog.setEndTime(df.format(new Date()));
// 返回结果
if (object == null) {
sysLog.setResult("无返回值");
} else {
sysLog.setResult(object.toString());
} logManager.addLog(sysLog); System.out.println("======authority-around開始之前after======"); return object;
} @AfterReturning("execution(* com.test.web.*.*(..))")
public void x(){
System.out.println("-------authority-afterreturning-------");
} // 获取方法的中文备注____用于记录用户的操作日志描写叙述
public static String getMthodRemark(ProceedingJoinPoint joinPoint)
throws Exception {
String targetName = joinPoint.getTarget().getClass().getName();
String methodName = joinPoint.getSignature().getName();
Object[] arguments = joinPoint.getArgs(); Class targetClass = Class.forName(targetName);
Method[] method = targetClass.getMethods();
String methode = "";
for (Method m : method) {
if (m.getName().equals(methodName)) {
Class[] tmpCs = m.getParameterTypes();
if (tmpCs.length == arguments.length) {
Logger methodCache = m.getAnnotation(Logger.class);
// 获得标记,为空时没有标记
if (methodCache != null) {
methode = methodCache.remark();
}
break;
}
}
}
return methode;
} }
从上面代码能够看出该权限服务类切入到了com.test.web包下的全部类的全部方法上。同理我们能够再建立缓存服务类。日志服务类等分别切入到其它业务类中,如今看来这个令人头疼的问题似乎被spring基于@Aspect风格的aop解决掉了,可是我们细致想一想问题真的攻克了吗?大家都知道这种切入方式粒度实在是太粗了,并非同样包下的全部类的全部方法都须要这种服务,举个非常easy的样例。就拿缓存服务和日志服务来讲,日志服务是须要具体记录的它能够切入不论什么类的不论什么方法上。可是缓存服务却并非这样。缓存服务仅仅可能存在于类的获取数据的方法上,此时将缓存服务切入到类的改动。删除等方法上就显得非常奇葩了,再通俗的将就是不论什么一个服务都可能仅仅存在于特定类的特定方法上,这个不确定性决定了仅仅使用Spring
@Aspect注解方式是难以解决下图问题的
此时我们须要如何做呢?还好Spring同一时候提供了基于xml配置方式的aop,这种方式在相当大的程度上弥补了@Aspect不足,他能够依据不同的配置方式将不同的服务切入到不同类的不同方法上。这样细的粒度足以让我们实现各种服务对业务的动态组合。说道这可能会有人问既然spring提供了基于xml配置的aop。那我们仅仅须要将不同的服务均配置在xml文件里不是相同能够实现业务与服务的动态组合吗?听上去似乎非常有道理,果真这种话似乎基于注解的aop似乎就能够被替代了。可是程序效率的问题又让我们不得不正确xml配置方式和注解方式进行再次衡量。
因为xml是在执行期动态联编确定切面的,解析xml的过程毫无疑问的会占用系统资源,假设将大量的aop配置配置在xml文件里将会得不偿失,而@Aspect方式支持编译期织入且不须要生成代理,这样就使得效率上会有优势。如此来看仅仅要将xml方式与@Aspect方式混合使用。将粗粒度的服务(如日志和权限服务)使用@Aspect方式进行切入,对于细粒度的服务(如缓存服务)使用xml方式配置在spring中就能够完美解决以上问题了。
以下是两种方式结合使用的实例。先来看配置文件:
<? xml version="1.0" encoding="UTF-8"? >
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.2.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.2.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd"> <!-- 注解扫描包 -->
<context:component-scan base-package="com.test" /> <!-- 开启注解 -->
<mvc:annotation-driven /> <!-- 静态资源(js/image)的訪问 -->
<mvc:resources location="/js/" mapping="/js/**" /> <!-- 定义视图解析器 -->
<bean id="viewResolver"
class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/"></property>
<property name="suffix" value=".jsp"></property>
</bean> <!-- 启用Spring对基于@AspectJ aspects的配置支持 -->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy> <bean id="logService" class="com.test.util.LogService"></bean>
<bean id="cacheService" class="com.test.util.CacheService"></bean> <aop:config proxy-target-class="true">
<aop:aspect ref="cacheService">
<aop:pointcut id="log" expression="execution(* com.test.web.UserController.getAllUser(..))"/>
<aop:before pointcut-ref="log" method="logAll"/>
<aop:after pointcut-ref="log" method="after"/>
<aop:after-returning pointcut-ref="log" method="x"/>
<aop:around pointcut-ref="log" method="around"/>
</aop:aspect>
</aop:config>
</beans>
接下来是日志服务类:
package com.test.util; @Aspect
public class LogService { @Autowired
private LogManager logManager; @Before("execution(* com.test.web.*.*(..))")
public void logAll(JoinPoint point) throws Throwable {
System.out.println("======log-before======");
} @After("execution(* com.test.web.*.*(..))")
public void after() {
System.out.println("=========cache-after=========");
} // 方法运行的前后调用
@Around("execution(* com.test.web.*.*(..))")
public Object around(ProceedingJoinPoint point) throws Throwable { System.out.println("======log-around開始之前before======");
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder
.getRequestAttributes()).getRequest(); // 获得具体时间
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); Log sysLog = new Log();
// 開始时间
sysLog.setStartTime(df.format(new Date()));
// 获取ip地址
String ip = TCPIPUtil.getIpAddr(request); String loginName;
String name; String methodRemark = getMthodRemark(point);
String methodName = point.getSignature().getName();
String packages = point.getThis().getClass().getName();
if (packages.indexOf("$$EnhancerByCGLIB$$") > -1) { // 假设是CGLIB动态生成的类
try {
packages = packages.substring(0, packages.indexOf("$$"));
} catch (Exception ex) {
ex.printStackTrace();
}
} // String operatingcontent = "";
// Object[] method_param = null; Object object;
try {
// method_param = point.getArgs(); //获取方法參数
// String param=(String) point.proceed(point.getArgs());
object = point.proceed();
} catch (Exception e) {
// 异常处理记录日志..log.error(e);
throw e;
} sysLog.setIp(ip);
sysLog.setClazz(packages);
// 包名.+方法名
// packages + "." + methodName
sysLog.setMethod(methodName);
sysLog.setMessage(methodRemark);
// 结束时间
sysLog.setEndTime(df.format(new Date()));
// 返回结果
if (object == null) {
sysLog.setResult("无返回值");
} else {
sysLog.setResult(object.toString());
} logManager.addLog(sysLog); System.out.println("======log-around開始之前after======"); return object;
} @AfterReturning("execution(* com.test.web.*.*(..))")
public void x(){
System.out.println("-------log-afterreturning-------");
} // 获取方法的中文备注____用于记录用户的操作日志描写叙述
public static String getMthodRemark(ProceedingJoinPoint joinPoint)
throws Exception {
String targetName = joinPoint.getTarget().getClass().getName();
String methodName = joinPoint.getSignature().getName();
Object[] arguments = joinPoint.getArgs(); Class targetClass = Class.forName(targetName);
Method[] method = targetClass.getMethods();
String methode = "";
for (Method m : method) {
if (m.getName().equals(methodName)) {
Class[] tmpCs = m.getParameterTypes();
if (tmpCs.length == arguments.length) {
Logger methodCache = m.getAnnotation(Logger.class);
// 获得标记,为空时没有标记
if (methodCache != null) {
methode = methodCache.remark();
}
break;
}
}
}
return methode;
} }
再接下来是缓存服务类;
package com.test.util; public class LogService { @Autowired
private CacheManager logManager; public void pointcut() {
} public void logAll(JoinPoint point) throws Throwable {
System.out.println("======cache-before======");
} public void after() {
System.out.println("=========cache-after=========");
} // 方法运行的前后调用
public Object around(ProceedingJoinPoint point) throws Throwable { System.out.println("======cache-around開始之前before======");
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder
.getRequestAttributes()).getRequest(); // 获得具体时间
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
// Calendar ca = Calendar.getInstance();
// String operDate = df.format(ca.getTime()); Log sysLog = new Log();
// 開始时间
sysLog.setStartTime(df.format(new Date()));
// 获取ip地址
String ip = TCPIPUtil.getIpAddr(request);
String loginName;
String name; String methodRemark = getMthodRemark(point);
String methodName = point.getSignature().getName();
String packages = point.getThis().getClass().getName();
if (packages.indexOf("$$EnhancerByCGLIB$$") > -1) { // 假设是CGLIB动态生成的类
try {
packages = packages.substring(0, packages.indexOf("$$"));
} catch (Exception ex) {
ex.printStackTrace();
}
} Object object;
try {
// method_param = point.getArgs(); //获取方法參数
// String param=(String) point.proceed(point.getArgs());
object = point.proceed();
} catch (Exception e) {
// 异常处理记录日志..log.error(e);
throw e;
} sysLog.setIp(ip);
sysLog.setClazz(packages);
// 包名.+方法名
// packages + "." + methodName
sysLog.setMethod(methodName);
sysLog.setMessage(methodRemark);
// 结束时间
sysLog.setEndTime(df.format(new Date()));
// 返回结果
if (object == null) {
sysLog.setResult("无返回值");
} else {
sysLog.setResult(object.toString());
} logManager.addLog(sysLog); System.out.println("======cache-around開始之前after======"); return object;
} public void x(){
System.out.println("-------cache-afterreturning-------");
} }
日志服务类和缓存服务类仅仅在详细处理过程中不一样,本文在日志服务的基础上稍加修改简化处理。大家能够在使用时依据自身情况进行缓存处理和日志处理。在详细运行过程中细心的朋友可能会发如今配置文件里不同服务的出现顺序决定了两个服务的运行顺序。也就是说在spring aop运行切入动作是通过装饰者模式来完毕,採用类似栈的先进后出方式来详细运行服务这一点大家一定要注意啊。
Spring容器装饰者模式应用之实现业务类与服务类自由组合的解决方式的更多相关文章
- springBoot中怎么减少if---else,怎么动态手动注册类进入Spring容器
由于业务中经常有需要判断的if--eles操作,层层嵌套,看起来程序的可读性太差,结合策略模式进行改造 方法一.一般有策略模式 + 工厂模式进行代码的优化,减少 if---else: 方法二.还有 ...
- 重学 Java 设计模式:实战装饰器模式(SSO单点登录功能扩展,增加拦截用户访问方法范围场景)
作者:小傅哥 博客:https://bugstack.cn 沉淀.分享.成长,让自己和他人都能有所收获! 一.前言 对于代码你有编程感觉吗 很多人写代码往往是没有编程感觉的,也就是除了可以把功能按照固 ...
- 大型Java进阶专题(八)设计模式之适配器模式、装饰者模式和观察者模式
前言 今天开始我们专题的第八课了.本章节将介绍:三个设计模式,适配器模式.装饰者模式和观察者模式.通过学习适配器模式,可以优雅的解决代码功能的兼容问题.另外有重构需求的人群一定需要掌握装饰者模式. ...
- Decorate Pattern 装饰者模式
装饰模式的定义: 动态地将责任附加到对象向,若要扩展功能,装饰模式提供了比继承更有弹性的替代方案. 遵循的设计原则是开闭原则,也是对扩展开放,对修改关闭. 下面是类图 示例代码 /** *定义被装饰者 ...
- 通过单元测试理解spring容器以及dubbo+zookeeper单元测试异常处理
一.先说一个结论:单元测试与主项目的spring容器是隔离的,也就是说,单元测试无法访问主项目spring容器,需要自己加载spring容器. 接下来是代码实例,WEB主项目出于运行状态,单元测试中可 ...
- Spring读书笔记-----使用Spring容器(二)
一.使用ApplicationContext 前面介绍了,我们一般不会使用BeanFactory实例作为Spring容器,而是使用ApplicationContext实例作为容器,它增强了BeanFa ...
- (转)Spring读书笔记-----使用Spring容器(二)
一.使用ApplicationContext 前面介绍了,我们一般不会使用BeanFactory实例作为Spring容器,而是使用ApplicationContext实例作为容器,它增强了BeanFa ...
- Spring--------web应用中保存spring容器
---恢复内容开始--- 问题:在一个web应用中我使用了spring框架,但有一部分模块或组件并没有托管给Spring,比如有的可能是一个webservice服务类,如果我想在这些非托管的类里使用托 ...
- Java设计模式(四) 装饰 代理模式
(七)装饰 Decorator 装饰是一个对象,以动态地增加一些新功能. 象与被装饰的对象须要实现同一个接口.装饰对象持有被装饰对象的实例. interface DecoratorSourceable ...
随机推荐
- day63-webservice 06.在web项目中发布以类的形式发布webservice
真正用的时候都是需要部署在WEB服务器里面. 不能写主函数来发布了,需要借助于我们WEB. 4.配置web.xml, <!DOCTYPE web-app PUBLIC "-//Sun ...
- C - Elephant(贪心)
Problem description An elephant decided to visit his friend. It turned out that the elephant's house ...
- P1732 活蹦乱跳的香穗子
题目描述 香穗子在田野上调蘑菇!她跳啊跳,发现自己很无聊,于是她想了一个有趣的事情,每个格子最多只能经过1次,且每个格子都有其价值 跳的规则是这样的,香穗子可以向上下左右四个方向跳到相邻的格子,并且她 ...
- Promise API 简介
Promise API 简介 译者注: 到处是回调函数,代码非常臃肿难看, Promise 主要用来解决这种编程方式, 将某些代码封装于内部. Promise 直译为"承诺",但一 ...
- jquery mobile datepicker
1.http://jquerymobile.com/demos/1.0a4.1/experiments/ui-datepicker/ 这个只能用在iOS和PC上,使用<input date,An ...
- H5 微信公众号 监听返回事件
/*-----监听返回事件-----*/ function pushHistory(returnUrl,currentUrl,currentTitle) { window.addEventListen ...
- YARN环境搭建 之 二:CentOS7.0 JDK配置
Hadoop是Java写的,他无法使用Linux预安装的OpenJDK,因此安装hadoop前需要先安装JDK(1.6以上) 原材料:在Oracle官网下载的32位JDK: 说明: 1.C ...
- ubuntu 安装 OpenCV-CUDA
参考链接:http://www.cnblogs.com/platero/p/3993877.html 官方指导:https://help.ubuntu.com/community/OpenCV 0.这 ...
- vc++如何创建程序-析构函数01
#include<iostream.h>class Point{public: int x; int y; Point() { x=0; y=0; }//构造函数是用来创建函数本身,那么, ...
- mybatis传入参数类型parameterType详解
前言 Mybatis的Mapper文件中的select.insert.update.delete元素中都有一个parameterType属性,用于对应的mapper接口方法接受的参数类型. ( res ...