AOP说明
面向切面编程:基于OOP基础之上新的编程思想,OOP面向的主要对象是类,而AOP面向的主要对象是切面,在处理日志、安全管理、事务管理 等方面有非常重要的作用。
AOP是Spring中重要的核心点,虽然IOC容器没有依赖AOP,但是AOP提供了非常强大的功能,用来对IOC做补充。
通过Proxy的方式,将重复的公共的代码抽离出去,动态的织入XXService(业务逻辑)中,而不改变原有的Service中的代码结构
AOP的通知类型
前置通知(Before advice): 在连接点之前运行但无法阻止执行流程进入连接点的通知(除非它引发异常)。
后置返回通知(After returning advice):在连接点正常完成后执行的通知(例如,当方法没有抛出任何异常并正常返回时)。
后置异常通知(After throwing advice): 在方法抛出异常退出时执行的通知。
后置通知(总会执行)(After (finally) advice): 当连接点退出的时候执行的通知(无论是正常返回还是异常退出)。
环绕通知(Around Advice):环绕连接点的通知,例如方法调用。这是最强大的一种通知类型,。环绕通知可以在方法调用前后完
自定义的行为。它可以选择是否继续执行连接点或直接返回自定义的返回值又或抛出异常将执行结束。
依赖包 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.13</version> <scope>test</scope> </dependency> <!-- spring架包--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>5.2.16.RELEASE</version> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9.5</version> </dependency> <dependency> <groupId>aopalliance</groupId> <artifactId>aopalliance</artifactId> <version>1.0</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.2.16.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> <version>5.2.3.RELEASE</version> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.20</version> </dependency>
实例
本次实例为日志的使用方式
编写dao Calculator接口
1 2 3 4 5 6 7 8 9 package xyz.shi.dao; public interface Calculator { public int add(int i,int j); public int sub(int i,int j); public int mul(int i,int j); public int div(int i,int j); }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 package xyz.shi.util; import java.lang.reflect.Method; import java.util.Arrays; import org.springframework.stereotype.Component; @Component public class LogUtil { public static void start(Method method, Object ... objects){ System.out.println(method.getName()+"方法开始执行,参数是:"+ Arrays.asList(objects)); } public static void stop(Method method,Object ... objects){ System.out.println(method.getName()+"方法执行完成,参数是:"+ Arrays.asList(objects)); } public static void logException(Method method,Exception e){ System.out.println(method.getName()+"方法出现异常:"+ e.getMessage()); } public static void end(Method method){ System.out.println(method.getName()+"方法执行结束了......"); } }
MyCalculatorService代码添加@Service注解
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 package xyz.shi.service; import org.springframework.stereotype.Service; @Service(value = "myCalculatorService") public class MyCalculatorService { public int add(int i, int j) { return i + j; } public int sub(int i, int j) { return i - j; } public int mul(int i, int j) { return i * j; } public int div(int i, int j) { return i / j; } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 package xyz.shi.util; import org.aspectj.lang.annotation.*; import org.springframework.stereotype.Component; import java.lang.reflect.Method; import java.util.Arrays; @Component @Aspect public class LogUtil { @Before("execution(public int xyz.shi.service.MyCalculatorService.*(int,int))") public static void start(){ System.out.println("方法开始执行,参数是:"); } @AfterReturning("execution(public int xyz.shi.service.MyCalculatorService.*(int,int))") public static void stop(){ System.out.println("方法执行完成,结果是:"); } @AfterThrowing("execution(public int xyz.shi.service.MyCalculatorService.*(int,int))") public static void logException(){ System.out.println("方法出现异常:"); } @After("execution(public int xyz.shi.service.MyCalculatorService.*(int,int))") public static void end(){ System.out.println("方法执行结束了......"); } }
开启基于注解的aop的功能 ,resources-application.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 <?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/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <!--扫描整个目标文件夹 --> <context:component-scan base-package="xyz.shi" /> <aop:aspectj-autoproxy></aop:aspectj-autoproxy> </beans>
1 2 3 4 5 6 7 8 9 10 11 12 13 import org.junit.Test; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import xyz.shi.dao.Calculator; public class MyTest { @Test public void test01() { ApplicationContext context = new ClassPathXmlApplicationContext("application.xml"); Calculator calculator=context.getBean( "myCalculatorService",Calculator.class); calculator.add(1, 2); } }
1 2 3 4 方法开始执行,参数是: 方法执行完成,结果是: 方法执行结束了......
cglib来创建代理对象 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 package xyz.shi.service; import org.springframework.stereotype.Service; @Service public class MyCalculatorService { public int add(int i, int j) { return i + j; } public int sub(int i, int j) { return i - j; } public int mul(int i, int j) { return i * j; } public int div(int i, int j) { return i / j; } }
只需要把我们的MyCalculatorService.java中实现的Calculator.java的接口去掉就行了
1 2 3 4 5 6 7 8 9 10 11 12 import org.junit.Test; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import xyz.shi.service.MyCalculatorService; public class MyTest { @Test public void test01() { ApplicationContext context = new ClassPathXmlApplicationContext("application.xml"); MyCalculatorService myCalculator = context.getBean("myCalculatorService", MyCalculatorService.class); myCalculator.add(1,2); }
当然也可以不写xml配置文件,直接写在class中,比如
1 2 3 4 5 6 7 8 9 10 11 12 package xyz.shi.config; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.EnableAspectJAutoProxy; @Configuration @ComponentScan("xyz.shi") //注意开启AOP的支持,并且代理设置为cglib动态代理,因为UserService没有接口 @EnableAspectJAutoProxy(proxyTargetClass = true) public class SpringConfig { }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 import org.junit.Test; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import xyz.shi.config.SpringConfig; import xyz.shi.service.MyCalculatorService; public class MyTest { @Test public void test01() { ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class); MyCalculatorService myCalculator = context.getBean("myCalculatorService", MyCalculatorService.class); myCalculator.add(1,2); } }
xml的AOP配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 <?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/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <context:component-scan base-package="xyz.shi"></context:component-scan> <aop:aspectj-autoproxy></aop:aspectj-autoproxy> <bean id="myCalculatorService" class="xyz.shi.service.MyCalculatorService"></bean> <bean id="logUtil" class="xyz.shi.util.LogUtil"></bean> <aop:config> <aop:aspect ref="logUtil"> <aop:pointcut id="logPoint" expression="execution(public int xyz.shi.service.MyCalculatorService.*(int,int))"/> <aop:before method="start" pointcut-ref="logPoint"></aop:before> <aop:after method="end" pointcut-ref="logPoint"></aop:after> <aop:after-returning method="stop" pointcut-ref="logPoint" returning="result"></aop:after-returning> <aop:after-throwing method="logException" pointcut-ref="logPoint" throwing="e"></aop:after-throwing> <aop:around method="myAround" pointcut-ref="logPoint"></aop:around> </aop:aspect> </aop:config> </beans>
切点表达式
切入点表达式标准格式:动作关键字(访问修饰符 返回值 包名.类/接口名.方法名(参数)异常名)
execution
execution(public User com.mszlu.service.UserService.findById(int))
execution在实际工作中,很少被使用,因为匹配的打击面非常大或者非常小,不能灵活应用
execution(public * com.mszlu.service.*.*(..))
基本能实现无差别全覆盖,即某个包下面的所有Bean的所有方法都会被拦截。
execution(public * update*(..))
从方法的前缀来区分,这种误伤的概率非常大,你不可能要求所有的程序员都按照这种书写习惯来。
annotation
定义注解
1 2 3 4 5 6 7 8 9 10 package com.mszlu.aop;import java.lang.annotation.*;@Target({ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface MsMetric { String value () default "" ; }
在需要监控的方法上,加上注解
1 2 3 4 @MsMetric public void registerUser (String mail,String password,String nickname) { }
实现性能监控AOP,切点使用annotation
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 package com.mszlu.aop;import lombok.extern.slf4j.Slf4j;import org.aspectj.lang.ProceedingJoinPoint;import org.aspectj.lang.annotation.Around;import org.aspectj.lang.annotation.Aspect;import org.aspectj.lang.annotation.Pointcut;import org.springframework.stereotype.Component;@Aspect @Component @Slf4j public class MetricAspect { @Pointcut("@annotation(MsMetric)") public void pt () {} @Around("pt()") public Object doLogging (ProceedingJoinPoint pjp) throws Throwable{ try { log.info("----------------metric start--------------------" ); long startTime = System.currentTimeMillis(); Object ret = pjp.proceed(); long endTime = System.currentTimeMillis(); log.info("方法执行时间:{}ms" , endTime-startTime); log.info("----------------metric end--------------------" ); return ret; }catch (Exception e){ log.error("异常信息" ,e); throw e; } } }