Spring AOP基于XML配置实现4种通知

前言:

AOP:面向方面编程

● 理解AOP的编程思想及 原理(略)
● 掌握AOP的实现技术

AOP编程思想:

Spring框架的AOP机制:简单的解释是可以让开发者把业务流程中的通用功能抽取出来,单独编写功能代码。在业务流程执行过程中,Spring框架会根据业务流程要求,自动把独立编写的功能代码切入到流程的合适位置。

由此AOP的好处:
-每个事物逻辑位于一个位置,代码不分散,便于维护和升级。
-业务模板更简洁,只包含核心代码。

例如,在一个业务系统中,用户登录是基础功能,凡是涉及到用户的业务流程都要求用户进行系统登录。如果把用户登录功能代码写入到每个业务流程中,会造成代码冗余,维护也非常麻烦,当需要修改用户登录功能时,就需要修改每个业务流程的用户登录代码,这种处理方式显然是不可取的。比较好的做法是把用户登录功能抽取出来,形成独立的模块,当业务流程需要用户登录时,系统自动把登录功能切入到业务流程中。下图是用户登录功能切入到业务流程示意图。

正文:

AOP实现技术---切面必须完成的工作(即称为:通知)

引包:
● aopalliance-1.0.jar(底层用的是aspectj的jar包)
● aspectjweaver-1.5.3.jar (aspectj是aop框架)

1)简单演示前置通知类:

任务:
每当之前addStudent()之前 自动执行前置通知logBefore();
addStudent();  业务方法(org.lanqiao.service.impl.StudentServiceImpl)
logBefore();  自动执行的aop前置通知(org.lanqiao.aop.LogBefore)

编写业务类 和 通知类:

org.lanqiao.dao.impl.StudentDaoImpl类(Dao层是辅助演示可忽略):

public class StudentDaoImpl  implements IStudentDao{
	public void addStudent(Student student) {
		System.out.println("增加学生...");
	}
}

org.lanqiao.service.impl.StudentServiceImpl类(业务类):

public class StudentServiceImpl  implements IStudentService{
	IStudentDao studentDao ;//自行配置Dao层	
	
	public void setStudentDao(IStudentDao studentDao) {
		this.studentDao = studentDao;
	}
	
	@Override
	public void addStudent(Student student) {
		studentDao.addStudent(student);
		System.out.println("=====进行增加操作========");
	}
}

 org.lanqiao.aop.LogBefore类(通知类):

//普通类 变 前置通知实现此接口
public class LogBefore implements MethodBeforeAdvice {
	//前置通知的具体类容
	@Override
	public void before(Method method, Object[] args, Object target) throws Throwable {
		System.out.println("前置通知");
	}
	
}

 基于XML的配置

<!-- addStudent()方法 的依赖类 -->
<bean id="studentDao" class="org.lanqiao.dao.impl.StudentDaoImpl">
</bean>


<!-- addStudent()所在方法 -->
 <bean id="studentService" class="org.lanqiao.service.impl.StudentServiceImpl">
 	<property name="studentDao" ref="studentDao"></property>
 </bean>


<!--  前置通知类 -->
<bean id="logBefore" class="org.lanqiao.aop.LogBefore">
 </bean>


<!--  将addStudent()方法与前置通知类关联 -->
<aop:config>
<!-- 配置切入点(在哪里) -->
	<aop:pointcut expression="execution(execution(public void org.lanqiao.service.impl.StudentServiceImpl.addStudent(org.lanqiao.entity.Student))" id="poioncut"/>
<!-- 左:配置切面(各种通知) 和 右:切点与切面建立联系   -->
	<aop:advisor advice-ref="logBefore" pointcut-ref="poioncut"  />
</aop:config>

拓展1:配置切入点时的语法,表达式expression的常见示例如表所示:
expression="execution(…)" 

举例

含义

public boolean addStudent(org.lanqiao.entity.Student))

所有返回类型为boolean、参数类型为org.lanqiao.entity.Student的addStudent()方法。

public boolean org.lanqiao.service.IStudentService.

addStudent(org.lanqiao.entity.Student)

org.lanqiao.service.IStudentService类(或接口)中的addStudent()方法,并且返回类型是boolean、参数类型是org.lanqiao.entity.Student

public * addStudent(org.lanqiao.entity.Student)

“*”代表任意返回类型

public void *( org.lanqiao.entity.Student)

“*”代表任意方法名

public void addStudent(..)

“..”代表任意参数列表

