Spring系列之AOP实现的两种方式
AOP常用的实现方式有两种,一种是采用声明的方式来实现(基于XML),一种是采用注解的方式来实现(基于AspectJ)。
首先复习下AOP中一些比较重要的概念:
Joinpoint(连接点):程序执行时的某个特定的点,在Spring中就是某一个方法的执行 。
Pointcut(切点):说的通俗点,spring中AOP的切点就是指一些方法的集合,而这些方法是需要被增强、被代理的。一般都是按照一定的约定规则来表示的,如正则表达式等。切点是由一类连接点组成。
Advice(通知):还是说的通俗点,就是在指定切点上要干些什么。
Advisor(通知器):其实就是切点和通知的结合 。
一、基于XML配置的Spring AOP
采用声明的方式实现(在XML文件中配置),大致步骤为:配置文件中配置pointcut, 在java中用编写实际的aspect 类, 针对对切入点进行相关的业务处理。
业务接口:
package com.spring.service; public interface IUserManagerService {
//查找用户
public String findUser(); //添加用户
public void addUser();
}
业务实现:
package com.spring.service.impl; import com.spring.service.IUserManagerService; public class UserManagerServiceImpl implements IUserManagerService{ private String name; public void setName(String name){
this.name=name;
} public String getName(){
return this.name;
} public String findUser(){
System.out.println("============执行业务方法findUser,查找的用户是:"+name+"=============");
return name;
} public void addUser(){
System.out.println("============执行业务方法addUser=============");
//throw new RuntimeException();
}
}
切面类:
package com.spring.aop; import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint; public class AopAspect { /**
* 前置通知:目标方法调用之前执行的代码
* @param jp
*/
public void doBefore(JoinPoint jp){
System.out.println("===========执行前置通知============");
} /**
* 后置返回通知:目标方法正常结束后执行的代码
* 返回通知是可以访问到目标方法的返回值的
* @param jp
* @param result
*/
public void doAfterReturning(JoinPoint jp,String result){
System.out.println("===========执行后置通知============");
System.out.println("返回值result==================="+result);
} /**
* 最终通知:目标方法调用之后执行的代码(无论目标方法是否出现异常均执行)
* 因为方法可能会出现异常,所以不能返回方法的返回值
* @param jp
*/
public void doAfter(JoinPoint jp){
System.out.println("===========执行最终通知============");
} /**
*
* 异常通知:目标方法抛出异常时执行的代码
* 可以访问到异常对象
* @param jp
* @param ex
*/
public void doAfterThrowing(JoinPoint jp,Exception ex){
System.out.println("===========执行异常通知============");
} /**
* 环绕通知:目标方法调用前后执行的代码,可以在方法调用前后完成自定义的行为。
* 包围一个连接点(join point)的通知。它会在切入点方法执行前执行同时方法结束也会执行对应的部分。
* 主要是调用proceed()方法来执行切入点方法,来作为环绕通知前后方法的分水岭。
*
* 环绕通知类似于动态代理的全过程:ProceedingJoinPoint类型的参数可以决定是否执行目标方法。
* 而且环绕通知必须有返回值,返回值即为目标方法的返回值
* @param pjp
* @return
* @throws Throwable
*/
public Object doAround(ProceedingJoinPoint pjp) throws Throwable{
System.out.println("======执行环绕通知开始=========");
// 调用方法的参数
Object[] args = pjp.getArgs();
// 调用的方法名
String method = pjp.getSignature().getName();
// 获取目标对象
Object target = pjp.getTarget();
// 执行完方法的返回值
// 调用proceed()方法,就会触发切入点方法执行
Object result=pjp.proceed();
System.out.println("输出,方法名:" + method + ";目标对象:" + target + ";返回值:" + result);
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"
xmlns:p="http://www.springframework.org/schema/p"
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"> <!-- 声明一个业务类 -->
<bean id="userManager" class="com.spring.service.impl.UserManagerServiceImpl">
<property name="name" value="lixiaoxi"></property>
</bean> <!-- 声明通知类 -->
<bean id="aspectBean" class="com.spring.aop.AopAspect" /> <aop:config>
<aop:aspect ref="aspectBean">
<aop:pointcut id="pointcut" expression="execution(* com.spring.service.impl.UserManagerServiceImpl..*(..))"/> <aop:before method="doBefore" pointcut-ref="pointcut"/>
<aop:after-returning method="doAfterReturning" pointcut-ref="pointcut" returning="result"/>
<aop:after method="doAfter" pointcut-ref="pointcut" />
<aop:around method="doAround" pointcut-ref="pointcut"/>
<aop:after-throwing method="doAfterThrowing" pointcut-ref="pointcut" throwing="ex"/>
</aop:aspect>
</aop:config>
</beans>
测试类:
package com.spring.test; import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext; import com.spring.service.IUserManagerService; public class TestAop { public static void main(String[] args) throws Exception{ ApplicationContext act = new ClassPathXmlApplicationContext("applicationContext3.xml");
IUserManagerService userManager = (IUserManagerService)act.getBean("userManager");
userManager.findUser();
System.out.println("\n");
userManager.addUser();
}
}
测试结果:
二、使用注解配置AOP
采用注解来做aop, 主要是将写在spring 配置文件中的连接点写到注解里面。
业务接口和业务实现与上边一样,具体切面类如下:
package com.spring.aop; import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before; @Aspect
public class AopAspectJ { /**
* 必须为final String类型的,注解里要使用的变量只能是静态常量类型的
*/
public static final String EDP="execution(* com.spring.service.impl.UserManagerServiceImpl..*(..))"; /**
* 切面的前置方法 即方法执行前拦截到的方法
* 在目标方法执行之前的通知
* @param jp
*/
@Before(EDP)
public void doBefore(JoinPoint jp){ System.out.println("=========执行前置通知==========");
} /**
* 在方法正常执行通过之后执行的通知叫做返回通知
* 可以返回到方法的返回值 在注解后加入returning
* @param jp
* @param result
*/
@AfterReturning(value=EDP,returning="result")
public void doAfterReturning(JoinPoint jp,String result){
System.out.println("===========执行后置通知============");
} /**
* 最终通知:目标方法调用之后执行的通知(无论目标方法是否出现异常均执行)
* @param jp
*/
@After(value=EDP)
public void doAfter(JoinPoint jp){
System.out.println("===========执行最终通知============");
} /**
* 环绕通知:目标方法调用前后执行的通知,可以在方法调用前后完成自定义的行为。
* @param pjp
* @return
* @throws Throwable
*/
@Around(EDP)
public Object doAround(ProceedingJoinPoint pjp) throws Throwable{ System.out.println("======执行环绕通知开始=========");
// 调用方法的参数
Object[] args = pjp.getArgs();
// 调用的方法名
String method = pjp.getSignature().getName();
// 获取目标对象
Object target = pjp.getTarget();
// 执行完方法的返回值
// 调用proceed()方法,就会触发切入点方法执行
Object result=pjp.proceed();
System.out.println("输出,方法名:" + method + ";目标对象:" + target + ";返回值:" + result);
System.out.println("======执行环绕通知结束=========");
return result;
} /**
* 在目标方法非正常执行完成, 抛出异常的时候会走此方法
* @param jp
* @param ex
*/
@AfterThrowing(value=EDP,throwing="ex")
public void doAfterThrowing(JoinPoint jp,Exception ex) {
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"
xmlns:p="http://www.springframework.org/schema/p"
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"> <!-- 声明spring对@AspectJ的支持 -->
<aop:aspectj-autoproxy/> <!-- 声明一个业务类 -->
<bean id="userManager" class="com.spring.service.impl.UserManagerServiceImpl">
<property name="name" value="lixiaoxi"></property>
</bean> <!-- 声明通知类 -->
<bean id="aspectBean" class="com.spring.aop.AopAspectJ" /> </beans>
测试类:
package com.spring.test; import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext; import com.spring.service.IUserManagerService; public class TestAop1 {
public static void main(String[] args) throws Exception{ ApplicationContext act = new ClassPathXmlApplicationContext("applicationContext4.xml");
IUserManagerService userManager = (IUserManagerService)act.getBean("userManager");
userManager.findUser();
System.out.println("\n");
userManager.addUser();
}
}
测试结果与上面相同。
注意:
1.环绕方法通知,环绕方法通知要注意必须给出调用之后的返回值,否则被代理的方法会停止调用并返回null,除非你真的打算这么做。
2.只有环绕通知才可以使用JoinPoint的子类ProceedingJoinPoint,各连接点类型可以调用代理的方法,并获取、改变返回值。
补充:
1.<aop:pointcut>如果位于<aop:aspect>元素中,则命名切点只能被当前<aop:aspect>内定义的元素访问到,为了能被整个<aop:config>元素中定义的所有增强访问,则必须在<aop:config>下定义切点。
2.如果在<aop:config>元素下直接定义<aop:pointcut>,必须保证<aop:pointcut>在<aop:aspect>之前定义。<aop:config>下还可以定义<aop:advisor>,三者在<aop:config>中的配置有先后顺序的要求:首先必须是<aop:pointcut>,然后是<aop:advisor>,最后是<aop:aspect>。而在<aop:aspect>中定义的<aop:pointcut>则没有先后顺序的要求,可以在任何位置定义。
.<aop:pointcut>:用来定义切入点,该切入点可以重用;
.<aop:advisor>:用来定义只有一个通知和一个切入点的切面;
.<aop:aspect>:用来定义切面,该切面可以包含多个切入点和通知,而且标签内部的通知和切入点定义是无序的;和advisor的区别就在此,advisor只包含一个通知和一个切入点。
3.在使用spring框架配置AOP的时候,不管是通过XML配置文件还是注解的方式都需要定义pointcut"切入点"
例如定义切入点表达式 execution(* com.sample.service.impl..*.*(..))
execution()是最常用的切点函数,其语法如下所示:
整个表达式可以分为五个部分:
(1)、execution(): 表达式主体。
(2)、第一个*号:表示返回类型,*号表示所有的类型。
(3)、包名:表示需要拦截的包名,后面的两个句点表示当前包和当前包的所有子包,com.sample.service.impl包、子孙包下所有类的方法。
(4)、第二个*号:表示类名,*号表示所有的类。
(5)、*(..):最后这个星号表示方法名,*号表示所有的方法,后面括弧里面表示方法的参数,两个句点表示任何参数。
Spring系列之AOP实现的两种方式的更多相关文章
- Spring加载properties文件的两种方式
在项目中如果有些参数经常需要修改,或者后期可能需要修改,那我们最好把这些参数放到properties文件中,源代码中读取properties里面的配置,这样后期只需要改动properties文件即可, ...
- Spring Boot定义系统启动任务的两种方式
Spring Boot定义系统启动任务的两种方式 概述 如果涉及到系统任务,例如在项目启动阶段要做一些数据初始化操作,这些操作有一个共同的特点,只在项目启动时进行,以后都不再执行,这里,容易想到web ...
- Spring Boot 中实现定时任务的两种方式
在 Spring + SpringMVC 环境中,一般来说,要实现定时任务,我们有两中方案,一种是使用 Spring 自带的定时任务处理器 @Scheduled 注解,另一种就是使用第三方框架 Qua ...
- Spring容器自动调用方法的两种方式
先看一个Spring中Bean的实例化过程: 1.配置文件中指定Bean的init-method参数 <bean class="com.jts.service.UserService& ...
- spring的面向切面实现的两种方式
面向切面:主要应用在日志记录方面.实现业务与日志记录分离开发. spring面向切面有两种实现方式:1.注解 2.xml配置. 1.注解实现如下: (1)配置如下: <?xml version= ...
- 基于aspectj实现AOP操作的两种方式——xml配置
1. 要导入的 jar 包: 常用的aspectj表达式: 权限修饰符可以省略,以下表示:返回值类型为任意,com.chy.service包以及其子包下的.任意类的.参数任意的.任意方法 execut ...
- Spring使用JMS传递消息的两种方式
方式一:同步收发消息,使用JMS template 消费者阻塞等待消息的到来. 方式二:异步收发消息,使用message listener container 消费者提供一个listener,注册一个 ...
- 基于aspectj实现AOP操作的两种方式——注解方式
- spring boot中读取配置文件的两种方式
application.properties test.name=测试 test.url=www.test.com 1.@Value注解 在controller里可以这样直接调用 @Value(&qu ...
随机推荐
- [有意思]The IT workers of Star Wars -- That's not a bug. It's a feature
Yeah, that Artoo is kinda mouthy... ... now select, "restore to factory settings." That'll ...
- linux下使用tar命令
解压语法:tar [主选项+辅选项] 文件或者目录 使用该命令时,主选项是必须要有的,它告诉tar要做什么事情,辅选项是辅助使用的,可以选用. 主选项: c 创建新的档案文件.如果用户想备份一个目录或 ...
- 在C#中该如何阻止虚方法的覆写
在开发过程中,我们为了让一个类更有生命力,有时会用virtual来修饰一个方法好让子类来覆写它.但是如果有更新的子子类来覆写时,我们又不想让其影响到上一层的覆写,这时候就要用到new virtual来 ...
- Qt 之 饼图
Qt 库由许多模块组成,其中的 Qt Charts,包含了一系列图表组件. 1 饼图 (pie chart) 1.1 Charts 模块 .pro 中添加如下语句: QT += charts 头文 ...
- 如何用ZBrush快速绘制身体
Fisker老师用了5节课详细讲解了僵尸的头部制作过程,用了大量时间完善细节部分,在ZBrush3D图形绘制软件中雕刻模型就是这样,需要反复调整与修改,每一个细节都做到极致才是最理想的状态.头部雕刻好 ...
- SPOJ GSS3 Can you answer these queries III[线段树]
SPOJ - GSS3 Can you answer these queries III Description You are given a sequence A of N (N <= 50 ...
- jdbc java数据库连接 5)CallableStatement 接口
CallableStatement执行存储过程(也是预编译语言) 首先在sql中执行以下带有输入参数的代码: DELIMITER $ CREATE PROCEDURE pro_findById(IN ...
- 关于ES6新增的东西(二)
六.原生Promise 就是一个对象,用来传递异步操作的数据(消息) pending(等待.处理中)-> Resolve(完成.fullFilled) -> Rejected(拒绝.失败) ...
- ThinkPHP常用查询
1.常规 $map2['state'] = 1; $User->where ( $map2 )->find(); 2. OR 查询 $where['name'] = array('neq' ...
- C#.NET 大型通用信息化系统集成快速开发平台 4.1 版本 - 角色成员功能的改进支持公司加入到角色
我们公司有1万多个网点,每个网点都可以看成是一个公司,公司对不同的网点有不同的策略,商业逻辑,每个网点的人员也都是在不断变化,全国有接近10万从业人员,当我们设计好业务逻辑程序后,不可能因为这些人员的 ...