AspectJ 通过连接点向Java添加一些新的程序元素来扩展Java,是Java面向切点一种实现。其主要包括连接点、切点、建议、切面及类型间声明。
连接点:程序在执行过程中明确的点。包括方法和构造函数调用及字段访问等。
切点:用来挑选连接点。
建议:在程序运行到由切点挑选的连接点时执行的程序片段。
切面:类似于类,成员包含了连接点、切点及切面等。
类型间声明:静态改变类的结构及层级关系。 可以为已有的类定义新的成员、方法。也可以使其继承新的类及实现接口。
aspectJ 需要使用ajc编译器进行编译。
切点类似于类中的方法,有访问权限修饰符、可以被定义为final,但也有些不同:1)不能重载。2)作用域包括切面声明及主体(方法的作用域是在类主体)。
if |
条件判断 |
within |
witin(全限定名),在某个类的全部连接点。 |
cflow |
cflow(pointcut), 连接点pointcut 及之后的且在其封闭区间内的所有连接点。包括在在该方法中所调用的其他方法的连接点。 |
get |
当某个字段被访问时会被捕获。(不能捕获参数) |
set |
当某个字段被赋值时会被捕获。(可以捕获参数) |
handler |
try..catch中的catch捕获的错误类型是Throwable类型及其子类型时不被捕获。(可以捕获参数) |
adviceexecution |
adviceexecution() 所有建议在执行时会被捕获。 |
表 切点的部分语法
public class PointcutEntity {
private String name;
public void fun(String val) {
this.name = val;
fun();
System.out.println("-----");
}
private void fun() {
System.out.println(this.name);
}
public void fun2() {
System.out.println("fun2");
try {
throw new Throwable();
} catch (Throwable e) {
System.out.println("处理");
}
}
}
public aspect PointcutEntityAspect perthis(withPointcut()){ // 相当于全局声明了,在这个切面中,所有的连接点都在PointcutEntity里运行
final pointcut withPointcut(): within(service.PointcutEntity); // PointcutEntity内的所有连接点
pointcut funPointcut(): execution(* service.PointcutEntity.fun());
pointcut cFlowPointcut(): cflow(funPointcut()); // funPointcut 连接点及之后切在封闭区间内的所以连接点
pointcut getPointcut(): get(String service.PointcutEntity.name); // 捕获PointcutEntity的name字段被访问
pointcut setPointcut(String name): set(String service.PointcutEntity.name) && args(name); // 捕获PointcutEntity的name字段被赋值
pointcut handlerPointcut(Throwable e): handler(Throwable) && args(e); // try..catch中的catch捕获的错误类型是Throwable类型及其子类型时不被捕获
// before(): cFlowPointcut() {
// System.out.println("cFlowPointcut---");
// System.out.println(thisJoinPoint.getSourceLocation());
// System.out.println(thisJoinPoint.getSignature());
// System.out.println(thisJoinPoint.getKind());
// System.out.println();
// }
// before(): getPointcut() {
// System.out.println("getPointcut---");
// System.out.println(thisJoinPoint.getSourceLocation());
// System.out.println(thisJoinPoint.getSignature());
// System.out.println(thisJoinPoint.getKind());
// System.out.println();
// }
// before(String name): setPointcut(name) {
// System.out.println("getPointcut---");
// System.out.println("赋值value:" + name);
// System.out.println(thisJoinPoint.getSourceLocation());
// System.out.println(thisJoinPoint.getSignature());
// System.out.println(thisJoinPoint.getKind());
// System.out.println();
// }
before(Throwable e): handlerPointcut(e) {
System.out.println("handlerPointcut---");
System.out.println("抛错:" + e);
System.out.println(thisJoinPoint.getSourceLocation());
System.out.println(thisJoinPoint.getSignature());
System.out.println(thisJoinPoint.getKind());
System.out.println();
}
public static void main(String[] args) {
PointcutEntity pointcutEntity = new PointcutEntity();
pointcutEntity.fun2();
}
}
有三种类型,before、after(包括 after() returning、after() throwing及after)及around。around 可以替换方法的返回值。
public class AdviceEntity {
public String fun() {
System.out.println("fun");
return "建议";
}
public void fun1() throws ClassNotFoundException {
System.out.println("fun1");
throw new ClassNotFoundException();
}
public String fun2() {
System.out.println("fun2");
return "fun2";
}
}
public aspect AdviceEntityAspect {
pointcut funPointcut(): execution(* service.AdviceEntity.fun*(..));
pointcut fun2Pointcut(): execution(* service.AdviceEntity.fun2(..));
after() returning (String val): funPointcut() {
System.out.println("after() returning");
System.out.println("返回值:" + val);
System.out.println(thisJoinPoint.getSourceLocation());
System.out.println(thisJoinPoint.getSignature());
System.out.println();
}
after() throwing (ClassNotFoundException e): funPointcut() {
System.out.println("after() throwing");
System.out.println("捕获错误:" + e);
System.out.println(thisJoinPoint.getSourceLocation());
System.out.println(thisJoinPoint.getSignature());
System.out.println();
}
String around(): fun2Pointcut() {
System.out.println("String around()");
System.out.println(thisJoinPoint.getSourceLocation());
System.out.println(thisJoinPoint.getSignature());
System.out.println();
return "around() String";
}
public static void main(String[] args) throws ClassNotFoundException {
AdviceEntity entity = new AdviceEntity();
entity.fun();
System.out.println("fun2的返回值:" + entity.fun2());
entity.fun1();
}
}
AspectJ 可以为类定义方法及字段,可以使其继承新的类及实现接口。
public class InterEntity {
private final String name = "inter_entity";
public void fun() {
System.out.println("InterEntity定义的name:" + this.name);
this.speak(); // 在类中可以直接使用在切面中定义的方法
}
}
public interface SpeakInterface {
void speak();
}
public aspect InterEntityAspect {
private String InterEntity.name = "InterEntityAspect"; // 给AdviceEntity定义name字段,虽然这个字段在原结构已存在,
// 但是因为这里和AdviceEntity中其访问类型是private,所以不会冲突
declare parents: InterEntity implements SpeakInterface; // 定义实现接口,还需要定义实现该接口的方法
public void InterEntity.speak() { // 上面接口方法的实现
System.out.println("InterEntityAspect ");
}
private void InterEntity.show() {
// 这里的this 是指InterEntity的实例
System.out.println(this.name); // InterEntityAspect
this.fun(); // InterEntity定义的name:inter_entity
this.speak();
}
public static void main(String[] args) {
InterEntity interEntity = new InterEntity();
interEntity.show();
}
}
declare soft: Type: Pointcut; 捕获类型为Type的异常并且重抛成SoftException。(Type 不能是RuntimeException)。
declare precedence: TypePatternList; 为切面定义优先级,TypePatternList为切面列表,“,”隔开,越前面优先级越高。
public class DeclareEntity {
public void fun() {
System.out.println("fun");
}
public void fun(int num) throws CustomException {
if (num == 0) throw new CustomException();
System.out.println(33 / num);
}
}
public abstract aspect DeclareEntityParentAspect pertarget(DeclareEntityClassPointcut()){
pointcut DeclareEntityClassPointcut(): within(service.DeclareEntity);
pointcut funPointCut(): execution(* fun(..));
declare precedence: DeclareEntityChildrenAspectB,DeclareEntityChildrenAspectA; // 定义切面的优先级
declare soft:CustomException: funPointCut();
public static void main(String[] args) throws CustomException {
DeclareEntity entity = new DeclareEntity();
entity.fun();
entity.fun(0);
}
}
public aspect DeclareEntityChildrenAspectA extends DeclareEntityParentAspect{
before(): funPointCut() {
System.out.println("DeclareEntityChildrenAspectA");
System.out.println(thisJoinPoint.getSourceLocation());
System.out.println();
}
}
public aspect DeclareEntityChildrenAspectB extends DeclareEntityParentAspect{
before(): funPointCut() {
System.out.println("DeclareEntityChildrenAspectB");
System.out.println(thisJoinPoint.getSourceLocation());
System.out.println();
}
}
切面像类一样也可以被继承,被继承的切面须为抽象切面。 也可以继承类及实现接口。
[issingleton()] |
默认类型,将不限定切点。 |
perthis(Pointcut) |
全局限定连接点在this(Pointcut)中。 |
pertarget(Pointcut) |
全局限定连接点在target(Pointcut)中。 |
percflow(Pointcut) |
全局限定连接点在cflow(Pointcut)z中。 |
percflowbelow(Pointcut) |
全局限定连接点在cflowbelow(Pointcut)中。 |
图 五种在切面中全局限定连接点的方式
我们在开发过程中,需要进行调试,有时会往业务代码中插入跟踪代码。在上线的时候我们要把这些代码删除,否则可能会降低系统性能。有时我们插入的代码会比较多和广,可能会漏删这些代码。因为有些人常常会通过写脚本的方式来进行调试。
而AspectJ 在这方面具有独特的优势,可以实现对调试代码的“插拔”。
需求:打印develop.service 包下所有类以get或set开头方法访问日志。
public class Student {
private String name;
private Integer age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
}
public class Teacher {
private String name;
private String course;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getCourse() {
return course;
}
public void setCourse(String course) {
this.course = course;
}
}
public class GetAndSetTrace {
public static void info(String className,String methName,String location,Object result,Object... args) {
String sb = "访问时间:" + new Date() + "\n" +
"类名:" + className + "\n" +
"方法:" + methName + "\n";
if (args != null) sb += "参数名:" + Arrays.asList(args) + "\n";
if (result != null) sb +="返回值:" + result + "\n";
sb += "连接点:" + location;
System.out.println(sb);
System.out.println("--------------------");
}
}
public aspect GetAndSetTraceAspect pertarget(targetClassPointcut()){
pointcut targetClassPointcut(): within(develop.service.*);
pointcut getPointcut(): execution(Object+ get*());
pointcut setPointcut(Object obj): execution(void set*(*)) && args(obj);
after() returning(Object obj): getPointcut() {
trace(thisJoinPoint,obj);
}
before(Object obj): setPointcut(obj) {
trace(thisJoinPoint,null);
}
private void trace(JoinPoint joinPoint,Object result) {
String className = joinPoint.getTarget().getClass().getName();
String methodName = joinPoint.getSignature().toShortString();
String location = joinPoint.getSourceLocation().getLine() + "";
GetAndSetTrace.info(className,methodName,location,result,joinPoint.getArgs());
}
public static void main(String[] args) {
Student student = new Student();
Teacher teacher = new Teacher();
student.setName("小名同学");
student.setAge(18);
teacher.setName("刘老师");
teacher.setCourse("英语");
System.out.println("学生:" + student.getName());
System.out.println("年龄:" + student.getAge());
System.out.println("教师:" + teacher.getName());
System.out.println("课程:" + teacher.getCourse());
}
}
AspectJ 可重用,比如上面需求有改动,对日志打印格式及要求传递的参数做了修改。如果不使用Aspect而使用传统方式-往业务代码插入跟踪代码。那么每个被插入的跟踪代码都需要修改。而对于AspectJ 就仅仅修改相关切面及跟踪类即可。
观察者模式有两种角色:Subject目标类,被观察的对象;Observer 观察者,将观察的目标的改变而做出改变。
PropertyChangeSupport和PropertyChangeListener类是Java中用来监听对象属性变化的。
public class User {
private String name;
private final PropertyChangeSupport propertyChangeSupport = new PropertyChangeSupport(this);
public void addPropertyChangeListener(PropertyChangeListener listener) {
propertyChangeSupport.addPropertyChangeListener(listener);
}
public void setName(String name) {
propertyChangeSupport.firePropertyChange("name",this.name,name);
this.name = name;
}
public String getName() {
return name;
}
public static void main(String[] args) {
User user = new User();
PropertyChangeListener listener = new PropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent evt) {
System.out.println(evt.getPropertyName() + "发生了改变,旧值:" + evt.getOldValue() + ",新值:" + evt.getNewValue());
}
}; // 监听器
user.addPropertyChangeListener(listener);
user.setName("黄先生");
user.setName("刘女士");
}
}
我们可以使用AspectJ 实现上面的功能。
public aspect PropertyChangeAspect{
private Set User.observerSet = new HashSet<>();
public void User.addListener(PropertyChangeObserver observer) {
observerSet.add(observer);
}
public Set User.getListenerSet() {
return this.observerSet;
}
pointcut setMethod(Object arg): execution(* set*(*)) && args(arg);
before(Object org): setMethod(org) {
Object target = thisJoinPoint.getTarget();
if (target instanceof User) {
for(PropertyChangeObserver listener : ((User) target).getListenerSet()) {
listener.update(thisJoinPoint.getSignature().getName(),org);
}
}
System.out.println();
}
public static void main(String[] args) {
User user = new User();
PropertyChangeObserver observer1 = (methodName, arg) -> {
System.out.println("观察者1,方法:" + methodName + ",参数:" + arg);
};
PropertyChangeObserver observer2 = new PropertyChangeObserver() {
@Override
public void update(String methodName, Object arg) {
System.out.println("观察者2,参数:" + arg);
}
};
user.addListener(observer1);
user.addListener(observer2);
user.setName("黄");
user.setName("刘");
}
// 自定义建设者
interface PropertyChangeObserver {
void update(String methodName,Object arg);
}
}