基本概念java
就是你想要的功能,也就是咱们常说的安全、事物、日志等。先定义好这些,而后再想用的地方用一下。包含Aspect的一段处理代码web
注意:其实这些功能(通知)并非咱们业务逻辑所必须的,只是为了安全,输出信息,或者其余的缘由,总之是为了方便咱们对项目维护而增长的操做,通常咱们会把这些功能封装成相关的方法,可是咱们又不想这些功能直接入侵咱们的正常业务代码,由于这样会增长关注度而且污染咱们的业务逻辑,因此咱们就用切面的思想来很好解决这个问题spring
就是spring容许你加 通知(Advice)的地方,那可就真多了,基本每一个方法的前、后(二者都有也行),或抛出异常时均可以是链接点,spring通常只支持方法链接点,除非引入其余的aop框架才能够实现更细粒度的链接点。其余如AspectJ还可让你在构造器或属性注入时都行,不过那不是我们关注的,只要记住,和方法有关的前先后后都是链接点express
在上面说的链接点的基础上,来定义切入点。例如:你的一个类里,有15个方法,那就有至少十几个链接点了对吧,可是你并不想在全部方法附近都使用通知(使用叫织入,下面再说),你只是想让其中几个,在调用这几个方法以前、以后或者抛出异常时干点什么,那么就用切入点来定义这几个方法,让切点来筛选链接点,选中那几个你想要的方法安全
注意:切入点定义的通常是你的业务中的某些方法(也有多是某些特殊地方,在“特例”下有个例子切点切的就是方法,而是自定义的注解),就是供切面实际切入的地方,也就是须要执行通知的地方框架
用来切插业务方法的类。fetch
切面是通知和切入点的结合。如今发现了吧,没链接点什么事,连接点就是为了让你好理解切点搞出来的,明白这个概念就好了。通知说明了干什么和何时干(何时经过方法名中的befor,after,around等就能知道),二切入点说明了在哪干(指定究竟是哪一个方法),这就是一个完整的切面定义spa
引入中所提到的目标类,也就是要被通知的对象,也就是真正的业务逻辑,他能够在绝不知情的状况下,被我们织入切面,而本身专一于业务自己的逻辑。代理
怎么实现整套AOP机制的,都是经过代理,这个一下子给细说日志
把切面应用到目标对象来建立新的代理对象的过程。有三种方式,spring采用的是运行时,为何是运行时,在上一文《Spring AOP开发漫谈之初探AOP及AspectJ的用法》中第二个标提到
项目原始的Java组件。
由AOP框架生成java对象。
代理方法= advice + 目标对象的方法。
xml方式配置详解
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.9</version>
</dependency>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.3.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd">
<!-- 定义普通的Bean实例 -->
<
bean
id
=
"adviceTest"
class
=
"com.abc.advice.AdviceTest"
/>
<
aop:config
>
<!-- 将容器中的adviceTest转换成切面Bean -->
<!-- 注意这里可使用order属性为Aspect指定优先级 -->
<
aop:aspect
id
=
"firstAspect"
ref
=
"adviceTest"
order
=
"2"
>
<!-- @Before切点 -->
<
aop:before
pointcut
=
"execution(* com.abc.service.*.*(..))"
method
=
"permissionCheck" arg-names="name,age"
/>
<!-- @After切点 -->
<
aop:after
pointcut
=
"execution(* com.abc.service.*.*(..))"
method
=
"releaseResource"
/>
<!-- @AfterReturning切点 -->
<
aop:after-returning
pointcut
=
"execution(* com.abc.service.*.*(..))"
method
=
"log" returning="discussion" arg-names="discussion"
/>
<!-- @AfterThrowing切点 -->
<
aop:after-throwing
pointcut
=
"execution(* com.abc.service.*.*(..))"
method
=
"handleException"
/>
<!-- @Around切点(多个切点提示符使用and、or或者not链接) -->
<
aop:around
pointcut
=
"execution(* com.abc.service.*.*(..)) and args(name,time,..)"
method
=
"process"
/>
<!-切入点第二种写法开始->
<!-单参数传参->
<aop:pointcut id="log4add" expression="execution(* com.tfedu.discuss.web.TeacherDiscussionController.delete(com.tfedu.discuss.abutment.TestAddLog)) and args(testAddLog)" />
<aop:after method="addLog" pointcut-ref="log4add" arg-names="testAddLog"/>
<!-切入点第二种写法结束->
<!-切点为注解时,也就是将切点切到注解上 开始->
</
aop:aspect
>
</
aop:config
>
Spring中的事务是经过aop来实现的,当咱们本身写aop拦截的时候,会遇到跟spring的事务aop执行的前后顺序问题,好比说动态切换数据源的问题,若是事务在前,数据源切换在后,会致使数据源切换失效,因此就用到了Order(排序)这个关键字.咱们能够经过在@AspectJ的方法中实现org.springframework.core.Ordered 这个接口来定义order的顺序,order 的值越小,说明越先被执行
特例(某注解为切点时)
通常咱们认为切点的位置都是某个业务方法以前,以后或者先后都有,可是有些状况下咱们也能够将切点切到某个方法的注解上,在解决某些问题时很方便,下面举例说明此种用法:
配置文件中内容
<bean id="abutmentAspect" class="com.tfedu.discuss.abutment.aop.LogAspect"></bean>
<aop:config>
<aop:aspect id="" ref="abutmentAspect">
<aop:pointcut id="addLog" expression="@annotation(com.tfedu.discuss.annotation.BehaviorLog)" />
<aop:after method="behaviorLog4Review" pointcut-ref="addLog"/>
</aop:aspect>
</aop:config>
注解类
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE, ElementType.METHOD, ElementType.FIELD })
@Documented
public @interface BehaviorLog {
OperTypeEnum value();//这里面定义的方法要在注解上用到,这里的方法名就是注解上的等号左边的值
String fiterName();
}
注意:里面的方法能够根据具体须要来定义
注解的使用
在业务方法上加上:@BehaviorLog(value = OperTypeEnum.REVIEW, fiterName = "historyDraftId")
通知方法代码
public void behaviorLog4Review(JoinPoint joinPoint) { Signature signature = joinPoint.getSignature(); MethodSignature methodSignature = (MethodSignature) signature; Method method = methodSignature.getMethod(); String fiterName = method.getAnnotation(BehaviorLog.class).fiterName();//这里获取的是注解中定义的各类方法的值,例如这里获取的注解上的fiterName等号后面的的值 Object[] args = joinPoint.getArgs();//这里获取的是方法里的参数不是注解上的值,这个切记 Long historyDraftId = getParam4Common(args, fiterName); HistoryDraftEntity historyDraftEntity = historyDraftBaseService.get(historyDraftId); if (Util.isEmpty(historyDraftEntity)) { ExceptionUtil.bEx("批阅中添加行为日志记录是,历史稿对象不能为空", historyDraftEntity); } addLog(historyDraftId, LogTypeEnum.WRITING.key(), historyDraftEntity.getTitle(), fetchUser.getCurrentUserId(), OperTypeEnum.REVIEW.key()); } private Long getParam4Common(Object[] args, String fiterName) { Optional<Object> obj = Arrays.stream(args).findFirst(); if (!obj.isPresent()) { return PARAM_ID; } return ConvertUtil.obj2long(BeanUtil.get((obj.get()), fiterName)); }