在不论什么一个项目中都不可或缺的存在两种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解决掉了,可是我们细致想一想问题真的攻克了吗?大家都知道这种切入方式粒度实在是太粗了,并非同样包下的全部类的全部方法都须要这种服务,举个非常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运行切入动作是通过装饰者模式来完毕,採用类似栈的先进后出方式来详细运行服务这一点大家一定要注意啊。