Spring容器装饰者模式应用之实现业务类与服务类自由组合的解决方案

在任何一个项目中都不可或缺的存在两种bean,一种是实现系统核心功能的bean,我们称之为业务类,另外一种是与系统核心业务无关但同时又提供十分重要服务bean,我们称之为服务类。业务类的bean根据每个系统自身核心功能的不同可以有任意多个,但是服务类的种类在各个系统之间的差异却并不是很大。在系统中经常用到的服务有以下几种,权限服务,日志服务,缓存服务,事务服务以及预警服务等。在整个系统的不断进化过程中,服务类与业务类的关系也不断的发生着变化,由当初垂直模式变为横切模式,这也是编程思想不断演化过程。服务类与业务类本来就不应耦合在一起,否则不但会造成大量的代码冗余同时也难以对服务进行可控性变更。那么如何解决根据不同业务类自身要求为其提供不同服务的问题呢?spring aop给出了完美解决 方案。Spring解决这个难题的核心思想是将服务类与业务类统统交由spring容器管理,根据不同业务类的不同要求动态配置服务类,也即动态联编服务类与业务类实现两者的自由组合。至于业务类与服务类之间的关系演变可以用下图简单呈现一下:

这张图是最原始的业务-服务关系图,看到这连想都不敢想了,同样的服务重复出现在N多个业务中,想想也是醉了,如果哪天服务内容改变除了跳楼估计也没有别的选择了,还好Spring AOP的出现将这个让人头疼的问题。使用Spring 注解方式将切面类横切到各个服务类中就可以一绝解决代码冗余,难于维护的问题

本图在代码中的提现如下:

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解决掉了,但是我们仔细想一想问题真的解决了吗?大家都知道这样的切入方式粒度实在是太粗了,并不是相同包下的所有类的所有方法都需要这样的服务,举个很简单的例子,就拿缓存服务和日志服务来讲,日志服务是需要详细记录的它可以切入任何类的任何方法上,但是缓存服务却并不是这样,缓存服务只可能存在于类的获取数据的方法上,此时将缓存服务切入到类的修改,删除等方法上就显得非常奇葩了,再通俗的将就是任何一个服务都可能只存在于特定类的特定方法上,这个不确定性决定了只使用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执行切入动作是通过装饰者模式来完成,采用类似栈的先进后出方式来具体执行服务这一点大家一定要注意啊。

版权声明:本文为博主原创文章,未经博主允许不得转载。

时间: 2024-10-13 14:50:50

Spring容器装饰者模式应用之实现业务类与服务类自由组合的解决方案的相关文章

springBoot中怎么减少if---else,怎么动态手动注册类进入Spring容器

由于业务中经常有需要判断的if--eles操作,层层嵌套,看起来程序的可读性太差,结合策略模式进行改造 方法一.一般有策略模式  +  工厂模式进行代码的优化,减少 if---else: 方法二.还有就是利用策略模式  +  SpringBoot提供的某些类  进行包装 本次介绍采用方法二的方式,大概的思路是: 1.策略模式:将所有同类型的操作抽象出来一个接口(这个接口包含一个动作方法) 和 一个实现了接口的抽象类(不实现方法):2.根据需求,将同类型的操作抽象成一个一个产品,继承第一步的抽象类

设计模式——装饰者模式

1.装饰者模式是在不必改变原类文件和使用继承关系的情况下,动态地扩展一个对象的功能.它是通过创建一个包装对象,也就是装饰来包裹真实的对象. 2.装饰模式的特点: 1)      装饰对象和真实对象有相同的接口.这样客户端对象就能以和真实对象相同的方式和装饰对象交互. 2)      装饰对象包含一个真实对象的引用 3)      装饰对象接受所有来自客户端的请求.它把这些请求转发给真实的对象 4)      装饰对象可以在转发这些请求以前或以后增加一些附加功能.这样就确保了在运行时,不用修改给定

自定义连接池(装饰者模式)

