AOP 面向切面编程
底层动态代理 一种动态代理的规范
1、jdk动态代理:使用java反射包中的类和接口实现动态代理的功能
反射包java.lang。reflect,里面的三个类
2、cglib动态代理(了解):cglib是第三方工具库,创建代理对象
原理:继承,通过继承目标类,继承他的子类,在子类中重写父类中同名的方法,实现功能的修改。
因为cglib是继承,重写方法,所以要求目标类不能是final的,方法也不能是final
cglib的要求比较宽松,只要能继承就行,所以在很多框架都有使用
动态代理的作用:
1、在目标类源代码不改变的情况下,增加功能
2、减少代码的重复
3、专注业务逻辑代码
4、解耦合,让你的业务功能和日志、事务非业务功能分离
面向切面编程: Aspect orient Programing
Aspect:切面,对你的目标类增加的功能,就是切面。
特点:非业务方法 ,可以独立使用
orient:面向,对着
Programing:编程
如何理解aop:
oop:面向对象编程
aop:面向切面编程
1、需要在分析项目功能时,找出切面。
2、合理的安排切面的执行时间
3、合理的安排切面执行的位置,在哪个类,哪个方法增强功能
术语:
Aspect:切面,表示增强的功能,就是一堆代码,完成某一个功能,非业务功能
日志,事务,统计信息,参数检查,权限验证
JoinPoint:连接点,连接业务方法和切面的位置。就是某个类中的业务方法
pointcut:切入点,多个连接点方法的集合
目标对象:给哪个类的方法增加功能
Advice通知:通知表示切面功能执行的时间
一个切面的三个要素:
1、切面的功能代码,切面干什么
2、切面的执行位置,使用pointcut表示切面的执行位置
3、切面的执行时间,使用advice表示时间,在目标方法之前,还是之后
aop的实现:
AOP 面向切面编程
底层动态代理 一种动态代理的规范
aop的技术实现框架:
spring:spring在内部实现的aop规范,能做aop的工作
spring主要在事务处理时使用aop
我们在项目开发中很少使用spring的sop,因为spring的aop比较笨重
aspectJ:一个开源的专门做aop的框架,spring集成类aspectJ,通过spring就能使用aspectJ的功能
实现方式:
1、xml配置方式:配置全局事务
2、注解实现:五个注解
aspectJ框架使用:
1、切面的执行时间,这个执行时间在规范中叫做advice(通知,增强)
在aspectJ这个框架中用注解表示,也可以使用xml配置文件中的标签
@Before
@AfterReturning
@Around
@AfterThrowing
@@After
2、切面的执行位置,使用的是切入点表达式
使用aop:
目的是给以及存在的一些类和方法,增加额外的功能,前提是不改变原来的类的代码
使用aspectJ的步骤:
1、新建maven项目
2、添加依赖 spring依赖和aspect依赖junit单元测试
3、创建目标类:接口和他的实现类
要做的是给类中的方法增加功能
4、创建切面类:普通类
/**
* @Aspect是aspectJ的注解
* 作用:表示当前类是切面类
* 切面类:使用类给业务方法增加功能的类,在这个类中有切面的功能代码
* 位置:类定义的上面
*/
@Aspect
public class Myaspect {}
1、在类的上面加入@Aspect
/**
* 定义方法,方法是实现切面功能的
* 方法定义要求:
* 公共方法
* 没有返回值
* 方法名称自定义
* 方法可以有参数,也可以没有
* 如果有参数,参数不是自定义的,有几个参数类型可以使用
*/
2、在类中定义方法,方法就是切面要执行的功能代码
在方法的上面加入aspectJ只能够的通知注解,例如@before
还需要切入点表达式execution()
5、创建spring的配置文件,声明对象,把对象交给容器统一管理。
<!-- 声明切面对象-->
<bean id="myAspect" class="ba02.Myaspect"></bean>
<!-- 声明自动代理生成器L:使用aspectJ框架内部的功能,创建目标对象的代理对象
创建代理对象是在内存中做的,修改目标对象的内存中的结构。创建为代理对象
所以目标对象就是被修改后的代理对象
aspectj-autoproxy:会把spring容器中的所有目标对象,一次性生成代理对象
-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
声明对象你可以使用注解或xml配置文件
1、声明目标对象
2、声明切面对象
3、声明aspectJ框架中的自动代理生成器标签
自动代理生成器:用来完成代理对象的自动创建功能
6、测试类,从spring容器中获取目标对象(实际是代理对象)
通过代理执行方法,实现aop的功能增强
表示切面位置的切入点表达式:execution(访问的修饰符 返回值 包名.类名.方法名称(方法的参数) 异常)
execution(public void ba02.SomeServiceImpl.doSome(String,Integer)
execution表达式
(方法的类型 方法的返回值类型 方法名的全限定名称 (参数的数据类型))
五个注解
@Before
/**
* @Before() 前置通知注解
* 属性value,是切入点表达式,表示切面的功能执行的位置
* 位置:在方法的上面
* 特点:
* 1、在目标方法之前执行的
* 2、不会改变目标方法的执行结果
* 3、不会影响目标方法的执行
*/
@Before("execution(public void ba02.SomeServiceImpl.doSome(String,Integer))")
/**
* 指定通知方法中的参数: joinpoint
* joinpoint:业务方法,要加入切面功能的业务方法
* 作用是:可以在通知方法中获取方法执行时的信息,例如方法名称吗,方法的实参
* 如果你的切面功能中需要用到方法的信息,就加入joinpoint
* 这个joinpoint参数的值是由框架赋予,必须是第一个参数的位置
*/
@Before("execution( void *..SomeServiceImpl.doSome(..)) ")
public void mybefore(JoinPoint jp){
System.out.println("获取方法的完整定义: "+jp.getSignature());
System.out.println("方法的名称: "+jp.getSignature().getName());
System.out.println(jp.getSignature().getDeclaringType());
System.out.println(jp.getSignature().getDeclaringTypeName());
}
@AfterReturning
/**
* @AfterReturning:后置通知
* 属性:1、value:切入点表达式
* 2、returning 自定义的变量,表示目标方法的返回值的
* 自定义变量名必须和通知方法的形参名一样
* 位置:方法定义上面
* 特点:
* 1、在目标方法之后执行
* 2、能够获取目标方法的返回值,可以根据这个返回值做不同的功能处理
* Object res=doother();
* 3、可以修改这个返回值
*
* 后置执行的通知:
* object res=doother()
* myafterReturning(Object res);
*
* @param res
*/
@AfterReturning(value = "execution(* *..SomeServiceImpl.doother(..))",
returning = "res")
public void myafterReturning(Object res){
// Object res:是目标方法执行后的返回值,根据返回值做你的切面的功能处理
System.out.println("后置通知:在目标方法之后执行,获取的返回值是:"+res);
if(res.equals("abcd")){
//做一些功能
}else{
//其他功能
}
//修改目标的返回值,看一下是否会影响
if(res!=null){
res="hello aspectJ";
System.out.println("res=="+res);
}
}
@Around
/**
* @Around 环绕通知
* 属性value:切入点表达式
* 位置:在方法定义的上面
* 特点:
* 1、功能最强的通知
* 2、在目标方法的前后都能增强
* 3、控制目标方法是否被调用执行
* 4、可以修改原来的目标方法的执行结果,影响最后的调用结果
*
* 环绕通知相当于jdk的动态代理
*
* 参数ProceedingJoinPoint pjp 相当于method
* 作用:用来执行目标方法
* 返回值:目标方法的执行结果,可以被修改
*ProceedingJoinPoint 继承了JoinPoint
* 可以获取参数值
*
* 环绕通知:经常做事务,在目标方法之前开启事务,执行目标方法,在目标方法之后提交事务
* @param pjp
*/
@Around("execution(* *..SomeServiceImpl.dofrist(..))")
public Object myAround(ProceedingJoinPoint pjp) throws Throwable {
//获取参数
String name="";
Object[] args=pjp.getArgs();
if(args!=null&&args.length>1){
Object arg=args[0];
name=(String) arg;
}
//实现环绕通知
Object res=null;//方法调用返回结果
System.out.println("环绕通知前:"+new Date());
if(name.equals("lixianji")){
//符合条件 调用方法
res=pjp.proceed();//method.invoke
}
//2、在目标方法的前后加入功能
System.out.println("环绕通知后!");
// 修改目标方法的执行结果,影响方法最后的调用结果
if(res!=null){
res="hello AspectJ";
}
return res;
}
@Pointcut
/**
* @pointcut :定义和管理接入点
* 如果你的项目中有多个切入点表达式 是重复的 是可以复用的
* 使用@pointcut
* 属性:value 切入点表达式
* 位置:自定义方法上面
* 特点:
* 当使用@Pointcut定义在一个方法的上面,此时这个方法的名称就是切入点表达式的别名
* 其他的通知中,value属性就可以使用这个方法名称,代替切入点表达式
*
*/
@Pointcut(value = "execution(* *..SomeServiceImpl.dosecond(..))")
private void mypt(){
}
@AfterThrowing
/**
* @AfterThrowing 异常通知
* 属性:1、value 切入点表达式
* 2、throwing 自定义的变量 表示目标方法抛出的异常对象
* 变量名必须和方法的参数名一样
* 特点:
* 1、在目标方法抛出异常时执行的
* 2、可以做异常的监控程序,监控目标方法执行时是不是有异常
* 如果有可以发送邮件,短信来通知
* @param ex
*/
@AfterThrowing(value = "mypt()"
,throwing = "ex")
public void myAfterThrowing(Exception ex){
System.out.println("异常通知,执行"+ex.getMessage());
}
@After
/**
* 最终通知方法的定义格式:
* 1、public
* 2、没有返回值
* 3、方法名称自定义
* 4、方法有没有参数,如果还有是JoinPoint
*/
/**
* @After 异常通知
* 属性:1、value 切入点表达式
* 位置L:方法上面
* 特点:
* 1、总是会执行
* 2、在目标方法之后执行
*/
@After(value = "execution(* *..SomeServiceImpl.dosecond(..))")
public void myafter(){
System.out.println("执行最终通知 ,终是会通知");
//一般做资源清除
}