Spring之AOP

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做的总结,尽量详细简单且全面,同时如有错误,希望指出,谢谢!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值