文章目录
认识AOP
AOP是基于动态代理的,底层实现就是动态代理。 框架把动态代理的实现封装了,就是aop。可以是jdk, cglib的动态代理
AOP的实现框架:
1.AspectJ
2.Spring
动态代理
jvm创建了一个类,并创建这个类的对象。 这个类就动态代理。
动态代理的实现方式(jdk动态代理、cglib):
- ** jdk动态代理**:使用java中反射包中的类和方法,创建代理的方式。
使用的类和接口:
1. )InvocationHandler接口:调用处理器,代理要做什么,写在接口的invoke()方法。
invoke()方法中主要做两件事情:1.执行目标类自己的方法 。2.增强功能。
2. )Method类:表示执行的目标方法,对这个方法的功能做代理。
执行目标方法使用的 method.invoke()
3. )Proxy类:使用Proxy类的静态方法 Proxy.newProxyInstance()创建代理对象。
jdk动态代理的使用要求是:目标类必须实现接口。 - ** cglib**:第三方工具库,实现动态代理的功能,实现原理是:继承。 子类就是代理cglib动态代理的使用要求:目标类不能是final,方法也不能是final的
初入AOP
概念
AOP:就是基于动态代理的,是对动态代理的总结和升华。AOP相当于是规范。定义了使用动态代理的步骤和方式。
AOP(Aspect Orient Programming),面向切面编程, 它是从动态的角度设计程序的。
Aspect:切面,就是指非业务功能, 这些功能需要在程序执行时增加给业务方法的。
常见的切面是日志,事务,权限检查等。
Orient:面向,对着。
Programming:编程
怎么理解面向切面编程?
1.以切面为编程的核心, 设计项目中需要找出可以作为切面功能把切面作为独立的模块设计,使用。
2.考虑切面的执行位置,执行时间
AOP中的三个要素
1.切面:表示给业务方法增加的功能,例如日志功能, 事务功能
2.位置:切面在哪个方法上执行
3.时间:切面在方法之前执行, 还是方法之后执行等等
AOP的作用:
1.减少重复代码
2.专注业务功能
3.实现业务功能和非业务功能的解耦合。
AOP编程术语
切面(Aspect
)
一般是指业务逻辑,常用的切面是通知(Advice)。目的就是对主业务逻辑的增强
连接点(JoinPoint
)
连接点指可以被切面织入的具体方法。通常业务接口中的方法均为连接点。
切入点(Pointcut
)
定义了切入的位置。声明了一个或多个连接点的集合
目标对象(Target
)
指要被增强的对象。要被增强的类是目标类,它的对象就是目标对象
通知(Advice
)
定义了切入到目标代码的时间点
AspectJ对AOP的实现
必要条件:添加 spring-aspects
依赖
通知类型
前置通知 @Before
后置通知 @AfterReturning
环绕通知 @Around
异常通知 @AfterThrowing
最终通知 @After
切入点表达式
格式:execution([访问权限类型] 方法值类型 [全限定性类名] 方法名(参数类型和参数个数) [抛出异常类型] )
【】这里可以省略
举例:
execution(public * *(..))
指定切入点为:任意公共方法。execution(* set*(..))
指定切入点为:任何一个以“set”开始的方法。execution(* com.xyz.service.*.*(..))
指定切入点为:定义在 service 包里的任意类的任意方法。execution(* com.xyz.service..*.*(..))
指定切入点为:定义在 service 包或者子包里的任意类的任意方法。“…”出现在类名中时,后
面必须跟“*”,表示包、子包下的所有类。execution(* *..service.*.*(..))
指定所有包下的 serivce 子包下所有类(接口)中所有方法为切入点
AspectJ基于注解实现AOP
案例一:
实现步骤:
- 定义业务接口和实现类
//接口
public Interface SomeService{
void doSome(String name,int age);
}
//接口实现类
public class SomeServiceImpl implements SomeService{
@Override
public void doSome(String name,int age){
System.out.println("doSome业务方法执行了");
}
}
- 定义切面类(这里以前置通知为例)
@Aspect 表示声明当前类是切面类
@Before 表示前置通知,当前方法在目标方法执行之前先执行,括号中的是目标方法的位置
@Aspect
public class MyAspect(){
@Before(value="execution(* com.ljf.Service.SomeService.doSome(..))")
public void myBefore(){
System.out.println("前置方法,在目标方法doSome之前执行了");
}
}
- 声明目标对象和切面类对象
在applicationContext.xml文件中声明
//声明目标对象
<bean id="someServiceImpl" class="com.ljf.service.SomeService" />
//声明切面类对象
<bean id="myAspect" class="com.ljf.aspect.MyAspect" />
- 注册AspectJ自动代理
在applicationComtext.xml文件中注册(默认有接口使用jdk动态代理,没有接口使用cglib动态代理)
可以有属性 proxy-target-class 如果值设置为true,则使用cglib动态代理
(做个区别:- jdk动态代理代理类基本长这样:
com.sun.proxy.$Proxy6
- cglib动态代理代理类基本长这样:
com.ljf.ba08.SomeServiceImpl$$EnhancerBySpringCGLIB$$5c397871
)
- jdk动态代理代理类基本长这样:
//声明自动代理生成器,创建代理
<aop:aspectj-autoproxy />
- 测试类中使用目标类对象id
public class MyTest{
@Test
public void test01(){
ApplicationContext ctx = new ClassPathXMLApplicationContext("applicationContext.xml");
SomeService service = (SomeService)ctx.getBean("someServiceImpl");
service.doSome("xiaoli",20);
}
}
执行结果:
前置方法,在目标方法doSome
之前执行了
doSome
业务方法执行了
- 前置通知
@Before
被修饰的方法可以有JoinPoint类型参数
JoinPoint类型参数作为方法形参必须放在第一个位置
@Before(value="execution(* com.ljf.Service.SomeService.doSome(..))")
public void myBefore(JoinPoint jp){
//JoinPoint 能够获取到方法的定义,方法的参数等信息
System.out.println("前置方法,在目标方法doSome之前执行了");
System.out.println("连接点的方法定义"+ jp.getSignature());
System.out.println("连接点的方法参数个数"+ jp.getArgs().length);
//方法参数信息
Object [] obj = jp.getArgs();
for(Object o : obj){
System.out.println(o);
}
}
- 后置通知
@AfterReturning
同前置通知 修饰位置,在目标方法执行完之后才会执行。returning 自定义的变量,表示目标方法的返回值。自定义的变量名必须和通知方法中的参数名一样。
在目标方法中形参Object result。result 用来接收目标方法的返回结果
注意:两个参数同时出现,第一个参数必须是JoinPoint
@AfterReturning(value="execution(* com.ljf.Service.SomeService.doSome(..))",returning = "result")
//Object类型的result,是因为不知道返回的结果是什么类型,且Object是所有类型的父类
public void myBefore(JoinPoint jp,Object result){
//JoinPoint 能够获取到方法的定义,方法的参数等信息
System.out.println("后置方法,在目标方法doSome之后执行了,返回结果是"+result.toString());
}
-
环绕通知
@Around
同前置通知 修饰位置,在目标方法执行之前之后执行。
被注解为环绕增强的方法要有返回值,Object 类型。并且方法可以包含一个ProceedingJoinPoint
类型的参数。
接口 ProceedingJoinPoint 其有一个proceed()方法
,用于执行目标方法。若目标方法有返回值,则该方法的返回值就是目标方法的返回值。最后,环绕增强方法将其返回值返回。该增强方法实际是拦截了目标方法的执行。环绕通知方法的定义和前置,后置有区别
1.方法是public
2.方法必须有返回值,推荐使用Object
,表示目标方法返回值
3.方法必须有参数ProceedingJoinPoint
特点:
1.在目标方法的前和后都能增强功能
2.控制目标方法是否执行
3.可以修改目标方法的执行结果
返回值
Object
:表示目标方法的执行结果(可以在通知方法中被修改),
参数:
ProceedingJoinPoint
:执行目标方法的(等同于Method)
@Around(value="execution(* com.ljf.Service.SomeService.doSome(..))")
public Object myBefore(ProceedingJoinPoint pjp) throws Throwable{
Object obj = null;
System.out.println("环绕通知,在目标方法doSome之后执行了,例如打印日志");
//执行目标方法的调用,相当于method.invoke(target,args)
obj = pjp.proceed();
System.out.println("环绕通知,在目标方法doSome之后执行了,例如处理事务");
return obj;
}
- 异常通知
@AfterThrowing
同前置通知 修饰位置,在目标方法抛出异常后执行。被注解为异常通知的方法可以包含一个参数 Throwable,表示发生的异常对象。
特点:
1.在目标方法抛出异常时才执行的。可以看做是对目标方法的监控程序。
2.它不是异常处理程序,异常还是抛出的。
@AfterThrowing(value="execution(* com.ljf.Service.SomeService.doSome(..))",throwing = "ex")
public void myBefore(Throwable ex) throws Throwable{
System.out.println("异常通知,在目标方法doSome抛出异常执行了"+ex.getMessage());
}
- 最终通知
@After
同前置通知 修饰位置,无论目标方法是否抛出异常,该增强均会被执行。一般做资源回收,类似于try{ }fanally{ } 中的finally
@After(value="execution(* com.ljf.Service.SomeService.doSome(..))")
public void myBefore(){
System.out.println("无论出现何种情况都会执行");
}
- **定义切入点 **
@Pointcut
作用:解决多个通知增强方法使用相同的 execution 切入点表达式
@After(value="mycut()")
public void myBefore(){
System.out.println("无论出现何种情况都会执行");
}
@Pointcut(value="execution(* com.ljf.Service.SomeService.doSome(..))")
public void mycut(){
//这个位置不需要代码
}
AspectJ基于XML实现AOP
实现步骤:
- 定义业务接口和实现类
//接口
public Interface SomeService{
void doSome(String name,int age);
}
//接口实现类
public class SomeServiceImpl implements SomeService{
@Override
public void doSome(String name,int age){
System.out.println("doSome业务方法执行了");
}
}
- 定义切面类(这里以前置通知为例)
public class MyAspect(){
public void myBefore(){
System.out.println("前置方法,在目标方法doSome之前执行了");
}
}
- 声明目标对象和切面类对象
在applicationContext.xml文件中声明
//声明目标对象
<bean id="someServiceImpl" class="com.ljf.service.SomeService" />
//声明切面类对象
<bean id="myAspect" class="com.ljf.aspect.MyAspect" />
- 在容器中定义AOP配置
在applicationComtext.xml文件中注册
aop:aspect 的ref 表示注册的切面类id
method:切面类中的方法名称, 这个方法是增加功能的方法
pointcut-ref:切入点表达式的id
<aop:config>
<aop:pointcut expression="execution(* com.ljf.Service.SomeService.doSome(..))" id="somePt" />
<aop:aspect id="myspect" ref="myAspect" >
<aop:before method="myBefore" pointcut-ref="somePt" />
</aop:aspect>
</aop:config>
配置文件中,在aop:config/中进行aop 的配置。而该标签的底层,会根据其子标签的配置,生成自动代理。
通过其子标签aop:pointcut/定义切入点,该标签有两个属性,id 与 expression。分别用于指定该切入点的名称及切入点的值。expression 的值为 execution 表达式。
通过子标签aop:aspect/定义具体的织入规则:根据不同的通知类型,确定不同的织入时间;将 method 指定的增强方法,按照指定织入时间,织入到切入点指定的目标方法中。
aop:aspect/的 ref 属性用于指定使用哪个切面。
aop:aspect/的子标签是各种不同的通知类型。不同的通知所包含的属性是不同的,但也有共同的属性。
method:指定该通知使用的切面中的增强方法。
pointcut-ref:指定该通知要应用的切入点。
AspectJ 的 5 种通知的 XML 标签如下:
➢ aop:before/:前置通知
➢ aop:after-returning/:后置通知
➢ aop:around/:环绕通知
➢ aop:after-throwing/:异常通知
➢ aop:after/:最终通知
5. 测试类中使用目标类对象id
public class MyTest{
@Test
public void test01(){
ApplicationContext ctx = new ClassPathXMLApplicationContext("applicationContext.xml");
SomeService service = (SomeService)ctx.getBean("someServiceImpl");
service.doSome("xiaoli",20);
}
}
执行结果:
前置方法,在目标方法doSome之前执行了
doSome业务方法执行了
注意
切面的通知是在加载配置文件时将对应的方法加入到目标方法的相应位置。在执行目标方法时,它对应的前置通知,后置通知等方法就已经存在了。