spring深入学习(四)-----spring aop
AOP概述
aop其实就是面向切面编程,举个例子,比如项目中有n个方法是对外提供http服务的,那么如果我需要对这些http服务进行响应时间的监控,按照传统的方式就是每个方法中添加相应的逻辑,但是这些逻辑是重复的,我无非是需要记录请求的时间以及响应时间,另外可能需要加上请求入参以及响应出参。这时候就可以把这些http服务看成切面,通过aop的方式在方法前和方法后去做点什么操作。
aop的实现者有很多,包括AspectJ、Spring AOP等等,当然我们重点就放在Spring aop上了。
在spring中,aop可以由jdk动态代理或cglib实现,这地方的示例可以看下本人之前的文章了动态代理
jdk动态代理跟cglib有个重要的区别就是jdk动态代理的类必须要实现了某个接口,但是cglib则没这个要求。
针对cglib,举例如下:
spring aop简单示例
业务代码
@Component("knight") public class BraveKnight { public void saying(){ System.out.println("我是骑士..(切点方法)"); } }
package com.cjh.aop2; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.After; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.Pointcut; import org.springframework.stereotype.Component; /** * 注解方式声明aop * 1.用@Aspect注解将类声明为切面(如果用@Component("")注解注释为一个bean对象,那么就要在spring配置文件中开启注解扫描,<context:component-scan base-package="com.cjh.aop2"/> * 否则要在spring配置文件中声明一个bean对象) * 2.在切面需要实现相应方法的前面加上相应的注释,也就是通知类型。 * 3.此处有环绕通知,环绕通知方法一定要有ProceedingJoinPoint类型的参数传入,然后执行对应的proceed()方法,环绕才能实现。 */ @Component("annotationTest") @Aspect public class AnnotationTest { //定义切点 @Pointcut("execution(* *.saying(..))") public void sayings(){} /** * 前置通知(注解中的sayings()方法,其实就是上面定义pointcut切点注解所修饰的方法名,那只是个代理对象,不需要写具体方法, * 相当于xml声明切面的id名,如下,相当于id="embark",用于供其他通知类型引用) * <aop:config> <aop:aspect ref="mistrel"> <!-- 定义切点 --> <aop:pointcut expression="execution(* *.saying(..))" id="embark"/> <!-- 声明前置通知 (在切点方法被执行前调用) --> <aop:before method="beforSay" pointcut-ref="embark"/> <!-- 声明后置通知 (在切点方法被执行后调用) --> <aop:after method="afterSay" pointcut-ref="embark"/> </aop:aspect> </aop:config> */ @Before("sayings()") public void sayHello(){ System.out.println("注解类型前置通知"); } //后置通知 @After("sayings()") public void sayGoodbey(){ System.out.println("注解类型后置通知"); } //环绕通知。注意要有ProceedingJoinPoint参数传入。 @Around("sayings()") public void sayAround(ProceedingJoinPoint pjp) throws Throwable{ System.out.println("注解类型环绕通知..环绕前"); pjp.proceed();//执行方法 System.out.println("注解类型环绕通知..环绕后"); } }
<?xml version="1.0" encoding="UTF-8"?> <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:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd"> <!-- 开启注解扫描 --> <context:component-scan base-package="com.cjh.aop2"/> <!-- 开启aop注解方式,此步骤不能少,这样java类中的aop注解才会生效 --> <aop:aspectj-autoproxy/> </beans>
测试:
package com.cjh.aop2; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class Test { public static void main(String[] args) { ApplicationContext ac = new ClassPathXmlApplicationContext("com/cjh/aop2/beans.xml"); BraveKnight br = (BraveKnight) ac.getBean("knight"); br.saying(); } }
自动创建代理
1、BeanNameAutoProxyCreator
通过名字就可以看出来是干嘛的,可以根据bean的名字来创建自动代理,示例如下:
业务类:
public interface UserService { void print(); } public class UserServiceImpl implements UserService { public void print(){ System.out.println(getClass()+"#print"); } }
拦截器:
public class MyMethodInterceptor implements MethodInterceptor { @Override public Object invoke(MethodInvocation invocation) throws Throwable { System.out.println(getClass()+"调用方法前"); Object ret=invocation.proceed(); System.out.println(getClass()+"调用方法后"); return ret; } }
配置类:
@Configuration public class AppConfig { //要创建代理的目标Bean @Bean public UserService userService(){ return new UserServiceImpl(); } //创建Advice或Advisor @Bean public Advice myMethodInterceptor(){ return new MyMethodInterceptor(); } //使用BeanNameAutoProxyCreator来创建代理 @Bean public BeanNameAutoProxyCreator beanNameAutoProxyCreator(){ BeanNameAutoProxyCreator beanNameAutoProxyCreator=new BeanNameAutoProxyCreator(); //设置要创建代理的那些Bean的名字 beanNameAutoProxyCreator.setBeanNames("userSer*"); //设置拦截链名字(这些拦截器是有先后顺序的) beanNameAutoProxyCreator.setInterceptorNames("myMethodInterceptor"); return beanNameAutoProxyCreator; } }
测试:
public class Main { public static void main(String[] args) { ApplicationContext applicationContext=new AnnotationConfigApplicationContext(AppConfig.class); UserService userService= applicationContext.getBean(UserService.class); userService.print(); } }
自定义注解+BeanNameAutoProxyCreator
基于上面说的,对一些http接口做个响应时间监控,先以一个接口为例吧!
注解(PerformanceMonitor):
package com.ty.annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; //代表此注解是运行过程中生效 @Retention(RetentionPolicy.RUNTIME) //此注解作用于方法上 @Target(ElementType.METHOD) public @interface PerformanceMonitor { String value() default ""; }
AnnotationController:
package com.ty.controller; import com.ty.annotation.PerformanceMonitor; import org.springframework.stereotype.Controller; @Controller public class AnnotationController { @PerformanceMonitor public String testServer() { System.out.println("do someThing"); return "test"; } }
BeanNameAutoProxyConfig:
package com.ty.configuration; import org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class BeanNameAutoProxyConfig { @Bean public BeanNameAutoProxyCreator create() { BeanNameAutoProxyCreator beanNameAutoProxyCreator = new BeanNameAutoProxyCreator(); //为所有的controller创建自动动态代理 beanNameAutoProxyCreator.setBeanNames("*Controller"); //代理的具体的业务逻辑交给bean-----performanceInterceptor beanNameAutoProxyCreator.setInterceptorNames("performanceInterceptor"); return beanNameAutoProxyCreator; } }
PerformanceInterceptor:
package com.ty.interceptor; import com.ty.annotation.PerformanceMonitor; import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; import org.springframework.cglib.proxy.MethodProxy; import org.springframework.stereotype.Service; import java.lang.reflect.Method; @Service public class PerformanceInterceptor implements MethodInterceptor { public Object invoke(MethodInvocation methodInvocation) throws Throwable { Object result = null; //判断被代理类的方法上是否使用了@PerformanceMonitor注解 PerformanceMonitor performanceMonitor = methodInvocation.getMethod().getAnnotation(PerformanceMonitor.class); if(performanceMonitor != null) { //具体的代理逻辑,实现aop的效果 System.out.println("----------before----" + methodInvocation.getMethod().getName()); result = methodInvocation.proceed(); System.out.println("----------after----" + methodInvocation.getMethod().getName() + ";该笔调用耗时:" + (System.currentTimeMillis() - t1) + "ms");
}else { //不使用该注解,则走原方法的调用逻辑 result = methodInvocation.proceed(); } return result; } }
applicationContext.xml:
<?xml version="1.0" encoding="UTF-8"?> <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" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <!--自动扫描包配置--> <context:component-scan base-package="com.ty"/> <bean id="student" class="com.ty.beans.Student"> <!-- property代表的是set方法注入--> <property name="age" value="27"></property> <property name="name" value="马云"></property> </bean> <bean id="school" class="com.ty.beans.School"> <!-- constructor代表的是构造器注入--> <constructor-arg ref="student"></constructor-arg> </bean> </beans>
测试类:
package com.ty.beans; import com.ty.controller.AnnotationController; import javafx.application.Application; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.xml.XmlBeanFactory; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.AbstractJUnit4SpringContextTests; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; @ContextConfiguration(locations = { "classpath:applicationContext.xml" }) @RunWith(SpringJUnit4ClassRunner.class) public class IOCTest { @Test public void testAnnotation() { ApplicationContext context = new ClassPathXmlApplicationContext("classpath:applicationContext.xml"); AnnotationController annotationController = (AnnotationController) context.getBean("annotationController"); annotationController.testServer(); } }
运行结果:
----------before----testServer
do someThing
----------after----testServer;该笔调用耗时:8ms
这样也就实现了性能监控的目的,并且以后项目中所有的controller的接口,只要需要这个功能,加个注解即可。
@AspectJ
基于@AspectJ实现aop是相对简单的方式,上面也有类似的案例,下面详细的说说这个玩意。
aop主要包括切面、切入点、增强方法等核心组成部分。
1、切面
//用来声明这是一个AspectJ @Aspect //在此处声明一个Component 是因为Spring扫描注解并不能识别AspectJ 因此在此处声明,不必在applicationContext.xml配置bean标签了 @Component public class ServiceLog { 。。。 }
2、切入点
@Pointcut("execution(* com.sample.service.impl..*.*(..))") public void pc() {}
a、execution()是最常用的切点函数,整个表达式可以分为五个部分,其语法如下所示:
- execution(): 表达式主体。
- 第一个*号:表示返回类型,*号表示所有的类型。
- 包名:表示需要拦截的包名,后面的两个句点表示当前包和当前包的所有子包,com.sample.service.impl包、子孙包下所有类的方法。
- 第二个*号:表示类名,*号表示所有的类。
- *(..):最后这个星号表示方法名,*号表示所有的方法,后面括弧里面表示方法的参数,两个句点表示任何参数。
b、@annotation
另外切入点中的execution()也可以用@Annotation代替,如下:
@AfterReturning("@annotation(com.ty.annotation.PerformanceMonitor)")
public void needTestFun() {
System.out.println("可以对注明@PerformanceMonitor的方法进行增强");
}
对于所有注明@PerformanceMonitor的方法进行增强。
或者还有一种用法,可以方便拿到注解上的信息
@Around(value = "@annotation(apiOperation)") public Object logApiCallInfo(ProceedingJoinPoint joinPoint, ApiOperation apiOperation) { //TODO 1、这里方便拿到注解的信息。不过注意@annotation(apiOperation)名称要与参数名称一致 String apiCode = apiOperation.value(); //TODO 调用业务接口 result = joinPoint.proceed(); //TODO 获取接口请求参数,并拼装成json,格式化输出 Object[] args = joinPoint.getArgs(); MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature(); String[] parameterNames = methodSignature.getParameterNames(); JSONObject jsonObject = new JSONObject(); if(parameterNames != null && parameterNames.length > 0 && args != null && args.length > 0) { for(int i = 0; i < parameterNames.length; i++) { jsonObject.put(parameterNames[i], args[i]); } //标准化输出 jsonObject.toJsonString(); } }
3、增强方法
//环绕通知注解,pc()则是上面的pointcut切入点 @Around("pc()") //环绕通知会多ProceedingJoinPoint这个参数 public Object log(ProceedingJoinPoint pjp) throws Throwable { 。。。 }
- @Before
- @AfterReturning
- @Around
- @AfterThrowing
- @After
4、applicationContext.xml
<aop:aspectj-autoproxy/>
posted on 2019-04-28 21:19 阿里-马云的学习笔记 阅读(310) 评论(0) 编辑 收藏 举报