什么是aop,为什么需要aop
AOP:aspect oriented parogramming面向切面编程,是对所有对象或者是某类对象编程,核心:在不增加代码的基础上还增加新功能
1就是为了方便,看一个国外很有名的大师说,编程的人都是“懒人”,因为他把自己做的事情都让程序做了。用了aop能让你少写很多代码,这点就够充分了吧
2就是为了更清晰的逻辑,可以让你的业务逻辑去关注自己本身的业务,而不去想一些其他的事情,这些其他的事情包括:安全,事物,日志等。
汇编:面向机器
C语言:面向过程,系统软件(防火墙,操作系统,数据库)大都是用C语言写的
Java语言:面向对象---->(面向切面aop)
aop相关概念
切点:定义拦截哪些方法
切面:定义对切点执行哪些操作
而这这些操作有可以分为:
前置通知:在方法执行前进行操作,切面类不调用切点的方法
后置通知:在方法执行后进行操作,切面类不调用切点的方法
环绕通知:在方法执行前后均有操作,切面类会调用切点的方法(如joinPoint.proceed())
所以:前置通知 + 后置通知 != 环绕通知
异常通知:仅仅处理有异常抛出的方法
aop实例-前置通知和自定义注解实现日志功能
1)自定义注解
@Target({ ElementType.PARAMETER, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Log {
/** 要执行的操作类型比如:add操作 **/
public String operationType() default "";
/** 要执行的具体操作比如:添加用户 **/
public String operationDesc() default "";
}
2)定义操作切面的实现类
/**
* 功能描述: 日志记录AOP实现
*/
public class LogAspect {
private static LogBusiness logBusiness;
static {
if (logBusiness == null) {
logBusiness = ContextLoader.getCurrentWebApplicationContext().getBean(LogBusiness.class);
}
}
public void beforeAdvice(JoinPoint joinPoint) {
try {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes())
.getRequest();
Signature signature = joinPoint.getSignature();
String methodName = signature.getName();
MethodSignature methodSignature = (MethodSignature) signature;
// 被拦截的方法
Method method = methodSignature.getMethod();
//获取被拦截方法上面的 @Log注解的内容
if (method.getAnnotation(Log.class) == null) {
return;
}
if (parameterTypes != null && arguments != null && parameterTypes.length == arguments.length) {
//获取被拦截方法上面的 @Log注解的内容值
operationType = method.getAnnotation(Log.class).operationType();
operationDesc = method.getAnnotation(Log.class).operationDesc();
AdminOperationLog log = new AdminOperationLog();
log.setOperationType(operationType);
******
log.setOperateTime(DateUtil.formatDate(new Date(), Constant.DATE_FORMAT_PATTENER));
logBusiness.addLog(log);
}
} catch (Exception e) {
// 记录本地异常日志
LOGGER.error("记录日志异常信息:{}", e);
}
}
/**
* 功能描述:删除不需要的参数项
*/
private void removeUnNeedArgs(List<Object> args) {
}
}
3)调用实例
@Log(operationType =“123”, operationDesc="wangwenjun")
public void do(String[] args) {
System.out.println("hello");
}
}
4)在配置文件中设置切入点和切面类
<!--注意:proxy-target-class属性值决定是基于接口的还是基于类的代理被创建。如果proxy-target-class 属性值被设置为true,那么基于类的代理将起作用(这时需要cglib库)。如果proxy-target-class属值被设置为false或者这个属性被省略,那么标准的JDK 基于接口的代理将起作用。即使你未声明 proxy-target-class="true" ,但运行类没有继承接口,spring也会自动使用CGLIB代理。高版本spring自动根据运行类选择 JDK 或 CGLIB 代理。-->
<!--<aop:aspectj-autoproxy proxy-target-class="true"/>-->
<!—定义bean-->
<bean id="logAspect" class="com.suning.gcpm.admin.util.log.LogAspect"></bean>
<!—定义切面信息-->
<aop:config>
<!-- 切面类 -->
<aop:aspect ref="logAspect">
<!-- 切入点 -->
<aop:pointcut expression="execution (* com.suning.gcpm.admin.web.controller..*.*(..))" id="myPointcut" />
<!-- 在方法执行前 执行切入的方法 -->
<aop:before method="beforeAdvice" pointcut-ref="myPointcut" />
<!-- 在方法执行后 执行切入的方法 -->
<!--<aop:after method="afterAdvice" pointcut-ref="myPointcut" /> -->
</aop:aspect>
</aop:config>
1.@aspect:表明是切面类
2.@Pointcut(value = "execution (* com.cms.admin.web.controller.*.*(..))"):定义统一拦截的切入点,其中第一个*表示任意返回类型,第二个*表示任意类名,假如第二个*前面是两个.则表示包括包里面的子包,第三个*表示任意方法名,后面的小括号表示任意参数值
3.@Before("controllerAspect()"):表示拦截方法执行前的动作
注意:文件中的aop的配置必须在service的实例化之后,即aop的配置切入点对象注入配置之后,aop的相关配置才会有效,否则无效。
异常通知:实现的接口是ThrowsAdvice
说明:该实例借鉴与https://www.cnblogs.com/sjcq/p/7450192.html
定义异常类
package com.apt.study.exception;
public class StudyException extends RuntimeException {
private static final long serialVersionUID = -6183216129830888521L;
/**
* 异常信息
*/
protected String message;
/**
* 异常编码
*/
protected int code;
public static final StudyException PARAM_VALIDATE_EXCEPTION = new StudyException(1234,"参数校验出错");
public StudyException(int code, String msgFormat, Object... args) {
super(String.format(msgFormat, args));
this.code = code;
this.message = String.format(msgFormat, args);
}
public StudyException(String message, Throwable cause) {
super(message, cause);
}
public StudyException(Throwable cause) {
super(cause);
}
public StudyException(String message) {
super(message);
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public int getCode() {
return code;
} public void setCode(int code) {
this.code = code;
}
}
定义异常处理类
package com.apt.study.exception;
import java.lang.reflect.Method;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.aop.ThrowsAdvice;
public class ExceptionLogHandler implements ThrowsAdvice{
public Logger logger = LoggerFactory.getLogger(ExceptionLogHandler.class);
public void afterThrowing(Method method, Object[] args, Object target, StudyException ex) {
logger.error("ExceptionLogHandler--StudyExcception");
logger.info("-------errCode:" + ex.getCode() + "; errMessage:" + ex.getMessage());
errorClassInfo(ex);
logger.info("-------" + ex.fillInStackTrace());
}
public void afterThrowing(Method method, Object[] args, Object target, Exception ex) {
logger.error("ExceptionLogHandler--Exception");
logger.error("------->Erro Class: "+ target.getClass().getName());
logger.error("------->Error method:"+ method.getName());
if(args != null) {
for(int i=0; i<args.length; i++) {
logger.error("------->args[" + i + "]: " + args[i]);
}
}
logger.error("------->Exception class: " + ex.getClass().getName());
errorClassInfo(ex);
logger.error("------->" + ex.fillInStackTrace());
}
//打印抛出异常地方的信息
private void errorClassInfo(Exception ex) {
/*
* ex.getStackTrace()返回堆栈跟踪元素的数组,每个元素表示一个堆栈帧。数组的第零个元素(假定数据的长度为非零)表示堆栈顶部,
* 它是序列中最后的方法调用。
* 通常,这是创建和抛出该 throwable 的地方。数组的最后元素(假定数据的长度为非零)表示堆栈底部,它是序列中第一个方法调用
*/
StackTraceElement[] stackTraceElementArr= ex.getStackTrace();
StackTraceElement stackTraceElement = stackTraceElementArr[0];
logger.error("------->Erro File:" + stackTraceElement.getFileName());
logger.error("------->Erro Method:" + stackTraceElement.getMethodName());
logger.error("------->Erro Line:" + stackTraceElement.getLineNumber());
}
}
application-context.xml的配置
<!-- 异常信息 拦截 -->
<bean id="exceptionLog" class="com.apt.study.exception.ExceptionLogHandler"></bean>
<aop:config>
<aop:pointcut id="exceptionTrade" expression="execution(* com.apt.study.service.*.*(..))" />
<aop:advisor pointcut-ref="exceptionTrade" advice-ref="exceptionLog"/>
</aop:config>
环绕通知实现记录每个方法的执行时间
定义实现MethodInterceptor的切面类
package org.cboard.services.aop;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.StopWatch;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* @ClassName: LogAspect
* @Description: 日志记录AOP实现
*/
public class LogAspect implements MethodInterceptor {
private static Logger logger = LoggerFactory.getLogger(LogAspect.class);
/**
* desc: 增加日志:被拦截方法的执行时间
*/
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
StopWatch clock = new StopWatch();
clock.start(); //计时开始
Object result = invocation.proceed();
clock.stop(); //计时结束
//方法参数类型,转换成简单类型
Class[] params = invocation.getMethod().getParameterTypes();
String[] simpleParams = new String[params.length];
for (int i = 0; i < params.length; i++) {
simpleParams[i] = params[i].getSimpleName();
}
logger.info("Takes:" + clock.getTime() + " ms ["
+ invocation.getThis().getClass().getName() + "."
+ invocation.getMethod().getName() + "(" + StringUtils.join(simpleParams, ",") + ")] ");
return result;
}
}
spring.xml中配置
<bean id="logAspect" class="org.cboard.services.aop.LogAspect"></bean>
<aop:config>
<aop:advisor id="logPoint" advice-ref="logAspect" pointcut="execution(* org.cboard..*.*(..))"/>
<!--<aop:advisor id="methodTimeLog" advice-ref="methodTimeAdvice" pointcut="execution(* *..service..*(..))"/>-->
</aop:config>
多个aop的坑
<bean id="dataSourceAspect" class="com.ruisitech.datasource.DataSourceAspect"></bean>
<!--定义切面信息-->
<aop:config>
<!-- 切面类 -->
<aop:aspect ref="dataSourceAspect">
<!-- 切入点 -->
<aop:pointcut expression="execution (* com.ruisitech.bi.service..*.*(..))" id="myPointcut" />
<!-- 切入的方法 -->
<aop:before method="beforeAdvice" pointcut-ref="myPointcut" />
<aop:after method="afterAdvice" pointcut-ref="myPointcut" />
</aop:aspect>
</aop:config>
<bean id="jobLockAspect" class="com.ruisitech.bi.job.manage.JobLockAspect"></bean>
<!--定义切面信息-->
<aop:config>
<!-- 切面类 -->
<aop:aspect ref="jobLockAspect">
<!-- 切入点 -->
<aop:pointcut expression="execution (* com.ruisitech.bi.job..*.*(..))" id="myPointcut" />
<!-- 切入的方法 -->
<aop:around method="cacheLockPoint" pointcut-ref="myPointcut" />
</aop:aspect>
</aop:config>
如果代码中用到了多个aop,并且有多个的配置,如上面的配置,注意两个aop配置中切点id相同,都是myPointcut,那上面DataSourceAspect对应切入点不再是com.ruisitech.bi.service包,而是com.ruisitech.bi.job包,很奇怪,应该是切入点名称相同时,以后来者为准,所以id为myPointcut的切入点对应的包最终对应为com.ruisitech.bi.job包。
但是因为 id
解决:
将两个切入点id修改为不同即可。
aop用好了简化代码,用不好采坑,看下面三个坑:
见:https://blog.csdn.net/h2604396739/article/details/102610610