在不论什么一个项目中都不可或缺的存在两种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容器装饰者模式应用之实现业务类与服务类自由组合的解决方式的更多相关文章

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

    由于业务中经常有需要判断的if--eles操作,层层嵌套,看起来程序的可读性太差,结合策略模式进行改造 方法一.一般有策略模式  +  工厂模式进行代码的优化,减少 if---else: 方法二.还有 ...

  2. 重学 Java 设计模式:实战装饰器模式(SSO单点登录功能扩展,增加拦截用户访问方法范围场景)

    作者:小傅哥 博客:https://bugstack.cn 沉淀.分享.成长,让自己和他人都能有所收获! 一.前言 对于代码你有编程感觉吗 很多人写代码往往是没有编程感觉的,也就是除了可以把功能按照固 ...

  3. 大型Java进阶专题(八)设计模式之适配器模式、装饰者模式和观察者模式

    前言 ​ 今天开始我们专题的第八课了.本章节将介绍:三个设计模式,适配器模式.装饰者模式和观察者模式.通过学习适配器模式,可以优雅的解决代码功能的兼容问题.另外有重构需求的人群一定需要掌握装饰者模式. ...

  4. Decorate Pattern 装饰者模式

    装饰模式的定义: 动态地将责任附加到对象向,若要扩展功能,装饰模式提供了比继承更有弹性的替代方案. 遵循的设计原则是开闭原则,也是对扩展开放,对修改关闭. 下面是类图 示例代码 /** *定义被装饰者 ...

  5. 通过单元测试理解spring容器以及dubbo+zookeeper单元测试异常处理

    一.先说一个结论:单元测试与主项目的spring容器是隔离的,也就是说,单元测试无法访问主项目spring容器,需要自己加载spring容器. 接下来是代码实例,WEB主项目出于运行状态,单元测试中可 ...

  6. Spring读书笔记-----使用Spring容器(二)

    一.使用ApplicationContext 前面介绍了,我们一般不会使用BeanFactory实例作为Spring容器,而是使用ApplicationContext实例作为容器,它增强了BeanFa ...

  7. (转)Spring读书笔记-----使用Spring容器(二)

    一.使用ApplicationContext 前面介绍了,我们一般不会使用BeanFactory实例作为Spring容器,而是使用ApplicationContext实例作为容器,它增强了BeanFa ...

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

    ---恢复内容开始--- 问题:在一个web应用中我使用了spring框架,但有一部分模块或组件并没有托管给Spring,比如有的可能是一个webservice服务类,如果我想在这些非托管的类里使用托 ...

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

    (七)装饰 Decorator 装饰是一个对象,以动态地增加一些新功能. 象与被装饰的对象须要实现同一个接口.装饰对象持有被装饰对象的实例. interface DecoratorSourceable ...

随机推荐

  1. Node.js:NPM 使用介绍

    ylbtech-Node.js:NPM 使用介绍 1.返回顶部 1. NPM 使用介绍 NPM是随同NodeJS一起安装的包管理工具,能解决NodeJS代码部署上的很多问题,常见的使用场景有以下几种: ...

  2. Mysql外键的变种 三种关系

    一.介绍 因为有foreign key的约束,使得两张表形成了三种了关系: 多对一 多对多 一对一 二.重点理解如果找出两张表之间的关系 分析步骤: #1.先站在左表的角度去找 是否左表的多条记录可以 ...

  3. springmvc-servlet.xml(springmvc-servlet.xml 配置 增强配置)

    <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.sp ...

  4. Hadoop MapReduce编程 API入门系列之网页流量版本1(二十一)

    不多说,直接上代码. 对流量原始日志进行流量统计,将不同省份的用户统计结果输出到不同文件. 代码 package zhouls.bigdata.myMapReduce.areapartition; i ...

  5. guice基本学习,guice的学习资料(十)

    这个是我前面几篇的参考. guice的学习资料下载:http://pan.baidu.com/s/1bDEPem 路途遥远,但是人确在走.不忘初心,方得始终.

  6. Java 开源博客 Solo 1.1.0 发布 - 告别 GAE

    Solo 1.1.0 正式发布了,感谢一直以来关注 B3log 开源的朋友! 在这个版本中,我们对项目结构和发布包进行了重大调整: 各式***尚未普及,所以决定去除 GAE 版本 H2 版本使用人数较 ...

  7. hdu1385 Minimum Transport Cost 字典序最小的最短路径 Floyd

    求最短路的算法最有名的是Dijkstra.所以一般拿到题目第一反应就是使用Dijkstra算法.但是此题要求的好几对起点和终点的最短路径.所以用Floyd是最好的选择.因为其他三种最短路的算法都是单源 ...

  8. SQL 字段类型详解

    bit    整型 bit数据类型是整型,其值只能是0.1或空值.这种数据类型用于存储只有两种可能值的数据,如Yes 或No.True 或False .On 或Off.    注意:很省空间的一种数据 ...

  9. C#操作Oracle数据库中文乱码问题解决

    最近公司有一个对外项目,采用的是oracle数据库,以前做的项目基本都是SQLserver,有和oracle对接的也就一些简单的增删查改. 还巧合的遇到乱码问题,网上各种查找,筛选,总算是把问题解决了 ...

  10. 关于maven-基本

    笔记 Maven项目对象模型(POM),可以通过一小段描述信息来管理项目的构建,报告和文档的软件项目管理工具. Maven是一个项目管理工具,它包含了一个项目对象模型 (Project Object ...