AOP中文名称叫面向切面编程,通俗就是在一个类A的方法a前面添加一个方法b,后面添加一个方法c,而这个b和c方法不属于A类的方法而是来自另一个或者另两个类的方法(其中b叫前置通知,c叫后置通知)。这是为了提高程序的可维护性,同时面向切面编程具有高扩展性,其扩展性就是表现在两个切面。如下图1-1所示。
图1-1
实现AOP的方式有两种,schema=based和aspectJ方式。首先,先介绍schema=based方式实现AOP(面向切面)前置和后置通知。
第一:使用schema-based方式和AspectJ实现前置和后置通知
1,使用schema-based方式前置和后置通知
首先创建切面类Demo和切面show1()(切面的本质是一个方法),具体代码如下。
//切面类
public class Demo {
//切面
public void show1(){
System.out.println("我是一个demo1");
}
}
之后再创建前置切面类Before并实现接口MethodBeforeAdvice,重写方法,代码如下:
//前置通知类
public class Before implements MethodBeforeAdvice{
//前置通知
public void before(Method arg0, Object[] arg1, Object arg2) throws Throwable {
System.out.println("前置");
}
}
创建后置通知类以及后置通知,代码如下:
public class After implements AfterReturningAdvice{
public void afterReturning(Object arg0, Method arg1, Object[] arg2, Object arg3) throws Throwable {
// TODO Auto-generated method stub
System.out.println("后置");
}
}
在spring配置文件配置,
<?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"
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.xsd">
<!--前面配置-->
<aop:config,execution(* com.yjb.springAop.Demo.show1())中为固定表达式,星号表示生命通配符,星号后空格加切面(面类的方法),-->
<!--切点配置-->
<aop:pointcut expression="execution(* com.yjb.springAop.Demo.show1())" id="myponit"/>
<!--前置通知-->
<aop:advisor advice-ref="mybefore" pointcut-ref="myponit"/>
<!--后置通知-->
<aop:advisor advice-ref="myafter" pointcut-ref="myponit"/>
</aop:config>
<!--前置通知类实现-->
<bean id="mybefore" class="com.yjb.springAop.Before"></bean>
<!--后置通知类实现-->
<bean id="myafter" class="com.yjb.springAop.After"></bean>
<!--切面类实现-->
<bean id="mydemo" class="com.yjb.springAop.Demo"></bean>
</beans>
测试类编写:
import org.apache.catalina.core.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.yjb.springAop.Demo;
public class Text {
public static void main(String[] args) {
ClassPathXmlApplicationContext app=new ClassPathXmlApplicationContext("applicationContext.xml");
Demo demo=app.getBean("mydemo", Demo.class);
demo.show1();
}
}
测试结果:
2,使用AspectJ实现前置和后置通知
代码如下:
package com.yjb.aspectjArround;
public class AspectjArround {
//前置通知,自己定的,把这个方法名引入带配置文件就可以
public void before(){
System.out.println("前置");
}
//后置通知
public void after(){
System.out.println("环绕后置");
}
}
配置文件为:
<?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"
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.xsd">
<bean id="demo" class="com.yjb.springAop.Demo"></bean>
<!-- aspectj通知-->
<!-- 通知类 -->
<bean id="arround" class="com.yjb.aspectjArround.AspectjArround"></bean>
<aop:config>
<!-- ref为通知类 -->
<aop:aspect ref="arround">
<aop:pointcut expression="execution(* com.yjb.springAop.Demo.show1())" id="myponit"/>
<!-- 把哪个方法定为后置通知 -->
<!-- <aop:after-returning method=""/>-->
<!-- 把哪个方法定为后置通知 -->
<aop:after method="after" pointcut-ref="myponit"/>
<!--
<aop:after-returning method=""/>只能在切点没有异常才能执行,
<aop:after method="" "/>切点有异常也会执行。
-->
<!-- 把哪个方法定为前置通知 -->
<aop:before method="before" pointcut-ref="myponit"/>
</aop:aspect>
</aop:config>
</beans>
切点类还是不变,测试类还是一样的。
第二:异常通知
异常通知是spring提供的一种切点异常处理机制,和前置通知无关(前置通知是在切点之前执行),只要切点出了异常,后置通知就不再执行。AOP的通知主要是对server层进行拦截,所以在通知不可以处理异常只能抛出异常,主要是为了切点异常能被异常通知捕捉到。
关键词:server层不抛异常,
注意:schema-base方式直接在标签<aop:config></aop:config>中配置切点和通知,
aspectJ方式是在<aop:config></aop:config>的子标签<aop:aspect></aop:aspect>中配置切点和异常,这也是区分这两个方式的主要手段。
1,schema-base方式实现异常通知
官方文档给出三种实现方式,第一种:实现ThrowsAdvice接口并实现规定的方法(不算方法重写,ThrowsAdvice没有方法,但是spring容器中有对这个方法的解析,方法名为afterThrowing)。
public class RemoteThrowsAdvice implements ThrowsAdvice {
public void afterThrowing(RemoteException ex) throws Throwable {
// Do something with remote exception
}
}
第二种方式:
public class ServletThrowsAdviceWithArguments implements ThrowsAdvice {
public void afterThrowing(Method m, Object[] args, Object target, ServletException ex) {
// Do something with all arguments
}
}
第三种方式:(第一种和第二种的结合)
public static class CombinedThrowsAdvice implements ThrowsAdvice {
public void afterThrowing(RemoteException ex) throws Throwable {
// Do something with remote exception
}
public void afterThrowing(Method m, Object[] args, Object target, ServletException ex) {
// Do something with all arguments
}
}
这里我们采用第一种方式,编写如下异常通知代码。
package com.yjb.mythrowadvice;
import org.springframework.aop.ThrowsAdvice;
public class SchemaBase implements ThrowsAdvice{
public void afterThrowing(Exception ex)throws Throwable {
System.out.print("schemabase异常通知"+"..."+ex.getMessage());
}
}
切点类为:注意看一下代码,出现异常不处理只抛出。
package com.yjb.springAop;
public class Demo {
public void show1()throws Exception{
int i=5/0;
System.out.println("我是一个demo1");
}
}
在spring配置文件中配置切点和通知:
<?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"
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.xsd">
<bean id="schemaBase" class="com.yjb.mythrowadvice.SchemaBase"></bean>
<aop:config>
<aop:pointcut expression="execution(* com.yjb.springAop.Demo.show1())" id="myponit"/>
<aop:advisor advice-ref="schemaBase" pointcut-ref="myponit"/>
</aop:config>
</beans>
text类运行结果:(测试类可以处理异常了)
package com.yjb.text;
import org.apache.catalina.core.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.yjb.springAop.Demo;
public class Text {
public static void main(String[] args) {
ClassPathXmlApplicationContext app=new ClassPathXmlApplicationContext("applicationContext.xml");
Demo demo=app.getBean("mydemo", Demo.class);
try {
demo.show1();
} catch (Exception e) {
}
}
}
2,AspectJ方式实现异常通知
AspectJ方式实现异常通知只需要写一个类一个方法就可以事项,无需实现或者继承任何接口或者方法。
编写异常通知类:
package com.yjb.mythrowadvice;
public class MyThrowAdvice {
public void myException(Exception ex){
System.out.println("这个是异常通知"+".."+ex.getMessage());
}
}
切点类和上面的一样:
package com.yjb.springAop;
public class Demo {
public void show1()throws Exception{
int i=5/0;
System.out.println("我是一个demo1");
}
}
配置文件写入:
<?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"
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.xsd">
<bean id="demo" class="com.yjb.springAop.Demo"></bean>
<bean id="myThrowAdvice" class="com.yjb.mythrowadvice.MyThrowAdvice"></bean>
<aop:config>
<aop:aspect ref="myThrowAdvice">
<aop:pointcut expression="execution(* com.yjb.springAop.Demo.show1())" id="myponit"/>
<aop:after-throwing method="myException" pointcut-ref="myponit" throwing="ex"/>
</aop:aspect>
</aop:config>
</beans>
测试类以及运行结果:
public class Text {
public static void main(String[] args) {
// TODO Auto-generated method stub
ClassPathXmlApplicationContext app=new ClassPathXmlApplicationContext("applicationContext.xml");
Demo demo=app.getBean("demo", Demo.class);
try {
demo.show1();
} catch (Exception e) {
// TODO Auto-generated catch block
}
}
}
四月 08, 2020 2:34:07 上午 org.springframework.context.support.ClassPathXmlApplicationContext prepareRefresh
信息: Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@218fcdce: startup date [Wed Apr 08 02:34:07 CST 2020]; root of context hierarchy
四月 08, 2020 2:34:07 上午 org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
信息: Loading XML bean definitions from class path resource [applicationContext.xml]
这个是AspectJ异常通知../ by zero
3:结果分析
通过schema-base方式和AspectJ方式实现异常通知,实现的原理基本相似,但是实际对异常的操作中有着明显的区别,具体体现为一句话:chema-base方式对配置文件要求少,对异常通知代码要求高;AspectJ方式对异常代码要求少,对配置文件要求高。主要是体现在对异常抛出的操作上,如果AspectJ方式想要实现对异常的获取例如代码中的打印异常信息时,需要将异常参数ex(参考代码)配置到配置文件中的属性throwing中,才可实现对异常信息的打印。而chema-base方式不需要这么做,但是需要对异常通知类有一定的要求,需要实现ThrowsAdvice接口并按照要求写方法afterThrowing(这个方法非接口的方法但是必须这么写,spring管理器有专门的类来管理这个方法)。
第三:环绕通知(schema-base方式和AspectJ方式)
环绕通知是指在在一个类中引入一个切点,在这个切点中前面实现什么,后面执行什么,解析图如下所示(学过JDK代理好理解,和代理差不多原理,就是把前置通知和后置通知写到一个通知类中)。
1,schema-base方式实现环绕通知(特别要注意两种方式的区分,前面有讲过)
创建一个环绕通知类MyArround,实现接口MethodInterceptor,重写该接口的方法,代码如下:
package com.yjb.arround;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
public class MyArround implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation arg0) throws Throwable {
//前置通知
System.out.println("环绕前置");
Object result=arg0.proceed();//放行,执行切点类方法
//后置通知
System.out.println("环绕后置");
return result;
}
}
配置spring文件:
<?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"
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.xsd">
<bean id="demo" class="com.yjb.springAop.Demo"></bean>
<bean id="arround" class="com.yjb.arround.MyArround"></bean>
<!--环绕通知 -->
<aop:config>
<aop:pointcut expression="execution(* com.yjb.springAop.Demo.show1())" id="myponit"/>
<aop:advisor advice-ref="arround" pointcut-ref="myponit"/>
</aop:config>
</beans>
切点类不变,测试类也不变运行结果为:
2,AspectJ方式实现环绕通知
环绕通知类,需要一个参数
package com.yjb.aspectjArround;
import org.aspectj.lang.ProceedingJoinPoint;
public class AspectjArround {
public Object arround(ProceedingJoinPoint p) throws Throwable{
System.out.println("AspectJ环绕前置");
Object result=p.proceed();
System.out.println("AspectJ环绕后置");
return result;
}
}
spring配置文件:
<?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"
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.xsd">
<bean id="demo" class="com.yjb.springAop.Demo"></bean>
<!-- 环绕通知类 -->
<bean id="arround" class="com.yjb.aspectjArround.AspectjArround"></bean>
<aop:aspect ref="arround"> <!-- 引入环绕通知类 -->
<aop:pointcut expression="execution(* com.yjb.springAop.Demo.show1())" id="myponit"/>
<!-- 环绕通知方法 -->
<aop:around method="arround" pointcut-ref="myponit"/>
</aop:aspect>
</aop:config>
</beans>
测试就不测了
第四:schema-base方式和AspectJ方式的区分和总结
做到这里,已经对两种方式有了具体的了解了,在进一步总结这两种方式。schema-base方式配置通知对通知类有要求,都会要求实现接口重写方法或者按照固定要求写方法,配置spring配置文件只需要在 <aop:config> </aop:config>标签配置切点和通知就可以了;AspectJ方式对通知类没有什么要求,只要写出满足我们需求的通知类就可以了,但是在spring配置文件中需要在<aop:config> </aop:config>标签的子标签<aop:aspect ref="">中配置切点和通知。
总结就八个字:schema-base:类高配低;AspectJ:类低配高
PS:特别注意,切点只抛出异常不处理异常,目的是为了让异常通知捕捉到异常。
第五:关于切点参数以及通知获取切点参数
1,AspectJ方式获取切点参数问题
创建如下带参切点
public class DemoClass {
public void show(String name,int age){
System.out.println(name+"_____"+age);
}
}
在spring配置文件中的配置形式:
<aop:pointcut expression="execution(* com.yjb.demo.DemoClass.show(String,int)) and args(name,age)" id="myYPointctu"/>
讲解:配置文件的方法参数可以使用通配符“..”标识匹配任意参数,如果需要通知中获取切点参数,就必须在配置文件中明确指定参数的类型以及参数名称,这里的参数名称为自定义,与切点参数名称无关系。
编写通知类代码如下:
public class GetArgs {
public void beforeShow(String name,int age){
System.out.println("前置:"+name+"_____"+age);
}
}
在spring配置文件中配置通知:
<aop:before method="beforeShow" pointcut-ref="myYPointctu" arg-names="name,age"/>
在通知配置中,要明确传递过来的参数名arg-names的属性和切点配置的参数名对应。即 and args(name,age)中的参数名和arg-names="name,age"的参数名对应,不仅仅如此,通知类中的参数名也不行要和and args(name,age)中定义的参数名对应。
2.关于expression中星号和..的使用
星号“*”表示匹配任意类任意方法任意一级包名。而“..”表示匹配任意参数。如下代码所示:
<aop:pointcut expression="execution(* com.*.demo.*.*(..))" />
表示匹配com包下任意包下demo包下任意类任意方法任意参数。
作为学完spring做的总结,尽量详细简单且全面,同时如有错误,希望指出,谢谢!