大家好,欢迎来到IT知识分享网。
AspectJ 通过连接点向Java添加一些新的程序元素来扩展Java,是Java面向切点一种实现。其主要包括连接点、切点、建议、切面及类型间声明。
连接点:程序在执行过程中明确的点。包括方法和构造函数调用及字段访问等。
切点:用来挑选连接点。
建议:在程序运行到由切点挑选的连接点时执行的程序片段。
切面:类似于类,成员包含了连接点、切点及切面等。
类型间声明:静态改变类的结构及层级关系。 可以为已有的类定义新的成员、方法。也可以使其继承新的类及实现接口。
1 AspectJ 语法
aspectJ 需要使用ajc编译器进行编译。
1.1 切点
切点类似于类中的方法,有访问权限修饰符、可以被定义为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(); } }
1.2 建议
有三种类型,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(); } }
1.3 类型间声明
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(); } }
1.4 切面
切面像类一样也可以被继承,被继承的切面须为抽象切面。 也可以继承类及实现接口。
[issingleton()] |
默认类型,将不限定切点。 |
perthis(Pointcut) |
全局限定连接点在this(Pointcut)中。 |
pertarget(Pointcut) |
全局限定连接点在target(Pointcut)中。 |
percflow(Pointcut) |
全局限定连接点在cflow(Pointcut)z中。 |
percflowbelow(Pointcut) |
全局限定连接点在cflowbelow(Pointcut)中。 |
图 五种在切面中全局限定连接点的方式
2 AspectJ 的应用场景
2.1 开发阶段
我们在开发过程中,需要进行调试,有时会往业务代码中插入跟踪代码。在上线的时候我们要把这些代码删除,否则可能会降低系统性能。有时我们插入的代码会比较多和广,可能会漏删这些代码。因为有些人常常会通过写脚本的方式来进行调试。
而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 就仅仅修改相关切面及跟踪类即可。
2.2 生产阶段
观察者模式有两种角色: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<PropertyChangeObserver> User.observerSet = new HashSet<>(); public void User.addListener(PropertyChangeObserver observer) { observerSet.add(observer); } public Set<PropertyChangeObserver> 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); } }
免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://haidsoft.com/127785.html