连接点(Joinpoint)(要被拦截的方法):并不是所有被spring管理的对象的所有方法都是连接点,在Spring AOP中,一个连接点总是表示一个方法的执行。
切入点:使用表达式,哪些切入点需要被切入。
切面:一个关注点的模块化,这个关注点可能会横切多个对象。就是要拦截的类。
织入(Weaving):把切面连接到其它的应用程序类型或者对象上,并创建一个被通知的对象。切点把切面织入进来。
通知(Advice):在切面的某个特定的连接点上执行的动作。其中包括了“around”、“before”和“after”等不同类型的通知
目标对象(Target Object): 被一个或者多个切面所通知的对象。也被称做被通知(advised)对象。被代理对象,实际对象
(切面上的通知)
当连接点被调用时,告知切入点要切入哪个方法,引入切面,切面拦截,然后去通知。
例子:导入aopalliance.jar、aspectjweaver.jar
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:tx="http://www.springframework.org/schema/tx" 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/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd" default-lazy-init="false" default-autowire="no"> <bean id="Myservice" class="org.hzy.services.MyService"/> <bean id="MyAdvice" class="org.hzy.advices.MyAdvice"/> <aop:config><!-- 只需要一个就可以包含多个切面 --> <aop:pointcut id="before" expression="execution(* org.hzy.services.MyService.add_user(..))"/><!-- 顶级切点 --> <aop:pointcut id="around" expression="execution(* org.hzy.services.MyService.add(Integer,String))"/> <aop:aspect ref="MyAdvice"><!-- 一个切面包含多个通知 --> <aop:before method="doBefore" pointcut-ref="before"/> <!-- <aop:after method="doAfter" pointcut-ref="before"/>--> <!-- returning 注册变量来接收方法的返回值 --> <aop:after-returning method="doAfter" pointcut="execution(* org.hzy.services.MyService.add())" returning="ss" /> <!-- throwing 注册变量来接收方法产生的异常--> <aop:after-throwing throwing="ex" pointcut="execution(* org.hzy.services.MyService.add(Integer))" method="doThrow"/> <aop:around method="doAround" pointcut-ref="around"/> </aop:aspect> </aop:config> </beans>传参数还可以用args,在表达式里面 在通知中args-name
自定义异常类Myexception.java
package org.hzy.exceptions; public class MyException extends Exception{//自定义异常 public MyException(String msg){ super(msg); } }
IMyService.java
package org.hzy.services; import org.hzy.exceptions.MyException; public interface IMyService { //实现接口的方法才是连接点 public void add_user(String uname); public String add(); public void add(Integer i) throws MyException; public void add(Integer i,String uname); }
MyService.java
package org.hzy.services; import org.hzy.exceptions.MyException; public class MyService implements IMyService{ public void add_user(String uname){ System.out.println("add_user!!!!!!"+uname); } public String add(){ System.out.println("add!!!!!!!!!!!"); return "hzy"; } public void add(Integer i) throws MyException{//声明异常 System.out.println("add with args i!!!!!!!!!!!"); String a=null; // try { //在这里捕获异常后将不会走拦截方法 // System.out.println(a.equals(i)); // } catch (Exception e) { // // TODO: handle exception // } throw new MyException("wrong!!!!!!!!!!"); // System.out.println(a.equals(i)); } public void add(Integer i,String uname){ System.out.println("this is add with args uname"+i+" "+uname); } }
MyAdvice.java(切面类)
package org.hzy.advices; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.ProceedingJoinPoint; public class MyAdvice { //前置通知不能改变参数,是只读的 public void doBefore(JoinPoint jp){ System.out.println(jp.getTarget());//得到目标对象,也是被代理对象,真正的对象 System.out.println(jp.getArgs()[0]); System.out.println(jp.getSignature().getName());//得到方法名 System.out.println("this is doBefore"); } //后置拦截,一般使用After-returning public void doAfter(JoinPoint jp,Object ss){//至少两个参数,这个参数名必须是ss且是第0位 System.out.println(ss); System.out.println("this is doAfter"); } public void doThrow(JoinPoint jp,Exception ex){//在异常发生前调用 System.out.println(ex+" "+ex.getMessage()); System.out.println("this is doThrow"); } //环绕通知,要手动调用,连接点被包住,使用连接点的子类ProceedingJoinPoint public void doAround(ProceedingJoinPoint pjp) throws Throwable{//Throwable异常比exception异常高 System.out.println("this is doAround"); if(Integer.parseInt(pjp.getArgs()[0].toString())<10){//传过来的值是12,aa System.out.println("小于10"); }else{ Object o=pjp.proceed(new Object[]{new Integer(20),"hzy"});//调用下一个,在调用连接点方法之前,可以改变值,不像前置通知,如果不改变的话,可以直接pjp.proceed(); System.out.println(o);//是null,void的返回值是null } } }自定义异常将会把异常消息被异常通知拦截
package org.hzy.test; import org.hzy.exceptions.MyException; import org.hzy.services.IMyService; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class MyTest1 { public static void main(String[] args) throws MyException { ApplicationContext ctx=new ClassPathXmlApplicationContext("beans1.xml"); // MyService my=ctx.getBean("Myservice",MyService.class);//Add CGLIB to the class path or specify proxy interfaces IMyService my=ctx.getBean("Myservice",IMyService.class);//由于返回的是代理,代理与MyService不相干,不能转换,所有要实现接口 // my.add_user("hzy"); // my.add(); // my.add(1); /* try { my.add(1); } catch (Exception e) { // TODO: handle exception System.out.println("错误!!!!!!!!!!!");//会先走拦截方法 }*/ my.add(12,"aa"); } }
注:new 出来的是不会别切入拦截的,要spring中的
当没有实现接口的时候,报错,增加CGLIB包或者增加代理接口Spring发现被切入的对象实例,返回的是代理,所以通过getBean得到的是代理($proxy)
向Spring容器要MyService对象,却返回个代理,这个代理和MyService对象不相关,不能转换,所以实现接口,代理也实现接口。所以,
接口中的方法才是真正的接入点。
动态代理:(因为编译好的类是不能修改的,所以需要代理)
MyServiceProxy也实现了接口,里面有MyService这个属性,当被切入拦截的时候,在代理的add_user()中,
MyService mys;是Spring产生的
public void add_user(String name){
加了拦截的代码
mys.add_user(name);
}