介紹
在開發(fā)中我們經常使用oop這種縱向結構來開發(fā)蹂随,但是卻會出現一些橫切的功能十嘿。譬如,日志記錄的功能岳锁,我們需要在每個方法執(zhí)行的詳細信息通過日志記錄绩衷,但是我們?yōu)槊總€方法去寫日志,明顯不合理激率。再如異常處理功能咳燕,我們需要在每個方法執(zhí)行拋出的異常都專門處理都不合理蓝翰。這樣就需要AOP面向切面開發(fā)來處理橫切問題豁生。
AOP術語
- 通知(advice):
通知主要是定義切面是什么以及何時使用逞度。
Before:在接合點之前執(zhí)行通知抬旺。
AfterReturning:在接合點執(zhí)行完成之后執(zhí)行通知。
AfterThrowing:如果從接合點拋出了任何異常译暂,都執(zhí)行通知永淌。
After:接合點執(zhí)行完成之后,無論是否拋出了異常健爬,都執(zhí)行通知。
Around:在接合點周圍執(zhí)行通知么介,意思就是可能在接合點之前執(zhí)行娜遵,也可能在接合點之后執(zhí)行。 - 連接點(join point):
意思就是代碼中的點壤短,在這個點上開始玩切面设拟。效果肯定是向應用程序中插入額外的邏輯。 - 切點(point cut):
用來選擇需要執(zhí)行一個或者多個連接點的表達式久脯。 - 切面(aspect):
切面就是切點和通知的結合纳胧。 - 織入(weaving):
將方面與目標對象結合在一起的過程。 - 引入(introduce):
動態(tài)地為已經存在的類添加屬性和方法帘撰。
XML方式實現AOP
配置文件:
<context:component-scan base-package="cn.spy"></context:component-scan>
<context:annotation-config></context:annotation-config>
<aop:config>
<aop:pointcut expression="execution(* cn.spy.service.impl.MyServiceImpl.*(..))" id="pc"/>
<aop:aspect ref="myAop">
<aop:before method="beforeExecute" pointcut-ref="pc"/>
<aop:after method="afterExecute" pointcut-ref="pc"/>
</aop:aspect>
</aop:config>
強制設置使用cglib代理:<aop:config proxy-target-class="true">
業(yè)務類和接口
public interface MyService {
public void serviceMed();
}
@Component("myService")
public class MyServiceImpl extends BaseLog implements MyService{
@Override
public void serviceMed() {
System.out.println("業(yè)務邏輯方法");
}
}
切面類:
@Component("myAop")
public class MyAop extends BaseLog {
public void beforeExecute() {
System.out.println("before execute");
}
public void afterExecute() {
System.out.println("after execute");
}
}
測試類:
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");
MyService myService = (MyService) context.getBean("myService");
myService.serviceMed();
}
結果:
注解方式實現AOP
切面類:
@Component("myAop")
@Aspect
public class MyAop extends BaseLog {
@Pointcut("execution(* cn.spy.service.impl.MyServiceImpl.*(..))")
public void cutMethod() {
}
@Before("cutMethod()")
public void beforeExecute() {
System.out.println("before execute");
}
@After("cutMethod()")
public void afterExecute() {
System.out.println("after execute");
}
}
強制使用cglib代理:@EnableAspectJAutoProxy(proxyTargetClass=true)
aop自定義注解實現記錄操作日志
- 自定義注解
/**
* 日志操作注解
*/
@Documented
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface LogOperation {
/**
* 操作所屬的模塊
* @return
*/
public String module() default "";
/**
* 操作
* @return
*/
public String operation() default "";
}
- 實現切面類
@Slf4j
@Component
@Aspect
public class LogAspect {
@Autowired
private MachineBO machine;
@Autowired
private LogMapper logMapper;
@Pointcut("@annotation(com.sunpy.permissionservice.log.LogOperation)")
public void logPoint() {
}
@AfterReturning(pointcut = "logPoint()", returning = "result")
public void doLog(JoinPoint joinPoint, Object result) {
// 從切面織入點處通過反射機制獲取織入點處的方法
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
// 獲取切入點所在的方法
Method method = signature.getMethod();
// 獲取請求的類名
String className = joinPoint.getTarget().getClass().getName();
LogOperation logOperation = method.getAnnotation(LogOperation.class);
Log log = new Log();
log.setLogModule(logOperation.module());
log.setLogOperation(logOperation.operation());
log.setLogClazz(className);
log.setLogMethod(method.getName());
log.setLogMethodResult(method.getReturnType().getName());
StringBuilder sb = new StringBuilder();
for (Parameter parameter : method.getParameters()) {
sb.append(parameter.getParameterizedType().getTypeName());
sb.append(",");
}
log.setLogMethodParam(sb.toString());
insertLog(log);
}
private void insertLog(Log log) {
long logId = new SnowFlakeIdUtil(machine.getWorkerId(), machine.getDatacenterId()).genNextId();
log.setLogId(logId);
log.setCreateTime(TimeUtil.getLocalDateTime());
logMapper.insert(log);
}
}
- 使用:
切點表達式
- 表達式語法:
execution(<scope> <return-type><fully-qualified-class-name>.*(parameters))
- 例子:
execution(* cn.spy.service.impl.MyServiceImpl.(..))
解釋:匹配cn.spy.service.impl.MyServiceImpl類下的所有方法
execution(public void cn.spy.service.impl.MyServiceImpl.(..))
解釋:匹配cn.spy.service.impl.MyServiceImpl類下的public void xx();方法跑慕。
execution(public void cn.spy.service.impl.MyServiceImpl.*(String,..))
解釋:匹配cn.spy.service.impl.MyServiceImpl類下第一個參數為String類型,無返回值的所有公共方法。 - 通配符:
..該通配符匹配方法定義中的任何數量的參數核行。(也可以匹配任意數量的包)
+該通配符匹配給定類的任何子類牢硅。
*該通配符匹配任意數量的字符。