* org.lanqiao.service.*.*(..)

org.lanqiao.service.IStudentService包中,包含的所有方法(不包含子包中的方法)

* org.lanqiao.service..*.*(..)

org.lanqiao.service.IStudentService包中,包含的所有方法(包含子包中的方法)

测试:

ApplicationContext conext = new ClassPathXmlApplicationContext("applicationContext1.xml") ;
IStudentService studentService = (IStudentService)conext.getBean("studentService") ;
Student student=new Student();//自行创建该javabean类
studentService.addStudent(student);

打印:
前置通知
增加学生...
=====进行增加操作========


2)简单演示 前置通知类+后置通知类+业务类的多个切入点+execution表达式详解:

任务:
在准备执行addStudent(Student student),之前执行前置通知类LogBefore(),之后执行后置通知类LogAfter(),然后在准备执行 deleteStudentByNo(int stuNo),前执行前置通知类LogBefore(),执行完后执行后置通知类LogAfter()。

与上面示例基础之上的变化:
org.lanqiao.service.impl.StudentServiceImpl类(业务类):
注:此类在上个演示基础之上增加了deleteStudentByNo(int stuNo)方法,用于演示业务类的多个切入点
切入点为:addStudent(Student student)和 deleteStudentByNo(int stuNo) 

public class StudentServiceImpl  implements IStudentService{
	IStudentDao studentDao ;//自行配置Dao层	
	
	public void setStudentDao(IStudentDao studentDao) {
		this.studentDao = studentDao;
	}
	
	@Override
	public void addStudent(Student student) {
		studentDao.addStudent(student);
		System.out.println("=====进行增加操作========");
	}
	  @Override
	  public void deleteStudentByNo(int stuNo) {
	  System.out.println("模拟删除...."); }

}

(新增)org.lanqiao.aop.LogAfter类(后置通知类): 

public class LogAfter implements AfterReturningAdvice{
	//target目标对象:哪个切入点(service内的方法)产生的此通知
	@Override
	public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
		System.out.println("**********后置通知:目标对象:"+target+",调用的方法名:"+method.getName()+",方法的参数个数:"+args.length+",方法的返回值:"+returnValue);
	}

}

基于xml配置:
注:此配置在上个演示基础之上增加了 前置通知的deleteStudentByNo()也一同作为切点后置通知且内部execution表达式的新语法。 

<!-- addStudent()方法 的依赖类 -->
<bean id="studentDao" class="org.lanqiao.dao.impl.StudentDaoImpl">
</bean>
<!-- addStudent()所在方法 -->
 <bean id="studentService" class="org.lanqiao.service.impl.StudentServiceImpl">
 	<property name="studentDao" ref="studentDao"></property>
 </bean>

<!--  前置通知类 -->
<bean id="logBefore" class="org.lanqiao.aop.LogBefore">
 </bean>
<aop:config>
	<aop:pointcut expression="execution(execution(public void org.lanqiao.service.impl.StudentServiceImpl.deleteStudentByNo(int))  or execution(public void org.lanqiao.service.impl.StudentServiceImpl.addStudent(org.lanqiao.entity.Student)))" id="poioncut"/>
	<aop:advisor advice-ref="logBefore" pointcut-ref="poioncut"  />
</aop:config>


<!--  后置通知-->
<bean id="logAfter" class="org.lanqiao.aop.LogAfter">
 </bean>
<aop:config>
	<!-- 配置切入点(在哪里) -->     <!--  *代表任意返回值类型 -->       <!--  *(..)表示:任意方法名,参数为任意类型的方法 -->
	<aop:pointcut expression="execution(public * org.lanqiao.service.impl.StudentServiceImpl.*(..))  " id="poioncut2"/>
	<!-- 左:配置切面(各种通知) 和 右:切点与切面建立联系   -->
	<aop:advisor advice-ref="logAfter" pointcut-ref="poioncut2"  />
</aop:config>

测试:

ApplicationContext conext = new ClassPathXmlApplicationContext("applicationContext1.xml") ;
IStudentService studentService = (IStudentService)conext.getBean("studentService") ;
Student student=new Student();
studentService.addStudent(student);
studentService.deleteStudentByNo(1);

//打印:
前置通知
增加学生...
=====进行增加操作========
**********后置通知:目标对象:org.lanqiao.service.impl.StudentServiceImpl@441772e,调用的方法名:addStudent,方法的参数个数:1,方法的返回值:null
前置通知
模拟删除....
**********后置通知:目标对象:org.lanqiao.service.impl.StudentServiceImpl@441772e,调用的方法名:deleteStudentByNo,方法的参数个数:1,方法的返回值:null