连接池概述: 管理数据库的连接, 作用: 提高项目的性能. 就是在连接池初始化的时候存入一定数量的连接,用的时候通过方法获取,不用的时候归还连接即可. 所有的连接池必须实现一个接口 javax.sql.DataSource接口 获取连接方法: Connection getConnection() 归还连接的方法就是以前的释放资源的方法.调用connection.close(); 增强方法: 1.继承 2.装饰者模式(静态代理) 3.动态代理 装饰者模式: 使用步骤: 1.装饰者和被装饰者实现同一

装饰者模式(不太理解的设计模式)

对于装饰者模式一直不太理解,不懂的他与桥接模式的区别在哪???? 23种设计模式之一,英文叫Decorator Pattern,又叫装饰者模式.装饰模式是在不必改变原类文件和使用继承的情况下,动态地扩展一个对象的功能.它是通过创建一个包装对象,也就是装饰来包裹真实的对象. 装饰模式的特点编辑 (1) 装饰对象和真实对象有相同的接口.这样客户端对象就能以和真实对象相同的方式和装饰对象交互. (2) 装饰对象包含一个真实对象的引用(reference) (3) 装饰对象接受所有来自客户端的请求.它把

设计模式(五): 装饰者模式

装饰器模式 装饰器模式(Decorator Pattern)允许向一个现有的对象添加新的功能,同时又不改变其结构.这种类型的设计模式属于结构型模式,它是作为现有的类的一个包装. 这种模式创建了一个装饰类,用来包装原有的类,并在保持类方法签名完整性的前提下,提供了额外的功能. 我们通过下面的实例来演示装饰器模式的用法.其中,我们将把一个形状装饰上不同的颜色,同时又不改变形状类. 介绍 意图:动态地给一个对象添加一些额外的职责.就增加功能来说,装饰器模式相比生成子类更为灵活. 主要解决:一般的,我们

Spring--------web应用中保存spring容器

---恢复内容开始--- 问题:在一个web应用中我使用了spring框架,但有一部分模块或组件并没有托管给Spring,比如有的可能是一个webservice服务类,如果我想在这些非托管的类里使用托管对象该怎么办呢,很自然的我们需要获得spring容器对象的引用ApplicationContext,我的想法是在服务启动后,想办法将ApplicationContext容器的应用保存到一个静态变量中,以后使用就简单了. 1)刚开始用的是spring+struts2,实力话spring用的是Cont

设计模式之装饰器模式

一.百科 概述: 23种设计模式之一,英文叫Decorator Pattern,又叫装饰者模式.装饰模式是在不必改变原类文件和使用继承的情况下,动态地扩展一个对象的功能.它是通过创建一个包装对象,也就是装饰来包裹真实的对象. 特点: (1) 装饰对象和真实对象有相同的接口.这样客户端对象就能以和真实对象相同的方式和装饰对象交互. (2) 装饰对象包含一个真实对象的引用(reference) (3) 装饰对象接受所有来自客户端的请求.它把这些请求转发给真实的对象. (4) 装饰对象可以在转发这些请

Java设计模式(四) 装饰器模式 代理器模式

(七)装饰器模式 Decorator 装饰器模式是为了动态的给一个对象增加一些新功能.装饰对象与被装饰的对象需要实现同一个接口,装饰对象持有被装饰对象的实例. interface DecoratorSourceable{ public void method(); } //被装饰类 class DecoratorSource implements DecoratorSourceable{ public void method(){ System.out.println("Source"

【设计者模式】装饰者模式

装饰器模式(Decorator Pattern)允许向一个现有的对象添加新的功能,同时又不改变其结构.这种类型的设计模式属于结构型模式,它是作为现有的类的一个包装. 这种模式创建了一个装饰类,用来包装原有的类,并在保持类方法签名完整性的前提下,提供了额外的功能. 我们通过下面的实例来演示装饰器模式的用法.其中,我们将把一个形状装饰上不同的颜色,同时又不改变形状类. 介绍 意图:动态地给一个对象添加一些额外的职责.就增加功能来说,装饰器模式相比生成子类更为灵活. 主要解决:一般的,我们为了扩展一个