Spring Aop 应用实例与设计浅析
0.代码概述
代码说明:第一章中的代码为了突出模块化拆分的必要性,所以db采用了真实操作。下面代码中dao层使用了打印日志模拟插入db的方法,方便所有人运行demo。
1.项目代码地址:https://github.com/kingszelda/SpringAopPractice
2.结构化拆分,代码包结构:org.kingszelda.version1
3.Spring AOP,代码包结构:org.kingszelda.version2
4.AspectJ AOP,代码包结构:org.kingszelda.version3
1.为什么会出现AOP
相信很多人和我一样,编程入门从c语言开始,之后接触的java等其他面向对象语言。刚接触编程语言时编写的代码以实现功能为首要目标,因此很少考虑模块化、封装等因素,以一个计算功能的web项目为例。该web项目有如下功能:
- 通过http接口提供加法计算功能
- 计算请求与结果需要留存
- 需统计接口调用次数,打印请求响应日志,打印方法运行时间
就算基础java语言而言,这个功能也比较简单。如果不考虑http协议的问题,单独编写demo功能只需要一部分。
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.util.HashMap;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory; /**
* 计算器
*/
public class CalculateService { private static final Logger logger = LoggerFactory.getLogger(CalculateService.class); //1.构造接口调用次数计数Map
public Map<String, Integer> countMap = new HashMap<String, Integer>(); public int add(int first, int second) throws Exception {
//2.获得计算开始时间
long start = System.currentTimeMillis();
//3.打印入口参数
logger.info("方法入参为:{}{}", first, second);
//4.将该方法调用次数+1后放入map
countMap.put("calcAdd", count);
//5.计算加法
int result = first + second;
//6.加载mysql驱动
Class.forName("com.mysql.jdbc.Driver");
//7.配置mysql连接属性,地址、用户名、密码
String url = "jdbc:mysql://localhost:3306/samp_db";
String userName = "root";
String passWord = "123456";
//8.获得mysql连接
Connection conn = DriverManager.getConnection(url, userName, passWord);
//9.生成插入sql
String sql = "INSERT INTO calcAdd (`first`,`second`,`result`) VALUES(?,?,?)";
//10.使用preparedStatement防止sql注入
PreparedStatement ps = conn.prepareStatement(sql);
ps.setString(1, String.valueOf(first));
ps.setString(2, String.valueOf(second));
ps.setString(3, String.valueOf(result));
//11.执行sql
ps.execute();
//12.释放sql与con连接
ps.close();
conn.close();
//13.打印返回参数
logger.info("方法结果为:{}", result);
//14.打印方法总耗时
logger.info("运行时间:{}ms", System.currentTimeMillis() - start);
//13.返回计算结果
return result;
}
}
1.1 模块化
上述代码完全可以满足要求,只是看起来有点长,当新增减法计算器等其他计算功能的时候,新增的代码重复的很多,比如计算的“+”号换成“-”号,存入数据库表从加法表换到减法表,然后计算接口调用次数。从模块化的角度来看,上面的加法计算器代码就可以做如下拆分:
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.Statement; /**
* 数据库工具类
*/
public class DbUtil { public static Connection getConnection() throws Exception {
//1.加载mysql驱动
Class.forName("com.mysql.jdbc.Driver");
//2.配置mysql连接属性,地址、用户名、密码
String url = "jdbc:mysql://localhost:3306/samp_db";
String userName = "root";
String passWord = "123456";
//3.获得mysql连接
return DriverManager.getConnection(url, userName, passWord);
} public static void closeCon(Connection connection, Statement statement) throws Exception {
//1.先关闭sql连接
statement.close();
//2.再关闭数据库连接
connection.close();
} public static Statement getInsertAddStatement(int first, int second, int result, Connection connection) throws Exception {
//7.生成插入sql
String sql = "INSERT INTO calcAdd (`first`,`second`,`result`) VALUES(?,?,?)";
//8.使用preparedStatement防止sql注入
PreparedStatement ps = connection.prepareStatement(sql);
ps.setString(1, String.valueOf(first));
ps.setString(2, String.valueOf(second));
ps.setString(3, String.valueOf(result));
return ps;
} }
import java.util.HashMap;
import java.util.Map; /**
* 方法调用次数计数器
*/
public class CountUtil { public static Map<String, Integer> countMap = new HashMap<>(); public static void countMethod(String methodName) {
Integer count = countMap.get(methodName);
count = (count != null) ? new Integer(count + 1) : new Integer(1);
countMap.put(methodName, count);
}
}
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.util.HashMap;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory; /**
* 计算器
*/
public class CalculateService { private static final Logger logger = LoggerFactory.getLogger(CalculateService.class); //1.构造接口调用次数计数Map
public Map<String, Integer> countMap = new HashMap<String, Integer>(); public int add(int first, int second) throws Exception {
//2.获得计算开始时间
long start = System.currentTimeMillis();
//3.打印入口参数
logger.info("方法入参为:{}{}", first, second);
//4.将该方法调用次数+1后放入map
CountUtil.countMethod("calcAdd");
//5.计算加法
int result = first + second;
//6.数据库操作
Connection conn = DbUtil.getConnection();
Statement ps = DbUtil.getInsertAddStatement(first, second, result, conn);
DbUtil.closeCon(conn, ps);
//7.打印返回参数
logger.info("方法结果为:{}", result);
//8.打印方法总耗时
logger.info("运行时间:{}ms", System.currentTimeMillis() - start);
//9.返回计算结果
return result;
}
}
经过上述拆分,功能由原来的一块代码变为2大块与3小块,大块分别是:
- 数据库相关处理
- 方法调用计数处理
小块则是:
- 打印请求
- 打印方法耗时
- 打印响应
- 统计方法调用次数
经过拆分以后,代码的复用性增强了很多,模块之间的边界也变得很清晰。并且随着设计模式的发展,上面的2大块可以进行进一步抽象,可以抽象出一个统一的主逻辑,然后加法计算进一步抽象为运算模块,这样就可以通过派生支持减法乘法等其他方法运算。这是数据设计模式的内容,这里不做赘述。此时代码拆分逻辑显然是垂直拆分,如图:
如图所示,模块化之前,代码是一整块,当功能越来越饿复杂之后,这块代码将无法区分边界,变得不好维护。模块化之后,代码分模块独立,边界清晰、好维护。由于代码总是一行一行的从上到下执行,所以很自然的拆分逻辑就是从上到下纵向拆分。
但是当我们仔细分析不同模块之间的区别之后,现有的纵向拆分并非达到了最佳状态。因为大块之间虽然清晰了,但是小块之间还是散落在各处,并且都是简单的两行,无法进行进一步封装。并且代码模块的业务也不相同。比如上述的存入数据库与计算方法调用次数之间就有区别。数据库模块关心具体业务,比如是哪个数据库,那张表,插入语句的具体内容。但是计算方法调用次数模块是不关心业务的,只是对调用次数进行统计,这种模块的应用有很多,比如打印日志,计算qps,计算运行时间等,不论是什么业务,这种运算总是相同的。所以,当业务变复杂之后,这些代码可以进行横向拆分。
至此,我们可以先给出模块化拆分之后的代码:
此时的代码结构是:
此时的代码为:
package org.kingszelda.version1.service; import org.kingszelda.common.dao.AddDao;
import org.kingszelda.common.dao.SubDao;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.RequestMapping; import javax.annotation.Resource; /**
* Created by shining.cui on 2017/7/15.
*/
@Service
@RequestMapping("version1")
public class CalculateService { private static final Logger logger = LoggerFactory.getLogger(CalculateService.class); @Resource
private AddDao addDao; @Resource
private SubDao subDao; @Resource
private MethodCounter methodCounter; public int add(int first, int second) {
//1.获得计算开始时间
long start = System.currentTimeMillis();
//2.打印入口参数
logger.info("方法入参为:{}{}", first, second);
//3.计算调用次数
methodCounter.count("sub");
//4.计算加法
int result = first + second;
//5.插入数据库
addDao.insert(first, second, result);
//6.打印返回参数
logger.info("方法结果为:{}", result);
//7.打印方法总耗时
logger.info("运行时间:{}ms", System.currentTimeMillis() - start);
//8.返回结果
return result;
} public int sub(int first, int second) {
//1.获得计算开始时间
long start = System.currentTimeMillis();
//2.打印入口参数
logger.info("方法入参为:{}{}", first, second);
//3.计算调用次数
methodCounter.count("sub");
//4.计算加法
int result = first - second;
//5.插入数据库
subDao.insert(first, second, result);
//6.打印返回参数
logger.info("sub 方法结果为:{}", result);
//7.打印方法总耗时
logger.info("运行时间:{}ms", System.currentTimeMillis() - start);
//8.返回结果
return result;
}
}
package org.kingszelda.version1.service; import com.google.common.collect.Maps;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component; import java.util.Map; /**
* Created by shining.cui on 2017/7/15.
*/
@Component
public class MethodCounter { private static final Logger logger = LoggerFactory.getLogger(MethodCounter.class); //防止并发
private static final Map<String, Integer> methodCountMap = Maps.newConcurrentMap(); /**
* 根据方法名进行调用次数计数
*/
public void count(String methodName) {
Integer methodCount = methodCountMap.get(methodName);
methodCount = (methodCount != null) ? new Integer(methodCount + 1) : new Integer(1);
logger.info("对方法{}进行次数加1,当前次数为:{}", methodName, methodCount);
methodCountMap.put(methodName, methodCount);
} public Map<String, Integer> getMethodCountMap() {
return methodCountMap;
}
}
1.2 切面织入
正如上面的代码一样,我们的项目开始支持加法与减法两种计算,此时的纵向拆分的代码是这样的结构:
这样的结构也很清晰,没有任何问题,但是存在了一些小瑕疵,那就是违反了DRY法则(Don't Repeat Yourself):计算次数、打印求响应、打印方法耗时出现在不同的程序的相同位置。虽然模块化之后的这两个功能调用都只需要一行,但是依然是发生了重复,这时候就是Aop登场的最佳时刻。
当时用了Aop横向拆分之后,业务模块就只关心业务(加法计算器只关心加法计算与存入加法表),不用再关心一些通用的处理功能——日志与qps。这时候的代码发生了本质上的改变,首先需要一个Aop模块功能,然后通过"配置"的方式横向“织入”想要的代码模块中。这时候就算新增了乘法计算器,也只需要编写业务功能——“乘法计算与入库”,然后配置之前的Aop模块即可生效。
从图中我们可以看到,进行切面编程有三个步骤:
- 定义切面功能,Advice即通知,比如打印请求,计算调用次数的功能。
- 定义切点,Pointcut即切点,比如开始的时候打印请求,结束的时候打印响应。对应功能1的调用时间定义。
- 组织功能,即Advisor通知器,将切面功能织入切点上。
是时候引出Aop的定义了,以下定义引自维基百科:
面向侧面的程序设计(aspect-oriented programming,AOP,又译作面向方面的程序设计、观点导向编程、剖面导向程序设计)是计算机科学中的一个术语,指一种程序设计范型。该范型以一种称为侧面(aspect,又译作方面)的语言构造为基础,侧面是一种新的模块化机制,用来描述分散在对象、类或函数中的横切关注点(crosscutting concern)。
2.AOP与Spring AOP
AOP的出现使得代码的整体设计括了一个维度,竖向写业务逻辑,横向切面写公共逻辑。如同其他概念一样,这项技术有着各种各样的实现,比较著名的有AspectJ,Javassist等。为了制定统一规范,于是出现了AOP联盟来起引导与约束的作用。
正如上图所示,切面拆分场景一般分为4种情况:
- 进入业务之前,比如统计qps,打印请求参数
- 完成业务之后,比如打印响应结果
- 环绕业务,比如计算方法耗时,需要在业务前计时,业务后取时间差
- 业务抛异常后,比如统一的异常处理。这一点上面的代码没有体现。
上面4种情况的的间隔其实比较模糊,比如环绕业务其实可以包含前、后、异常这三种情况,因为本质上都是在业务运行前后加通用逻辑,其实Spring AOP的AfterReturningAdvice,MethodBeforeAdvice,ThrowsAdvice也是基于MethodInterceptor的环绕业务实现的。
对于Spring来说,其核心模块是IoC与AOP,其AOP是与IoC结合使用的。Spring不仅支持本身的Aop实现,同时也支持AspectJ的AOP功能。因此我们说:
- AOP是一种技术规范,本身与Spring无关
- Spring 实现了自身的AOP,即Spring AOP
- Spring 同时支持AspectJ的AOP功能
3.使用Spring AOP架构的代码
使用Spring AOP调整后的业务代码得到了一定的精简,整体代码结构如图:
首先是定义三个切面业务Advice,进行打印日志、运行时间与统计qps功能。
package org.kingszelda.version2.aop; import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.aop.AfterReturningAdvice;
import org.springframework.stereotype.Component; import java.lang.reflect.Method; /**
* 方法后切面,打印响应结果
* Created by shining.cui on 2017/7/15.
*/
@Component
public class CalculateAfterAdvice implements AfterReturningAdvice { private static final Logger logger = LoggerFactory.getLogger(CalculateAfterAdvice.class); public void afterReturning(Object returnObject, Method method, Object[] args, Object target) throws Throwable {
String methodName = method.getDeclaringClass().getSimpleName() + "." + method.getName();
String returnValue = String.valueOf(returnObject);
logger.info("方法{}的响应结果为{}", methodName, returnValue);
}
}
package org.kingszelda.version2.aop; import org.kingszelda.version2.service.MethodCounter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.aop.MethodBeforeAdvice;
import org.springframework.stereotype.Component; import java.lang.reflect.Method;
import java.util.Arrays; /**
* 方法前切面,打印请求参数,统计调用次数
* Created by shining.cui on 2017/7/15.
*/
@Component
public class CalculateBeforeAdvice extends MethodCounter implements MethodBeforeAdvice { private static final Logger logger = LoggerFactory.getLogger(CalculateBeforeAdvice.class); public void before(Method method, Object[] args, Object target) throws Throwable {
String methodName = method.getDeclaringClass().getSimpleName() + "." + method.getName();
String argStr = Arrays.toString(args);
logger.info("方法{}的请求参数为{}", methodName, argStr);
count(methodName);
}
}
package org.kingszelda.version2.aop; import org.aopalliance.intercept.MethodInvocation;
import org.kingszelda.version2.service.CalculateService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.aop.IntroductionInterceptor;
import org.springframework.stereotype.Component; /**
* 方法后切面,打印响应结果
* Created by shining.cui on 2017/7/15.
*/
@Component
public class CalculateMethodInterceptor implements IntroductionInterceptor { private static final Logger logger = LoggerFactory.getLogger(CalculateMethodInterceptor.class); public Object invoke(MethodInvocation methodInvocation) throws Throwable {
//1.获得计算开始时间
long start = System.currentTimeMillis();
String methodName = methodInvocation.getMethod().getDeclaringClass().getSimpleName() + "." + methodInvocation.getMethod().getName();
//2.运行程序
Object proceed = methodInvocation.proceed();
//3.打印间隔时间
logger.info("方法{}运行时间:{}ms", methodName, System.currentTimeMillis() - start);
return proceed;
} public boolean implementsInterface(Class<?> aClass) {
//满足CalculateService接口的方法都进行拦截
return aClass.isAssignableFrom(CalculateService.class);
}
}
此时的业务代码精简为如下:
package org.kingszelda.version2.service.impl; import org.kingszelda.common.dao.AddDao;
import org.kingszelda.common.dao.SubDao;
import org.kingszelda.version2.service.CalculateService; import javax.annotation.Resource; /**
* Created by shining.cui on 2017/7/15.
*/
public class CalculateServiceImpl implements CalculateService { @Resource
private AddDao addDao; @Resource
private SubDao subDao; @Override
public int add(int first, int second) {
//1.计算加法
int result = first + second;
//2.插入数据库
addDao.insert(first, second, result);
//3.返回结果
return result;
} @Override
public int sub(int first, int second) {
//1.计算加法
int result = first - second;
//2.插入数据库
subDao.insert(first, second, result);
//3.返回结果
return result;
}
}
到目前为止,切面逻辑已经与业务逻辑分离,接下来需要做的就是定义切点PointCut与通知器Advisor,即将业务与切面在合适的时候组合起来。这也是最容易出错的地方。
<?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:mvc="http://www.springframework.org/schema/mvc"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <!--spring mvc注解默认配置-->
<context:component-scan base-package="org.kingszelda"/>
<mvc:annotation-driven/> <!--下面是version2的配置,二选一均可--> <!-- version2 配置方案1 -->
<bean id="calculateServiceV2" class="org.springframework.aop.framework.ProxyFactoryBean">
<!-- 需要代理的接口 -->
<property name="proxyInterfaces" value="org.kingszelda.version2.service.CalculateService"/>
<!-- 被代理的实体,一定要是上面接口的实现类 -->
<property name="target">
<bean class="org.kingszelda.version2.service.impl.CalculateServiceImpl"/>
</property>
<!-- 调用代理对象方法时的额外操作 -->
<property name="interceptorNames">
<!--这里的顺序对责任链顺序有直接影响,所以能合并的都可以合并在一个过滤器中,本例都可以合并到middleInterceptor中-->
<list>
<value>calculateMethodInterceptor</value>
<value>calculateBeforeAdvice</value>
<value>calculateAfterAdvice</value>
</list>
</property>
</bean> <!-- version2 配置方案2 -->
<!-- 业务前切点 -->
<bean id="beforeAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor">
<constructor-arg ref="calculateBeforeAdvice"/>
<constructor-arg ref="pointcut"/>
</bean> <!-- 返回前切点 -->
<bean id="afterAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor">
<constructor-arg ref="calculateAfterAdvice"/>
<constructor-arg ref="pointcut"/>
</bean> <!-- 环绕式切点,相对于before after throw,这是属于高一层设计,因为before等也是通过Interceptor实现的 -->
<bean id="middleInterceptor" class="org.springframework.aop.support.DefaultPointcutAdvisor">
<constructor-arg ref="calculateMethodInterceptor"/>
<constructor-arg ref="pointcut"/>
</bean> <!-- 定义切点位置,只有满足正则的才拦截,如果不配置切点,则拦截所有接口方法 -->
<bean id="pointcut" class="org.springframework.aop.support.JdkRegexpMethodPointcut">
<property name="pattern" value=".*version2.*add|.*version2.*sub"/>
</bean> </beans>
可以看到,Spring AOPde 配置方式可以分为两种:一种是手动配置被代理对象,只对配置对象织入。另外一种是只声明Advisor通知器,由Spring 根据定义的切点PointCut进行织入。两种方法都可以,第一种更贴近于源码的实现。
至此,基于Spring AOP的整合已经结束,运行代码后发现虽然业务没有关系日志等操作,但切面逻辑已经完全织入业务中,业务代码整洁了好多。
这里需要注意一点,我们已经不能像之前使用calculateService那样直接注入使用了,因为我们需要使用的是calculateService的代理类,这个代理类在调用真正的业务实现之前根据配置依次调用切面逻辑。上面的配置很明朗,我们使用的calculateServiceV2是由ProxyFactoryBean生成的对于接口proxyInterfaces的Proxy实现,真正被代理的对象对象是target,在调用target之前会依次执行interceptorNames配置的拦截器责任链。与此相关的代码解释的文章很多,这里就扒源码了。
4.Spring集成AspectJ的AOP功能
在我看来Spring的AOP功能做的不够有决心,要么就定义好各种Spring AOP且切面场景,要么则都使用Intercepter由用户统一管理。两者都兼顾的Spring AOP在让用户使用的时候会产生一定的困惑,究竟使用那种方式更加好一些。并且Spring的老毛病也凸现出来,那就是xml如何妥善配置。
而Spring对AspectJ的集成则方便了很多。项目结构如图:
相对于Spring AOP时的代码,变动只有一个切面类与xml配置
package org.kingszelda.version3.aop; import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component; import java.util.Arrays; /**
* 基于AspectJ的切面功能
*
* @author shining.cui
* @since 2017-07-16
*/
@Aspect
@Component
public class ControllerAop extends MethodCounter { private static final Logger logger = LoggerFactory.getLogger(ControllerAop.class); // 拦截version3包中的所有service
@Pointcut("(execution(* org.kingszelda.version3.service.*.*(..)))")
public void pointcut() {
} @Around("pointcut()")
public Object controllerHandler(ProceedingJoinPoint joinPoint) {
String signature = joinPoint.getSignature().toShortString();
//1.统计调用次数
count(signature);
String argStr = Arrays.toString(joinPoint.getArgs());
//2.打印请求
logger.info("方法{}的请求参数为{}", signature, argStr);
Object result = null;
//3.获得计算开始时间
long start = System.currentTimeMillis();
try {
//4.运行业务
result = joinPoint.proceed();
} catch (Throwable e) {
logger.error("web 应用发生异常", e);
}
//5.打印运行时间
logger.info("方法{}运行时间:{}ms", signature, System.currentTimeMillis() - start);
String returnValue = String.valueOf(result);
//6.打印响应
logger.info("方法{}的响应结果为{}", signature, returnValue);
return result;
} }
配置也简单了很多:
<!-- 集成AspectJ的AOP功能 -->
<aop:aspectj-autoproxy />
程序运行结果与上一章相同。
5.总结
Spring AOP是AOP联盟规范中Spring的一种实现形式,同时Spring也支持了AspectJ的AOP实现。相对于应用来说,AspectJ的AOP结构更加清晰,配置也更加简单。Spring中的AOP实现都是通过代理完成的,在默认的情况下,如果代理类是接口,则使用jdk动态代理,如果不是则使用CGLIB进行代理。
通过AOP功能可以很好的将通用逻辑与业务逻辑分离,使得结构化更明朗。对于打印日志,统计qps,统计运行时间,发送消息,写入请求记录表等操作均可以很好的支持。Spring框架本身的一些功能也是基于AOP实现的,比如Spring的事务管理。因此研究Spring AOP总是会给我们的学习与工作带来好处。
最后,以上代码均可运行,地址参见第0章。
Spring Aop 应用实例与设计浅析的更多相关文章
- Spring AOP应用实例demo
AOP(Aspect-Oriented Programming.面向方面编程).能够说是OOP(Object-OrientedPrograming.面向对象编程)的补充和完好.OOP引入封装.继承和多 ...
- Spring AOP 入门实例详解
目录 AOP概念 AOP核心概念 Spring对AOP的支持 基于Spring的AOP简单实现 基于Spring的AOP使用其他细节 AOP概念 AOP(Aspect Oriented Program ...
- jdk动态代理与cglib代理、spring Aop代理原理-代理使用浅析
原创声明:本博客来源为本人原创作品,绝非他处摘取,转摘请联系博主 代理(proxy)的定义:为某对象提供代理服务,拥有操作代理对象的功能,在某些情况下,当客户不想或者不能直接引用另一个对象,而代理对象 ...
- Spring学习(十五)----- Spring AOP通知实例 – Advice
Spring AOP(面向方面编程)框架,用于在模块化方面的横切关注点.简单得说,它只是一个拦截器拦截一些过程,例如,当一个方法执行,Spring AOP 可以劫持一个执行的方法,在方法执行之前或之后 ...
- Spring AOP通知实例 – Advice
Spring AOP(面向方面编程)框架,用于在模块化方面的横切关注点.简单得说,它只是一个拦截器拦截一些过程,例如,当一个方法执行,Spring AOP 可以劫持一个执行的方法,在方法执行之前或之后 ...
- [Spring] AOP, Aspect实例解析
最近要用到切面来统一处理日志记录,写了个小实例练了练手: 具体实现类: public interface PersonServer { public void save(String name); p ...
- Spring AOP分析(2) -- JdkDynamicAopProxy实现AOP
上文介绍了代理类是由默认AOP代理工厂DefaultAopProxyFactory中createAopProxy方法产生的.如果代理对象是接口类型,则生成JdkDynamicAopProxy代理:否则 ...
- Spring AOP实例——异常处理和记录程序执行时间
实例简介: 这个实例主要用于在一个系统的所有方法执行过程中出线异常时,把异常信息都记录下来,另外记录每个方法的执行时间. 用两个业务逻辑来说明上述功能,这两个业务逻辑首先使用Spring AOP的自动 ...
- (转)实例简述Spring AOP之间对AspectJ语法的支持(转)
Spring的AOP可以通过对@AspectJ注解的支持和在XML中配置来实现,本文通过实例简述如何在Spring中使用AspectJ.一:使用AspectJ注解:1,启用对AspectJ的支持:通过 ...
随机推荐
- 第一个asp.net实例——生日邀请以及回函
22回校后,看了论文游了西湖,今天开始接触asp.net,从图书馆选了两本书:<精通ASP.NET 4.5 (第五版)>,<ASP.NET全能速查手册>.一本练手细看,一本翻查 ...
- Python-cookies
附新手工具: python新手工具下载链接:http://pan.baidu.com/s/1eS8WMR4 密码:7eso 注册码:http://idea.liyang.io py和编辑器安装教程链接 ...
- centos中安装mysql
一.首先输入指令 rpm -qa|grep mysql 检查操作系统中是否已经安装了MySQL 可以通过 yum list | grep mysql 命令来查看yum上提供的mysql数据库可下载的版 ...
- Hardcoded string XXX,&…
eclipse布局文件警告:Hardcoded string XXX, should use @string resource
- 玛雅游戏[NOIP2011]
题目描述 Mayan puzzle 是最近流行起来的一个游戏.游戏界面是一个7 行5 列的棋盘,上面堆放着一些方块,方块不能悬空堆放,即方块必须放在最下面一行,或者放在其他方块之上.游戏通关是指在规定 ...
- vue指令v-bind示例解析
1.绑定一个属性 <img id="app" v-bind:src="imageSrc"> <script> var app = Vue ...
- 数据结构-->栈
首先,栈是什么? 在代码当中,栈主要是一种实现特殊功能的一种数据结构,而不是像数组.集合之类的数据存储工具.它最大的特点就是后进先出. 那么后进先出是什么? 假设有一个数组,我们向里面添加一个数据,再 ...
- let const 下篇
1.不存在变量提升 在之前的js代码中,声明一个变量或者是函数,会存在变量提升的现象,也就是说变量可以在声明之前使用,值为undefined: es5: console.log(a); //undef ...
- latch相关视图整理
latch相关视图整理(原创) V$LATCH V$LATCH视图在选取X$KSLLT记录时,进行了Group By及SUM运算,从而得出了一个汇总信息,保存了自实例启动后各类栓锁的统计信息.常用于当 ...
- DOS批处理中%cd%和%~dp0的区别[forward]
DOS批处理中%cd%和%~dp0的区别 在DOS的批处理中,有时候需要知道当前的路径. 在DOS中,有两个环境变量可以跟当前路径有关,一个是%cd%, 一个是%~dp0. 这两个变量的 ...