小结:execution表达式:

execution( [修饰符] 返回值类型 包名.类名.方法名 (参数) )

参数:
()匹配一个无参方法

(..)参数列表可以使用..表示有无参数均可,有参数可以是任意类型

(*)参数列表可以使用*,表示参数可以是任意数据类型,但是必须有参数

(*,Integer)匹配一个接受两个参数的方法,第一个可以为任意类型,第二个必须为Integer。

符号作用:
*:代表一个任意类型的参数;
..:代表零个或多个任意类型的参数。

execution 表达式例子:
1、匹配指定包下所有类方法 :execution(* com.baidu.dao.*(..)) 不包含子包
2.   匹配指定包以及及指定包下面的子包所有类 :  execution(* com.baidu.dao..*(..))  ..*表示当前包、子孙包下所有类
3、匹配指定类所有方法 ;  execution(* com.baidu.service.UserService.*(..))
4、匹配实现特定接口所有类方法 : execution(* com.baidu.dao.GenericDAO+.*(..))
5、匹配所有save开头的方法 :  execution(* save*(..))

3)异常通知(略)

在方法执行时出现异常时该发生,需实现ThrowsAdvice接口
且必须实现此方法,不是重写!!!(是一种规定,必须实现此方法)

新增:异常通知类

public void afterThrowing(Method method, Object[] args ,Object target, NullPointerException ex)//只捕获NullPointerException类型的异常
{
	System.out.println("00000000000异常通知:目标对象:"+target+",方法名:"+method.getName()+",方法的参数个数:"+args.length+",异常类型:"+ex.getMessage());
}

​​​​​4)环绕通知

 前置通知-->环绕通知---> 业务类 --->  环绕通知-->后置通知 (注:业务类可能之后是 异常通知(略))

业务类、部分通知类、xml配置,不再重复演示(方法已经从上述例子说明到位了)

新增:环绕通知类

public class LogAround  implements MethodInterceptor{

	@Override
	public Object invoke(MethodInvocation invocation) throws Throwable {
		Object result  = null ;
		//方法体1...
		try {
			//方法体2...
			System.out.println("用环绕通知实现的[前置通知]...");
			
			//invocation.proceed() 之前的代码:前置通知
			
			
			result  = invocation.proceed() ;//控制着目标方法的执行  ,addStudent()。 如果注释点则不会执行此方法,但是环绕的前置 和后置 依然会执行!
			//result 就是目标方法addStudent()方法的返回值,通过最后的return传递给后置通知的参数
			
			 
			 //invocation.proceed() 之后的代码:后置通知
			System.out.println("用环绕通知实现的[后置通知]...:");
			System.out.println("-----------------目标对象target"+invocation.getThis()+",调用的方法名:"+invocation.getMethod().getName()+",方法的参数个数:"+invocation.getArguments().length+",返回值:"+result);
			

			
		}catch(Exception e) {
			//方法体3...
			//异常通知
			System.out.println("用环绕通知实现的[异常通知]...");
		}
		
		return result;//目标方法的返回值
	}

}

测试:

ApplicationContext conext = new ClassPathXmlApplicationContext("applicationContext1.xml") ;
IStudentService studentService = (IStudentService)conext.getBean("studentService") ;
Student student=new Student();
studentService.addStudent(student);
studentService.deleteStudentByNo(1);

测试结果:

前置通知
用环绕通知实现的[前置通知]...
增加学生...
=====进行增加操作========
用环绕通知实现的[后置通知]...:
-----------------目标对象targetorg.lanqiao.service.impl.StudentServiceImpl@441772e,调用的方法名:addStudent,方法的参数个数:1,返回值:null
**********后置通知:目标对象:org.lanqiao.service.impl.StudentServiceImpl@441772e,调用的方法名:addStudent,方法的参数个数:1,方法的返回值:null
前置通知
用环绕通知实现的[前置通知]...
模拟删除....
用环绕通知实现的[后置通知]...:
-----------------目标对象targetorg.lanqiao.service.impl.StudentServiceImpl@441772e,调用的方法名:deleteStudentByNo,方法的参数个数:1,返回值:null
**********后置通知:目标对象:org.lanqiao.service.impl.StudentServiceImpl@441772e,调用的方法名:deleteStudentByNo,方法的参数个数:1,方法的返回值:null

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值