Spring-AOP
Posted 再来半包
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring-AOP相关的知识,希望对你有一定的参考价值。
AOP
AOP面向切面编程
动态代理
实现方式:jdk动态代理,使用jdk中的Proxy,Method ,InvocationHanderl创建代理对象
jdk动态代理要求目标类必须实现接口
cglib动态代理:第三方的工具库,创建代理对象,原理是继承,通过继承目标类,创建子类。
子类就是代理对象。要求目标类不能时final的,方法也不能是final的
动态代理的作用
1)在目标类源代码不改变的情况下,增加功能。
2)减少代码的重复
3)专注业务逻辑代码
4)解耦合,让你的业务功能和日志,事务非业务功能分离。
实例
接口类
public interface SomeService {
void doSome();
void doOther();
}
实现类
public class SomeServiceImpl implements SomeService {
@Override
public void doSome() {
System.out.println("执行业务方法doSome");
}
@Override
public void doOther() {
System.out.println("执行业务方法doOther");
}
}
service类的代码不修改,也能够增加 输出时间,事务
serviceTools类
public class serviceTools {
public static void doLog(){
System.out.println("非业务方法,方法的执行时间"+ new Date());
}
public static void doTrans(){
System.out.println("非业务方法,方法执行完毕后,提交事务");
}
}
MyIncationHandler类
public class MyIncationHandler implements InvocationHandler {
private Object target; //SomeServiceImpl类
//目标对象
public MyIncationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//通过代理对象执行方法时,会调用执行这个invoke();
Object res=null;
serviceTools.doLog();//工具类中的时间方法
//执行目标类的方法,通过Method类实现
res= method.invoke(target, args);//SomeServiceImpl.doOther(),doSome()
serviceTools.doTrans();//工具类中的事务方法
return res;
}
}
通过构造器将要代理的对象传进来
method是要代理的方法
测试类
public class MyApp {
public static void main(String[] args) {
//使用jdk的proxy创建代理对象
//创建目标对象
SomeService target =new SomeServiceImpl();
//创建InvocationHandler对象
InvocationHandler handler=new MyIncationHandler(target);
//创建SomeService接口的所有实现类
Class[] classes={SomeService.class};
//创建代理对象
SomeService o = (SomeService) Proxy.newProxyInstance(target.getClass().getClassLoader(), classes, handler);
//调用方法
o.doOther();
o.doSome();
}
}
Proxy.newProxyInstance方法:
第一个参数是目标对象的Loader
第二个参数是目标接口的所有实现类
第三个对象是代理类
扩展
想要doOther方法有时间和事务,而doSOme方法没有
需要修改代理类的invoke方法
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//通过代理对象执行方法时,会调用执行这个invoke();
Object res=null;
if (method.getName().equals("doOther")){
serviceTools.doLog();
//执行目标类的方法,通过Method类实现
res= method.invoke(target, args);//SomeServiceImpl.doOther(),doSome()
serviceTools.doTrans();
}else {
res=method.invoke(target,args);
}
return res;
}
因为method是执行的方法,想要只有doOther方法有时间和事务,则需method.getName().equals(“doOther”)进行一个判断即可
什么是AOP
AOP简介
AOP(Aspect Orient Programming),面向切面编程,面向切面编程时从动态角度考虑程序运行过程。
AOP底层,就是采用动态代理模式实现的,采用了两种代理:JDK的动态代理,与CGLIB的动态代理
AOP就是动态代理的规范化,把动态代理的实现步骤,方式都定义好了,让开发人员用一种统一的方式,使用动态代理
AOP(Aspect Orient Programming),面向切面编程
Aspect:切面,给你的目标类增加的功能,就是切面,像上面用的时间,事务就是切面
切面的特点:一般都是非业务方法,独立使用的。
Orient:面向,对这。
Programming:编程
OOP:面向对象编程
怎么理解面向切面编程
1)需要在分析项目功能时,找出切面
2)合理的安排切面的执行时间(在目标方法前,还是目标方法后)
3)合理的安排切面执行的位置,在哪个类,哪个方法增加增强功能
术语:
1)Aspect:切面,表示增强的功能,就是一推代码,完成某个一个功能。非业务功能。
常见的切面功能有日志,事务,统计信息,参数检查,权限验证。
2)JoinPoint:连接点,连接业务方法和切面的位置。就某类中的业务方法
3)Pointcut:切入点,指多个连接点方法的集合。
4)目标对象:给哪个类的方法增加功能,这个类就是目标对象
5)Advice:通知,通知表示切面功能执行的时间。
说一个切面有三个关键的要素:
1)切面的功能代码,切面干什么
2)切面的执行位置,使用Pointcut表示切面执行的位置
3)切面的执行时间,使用Advice表示时间,在目标方法之前,还是目标方法之后。
AOP的实现
aop是一个规范,时动态代理的一个规范化,一个标准
aop的技术实现框架:
1、Spring:Spring在内部实现了aop规范,能做aop的工作
SPring主要在事务处理时使用aop。
我们项目开发中很少使用spring的aop实现,因为spring的aop比较笨重
2、aspectJ:一个开源的专门做aop的框架。spring框架中继承了aspectj框架,通过spring就能使用aspectj的功能。
aspectj框架实现aop有两种方式:
1、使用xml的配置文件
2、使用注解,我们在项目中要做zop功能,一般都是用注解,aspectj有5个注解。
aspectj框架
切面的执行时间
1)切面的执行时间,这个执行时间在规范中叫做advice(通知,增强)
在aspectj框架中使用注解表示的。也可以使用xml配置文件中的标签
1)@Before
2)@AfterReturning
3)@Around
4)@AfterThrowing
5)@After
2)表示切面执行的位置,使用的是切入点表达式
AspectJ的切入点表达式
Aspectj定义了专门的表达式用于指定切入点。表达式的原型是:
execution(modifiers-pattern? ret-type-pattern
declaring-type-pattern?name-pattern(param-pattern)
throws-pattern?)
解释:
modifiers-pattern:访问权限类型 例如:public
ret-type-pattern:返回值类型 例如:void
declaring-type-pattern:包名类名 可以没有
name-pattern(param-pattern):方法名(参数类型合参数个数)
throws-pattern:抛出异常类型
?表示可选的部分
以上表达式共4个部分。
execution(访问权限 方法返回值 方法声明(参数) 异常类型)
pattern的地方可以使用通配符
符号 | 意义 |
---|---|
* | 0至多个任意字符 |
… | 用在方法参数中,表示任意多个参数 用在包名后,表示当前包及其子包路径 |
+ | 用在类名后,表示当前类及其子类 用在接口后,表示当前接口及其实现类 |
举例:
execution(public * *(..))
指定切入点为:任意公共方法。
execution(* set*(..))
指定切入点为:任何一个以“set”开始的方法。
execution(* com.xyz.service.*.*(..))
指定切入点为:定义在service包里的任意类的任意方法
execution(* com.xyz.service..*.*(..))
指定切入点为:定义在service包或者子包里的任意类的任意方法。”…“ 出现在类名中时,后面必须跟”*“ , 表示包、子包下的所有类。
execution(* *..service.*.*(..))
指定所有包下的service子包所有类(接口)中所有方法为切入点
使用aspectj实现aop的基本步骤
1、新建maven项目
2、加入依赖
1)spring依赖
2)aspectj依赖
3)junit单元测试
3、创建目标类:接口和他的实现类。
要做的是给类中的方法增加功能
4、创建切面类:普通类
1)在类的上面加入@Aspect
2)在类中定义方法,方法就是切面要执行的功能代码
在方法的上面加入aspectj中的通知注解,例如@Before
有需要指定切入点表达式execution()
5、创建spring的配置文件:声明对象,把对象交给容器统一管理
声明对象你可以使用注解或者xml配置文件
1)声明目标对象
2)声明切面类对象
3)声明aspectj框架中的自动代理生成器标签。
自动代理生成器:用来完成代理对象的自动创建功能的
6、创建测试类,从spring容器中获取目标对象(实际就是代理对象)。
通过代理执行方法,实现aop的功能增强
实例
给doSOme方法增加一个功能,在doSome()执行之前,输出方法的执行时间
接口类
public interface SomeService {
void doSome(String name,Integer age);
}
实现类
public class SomeServiceImpl implements SomeService {
@Override
public void doSome(String name, Integer age) {
System.out.println("目标方法doSOme()");
}
}
@Aspect
public class MyAspect {
@Before(value = "execution(public void com.hr.qjb.ba01.SomeServiceImpl.doSome(String,Integer))")//参数为切入点表达式
public void myBefore(){
//就是你切面要执行的功能代码
System.out.println("前置通知,切面功能:在目标方法之前输出执行时间"+new Date());
}
}
Aspect
Aspect:是aspectj框架中的注解。
作用:表示当前类是切面类
切面类:是用来给业务方法增加功能的类,在这个类中有切面的功能代码
位置:在类定义的上面
定义方法
定义方法,方法是实现切面功能的。
方法的定义要求:
1、公共方法public
2、方法没有返回值
3、方法名称自定义
4、方法可以有参数,也可以没有参数
如果有参数,参数不是自定义的,有几个参数类型可以使用。
@Before:表示前置通知注解
属性:value,是切入点表达式,表示切面的功能执行的位置
位置:在方法的上面
特点:1、在目标方法之前先执行的
2、不会改变目标方法的执行结果
3、不会影响目标方法的执行。
配置文件
<?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 https://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 把对象交给spring容器,由spring容器同一创建,管理对象-->
<!-- 声明目标对象-->
<bean id="someService" class="com.hr.qjb.ba01.SomeServiceImpl"/>
<!-- 声明切面对象-->
<bean id="myAspect" class="com.hr.qjb.ba01.MyAspect" />
<!-- 声明自动代理生成器:使用aspectj框架内部的功能,创建目标对象的代理对象。
创建代理对象是在内存中实现的,修改目标对象的内存中的结构。创建为代理对象
所以目标对象就是被修改后的代理对象
aspectj-autoproxy:会把spring容器中的所有目标对象,一次性都生成代理对象
-->
<aop:aspectj-autoproxy/>
</beans>
首先测试类会去先去解析xml配置文件,首先找<bean id="someService" class="com.hr.qjb.ba01.SomeServiceImpl"/>
这段代码,因为里面只是一个实现类,一个doSome方法,在去找<bean id="myAspect" class="com.hr.qjb.ba01.MyAspect" />
这段代码,找到以后因为MyAspect是被@Aspect注解的,所以被认为是切面类,后来在往下执行发现@Before(value = "execution(public void com.hr.qjb.ba01.SomeServiceImpl.doSome(String,Integer))")
这个注解,参数指定的是目标对象,就是SomeServiceImpl这个类中的doSome方法,就去会实现代理,在这个方法的前面执行切面要执行的功能代码
切入表达式的多种写法
最为准确的写法:准确锁定到SomeServiceImpl类中的doSome方法
@Before(value = "execution(public void com.hr.qjb.ba01.SomeServiceImpl.doSome(String,Integer))")
public void myBefore(){
//就是你切面要执行的功能代码
System.out.println("前置通知,切面功能:在目标方法之前输出执行时间"+new Date());
}
省略访问修饰符写法:将public 省略
@Before(value = "execution( void com.hr.qjb.ba01.SomeServiceImpl.doSome(String,Integer))")
public void myBefore(){
//就是你切面要执行的功能代码
System.out.println("1====前置通知,切面功能:在目标方法之前输出执行时间"+new Date());
}
省略访问修饰符并利用通配符是所有包中的SomeServiceImpl类中的doSome的方法
@Before(value = "execution( void *..SomeServiceImpl.doSome(String,Integer))")
public void myBefore(){
//就是你切面要执行的功能代码
System.out.println("2====前置通知,切面功能:在目标方法之前输出执行时间"+new Date());
}
所有返回值类型以及利用通配符是所有包中的SomeServiceImpl类中的doSome的方法
@Before(value = "execution( * *..SomeServiceImpl.doSome(..))")
public void myBefore(){
//就是你切面要执行的功能代码
System.out.println("3====前置通知,切面功能:在目标方法之前输出执行时间"+new Date());
}
还可以定义多个前置通知
@Before(value = "execution( * *..SomeServiceImpl.do*(..))")
public void myBefore(){
//就是你切面要执行的功能代码
System.out.println("4====前置通知,切面功能:在目标方法之前输出执行时间"+new Date());
}
@Before(value = "execution( * *..SomeServiceImpl.do*(..))")
public void myBefore2(){
//就是你切面要执行的功能代码
System.out.println("5====前置通知,切面功能:在目标方法之前输出执行时间"+new Date());
}
JoinPoint
写在切面类中的通知方法的参数里,可以获得业务方法中的参数以及一些方法的信息
getSignature方法:获取方法的签名(定义)
getSignature.getName方法:获取方法的名称
getArgs方法:获取方法中的参数,返回的是一个Object的数组
@AfterReturning
定义方法
后置通知定义方法,方法是实现切面功能的。
方法的定义要求:
1、公共方法public
2、方法没有返回值
3、方法名称自定义
4、方法有参数的
如果有参数,参数不是自定义的,有几个参数类型可以使用。
@AfterReturning:后置通知
属性:1、value 切入点表达式
2、returning 自定义的变量,表示目标方法的返回值的。
例如:
自定义变量名必须和通知方法的形参名一样
位置:在方法定义的上面
@AfterReturning(value = "execution(* *..SomeServiceImpl.doOther2(..))",
returning = "res")
特点:
1、在目标方法之后执行的
2、能够获取目标方法的返回值,可以根据这个返回值做不同的处理功能
3、可以修改这个返回值
环绕通知
环绕通知方法的定义格式
1、public
2、必须有一个返回值,推荐使用Object
3、方法名称自定义
4、方法有参数,固定的参数ProceedingJoinPoint
例如
public Object myAround(ProceedingJoinPoint pjp){
}
@Around:环绕通知
属性:value:切入点表达式
位置:在方法的定义上面
例如
@Around(value = "execution(* *..SomeServiceImpl.doFirst(..))")
特点:
1、它是功能最强的通知
2、在目标方法的前和后都能增强功能
3、控制目标方法是否被调用执行
4、修改原来的目标方法的执行结果。影响最后的调用结果
环绕通知,等同于jdk动态代理的,InvocationHandler接口
参数:ProceedingJoinPoint 就等同于 method
作用:执行目标方法的
返回值:就是目标方法的执行结果,可以被修改。
实例
接口类方法
@Override
public String doFirst(String name, Integer age) {
System.out.println("业务方法doFirst()");
return "doFirst";
}
切面类
@Around(value = "execution(* *..SomeServiceImpl.doFirst(..))")
public Object myAround(ProceedingJoinPoint pjp) throws Throwable {
//实现环绕通知
Object result=null;
//1、目标方法调用
System.out.println("环绕通知"+new Date());
result = pjp.proceed();//method.invoke(); Objcet result = doFirst();
System.out.println("环绕通知,在目标方法之后,提交事务");
//2、在目标方法的前或者后加入功能
//返回目标方法的执行结果
return result;
}
测试类
@Test
public void ttest01(){
String config="applicationContext.xml";
ApplicationContext ctx=new ClassPathXmlApplicationContext(config);
//从容器中获取目标对象
SomeService proxy = (SomeService)ctx.getBean("someService");
//通过代理的对象执行方法,实现目标方法执行时,增强了功能
String s = proxy.doFirst("才", 20);//执行myAround()
System.out.println(s);
}
因为ProceedingJoinPoint继承了JoinPoint,所以可以同ProceedingJoinPoint获得方法中的参数,从参数上进行判断,事务的处理
测试类中的proxy.doFirst方法,也就相当于是去执行了切面类中的环绕方法
环绕通知:经常做事务,在目标方法之前开启事务,执行目标方法,在方法之后提交事务
@AfterThrowing
异常通知方法的定义格式
1、public
2、没有返回值
3、方法名称自定义
4、方法有一个Exception参数,如果还有就是JoinPoint
例如:
public void myAfterThrowing(Exception ex){
}
@AfterThrowing:异常通知
属性:1、value:切入点表达式
2、throwing:自定义变量,表示目标方法抛出的异常对象。
变量名必须和方法的参数名一样
特点:
1、在目标方法抛出异常时执行的
2、可以做异常的监控程序,监控目标方法执行时是不是又异常
如果有异常,可以发送邮件,短信进行通知
try{
SomeServiceImpl.doSecond(..)
}catch(Exception e){
myAfterThrowing(e)
}
如果没有发生异常则正常执行业务方法,如果发生了异常,去执行切面类中的方法
@After
最终通知方法的定义格式
1、public
2、没有返回值
3、方法名称自定义
4、方法没有参数,如果还有时JoinPoint
@After:最终通知
属性:value 切入点表达式
位置:在方法的上面
特点:
1、它总是会执行
2、在目标方法之后执行的。
实例
切面类
@After(value = "execution(* *..SomeServiceImpl.doThird(..))")
public void myAfter(){
System.out.println("执行最终通知,总是会被执行的代码");
//一般做资源清楚工作的
}
@Pointcut
@Pointcut:可以定义和管理切入点,如果你的项目中有多个切入点表达式是重复的,可以复用的。可以使用@Pointcut
属性:value 切入点表达式
位置:在自定义的方法上面
特点:
当我们使用@Pointcut定义在一个方法的上面,此时这个方法的名称就是切入点表达式的别名。
其他的通知中,value属性就可以使用这个方法名称,代替切入点表达式了
@Pointcut(value = "execution(* *..SomeServiceImpl.doThird(..))")
public void mypt(){
//无需代码,
}
测试
@After(value = "mypt()")
public void myAfter(){
System.out.println("执行最终通知,总是会被执行的代码");
//一般做资源清楚工作的
}
会被执行
注意:不要忘记加括号,因为是方法,一般为私有
框架的代理分类
目标类没有接口,使用cglib动态代理,spring框架会自动应用cglib
如果你期望目标类有接口,使用cglib代理
配置文件中的加入
<aop:aspectj-autoproxy proxy-target-class="true"/>
设置proxy-target-class="true"为真,表明要强制使用cglib接口
以上是关于Spring-AOP的主要内容,如果未能解决你的问题,请参考以下文章