示例环境
这里面的示例用gradle,初学者可以参考以下build.gradle配置,熟悉者略过。
以下build.gradle文件是通过spring initializer (https://start.spring.io/, 强烈推荐大家使用,特别是新建一个工程时,减少了很多繁琐的创建class / config / test 结构的过程) 快速生成的一个模板工程,然后加进了spring-boot-starter-aop的依赖。
注意Spring AOP 是一个保证了Spring 与 AOP的良好集成的实现,并非AspectJ, 后者是一门独立的语言,实现更全面。虽然两者有很多类似的概念。
plugins {
id 'org.springframework.boot' version '2.2.0.RELEASE'
id 'io.spring.dependency-management' version '1.0.8.RELEASE'
id 'java'
}
group = 'com.example'
version = '0.0.1-SNAPSHOT'
repositories {
mavenCentral()
}
dependencies {
compile 'org.springframework.boot:spring-boot-starter-aop'
implementation 'org.springframework.boot:spring-boot-starter-web'
//classpath "gradle.plugin.aspectj:gradle-aspectj:0.1.6"
testImplementation('org.springframework.boot:spring-boot-starter-test') {
exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
}
}
test {
useJUnitPlatform()
}
程序示例
- Horseman 类
骑士可以冲刺rush(),可以攻击chop()。
exercise()是可以通过锻炼增加经验值。
printLatestStrength() 是打印当前经验值。
package com.example.demo;
public class Horseman {
public int strength;
Horseman () {
strength = 5;
}
public void rush(String enemy) {
System.out.println(this.getClass().getSimpleName() + "rush" + enemy);
}
public int exercise(int experience) {strength += experience; return strength;}
public void chop(String enemy) {
System.out.println(this.getClass().getSimpleName() + "attack" + enemy);
}
public void printLatestStrength() {System.out.println("Latest Strength: " + strength);}
}
- Swordman 类
剑客可以阻拦 block(),可以攻击chop().
package com.example.demo;
public class Swordman {
public int strength;
Swordman () {
strength = 3;
}
public void block(String enemy) {
System.out.println(this.getClass().getSimpleName() + "hinder" + enemy);
}
public void chop(String enemy) {
System.out.println(this.getClass().getSimpleName() + "attack" + enemy);
}
public int exercise(int experience) {strength += experience; return strength;}
}
- StorageAdvisor 类
这是定义Aspect的地方。就是你想让程序在调用一个方法之前(或之中,之后)做什么。
@Before(“execution(* chop(…))”) 标记表明我们希望在执行任何 chop() 方法前注入逻辑。其中 *指任何返回类型, chop匹配方法名, …指任何参数。
package com.example.demo;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
@Aspect
public class StorageAdvisor {
@Before("execution(* chop(..))")
public void beforeAttack(JoinPoint point) {
System.out.println("Advice: " + point.getTarget().getClass().getSimpleName() + " prepare ");
}
@Around("execution(* exercise(..))")
public Object process(ProceedingJoinPoint point) throws Throwable {
System.out.println("@Around:before executing target method...");
//访问目标方法的参数:
Object[] args = point.getArgs();
if (args != null && args.length > 0 && args[0].getClass() == Integer.class) {
args[0] = 10;
}
//用改变后的参数执行目标方法
Object returnValue = point.proceed(args);
System.out.println("@Around:after executing target method...");
System.out.println("@Around:wove target object: " + point.getTarget());
return returnValue;
}
}
- AspectTest类
这个类负责测试。第一个是对调用前进行测试。
第二个演示了调用中如何获取原来的参数并注入逻辑。角色的exercise() 方法是简化版的通过锻炼获得更多能量值。不同的角色获得能量值的速度可能不一样,所以可能有很多个实现。那么可能发布一个版本后,后续需要对获得能量值的方式进行某些调整,但是又不想跑到每个方法里去改动。这样就可以通过一个aspect编程实现。注意对原来的角色类code没有做任何改动,hm.printLatestStrength()打印出了经过注入逻辑修改后的能量值。
package com.example.demo;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class AspectTest {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
Horseman hm = (Horseman)context.getBean("horseman");
hm.rush("Ghoul");
hm.chop("Ghoul");
Swordman sm = (Swordman)context.getBean("swordman");
sm.block("Ghoul");
sm.chop("Ghoul");
hm.exercise(8);
hm.printLatestStrength();
}
}
- ApplicationContext.xml
这是用的xml式的配置方式,定义哪些类(bean)需要注入这个aspect. 如果使用Spring boot的话,文件放在能被找到的目录下即可。比如workdir/, resources/。
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">
<aop:aspectj-autoproxy />
<bean id="horseman" class="com.example.demo.Horseman" />
<bean id="swordman" class="com.example.demo.Swordman" />
<bean class="com.example.demo.StorageAdvisor" />
</beans>