Aspect orient programming
横切关注点分离 (Cross-cut concern seperate)
1 public class StudentService{ 2 public void insert(){ 3 sout("业务代码第一行"); 4 sout("业务代码第二行"); 5 sout("业务代码第三行"); 6 } 7 }
增加需求:要把整个项目中1000个业务类的8000个业务方法,都增强其功能,就是记录方法被调用的日志。
1 public class StudentService{ 2 public void insert(){ 3 sout("此业务方法开始被调用"); 4 sout("业务代码第一行"); 5 sout("业务代码第二行"); 6 sout("业务代码第三行"); 7 } 8 }
-
违反了单一职责的原则
-
代码重复,样板化
既然这样,我们就应该分开他们
业务类
1 public class StudentService{ 2 public void insert(){ 3 sout("业务代码第一行"); 4 sout("业务代码第二行"); 5 sout("业务代码第三行"); 6 } 7 }
日志类
1 public class Log{ 2 public void start(){ 3 sout("此业务方法开始被调用"); 4 } 5 }
为了叙述的方便,像核心业务类,也就是需要被增强的类的方法,这个需要被增强的方法(也称之为连接点JointPoint),它所在的类,称之为目标类(target),还有一个是用来增强别人的方法,此方法称之为增强方法(也叫作通知Advice),增强方法所在的类,就称之切面类
我们可能找代码生成器来完成这个事情,可可能利用spring这种框架来完成,但是不管怎么样,最终的结果,一定是有这么一个方法
1 public void insert(){ 2 sout("此业务方法开始被调用") 3 sout("业务代码第一行"); 4 sout("业务代码第二行"); 5 sout("业务代码第三行"); 6 7 }
方法放到哪里去?
1 public class StudentServiceChild extends StudentSerivce{ 2 @Override 3 public void insert(){ 4 new Log().start(); 5 super.insert(); 6 } 7 }
上面的类就称之为代理类
1 main(){ 2 StudentService service= new StudentServiceChild(); 3 service.insert(); 4 }
两个类如何整合的问题,所以实质上是2个类中2个方法进行整合。
你需要传递给工具类或者框架的信息有以下几个,它才能帮你自动生成最终的结果类型,也就是代理类
被增强的一方
-
类是那一个?
-
方法是哪一个?
用来增强别人的样
-
类是哪一个?
-
方法是哪一个?
-
何时增强
Spring AOP 原生API
1 StudentService studentService = new StudentService(); 2 3 LogAspect logAspect = new LogAspect(); 4 LogEndAspect logEndAspect = new LogEndAspect(); 5 6 ProxyFactory factory = new ProxyFactory(); 7 8 factory.setTarget(studentService); 9 10 factory.addAdvice(logAspect); 11 12 factory.addAdvice(logEndAspect); 13 14 StudentService studentService1Proxy = (StudentService) factory.getProxy(); 15 16 studentService1Proxy.insert();
因为上面的写法与spring的用法不一致,也比较底层。推荐的用法就是下面的这种
<!-- 确定目标类--> <bean id="target" class="springaopapi.StudentService"/> <bean id="firstAspect" class="springaopapi.LogAspect"/> <bean id="proxyGenerator" class="org.springframework.aop.framework.ProxyFactoryBean"> <property name="target" ref="target"/> <property name="interceptorNames" value="firstAspect"/> </bean>
1 ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml"); 2 StudentService service = applicationContext.getBean("proxyGenerator", StudentService.class); 3 service.insert();
所以Spring推出了下面的方式,这样上面的问题就都解决了。
业务类
1 public class StudentService { 2 3 public void update(){ 4 System.out.println("update "); 5 } 6 7 public void insert(){ 8 System.out.println("insert "); 9 } 10 }
切面类,没有实现任何接口
1 public class MyAspect { 2 3 public void beforeA(){ 4 System.out.println("before-----"); 5 } 6 }
配置文件
<bean id="target" class="aop.StudentService"/> <bean id="aspect" class="aop.MyAspect"/> <aop:config> <aop:aspect ref="aspect"> <aop:before method="beforeA" pointcut="execution( public * aop.StudentService.update())"/> </aop:aspect> </aop:config> <!-- 启动自动代理--> <aop:aspectj-autoproxy/>
使用
1 ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml"); 2 3 StudentService studentService = applicationContext.getBean("target", StudentService.class); 4 studentService.update(); 5 System.out.println("-------------------"); 6 studentService.insert();
-
目标类(target):被增强类
-
连接点(Jointpoint):被增强的方法
-
切面(aspect):用来增强别人的,由切点与增强组成
-
通知(advice):也叫增强,用来增强别人功能的方法,在切面类中
-
织入(weaver): 目标类与切面整合在一起的过程,也就是把通知切入到连接点的过程
-
代理类:完成织入之后的结果类型
-
切点(Pointcut):也称之为切点表达式,用来描述需要被增强的连接点信息,所以也称之为连接点的集合
结构
modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern) throws-pattern?)
-
访问修饰符(可选的)
-
返回类型(必须的)
-
声明的类型模式(包含所在的包与类):可选
-
名称(必须的)
-
参数(必填)
示例
public void com.nf.A.insert()
可以简写为
void insert()
三个通配符
-
* (星号)表示任意
-
.. (两个点) 表示任意字符数量
-
+ (加号) 它放在类的后面,表示此类的子类型
com.*.service ->com.nf.service(ok) com.util.service(ok) com.nf.util.service(no)
com..service ->com.nf.service(ok) com.util.service(ok) com.nf.util.service(no)
execution(public * *(..))
-
the execution of any method with a name beginning with "set":
execution(* set*(..))
-
the execution of any method defined by the
AccountService
interface:
execution(* com.xyz.service.AccountService.*(..))
-
the execution of any method defined in the service package:
execution(* com.xyz.service.*.*(..))
-
the execution of any method defined in the service package or a sub-package:
execution(* com.xyz.service..*.*(..))
-
any join point (method execution only in Spring AOP) within the service package:
within(com.xyz.service.*)
-
any join point (method execution only in Spring AOP) within the service package or a sub-package:
within(com.xyz.service..*)
-
any join point (method execution only in Spring AOP) where the proxy implements the
AccountService
interface:
this(com.xyz.service.AccountService)
-
-
&& (and)
-
|| (or)
-
! (not)
this(com.xyz.service.AccountService) and within(com.xyz.service..*)
指示器
within - limits matching to join points within certain types (simply the execution of a method declared within a matching type when using Spring AOP)
this - limits matching to join points (the execution of methods when using Spring AOP) where the bean reference (Spring AOP proxy) is an instance of the given type
target - limits matching to join points (the execution of methods when using Spring AOP) where the target object (application object being proxied) is an instance of the given type
args - limits matching to join points (the execution of methods when using Spring AOP) where the arguments are instances of the given types
@target - limits matching to join points (the execution of methods when using Spring AOP) where the class of the executing object has an annotation of the given type
@args - limits matching to join points (the execution of methods when using Spring AOP) where the runtime type of the actual arguments passed have annotations of the given type(s)
@within - limits matching to join points within types that have the given annotation (the execution of methods declared in types with the given annotation when using Spring AOP)
@annotation - limits matching to join points where the subject of the join point (method being executed in Spring AOP) has the given annotation
-
before:前置通知 :在被拦截方法之前执行
-
after-returning:后置通知 ,被拦截方法正常执行之后才会执行,如果被拦截方法抛出了异常,就不执行
-
after通知:最终通知,不管被拦截方法有没有抛出异常,都会执行
-
after-throw:异常通知,被拦截的方法抛出了异常才会执行,不抛出异常,不会得到执行
-
around:环绕通知:最强大的通知,可以处理上面4种通知。
学习研究通知考虑点
-
连接点方法(被拦截方法)考虑2个方面
-
抛出异常
-
不抛出异常
-
-
通知考虑以下几个方面
-
通知方法与连接点方法执行的顺序
-
理解哪些通知方法会执行,不会执行
-
理解同类型通知执行顺序问题
-
理解通知方法参数的问题
-
多个环绕通知执行情况
-
测试:
-
被拦截方法如果没有执行,afterReturning通知会执行吗?
参数处理
看一下下面的代码
1 ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml"); 2 3 StudentService studentService = applicationContext.getBean("target", StudentService.class); 4 studentService.insert("cj");
-
利用JoinPoint :所有的通知方法第一个参数是这个类型,spring框架自动帮你赋值
-
参数绑定
joinPoint作为通知方法的参数传递参数
利用JoinPoint传递参数给通知方法的重要的要求是,此类型必须 是第一个参数。下面是实现过程
1.编写目标类:
1 public class TeacherService { 2 3 public void update(String a,String b){ 4 System.out.println(" --" + a + "---" + b); 5 } 6 }
2.编写切面类
1 public class TeacherAspect { 2 3 public void before(JoinPoint joinPoint){ 4 System.out.println(joinPoint.getArgs().length); 5 System.out.println(joinPoint.getArgs()[0]); 6 System.out.println(joinPoint.getArgs()[1]); 7 // System.out.println("****" + aa + "***" + bb); 8 } 9 }
3.编写配置
1 <bean id="target" class="param.TeacherService"/> 2 <bean id="asp" class="param.TeacherAspect"/> 3 <aops:config> 4 <!-- 切点表达式;pointcut切入点--> 5 <aops:pointcut id="mypc" expression="args(String,String)"></aops:pointcut> 6 <aops:aspect ref="asp"> 7 <aops:before method="before" pointcut-ref="mypc"/> 8 </aops:aspect> 9 </aops:config>
上面的args(String,String) 表达式的意思是:寻找方法,并不是绑定参数,所以只需要类型就可以了
4.使用
1 public static void main(String[] args) { 2 ApplicationContext applicationContext = new ClassPathXmlApplicationContext("paramContext.xml"); 3 4 TeacherService studentService = applicationContext.getBean("target", TeacherService.class); 5 studentService.update("1111","|2222"); 6 7 }
arg-names传递参数
1.编写目标类:
1 public class TeacherService { 2 3 public void update(String a,String b){ 4 System.out.println(" --" + a + "---" + b); 5 } 6 }
2.编写切面类
1 public class TeacherAspect { 2 public void before(String aa,String bb){ 3 System.out.println("****" + aa + "***" + bb ); 4 } 5 }
3.编写配置
-
不是所有的切点指示器都支持参数传递的,args指示器是支持的
-
下面切点表达式中的args(xx,yy)的xx,yy名字可以随便取,不需要与目标方法参数名一样。
-
在配置通知的arg-names时,里面的名字必须与切点表达式的参数名一样,个数也一样,但顺序可以不一样。也不要求与切面类中的通知方法的参数名一样。
1 <bean id="target" class="param.TeacherService"/> 2 <bean id="asp" class="param.TeacherAspect"/> 3 <aops:config> 4 <!-- 切点表达式--> 5 <aops:pointcut id="mypc" expression="args(xx,yy)"></aops:pointcut> 6 <aops:aspect ref="asp"> 7 <aops:before method="before" pointcut-ref="mypc" arg-names="yy,xx"/> 8 </aops:aspect> 9 </aops:config>
4.使用
1 public static void main(String[] args) { 2 ApplicationContext applicationContext = new ClassPathXmlApplicationContext("paramContext.xml"); 3 4 TeacherService studentService = applicationContext.getBean("target", TeacherService.class); 5 studentService.update("1111","|2222"); 6 7 }
5.输出
****|2222***1111 --1111---|2222
这种方式缺点是通知方法个数与连接点方法个数要求一样,优点是无需JoinPoint作为通知方法的参数,没有侵入
通知者。是一个官方的切面类的表示,里面只有一个通知方法说法