Spring 基于IOC容器管理Bean的方式,使得其有能力对IOC容器中的所有Bean进行无限可能的操作,Spring AOP是基于 IOC容器的高级特性,借助与AOP能实现一些可插拔模块,而不影响原有系统的设计.
本节结合Spring Boot 在一个已有的Web项目中使用AOP提供一种后续的日志补救方案.更多关于IOC容器的概念请查询相关资源,附件中提供AOP学习的简要笔记,仅供参考
- Spring Boot 程序主类 ZkClientApplication, 考虑到该类的注解已经"足够多",当然基于Java注解的配置方式是不可避免的,决定在维护一个配置类ZkClientConfiguration,相关代码如下:
ZkClientApplication.java ,该类中仅是Spring Boot +Spring Cloud 相关的配置,无须关注.
@SpringBootApplication
// 启用REST 客户端
@EnableFeignClients
// 启用客户端负载均衡
@RibbonClient(name = "my", configuration = RibbonConfig.class)
// 启用服务发现
@EnableDiscoveryClient
// 启用断路器
@EnableHystrix
@ComponentScan("cn.com.xiaofen")
public class ZkClientApplication {
public static void main(String[] args) {
SpringApplication.run(ZkClientApplication.class, args);
}
/* Spring JDBC */
@Bean
public JdbcTemplate primaryJdbcTemplate(DataSource dataSource) {
return new JdbcTemplate(dataSource);
}
/* TOMCAT DataSourcePool */
@Bean
@ConfigurationProperties(prefix = "spring.datasource")
public DataSource dataSource() {
return DataSourceBuilder.create().build();
}
}
ZkClientConfiguration.java ,注解@EnableAspectJAutoProxy启用Spring对AOP的支持并允许通过注解的方式定义AOP实例.
@Configuration
// 启用AOP注解特性
@EnableAspectJAutoProxy
public class ZkClientConfiguration {
- 本接目标是完成对cn.com.xiaofen.controller 包下的控制器的访问进行监控,记录访问的行为,当然如果有一种可行的思路,那就有无限可能.项目包结果如下:
- 在log包下定义RequestLog.java 用于定义AOP切面并切入所有controller包下的类打印访问日志,这里并没有复杂的操作,实际上在生产环境中日志可以有更高级的处理方式如统一发送到消息系统kafka,mq等统一处理.
RequestLog.java , 源码如下,稍后做简单的答疑.
@Aspect
@Component
public class RequestLog {
public static final Log log = LogFactory.getLog(RequestLog.class);
// defined aop pointcut
@Pointcut("execution(* cn.com.xiaofen.controller.*.*(..))")
public void controllerLog() {
}
// log all of controller
@Before("controllerLog()")
public void before(JoinPoint joinPoint) {
log.info(joinPoint.getSignature().getDeclaringType() + ",method:" + joinPoint.getSignature().getName()
+ ",params:" + Arrays.asList(joinPoint.getArgs()));
}
// result of return
@AfterReturning(pointcut = "controllerLog()", returning = "retVal")
public void after(JoinPoint joinPoint, Object retVal) {
System.out.println(retVal);
}
}
- @Aspect 申明当前类赋予一个界面的特殊使命,@Component 注解是为了让当前类的实例为IOC容器感知, Spring IOC容器会自动检查加载@Aspect 标识的实例以提取定义的AOP特性.
- 在使用AOP之前需要定一个PointCut 切口,即那具体到哪些方法可以被切面切入. @Pointcut("execution(* cn.com.xiaofen.controller.*.*(..))") 注解标识的方法定义了一个PointCut 定义了cn.com.xiaofen.controller 包下所有类的任意数量参数的方法. 引用该切口的方式为当前方法名 +() 即可.
- @Before("controllerLog()") 定义前置通知,是AOP的基本单元即切点,即在掉用目标方法之前处理一些操作, 注解标注的方法中可以使用JoinPoint 来访问当前正要访问的方法的签名信息等.此处获取了类名,方法名,以及提交的参数信息等,正是进行日志打印所需要的.
- @AfterReturning 定义返回通知,方法钓用结束,可以获取到目标方法执行的结果.其它可参考附件
附件:
一、String AOP编程
.AOP(Aspect-Orientrd Programming,面向切面编程),是一种新的方法论,是对传统OOP(Object Oriented Programming,面向对象编程)的补充。
.AOP 的主要编程对象是切面(aspect) ,而切面模块化横切关注点
.在引用AOP编程时,任然需要定义公共功能,但可以明确定义这个功能在哪里,以什么方式应用,并且不修改受影响的类,这样一来横切关注点就被模块化到特俗的对象(切面)里
.AOP的好处
.每个事物逻辑位于一个位置,代码不分散,便于升级和维护
.业务模块更加简洁,只包含核心业务代码
二、AOP术语
.切面(Aspect),横切关注点(跨越应用程序多个模块的功能)被模块化的特殊对象
.通知(Advice),切面必须要完成的工作
.代理(Proxy),向目标对象应用通知之后创建的对象
.@链接点(Joinpoint) 程序执行的某个特定位置
.@切点(Pointcut) 每个类拥有多个连接点
三、Spring AOP
.AspectJ:Java社区里最完整最流行的AOP框架
.在Spring2.0以上版本中,可以使用AspectJ注解或基于XML配置AOP
1.Spring中启用AspectJ
.加入Spring 的 jar && AspectJ jar && aopalliance-1.0.jar
spring-aop-4.2.3.RELEASE.jar
spring-aspects-4.2.3.RELEASE.jar
spring-beans-4.2.3.RELEASE.jar
spring-context-4.2.3.RELEASE.jar
spring-core-4.2.3.RELEASE.jar
spring-expression-4.2.3.RELEASE.jar
commons-logging-1.2.jar
aspectjrt.jar
aspectjtools.jar
aspectjweaver.jar
org.aspectj.matcher.jar
.在Spring配置文件中加入AOP的命名空间
.xmlns:aop="http://www.springframework.org/schema/aop"
.基于注解的配置
.<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
.把横切关注点的代码抽象到切面的类中
.将切面加入IOC容器中
.@Component
.声明为切面
.@Aspect
.声明通知
.申明一个方法
.@Before -->前置通知,在方法执行之前执行
.@Before("execution(public void cn.com.zjf.hello.Hello.*(String ))")
.@After -->后置通知,在方法执行之后执行
.@AfterRunning -->返回通知,在方法返回结果之后执行
.@AfterThorwing -->异常通知,在方法抛出异常之后
.@Around -->环绕通知,围绕着方法执行
**使用方法签名编写AdpectJ 切入点表达式
.execution * com.zjf.hello.*(..):
.第一个 * 代表任意修饰符及任意返回值.
.第二个 * 代表任意方法. .. 匹配任意数量的参数.
.若目标类与接口与该切面在同一个包中, 可以省略包名.
.execution public String hello.*(double, ..)
.匹配第一个参数为 double 类型的方法,
^.. 匹配任意数量任意类型的参数
**可以在通知的方法中加入 JoinPoint joinPoint 来获取通知的细节
.public void beforeMethod(JoinPoint joinPoint){}
.@After 后置通知
.同前置通知
.不能目标访问方法的返回结果
.@AfterReturning
.返回通知,方法正常执行时的通知
.可以访问目标方法的返回值
.@AfterThrowing
.异常通知,方法异常执行后的通知
.可以访问异常信息,及具体的异常对象
.@Around
.环绕通知
. @Around(value="execution(* cn.com.zjf.hello.Hello.*(String ))")
public void afterAroundMethod(ProceedingJoinPoint pjp){
2.指定切面的优先级
.@Order(1) -->值越小优先级越高
3.重用切入点表达式
.添加一个方法用来申明切入点表达式,该方法中不在需要加入其它的代码
.使用@PointCut来申明切入点表达式
.@Pointcut(value="execution(* cn.com.zjf.hello.Hello.*(String ))")
public void declareJoinPointExpression(){
}
.后面的其他切入点使用方法名来引用
.@Before("declareJoinPointExpression()")
.不同包中可以使用包名.类名.方法名()来访问
4.基于配置文件的方式来配置AOP
.配置切面的Bean
.<bean id="logginAspect" class="cn.com.zjf.hello.LogginAspect"></bean>
.使用<aop:config> 配置AOP
.配置切点表达式
.<aop:pointcut expression="execution(* cn.com.zjf.hello.Hello.*(..))" id="logging"/>
.配置切面及通知
.<aop:aspect ref="validateAdpect" order="1">
<aop:before method="validate" pointcut-ref="logging"/>
</aop:aspect>