Beginning Spring学习笔记——第8章 Spring AOP
来源:互联网 发布:锁屏主题软件 编辑:程序博客网 时间:2024/06/08 09:25
AOP
AOP(Aspect Oriented Programming)面向方面编程,关注软件系统中的横切关注点,通过分离这些横切关注点而增加模块化。
相关术语:
- 接合点(Join-point):实际代码中的点,通过在其上执行方面向应用程序中插入额外逻辑。
- 通知(Advice):特定接合点中由“方面”所执行的行为。
- 切入点(Point-cut):用来选择一组接合点的表达式。
- 目标(Target):执行流被”方面”更改的对象。
- 编织(Weaving):将”方面”和对象结合的过程,分编译时编织、加载时编织和运行时编织。
在Spring中开始使用AOP
Spring AOP仅使用了运行时编织,是一种很动态的方法,通过运行时创建代理类实现。尽管它底层使用的AspectJ,但并没有保留后者的编译时编织和加载时编织。
方面、切入点和通知的定义
本章项目需要依赖如下:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>org.springframework.samples</groupId> <artifactId>executionTimeLoggingAspectJ</artifactId> <version>0.0.1-SNAPSHOT</version> <properties> <!-- Generic properties --> <java.version>1.6</java.version> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <!-- Spring --> <spring-framework.version>4.3.10.RELEASE</spring-framework.version> <!-- Hibernate / JPA --> <hibernate.version>5.2.10.Final</hibernate.version> <!-- Logging --> <logback.version>1.2.3</logback.version> <slf4j.version>1.7.25</slf4j.version> <!-- Test --> <junit.version>4.12</junit.version> <!-- AspectJ --> <aspectj.version>1.8.10</aspectj.version> </properties> <dependencies> <!-- Spring and Transactions --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-tx</artifactId> <version>${spring-framework.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aop</artifactId> <version>${spring-framework.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>${spring-framework.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-beans</artifactId> <version>${spring-framework.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>${spring-framework.version}</version> </dependency> <!-- Logging with SLF4J & LogBack --> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>${slf4j.version}</version> <scope>compile</scope> </dependency> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <version>${logback.version}</version> <scope>runtime</scope> </dependency> <!-- Hibernate --> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-entitymanager</artifactId> <version>${hibernate.version}</version> </dependency> <!-- Test Artifacts --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>${spring-framework.version}</version> <scope>test</scope> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>${junit.version}</version> <scope>test</scope> </dependency> <!-- AspectJ --> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>${aspectj.version}</version> </dependency> </dependencies> </project>
项目目录结构如图:
首先创建如下接口和类:
public interface MyBean { void sayHello();}@Componentpublic class MyBeanImpl implements MyBean { @Override public void sayHello() { System.out.println("Hello..!"); }}public interface MyOtherBean { void sayHelloDelayed() throws InterruptedException;}@Componentpublic class MyOtherBeanImpl implements MyOtherBean { @Override public void sayHelloDelayed() throws InterruptedException { Thread.sleep(1000); System.out.println("Hello..!"); }}
如图创建了两个类,一个类可以打印一行消息,另一个类则延时打印消息,接下来创建executionTimeLoggingSpringAOP通知Bean来计算每个方法执行用时。
public class ExecutionTimeLoggingSpringAOP implements MethodBeforeAdvice, AfterReturningAdvice { long startTime = 0; @Override public void before(Method method, Object[] args, Object target) throws Throwable { startTime = System.nanoTime(); } @Override public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable { long elapsedTime = System.nanoTime() - startTime; String className = target.getClass().getCanonicalName(); String methodName = method.getName(); System.out.println("Execution of " + className + "#" + methodName + " ended in " + new BigDecimal(elapsedTime).divide(new BigDecimal(1000000)) + " milliseconds"); }}
其中before和afterReturning两个方法分别在接合点方法执行前和返回值后立即执行。
然后创建上下文配置文件定义通知和接合点:
<?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-4.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd"> <context:component-scan base-package="com.wiley.beginningspring.ch8" /> <context:annotation-config /> <bean id="executionTimeLoggingSpringAop" class="com.wiley.beginningspring.ch8.aspect.ExecutionTimeLoggingSpringAOP" /> <aop:config> <aop:pointcut id="executionTimeLoggingPointcut" expression="execution(public * *(..))" /> <aop:advisor id="executionTimeLoggingAdvisor" advice-ref="executionTimeLoggingSpringAop" pointcut-ref="executionTimeLoggingPointcut" /> </aop:config></beans>
其中<aop:pointcut>标签的id属性定义了该切点的名字,expression标签定义了切点方法的类型。之后的<aop:advisor>标签的advice-ref指向通知Bean,而pointcut-ref指向刚刚定义的切点名。这样将切点和通知结合在一起。
最后就可以在Main方法中测试函数了。
public class Main { public static void main(String... args) throws InterruptedException { ApplicationContext context = new ClassPathXmlApplicationContext("/applicationContext.xml", Main.class); MyBean myBean = context.getBean(MyBean.class); myBean.sayHello(); MyOtherBean myOtherBean = context.getBean(MyOtherBean.class); myOtherBean.sayHelloDelayed(); }}
运行得到结果:
可以看到两个函数的运行时间被分别打印了出来。
通知类型
Before
在实际方法调用前被调用。该方面应该实现MethodBeforeAdvice接口。
After Returning
在实际方法执行并返回值后调用。应该实现AfterReturningAdvice接口。
After Throwing
在方法抛出异常并被调用方法捕获之前执行,实现ThrowsAdvice接口。
After
不管接合点返回值或者抛出异常都会执行的通知。
Around
在接合点之前和之后执行。可以替换Before+AfterReturning:
public class ExecutionTimeLoggingWithAroundAdvice { public void executiontimeLogging(ProceedingJoinPoint jp) throws Throwable { long startTime = System.nanoTime(); String className = jp.getTarget().getClass().getCanonicalName(); String methodName = jp.getSignature().getName(); jp.proceed(); long elapsedTime = System.nanoTime() - startTime; System.out.println("Execution of " + className + "#" + methodName + " ended in " + new BigDecimal(elapsedTime).divide(new BigDecimal(1000000)) + " milliseconds"); }}
在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" 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.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd"> <context:component-scan base-package="com.wiley.beginningspring.ch8" /> <context:annotation-config /> <bean id="executionTimeLoggingWithAroundAdvice" class="com.wiley.beginningspring.ch8.aspect.ExecutionTimeLoggingWithAroundAdvice" /> <aop:config> <aop:aspect ref="executionTimeLoggingWithAroundAdvice"> <aop:pointcut id="logPointCut" expression="execution(public * *(..))" /> <aop:around pointcut-ref="logPointCut" method="executiontimeLogging" /> </aop:aspect> </aop:config></beans>
定义切入点指示符
类型签名表达式
Within(<type name>/<package name>/<class name>)
匹配某个类型/包/类中的所有方法。
- within(com.wiley..*):匹配com.wiley包及其子包中所有类的所有方法。
- within(com.wiley.spring.ch8.MyService):匹配MyService类的所有方法。
- within(MyServiceInterface+):匹配MyServiceInterface接口的所有实现类的所有方法。
- within(com.wiley.spring.ch8.MyBaseService+):匹配MyBaseService类及其所有子类的所有方法
方法签名表达式
execution(<scope><reuturn-type><fully-qualified-class-name>.*(parameters))
- execution(*com.wiley.spring.ch8.MyBean.*(..)):匹配MyBean中所有方法。
- execution(public *com.wiley.spring.ch8.MyBean.*(..)):匹配MyBean中所有公共方法。
- execution(public String com.wiley.spring.ch8.MyBean.*(..)):匹配MyBean中所有返回String的公共方法。
- execution(public * com.wiley.spring.ch8.MyBean.*(long, ..)):匹配MyBean中第一个参数为long 的所有公共方法。
其他替代切入点指示符
- bean(*Service):与名称后缀为Service的Bean匹配。
- @annotation(com.wiley.spring.ch8.MarkerMethodAnnotation):匹配使用了MarkerMethodAnnotation注解的方法。
- @within(com.wiley.spring.ch8.MarkerMethodAnnotation):within指示的类、包、接口中筛选出该注解注释的方法。
- This(someInterface):=within(someInterface+)。
使用注解定义AOP
@Before
产生实际方法调用之前的通知方法。可以访问之际方法的传入参数。
@Component@Aspect@Order(100)public class ExecutionOrderBefore { @Before(value = "execution(public * *(..))") public void before(JoinPoint joinPoint) { System.out.println("===1. Before Advice."); }}
value属性定义了切点方法的信息,而@Order注解表明了执行优先级。越小越先执行。
除了在value属性中定义以外,还可以额外定义切点:
@PointCut
@Component@Aspect@Order(110)public class ExecutionOrderBeforeWithPointCut { @Pointcut("execution(public * *(..))") public void anyPublicMethod() { } @Before("anyPublicMethod()") public void beforeWithPointCut(JoinPoint joinPoint) { System.out.println("===1.1. Before Advice with @PointCut."); }}
此时@PointCut注解的方法名可直接使用为切点名,其代指的方法在属性中写出。该注解的属性中采用其他切点指示符以及使用Boolean表达式创建更大切点:
@Component@Aspect@Order(110)public class ExecutionOrderAfterWithMultiplePointCut { @Pointcut("execution(public * *(..))") public void anyPublicMethod() { } @Pointcut("@annotation(com.wiley.beginningspring.ch8.MarkerAnnotation)") public void annotatedWithMarkerAnnotation() { } @After(value = "anyPublicMethod() & annotatedWithMarkerAnnotation()") public void afterWithMultiplePointcut(JoinPoint joinPoint) { System.out.println("===5.1. After Advice with Multiple Pointcut applied on method."); }}
其中MarkerAnnotation定义如下:
@Target(value = {ElementType.METHOD, ElementType.TYPE})@Retention(value = RetentionPolicy.RUNTIME)public @interface MarkerAnnotation {}
@After
产生实际方法调用之后的通知方法。
@Component@Aspect@Order(150)public class ExecutionOrderAfter { @After(value = "execution(public * *(..))") public void after(JoinPoint joinPoint) { System.out.println("===5. After Advice."); }}
@AfterReturning
定义接合点执行完毕且成功返回值后执行的通知。可以访问返回值。
@Component@Aspect@Order(100)public class ExecutionOrderAfterReturning { @AfterReturning(value = "execution(public * *(..))") public void afterReturning(JoinPoint joinPoint) { System.out.println("===6. After Returning Advice."); }}
@AfterThrowing
定义在异常抛出后调用方法捕捉之前执行的通知方法。
@Aspect
在Bean的类级别上使用该注解将其声明为一个方面。此外还应该随之使用@Component及其派生注解类。
@Around
定义在目标方法执行之前和之后启动的通知方法。
@Component@Aspect@Order(200)public class ExecutionOrderAround { @Around("execution(public * *(..))") public void around(ProceedingJoinPoint jp) throws Throwable { System.out.println("===2. Before proceeding part of the Around advice."); jp.proceed(); System.out.println("===4. After proceeding part of the Around advice."); }}
@DeclaredParents
用于动态继承接口。
最后查看注解配置类和Main类:
@Configuration@ComponentScan(basePackages = {"com.wiley.beginningspring.ch8"})@EnableAspectJAutoProxypublic class ApplicationConfig {}public class Main { public static void main(String... args) { ApplicationContext context = new AnnotationConfigApplicationContext(ApplicationConfig.class); MyBean myBean = context.getBean(MyBean.class); myBean.sayHello(); }}
运行得到结果:
项目目录结构如下:
AspectJ和Spring整合
几个Bean和他们的接口定义不变。采用AspectJ通知Bean:
@Component@Aspectpublic class ExecutionTimeLoggingAspectJ { @Around("execution(public * *(..))") public Object profile(ProceedingJoinPoint pjp) throws Throwable { long startTime = System.nanoTime(); String className = pjp.getTarget().getClass().getCanonicalName(); String methodName = pjp.getSignature().getName(); Object output = pjp.proceed(); long elapsedTime = System.nanoTime() - startTime; System.out.println("Execution of " + className + "#" + methodName + " ended in " + new BigDecimal(elapsedTime).divide(new BigDecimal(1000000)) + " milliseconds"); return output; }}
上下文配置文件如下,采用方面的自动代理:
<?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-4.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd"> <context:component-scan base-package="com.wiley.beginningspring.ch8" /> <context:annotation-config/> <aop:aspectj-autoproxy/></beans>
创建Main类来测试程序:
public class Main { public static void main(String... args) throws InterruptedException { ApplicationContext context = new ClassPathXmlApplicationContext("/applicationContext.xml", Main.class); MyBean myBean = context.getBean(MyBean.class); myBean.sayHello(); MyOtherBean myOtherBean = context.getBean(MyOtherBean.class); myOtherBean.sayHelloDelayed(); }}
结果还是运行时间。
- Beginning Spring学习笔记——第8章 Spring AOP
- Beginning Spring学习笔记——第1章
- Beginning Spring学习笔记——第9章 SpEL
- Beginning Spring学习笔记——第10章 缓存
- Beginning Spring学习笔记——第2章(一)Spring IoC容器
- Beginning Spring学习笔记——第2章(三)Spring的Bean管理
- Beginning Spring学习笔记——第3章(一)Spring MVC基础
- Beginning Spring学习笔记——第4章(一)Spring JDBC连接的配置
- Beginning Spring学习笔记——第5章(二)Spring的JPA支持
- Beginning Spring学习笔记——第6章(一)Spring事务管理基础
- Beginning Spring学习笔记——第7章 使用Spring进行测试驱动开发
- Beginning Spring学习笔记——第2章(二)依赖注入
- Beginning Spring学习笔记——第3章(二)表单处理
- Beginning Spring学习笔记——第5章(一)ORM和JPA基础
- Beginning Spring学习笔记——第4章(二)使用Spring执行数据访问操作
- Beginning Spring学习笔记——第6章(二)使用Spring进行声明式事务管理
- Beginning Spring学习笔记——第6章(三)使用Spring进行编程式事务管理
- Beginning Spring学习笔记——第11章 使用Spring开发REST风格的Web服务
- vb.net操作Excel常用命令
- white-space、word-wrap和word-break
- Dos命令查看端口占用及关闭进程
- 【Redis源码剖析】
- QT中的图片透明
- Beginning Spring学习笔记——第8章 Spring AOP
- LeetCode
- [杂记] bsp与apk
- 生存期
- HDU 6191 Query on A Tree 可持久化trie + dfs建树 || 启发式合并trie
- Android中数值计算的精度
- springMVC
- 从头开始学算法:考研机试题练习(C/C++)--入门模拟
- QT文字绘制