要使用Spring的切片(Aspect),首先要在Maven项目中添加以下依赖,然后我们可以按XML或Annotation(标注)的方法,进行AOP的配置。
<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>com.funnybear.springaop</groupId> <artifactId>springaop</artifactId> <version>0.0.1-SNAPSHOT</version> <build> <sourceDirectory>src</sourceDirectory> <plugins> <plugin> <artifactId>maven-compiler-plugin</artifactId> <version>3.5.1</version> <configuration> <source>1.8</source> <target>1.8</target> </configuration> </plugin> </plugins> </build> <dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>4.3.0.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>4.3.0.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-beans</artifactId> <version>4.3.0.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> <version>4.3.0.RELEASE</version> </dependency> </dependencies> </project>
按XML配置
1. 新建Camera类。我们想在这个类执行操作时,输出日志,但不改动类本身的代码。
package com.funnybear.springaop; public class Camera { public void snap(){ System.out.println("snap!"); } }
2. 新建Logger类。这个类就是我们的切片(Aspect)
package com.funnybear.springaop; public class Logger { public void aboutToTakePhoto(){ System.out.println("about to snap..."); } }
3. 在beans.xml中,注册Camera和Logger类,并添加AOP配置
其中,<aop:pointcut>是用来指定我们关心的类方法;<aop:aspect>用来指定充当切片的类;<aop:before>是我们想在pointcut所指定的方法执行前,运行切片类中的指定方法。
<?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: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"> <bean id="camera" class="com.funnybear.springaop.Camera"></bean> <bean id="logger" class="com.funnybear.springaop.Logger"></bean> <aop:config> <aop:pointcut expression="execution(void com.funnybear.springaop.Camera.snap())" id="camerasnap" /> <aop:aspect ref="logger" id="loggeraspect"> <aop:before method="aboutToTakePhoto" pointcut-ref="camerasnap" /> </aop:aspect> </aop:config> </beans>
4. 从ApplicationContext中取出 Camera类,并执行。
package com.funnybear.springaop; import org.springframework.context.support.ClassPathXmlApplicationContext; public class App { public static void main(String[] args) { ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("com/funnybear/springaop/beans.xml"); Camera camera = (Camera)context.getBean("camera"); camera.snap(); context.close(); } }
按Annotation配置
1. 新建Camera类,@Component把它标记为一个可导出的bean
package com.funnybear.springaop; import org.springframework.stereotype.Component; @Component("camera") public class Camera { public void snap(){ System.out.println("snap!"); } }
2. 新建Logger类,@Aspect把它标记为一个切片,@Component把它标记为一个bean
@Pointcut指定我们关心的,想对其添加切片的方法。这样单独指定,是为了方便复用切片入口
@Before指定我们的切片方法
package com.funnybear.springaop; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.Pointcut; import org.springframework.stereotype.Component; @Aspect @Component("logger") public class Logger { @Pointcut("execution(void com.funnybear.springaop.Camera.snap())") public void cameraSnap(){ } @Before("cameraSnap()") public void aboutToTakePhoto(){ System.out.println("about to snap..."); } }
3. 在beans.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:aop="http://www.springframework.org/schema/aop" 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-4.3.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd"> <context:component-scan base-package="com.funnybear.springaop"></context:component-scan> <context:annotation-config></context:annotation-config> <aop:aspectj-autoproxy></aop:aspectj-autoproxy> </beans>
4. 从ApplicationContext中取出 Camera类,并执行。
package com.funnybear.springaop; import org.springframework.context.support.ClassPathXmlApplicationContext; public class App { public static void main(String[] args) { ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("com/funnybear/springaop/beans.xml"); Camera camera = (Camera)context.getBean("camera"); camera.snap(); context.close(); } }
深入:
1. Pointcut(切片入口)匹配
第一个星号(*)匹配任意返回值,第二个星号(*)匹配Camera类的所有方法名,(..)匹配任意参数
类似地,我们可以实现很灵活的模糊匹配
@Pointcut("execution(* com.funnybear.springaop.Camera.*(..))")
也可以用within来匹配包或类中的所有方法
@Pointcut("within(com.funnybear.springaop..*)")
@Pointcut("within(com.funnybear.springaop.Camera)")
匹配被特定Annotaion标记的类
@Pointcut("within(@org.springframework.stereotype.Component com.funnybear.springaop..*)")
匹配被特定Annotation标记的方法
@Pointcut("@annotation(java.lang.Deprecated)")
匹配特定id为"camera"的bean
@Pointcut("bean(camera)")
匹配接受特定参数的方法。".."匹配任意数量的任意参数,"*"匹配单个任意参数
@Pointcut("args(int, ..)")
@Pointcut("args(int, double, *)")
2. 获取被切片方法的参数
方法一:为切片方法添加JoinPoint参数。我们无法或者参数的类型。
package com.funnybear.springaop; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.Pointcut; import org.springframework.stereotype.Component; @Aspect @Component("logger") public class Logger { @Pointcut("within(com.funnybear.springaop.Camera)") public void cameraSnap(){ } @Before("cameraSnap()") public void beforeAdvice(JoinPoint jp){ System.out.println("before advice ..."); for(Object obj : jp.getArgs()){ System.out.println("arg: " + obj); } } }
方法二:为用args来匹配要切片的方法,并添加参数占位符,显示指定参数类型。
package com.funnybear.springaop; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.Pointcut; import org.springframework.stereotype.Component; @Aspect @Component("logger") public class Logger { @Pointcut("args(exposure)") public void cameraSnap(int exposure){ } @Before("cameraSnap(exposure)") public void beforeAdvice(JoinPoint jp, int exposure){ System.out.println("before advice ..."); System.out.println("exposure:" + exposure); } }
3. 合并切片入口
可以用多个切片入口去联合指定切面方法,也可以在定义一个切面入口时,结合多个条件。
package com.funnybear.springaop; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.Pointcut; import org.springframework.stereotype.Component; @Aspect @Component("logger") public class Logger { @Pointcut("within(com.funnybear.springaop.Camera)") public void camera(){ } @Pointcut("args(exposure)") public void cameraSnap(int exposure){ } @Before("camera() && cameraSnap(exposure)") public void beforeAdvice(JoinPoint jp, int exposure){ System.out.println("before advice ..."); System.out.println("exposure:" + exposure); } }
4. Aspect Proxy 切片代理
从Application Context中取出的Camera对象,是由Spring Aspect拓展过的Camera的子类。我们添加的切片方法,最终被添加到了这个子类中。