所谓AOP就是面向切面编程,简单的说就是在原来的方法基础上通过AOP切面对该方法进行增强。
一、Spring默认不支持@AspectJ风格的切面声明,为了支持需要在配置文件中添加如下配置:
<aop:aspectj-autoproxy/>
如下所示:
注:以上配置如果在controller层和service层中都用到,那么都需要在controller的配置和spring主配置都添加,如果不添加的效果在下面会有讲解。
二、定义切面aspect
1、定义切入面:在所定义的类中使用注解@Aspect即表示该类作为切面,如:
2、定义切入点:在这里我使用的是注解的方式(也可以使用较常用的通配符方式)
2.1 定义注解:
3、定义通知:根据业务需求自己定义,在这里我使用的是后置通知:即表示的是在使用该注解@LogController方法执行完之后再执行此切面方法
getControllerMethodDescription()
/**
* 获取注解中对方法的描述信息 用于Controller层注解
*
* @param joinPoint 切入点
* @return 方法描述
* @throws Exception
*/
@SuppressWarnings("rawtypes")
private Map<String, String> getControllerMethodDescription(JoinPoint joinPoint) throws Exception {
Map<String, String> result = new HashMap<String, String>();
//类名
String targetName = joinPoint.getTarget().getClass().getName();
//方法名称
String methodName = joinPoint.getSignature().getName();
//获取入参列表
Object[] arguments = joinPoint.getArgs();
//类对象
Class<?> targetClass = Class.forName(targetName);
Method[] methods = targetClass.getMethods();
for (Method method : methods) {
if (method.getName().equals(methodName)) {
Class[] clazzs = method.getParameterTypes();
if (clazzs.length == arguments.length) {
//获取注解对象
LogController logController=method.getAnnotation(LogController.class);
result.put("logType", logController.logType().toString());
result.put("operation", logController.operation());
result.put("description", targetName+joinPointStr+methodName+leftJoinStr+getParamsRemark(joinPoint,logController, arguments)+rightJoinStr);
break;
}
}
}
return result;
}
getParamsRemark()
private String getParamsRemark(JoinPoint joinPoint,LogController logController, Object[] param){
System.out.println(logController.propertys()
+"==========");
//如果没传递参数属性,说明传递的是对象
if(StringUtils.isBlank(logController.propertys())){
return getMethodParams(joinPoint);
}
if(param ==null || param.length==0){
param[0]="";
}
//获取参数属性 name,age
String propertys = logController.propertys();
StringBuffer sb = new StringBuffer();
if(propertys == null || "".equals(propertys)){
return sb.toString();
}
//分割获取参数属性名称 name age
String[] propertysRemark = propertys.split(splitParaStr);
if(propertysRemark == null || propertysRemark.length == 0){
return sb.toString();
}
int count = 0;
for (int i=0;i<propertysRemark.length;i++) {
Object name=null;
if(param[0] instanceof String){
//表明是多个参数,否则就是对象
name=param[i];
}else{
name=param[0];
}
//参数属性对应的值
String value = propertysRemark[i];
if(count == propertysRemark.length){
sb.append(propertysRemark[i] + splitNameValueStr + getID(name, value) +splitParaStr);
}else{
sb.append(propertysRemark[i] + splitNameValueStr + getID(name, value));
}
count++;
sb.append(splitParaStr);
}
return sb.substring(0, sb.length()-1).toString();
}
getID()
/**
* 通过java反射来从传入的参数object里取出我们需要记录的id,name等属性,
* 此处我取出的是id
*/
private String getID(Object obj,String param){
if(obj instanceof String){
return obj.toString();
}
if(obj instanceof Integer){
return obj.toString();
}
//构造set和get
PropertyDescriptor pd = null;
Method method = null;
String v = "";
try{
pd = new PropertyDescriptor(param,obj.getClass());
method = pd.getReadMethod();
v = String.valueOf(method.invoke(obj));
}catch (Exception e) {
e.printStackTrace();
}
return v;
}
getMethodParams()
/**
*
* @描述:获取方法参数
* @创建人:rz.li
* @创建时间:2017年7月18日上午9:36:04
* @param joinPoint
* @return
*/
private String getMethodParams(JoinPoint joinPoint) {
String params = "";
if (joinPoint.getArgs() != null && joinPoint.getArgs().length > 0) {
for (int i = 0; i < joinPoint.getArgs().length; i++) {
Object object = joinPoint.getArgs()[i];
try {
params += JSONUtils.obj2json(object) + ";";
} catch (Exception e) {
e.printStackTrace();
}
}
}
return params;
}
4、开始使用切面:
在Controller层中
5、测试结果:
三、异常信息的处理
1、定义:对于异常信息的AOP,跟上面定义的方式一样,同样使用注解方式,代码如下:
@Pointcut("@annotation(com.demo.annotation.LogService)")
public void serviceAspect() {}
2、注解:
@Target({ElementType.PARAMETER, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface LogService {
/** 要执行的操作类型比如:add操作 **/
public LogTypeEnum logType() default LogTypeEnum.SYS_LOG;
/** 要执行的具体操作比如:添加用户 **/
public String description() default "";
}
3、使用:
使用后置异常通知进行对Service层异常信息的捕获,此时在service方法中不需要try-catch捕获异常信息,如下所示:
@LogService(logType=LogTypeEnum.SYS_LOG,description="测试aop异常捕获")
@Override
public void testAop(BatchSellPO b) {
int i=1/0;
System.out.println(i);
}
具体异常处理逻辑
@AfterThrowing(pointcut = "serviceAspect()", throwing = "e")
public void doAfterThrowing(JoinPoint joinPoint, Throwable e) {
try {
//TODO 获取用户的信息
String userId="123";
System.out.println("=====异常通知开始=====");
System.out.println("异常代码:" + e.getClass().getName());
System.out.println("异常信息:" + e.getMessage());
System.out.println("异常方法:" + joinPoint.getTarget().getClass().getName()+joinPointStr+joinPoint.getSignature().getName() + "()");
System.out.println("方法描述:" + getServiceMethodDescription(joinPoint));
System.out.println("请求人ID:" + userId);
System.out.println("请求IP:" + request.getRemoteAddr());
System.out.println("请求参数:" + getMethodParams(joinPoint));
/*==========数据库日志=========*/
SysLog log = new SysLog();
//保存数据库
//sysLogService.insert(log);
System.out.println("=====异常通知结束=====");
} catch (Exception e2) {
logger.error(e2.getMessage(), e2);
}
}
getServiceMethodDescription()
/**
* @描述:获取注解中对方法的描述信息 用于Service层注解
* @创建人:rz.li
* @创建时间:2017年8月31日上午10:26:16
* @param joinPoint
* @return 方法描述
* @throws Exception
*/
@SuppressWarnings("rawtypes")
private Map<String, Object> getServiceMethodDescription(JoinPoint joinPoint) throws Exception {
Map<String, Object> result = new HashMap<String, Object>();
String targetName = joinPoint.getTarget().getClass().getName();
String methodName = joinPoint.getSignature().getName();
Object[] arguments = joinPoint.getArgs();
Class<?> targetClass = Class.forName(targetName);
Method[] methods = targetClass.getMethods();
for (Method method : methods) {
if (method.getName().equals(methodName)) {
Class[] clazzs = method.getParameterTypes();
if (clazzs.length == arguments.length) {
result.put("logType", method.getAnnotation(LogService.class).logType());
result.put("description", method.getAnnotation(LogService.class).description());
break;
}
}
}
return result;
}
4、测试
在访问的时候没进入代理方法,看异常信息发现默认使用的是jdk代理(接口代理),而AOP切面的注解时基于类的代理,故改为cglib代理(类代理),由此可判断在spring主配置文件中没有添加
<aop:aspectj-autoproxy proxy-target-class="true"/>
添加配置之后测试结果如下:
说明异常切面已成功!