什么是AOP?
基本概念
切面(aspect):横切关注点被模块化的特殊对象。
通知(advice):切面必须要完成的工作。切面中的每个方向称之为通知。通知是在切面对象中的。
目标(target):被通知的对象。
代理(proxy):向目标对象应用通知后创建的对象。
连接点(joinpoint):目标对象的程序执行的某个特定位置。如某个方向调用前,调用后的位置。包括两个信息:1.目标程序的哪个方法?2.方法执行前还是执行后?
切点(pointcut):每个类会有多个连接点,AOP通过切点定位到特定的边接点。
类比,连接点相当于数所成方圆 中的记录,而切点相当于查询条件。一个切点匹配多个连接点。
一、注解(Annotation)方式实现Spring面向切面
首先定义一个接口(为了不违反开闭原则和更好的可扩展性,目标对象实际上是实现了已定义好的某个接口)
package com.entities; public interface IHuman { public void eat(); public void sleep(); }
创建一个继承自IHuman接口的类
为该类加上@Compoent注解
package com.entities; import org.springframework.stereotype.Component; @Component public class Chinese implements IHuman { @Override public void eat() { System.out.println("中国人在吃饭"); //int i =1/0; } @Override public void sleep() { System.out.println("中国人在睡觉"); } }
定义一个切面类
为该类加上@Aspect注解@Compoent注解
package com.aop; import org.aspectj.lang.annotation.After; import org.aspectj.lang.annotation.AfterReturning; import org.aspectj.lang.annotation.AfterThrowing; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.springframework.stereotype.Component; @Aspect @Component public class HumanAOP { @Before("execution(* com.entities.*.eat(..))")//before是在方法执行之前 public void beforeeat(){ System.out.println("先洗 手"); } @After(value = "execution(* com.entities.*.eat(..))")//after是在方法执行之后 public void aftereat(){ System.out.println("去漱口"); } @AfterThrowing(value="execution(* com.entities.*.eat(..))",throwing="ex")//AfterThrowing是在方法报错抛异常时执行 public void except(Exception ex){ System.out.println(ex.getMessage()); System.out.println("撑死了"); } @AfterReturning(value="execution(* com.entities.*.eat(..))",returning="result")//AfterReturning在目标方法执行成功后执行的通知 public void fanhui(Object result){ System.out.println(result); System.out.println("吃完了"); } @Before("execution(* com.entities.*.sleep(..))") public void beforesleesp(){ System.out.println("先洗澡"); } }
创建xml文件
<aop:aspectj-autoproxy proxy-target-class="true"></aop:aspectj-autoproxy>自动为匹配的类生成代理对象
<?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:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd"> <!-- 自动扫描包下的类,并将其实例化。多个包之间用,隔开 --> <context:component-scan base-package="com.entities,com.aop"></context:component-scan> <!-- 配置文件中启动AspectJ的注解功能 ,默认是false,要将其改为true --> <aop:aspectj-autoproxy proxy-target-class="true"></aop:aspectj-autoproxy> </beans>
定义一个main函数类进行效果实现
package com.test; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import com.entities.Chinese; public class Test { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml"); Chinese c= context.getBean(Chinese.class); c.eat(); c.sleep(); } }
先洗 手 中国人在吃饭 去漱口 null 吃完了 先洗澡 中国人在睡觉
二、XML方式实现Spring面向切面
同理,先定义一个接口
package com.entities; public interface IHuman { public void eat(); public void sleep(); }
创建一个继承自IHuman接口的类
package com.entities; public class Chinese implements IHuman { @Override public void eat() { System.out.println("中国人在吃饭"); //int i =1/0; } @Override public void sleep() { System.out.println("中国人在睡觉"); } }
定义一个切面类
package com.aop; public class HumanAOP { public void beforeeat(){ System.out.println("先洗 手"); } public void aftereat(){ System.out.println("去漱口"); } public void except(Exception ex){ System.out.println(ex.getMessage()); System.out.println("撑死了"); } public void fanhui(Object result){ System.out.println(result); System.out.println("吃完了"); } public void beforesleesp(){ System.out.println("先洗澡"); } }
创建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:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd" default-autowire="byName" > <bean id="chinese" class="com.entities.Chinese"></bean> <bean id="gou" class="com.entities.Gou"></bean> <bean id="humanaop" class="com.aop.HumanAOP"></bean> <aop:config> <aop:pointcut expression="execution(* com.entities.*.eat())" id="aftereat"/> <aop:pointcut expression="execution(* com.entities.*.eat())" id="beforeeat"/> <aop:aspect ref="humanaop"> <aop:after method="aftereat()" pointcut-ref="aftereat"/> <aop:before method="beforeeat()" pointcut-ref="beforeeat"/> </aop:aspect> </aop:config> </beans>
定义一个main函数来执行
package com.test; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import com.entities.Chinese; import com.entities.IHuman; public class Test { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml"); IHuman c= (IHuman)context.getBean("chinese"); c.eat(); } }
先洗 手
中国人在吃饭
去漱口
通知分类:
前置通知(@Before) -- 在目标方法执行前执行的通知。
后置通知(@After) -- 在目标方法执行后执行的通知。无论是否发生异常。后置通知中,无法读取目标方法返回的结果。
返回通知(@AfterReturnning) --在目标方法执行成功后执行的通知。在返回通知中可以访问目标返回结果的。
@AfterReturnning(value="execution(* com.itnba..*(..))",returning="result")
public void afterRun(Object result){
System.out.println(result);
}
异常通知(@AfterThrowing) -- 在目标方法执行出错后执行的通知。
@AfterThrowing(value="execution(* com.itnba..*(..))",throwing="ex")
public void afterThrowing(Exception ex){
System.out.println(ex.getMessage());
}
环绕通知(@Around) -- 需要切面方法携带ProceedingJoinPoion参数,类似于一个完整的动态代理,环绕通知必须要有一个返回值,是目标方法的返回值。
@Around("execution(* com.itnba..*(..))") public object aroundAdvice( ProceedingJoinPoint pjp){ object obj = null; try{ //做前置通知 obj = pjp.proceed(); //做返回通知 } catch(Exception ex){ //做异常通知 } //做后置通知 return obj; }
添加日志:
切面方法可以加入参数(JoinPoint) joinPost.getSignature().getXXX()获得相关方法信息
切面方法中可以加入Object参数,用来获得目标方法的返回值(只对返回通知起作用)