目录
1.1.1 AOP、Aspects和Instrumentation
springIOC_sy1_wangZY的博客-CSDN博客
1.0 切面编程 (AOP)
AOP为Aspect Oriented Programming,意为:面向切面编程
,通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。
AOP是OOP(面向对象编程)的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。
利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,将日志记录,性能统计,安全控制,事务处理,异常处理等相同代码从业务逻辑代码中划分出来
,通过对这些行为的分离,可以将它们独立到非指导业务逻辑的方法中,进而改变这些行为的时候不影响业务逻辑的代码。提高程序的可重用性,同时提高了开发的效率。
1.1.1 AOP、Aspects和Instrumentation
- Spring-aop模块:提供了面向方面(切面)的编程实现,允许你定义
方法拦截器
和切入点
对代码进行干净地解耦,从而使实现功能的代码彻底的解耦出来。能够将那些与业务无关,却为业务模块所共同调用的逻辑或责任(例如事务处理、日志管理、权限控制等)封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可拓展性和可维护性。aop就是在某一个类或方法执行前后打个标记,声明在执行到这里之前要先执行什么,执行完这里之后要接着执行什么,插入新的执行方法。在Spring中,它是以JVM的动态代理技术为基础,然后设计一系列AOP横切实现,比如前置通知、返回通知、异常通知等,同时Pointcut接口来匹配切入点,可以使用现有切入点来设计横切面,也可以扩展相关方法根据需求进行切入。 - Spring-aspects模块:提供了与 AspectJ 的集成,这是一个功能强大且成熟的面向切面编程(AOP)框架。 主要是为Spring AOP提供多种AOP实现方法。
- Spring aspect 两种方式实现五种增强
- Spring-instrument模块:提供一些类级的工具支持和ClassLoader级的实现,用于服务器。该模块是基于JAVA SE中的“java.lang.instrument”进行设计的,应该算是AOP的一个支援模块,主要作用是在JVM启用时,生成一个代理类,程序员通过代理类在运行时修改类的字节,从而改变一个类的实现。
动态代理的两种实现方式的区别:
基于接口:JDK动态代理
基于没有接口:CGLib动态代理
代理类 解耦合 代理模式 外包模式
ServiceProxy 是ServiceImpl的代理类
调用ServiceProxy类方法时候,调用了真正的目标方法, * 调用目标方法的时候,增加了一些功能 * * ServiceProxy 叫做代理 代理对目标的操作 * * 创建代理,可以完成对目标方法的调用,增减功能。 * 保持目标方法内容不变。
Aop的技术可以完成这个代理所完成的功能
(保持原始方法不动 在此基础上增加功能)通过Aop创建代理对象,由代理对象完成真正的目标方法的调用,在调用时额外的增加功能
怎么理解面向切面编程? (以切面为核心设计开发应用)
1)设计项目时,找出切面的功能
2)安排切面的时间,执行的位置上
1.2.2AOP作用
1)让切面功能复用
2)让开发人员专注业务逻辑。提高开发效率。
3)实现业务功能和其他非业务功能解耦合。
4)给存在的业务方法,增加功能,不用修改原来的代码
1.2.3 AOP术语
1)Aspect:切面,给业务方法增加的功能。
2)JoinPoint:连接点,连接切面的业务方法。在业务方法执行时,会同时执行切面功能。
3) Pointcut: 切入点,是一个或多个连接点集合。表示这些方法执行时, 都能增加切面的功能。
表示切面执行的位置。
4) target: 目标对象,给那个对象增加切面的功能,这个对象就是 目标对象。
5) Advice: 通知(增强),表示切面的执行时间。在目标方法之前执行切面,还是目标方法之后执行切面。
AOP中重要的三个要素: Aspect, Pointcut , Advice
.这个概念的理解是:在Advice的时间, 在Pointcut的位置,执行Aspect.
AOP是一个动态的思想。在程序运行期间,创建代理(ServiceProxy),使用代理执行方法时,增加切面的功能。这个代理对象是存在内存中的。
1.2.4什么时候你想用AOP
你要给某些方法 增加相同的一些功能。源代码不能该。给业务方法增加非业务功能,也可以使用AOP
1.2.5AOP技术思想的实现
使用框架实现AOP。实现AOP的框架有很多。有名的两个
1)Spring: Spring框架实现AOP思想中的部分功能。Spring框架实现AOP的操作比较繁琐,笨重。
2)AspectJ:独立框架,专门搞AOP。属于Eclipse
2.1使用AspectJ框架实现AOP
2.1.1通知
Aspectj表示切面执行时间,用的通知(Advice) 。这个通知可以使用注解表示。
讲5个注解,表示切面的5个执行时间, 这些注解叫做通知注解。
@Before :前置 通知
@AfterRetunring:返回通知
@Around:环绕通知
@AfterThrowing:异常通知
@After:后置通知
2.1.2PointCut 位置
PointCut用来表示切面执行的位置,使用AspectJ中切入点表达式。
切入点表达式语法:execution(访问权限 方法返回值 方法声明(参数)异常类型)
2.1.3 前置通知
前置通知@Before
/**
* 切面类中的通知方法,可以有参数
* JoinPoint必须是他,
*
* JoinPoint: 表示正在执行的业务方法。 相当于反射中 Method
* 使用要求:必须是参数列表的第一个
* 作用:获取方法执行时的信息,例如方法名称,方法的参数集合
*/
@Before(value = "execution(* *..SomeServiceImpl.do*(..))")
public void myBefore2(JoinPoint jp){
//获取方法的定义
System.out.println("前置通知中,获取目标方法的定义:"+jp.getSignature());
System.out.println("前置通知中,获取方法名称="+jp.getSignature().getName());
//获取方法执行时参数
Object args []=jp.getArgs();//数组中存放的是 方法的所有参数
for(Object obj: args){
System.out.println("前置通知,获取方法的参数"+obj);
}
String methodName = jp.getSignature().getName();
if("doSome".equals(methodName)){
//切面的代码
System.out.println("doSome输出日志====前置通知,切面的功能,在目标方法之前先执行==;"+ new Date());
}else if("doOther".equals(methodName)){
System.out.println("doOther前置通知,作为方法名称,参数的记录。");
}
//切面的代码
System.out.println("===2222前置通知:切面的功能,在目标方法之前先执行:"+new Date());
//
}
2.1.3.1 没加代理的处理
public class MyTest {
@Test
public void test01(){
String config="applicationContext.xml";
ApplicationContext ctx = new ClassPathXmlApplicationContext(config);
SomeService service = (SomeService) ctx.getBean("someService");
System.out.println("service=="+service.getClass().getName());
service.doSome("lisi",20);
}
}
在这个情况下 打印目标对象 发现
原来是什么还是什么样子的
加入代理处理后的类
@Test
public void test02(){
String config="applicationContext.xml";
ApplicationContext ctx = new ClassPathXmlApplicationContext(config);
/* 加入代理的处理 :
1)目标方法执行时,没有切面功能
2)service对象没有被改变*/
SomeService service = (SomeService) ctx.getBean("someService");
//控制台输出 : service==com.sun.proxy.$Proxy8
System.out.println("service=="+service.getClass().getName());
service.doSome("lisi",20);
}
模拟一下加入代理之后的类是什么样子的
原来的类
public class ServiceProxy implements SomeService {
//真正的目标
SomeService target = new SomeServiceImpl();
@Override
public void doSome() {
System.out.println("日志功能,记录方法的执行时间="+new Date());
target.doSome();
System.out.println("事物功能,业务执行方法只有,提交事物");
}
加入代理之后的类
public class ServiceProxy implements SomeService {
//真正的目标
SomeService target = new SomeServiceImpl();
MyAspect aspect = new MyAspect();
@Override
public void doSome(String name,Integer age) {
aspect.myBefore();
target.doSome("lisi",20);
}
}
2.1.4 后置通知@AfterReturning
@AfterReturning:在目标方法之后执行
注:后置通知用JoinPoint的时候一定要放在参数第一的位置上 否者找不到切入点
/**
* @AfterReturning:后置通知
* 属性:value 切入点表达式
* returning 自定义的变量。表示目标方法的返回值的。
* 自定义变量名称必须和通知方法形参名一样
* 位置: 在方法上面
* 特点:
* 1.在目标方法之后,执行的
* 2.能获取到目标方法的执行结果。
* 3.不会影响目标方法的执行
*
* 方法的参数:
* Object res: 表示目标方法的返回值,使用res接受doOther的调用结果。
* Object res = doOther();
*
* 后置通知的执行顺序
* Object res = SomeServiceImpl.doOther(..);
* myAfeterReturning(res )
*
* 思考:
* 1 doOther 方法返回的是String, Integer ,Long等基本类型。
* 在后置通知中,修改返回值,是不会影响目标方法的最终调用结果的。
* 2 doOther 返回的结果是对象类型,例如Student。
* 在后置通知方法中,修改这个Student 对象的属性值。会不会影响最后的调用结果呢?
*/
@AfterReturning(value = "execution(* *..SomeServiceImpl.doOther(..))",returning = "res")
public void myAfterReturning(JoinPoint jp,Object res){
//修改目标方法的返回值
if(res!= null){
res = "HELLO AspectJ";//注:这块改了注意 test里的 ret是否改变 答 变
}
System.out.println("后置通知,在目标方法之后,执行的。能拿到执行结果:"+res);
//Object res 有什么用
/* if("abcd".equals(res)){
System.out.println("根据返回值的不同,做不同的增强功能");
}else if ("add".equals(res)){
System.out.println("doOther做了添加数据库,我做了备份数据");
}*/
}
2.1.5 @Around 环绕通知
@Around(value="切入点表达式")
使用环绕通知 : 就是调用 切面类中的通知方法。
其实ProceedingJoinPoint pjp 是继承的JoinPoint
让环绕通知里的切面类中执行目标方法(service.doFirst)
若设定目标方法的参数是李四才执行目标方法
此图可看出目标方法参数不是lisi 所以没执行目标方法
下图目标方法是lisi 就执行了目标方法
/**
* 定义一个问题 如果参数是lisi的话就执行目标方法 ,反之不执行
* @param pjp
* @return
* @throws Throwable
*/
@Around(value ="execution(* *..SomeServiceImpl.doFirst(..))")
public Object myAround(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("执行了环绕通知的myAround方法");
//获取方法执行参数
String name = "";
Object args[] =pjp.getArgs();
if( args !=null && args.length > 0){
Object arg = args[0];
if(arg != null){
name=(String)arg;
}
}
Object methodReturn = null;
System.out.println("执行了环绕通知,在目标方法之前,输出日志时间=="+ new Date());
//!!!想要执行目标方法 ProceedingJoinPoint 表示 doFirst
if("lisi".equals(name)){
methodReturn = pjp.proceed();//mythod.invoke().表示执行doFirst()方法本身
}
System.out.println("环绕通知,在目标方法之后,增强了事物提交功能");
// 对应特点 3. return "HelloAround,不是目标方法的执行结果";
//返回目标方法执行结果,没有修改的
return methodReturn;
}
2.1.6 @AfterThrowing 异常通知
语法:@AfterThrowing(value = "execution(* *..SomeServiceImpl.doSecond(..))",throwing = "ex")
/** * @AfterThrowing : 异常通知 * 属性:value 切入点表达式 * throwing 自定义变量。表示目标犯法抛出的异常 * 变量名必须和通知方法的形参名一样 * 位置:在方法的上面 * 特点 * 1.在目标方法抛出异常后执行的,没有异常不执行 * 2.能够获取到目标方法的异常信息 * 3.不是异常处理程序,可以得到发生异常的通知,可以发送邮件短信 通知开发人员 * 4.看做是目标方法的监控 * 异常通知的执行 * try{ * SomeServiceImpl.dosecond(..) * }catch(Exceptoin e){ * myAfterThrowing(e); * } */
@AfterThrowing(value = "execution(* *..SomeServiceImpl.doSecond(..))",throwing = "ex")
public void myAfterThrowing(Exception ex){
System.out.println("异常通知,在目标方法抛出异常时执行的,异常原因是:"+ex.getMessage());
/*
异常发生可以做:
1.记录异常的时间,位置,等信息
2.发送邮件,短信,通知开发人员
*/
}
2.1.7 @After 最终通知
语法:@After(value="")
/** * @After : 最终通知 * 属性: value 且瑞安表达式 * 位置: 方法上面 * 特点: * 1.在目标方法之后执行的 * 2.总是会被执行。 * 3.可以用来做程序最后的收尾工作。eg:清除临时数据,变量。清理内存 * * 最终通知 * try{ * someServiceImpl.doThird(..) * }finally{ * myAfter() * } */
@After(value = "execution(* *..SomeServiceImpl.doThird(..))")
public void myAfter(){
System.out.println("最终通知,总是会被执行");
}
@Pointcut(value="切入点表达式")
@Before(value = "pointcut1()")
public void myBefore(JoinPoint joinPoint){
System.out.println("前置通知,在目标方法之前先执行的"+ joinPoint);
}
@After(value = "pointcut1()")
public void myAfter(){
System.out.println("最终通知,总是会被执行");
/*
* @Pointcut : 定义和管理切入点,不是通知注解。
* 属性: value 切入点表达式
* 位置: 在一个自定义方法的上面,这个方法看做是切面表达式的别名。
其他的通知注解中,可以使用方法名称,就表示使用这个切入点表达式了
*/
}
@Pointcut("execution(* *..SomeServiceImpl.doThird(..))")
public void pointcut1(){
//无需代码
}
2.2Aop总结
AOP是一种动态的技术思想,目的是实现业务功能和非业务功能的解耦合。业务功能是独立的模块,其他功能是独立的模块。例如事物功能,日志等等。让这些事务,日志功能是可以被复用的。
当目标方法需要一些功能时,可以不在修改,不能修改源代码的情况下,使用aop技术在程序执行期间,生成代理对象,通过代理执行业务方法,同时增加功能。
@****(value="切入点表达式")这个切入点表达式告诉给谁加功能,给谁加谁就是目标对象