java版云笔记(六)之AOP
今天主要是利用aop技术追加service的响应时间的计算和异常的日志记录。
AOP
AOP(Aspect Oriented Programming),即面向切面编程,可以说是OOP(Object Oriented Programming,面向对象编程)的补充和完善。面向对象编程将程序分解成各个层次的对象,面向切面编程将程序运行过程分解成各个切面。
AOP从程序运行角度考虑程序的结构,提取业务处理过程的切面,oop是静态的抽象,aop是动态的抽象, 是对应用执行过程中的步骤进行抽象,从而获得步骤之间的逻辑划分。
面向切面编程(AOP)完善spring的依赖注入(DI),面向切面编程在spring中主要表现为两个方面
1.面向切面编程提供声明式事务管理 **
2.spring支持用户自定义的切面**
特点:在不修改系统业务逻辑的前提下,给系统追加功能。
AOP核心概念
- 1、横切关注点
对哪些方法进行拦截,拦截后怎么处理,这些关注点称之为横切关注点
- 2、切面(aspect)
类是对物体特征的抽象,切面就是对横切关注点的抽象
- 3、连接点(joinpoint)
被拦截到的点,因为Spring只支持方法类型的连接点,所以在Spring中连接点指的就是被拦截到的方法,实际上连接点还可以是字段或者构造器
- 4、切入点(pointcut)
对连接点进行拦截的定义
- 5、通知(Advice):
在切面的某个特定的连接点(Joinpoint)上执行的动作。通知有各种类
型,其中包括"around"、"before”和"after"等通知。许多AOP框架,包括Spring,都是以拦截器做通知模型, 并维护一个以连接点为中心的拦截器链。
- 6、目标对象(Target):
就是那些即将切入切面的对象,也就是那些被通知的对象。
- 7、代理对象(Proxy):
将通知应用到目标对象之后被动态创建的对象。可以简单地理解为,代理对象的功能等于目标对象的核心业务逻辑功能加上共有功能。代理对象对于使用者而言是透明的,是程序运行过程中的产物。
- 8、织入(weave)
将切面应用到目标对象并导致代理对象创建的过程
- 9、引入(introduction)
在不修改代码的前提下,引入可以在运行期为类动态地添加一些方法或字段
表达式:三种
AspectJ类型匹配的通配符:
* :匹配任何数量字符;
..:(两个点)匹配任何数量字符的重复,如在类型模式中匹配任何数量子包;而在方法参数模式中匹配任何数量参数。
**+ **:匹配指定类型的子类型;仅能作为后缀放在类型模式后边。
- 方法限定表达式:execution
为某个组件的部分方法追加功能
- execution(修饰符? 返回类型 方法名(参数) 抛出异常?)
匹配所有以add开头的方法
execution(* add*(..))
匹配UserService组件下所有的方法
execution(* cn.tedu.service.UserService.*(..))
匹配的service下所有组件的所有方法
execution(* cn.tedu.service.*.*(..))
匹配的service包及子包中的所有方法
execution(* cn.tedu.service..*.*(..))
案例:
@Before execution(* cn.tedu.cloudnote.service..*.*(..))
- 类级限定表达式:within
匹配UserService组件下的所有方法
within(cn.tedu.cloudnote.service.UserService)
匹配到service包下所有类的所有方法
within(cn.tedu.cloudnote.service.*)
匹配到service包及子包下的所有类的所有方法
within(cn.tedu.cloudnote.service..*)
- bean限定表达式:bean(id名)
匹配userService组件的所有方法
bean(userService)
匹配所有以service结尾的组件的所有方法
bean(*Service)
@Bfore("bean(userController)")
通知(Advice)类型
为了符合现实的各种需求,通知类型提供了5种,可以对目标方法进行全方位处理;
- Before advice:在某连接点(JoinPoint)之前执行的通知,但这个通知不能阻止连接点前的执行。
ApplicationContext中在aop:aspect里面使用aop:before元素进行声明。
- After advice:当某连接点退出的时候执行的通知(不论是正常返回还是异常退出)。
ApplicationContext中在aop:aspect里面使用aop:after元素进行声明。
- After returnadvice:在某连接点正常完成后执行的通知,不包括抛出异常的情况。
ApplicationContext中在aop:aspect里面使用aop:after-returning元素进行声明。
- Around advice:包围一个连接点的通知,类似Web中Servlet规范中的Filter的doFilter方法。可以在方法的调用前后完成自定义的
行为,也可以选择不执行。
ApplicationContext中在aop:aspect里面使用aop:around元素进行声明。
- Afterthrowing advice:在方法抛出异常退出时执行的通知。
ApplicationContext中在aop:aspect里面使用aop:after-throwing元素进行声明。
AOP 代理
OP支持2种代理,Jdk的动态代理和CGLIB实现机制。
Spring中AOP代理由Spring的IOC容器负责生成、管理,其依赖关系也由IOC容器负责管理。因此,AOP代理可以直接使用容器中的其它bean实例作为目标,这种关系可由IOC容器的依赖注入提供。Spring创建代理的规则为:
-
1、默认使用Java动态代理来创建AOP代理,这样就可以为任何接口实例创建代理了
-
2、当需要代理的类不是代理接口的时候,Spring会切换为使用CGLIB代理,也可强制使用CGLIB
二者区别:
Jdk基于接口实现:JDK动态代理对实现了接口的类进行代理。
CGLIB基于继承:CGLIB代理可以对类代理,主要对指定的类生成一个子类,因为是继承,所以目标类最好不要使用final声明。
通常情况下,鼓励使用jdk代理,因为业务一般都会抽象出一个接口,而且不用引入新的东西。如果是遗留的系统,以前没有实现接口,那么只能使用CGLIB。
AOP配置
Spring AOP配置有两种风格:
XML风格 = 采用声明形式实现Spring AOP
AspectJ风格 = 采用注解形式实现Spring AOP
XML风格
<!-- com.tedu.cloudnote.aspect.LoggerBean为类名 -->
<bean id="loggerBean" class="com.tedu.cloudnote.aspect.LoggerBean">
</bean>
<aop:config>
<!-- 通过ref关联组建类 -->
<aop:aspect ref="loggerBean">
<!-- 通过method指定处理方法,logController为方法名 -->
<aop:before method="logController"
pointcut="within(com.tedu.cloudnote.controller..*)"/>
<!-- 方法限定类型 -->
<aop:before method="logController"
pointcut="execution(* com.tedu.cloudnote.service.*.*(..))" />
<!-- bean名称限定类型-->
<aop:before method="logController"
pointcut="bean(userLoginController)"/>
</aop:aspect>
</aop:config> -->
<bean id="helloWorldImpl1" class="com.xrq.aop.HelloWorldImpl1" />
<bean id="helloWorldImpl2" class="com.xrq.aop.HelloWorldImpl2" />
<bean id="timeHandler" class="com.xrq.aop.TimeHandler" />
<aop:config>
<aop:aspect id="time" ref="timeHandler">
<aop:pointcut id="addAllMethod" expression="execution(* com.xrq.aop.HelloWorld.*(..))" />
<aop:before method="printTime" pointcut-ref="addAllMethod" />
<aop:after method="printTime" pointcut-ref="addAllMethod" />
</aop:aspect>
</aop:config>
AspectJ风格
xml配置文件
<?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:context="http://www.springframework.org/schema/context"
xmlns:jdbc="http://www.springframework.org/schema/jdbc" xmlns:jee="http://www.springframework.org/schema/jee"
xmlns:tx="http://www.springframework.org/schema/tx" xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:util="http://www.springframework.org/schema/util"
xmlns:jpa="http://www.springframework.org/schema/data/jpa"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.2.xsd
http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc-3.2.xsd
http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-3.2.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.2.xsd
http://www.springframework.org/schema/data/jpa http://www.springframework.org/schema/data/jpa/spring-jpa-1.3.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.2.xsd
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-3.2.xsd">
<!-- 注解 AOP配置 -->
<context:component-scan
base-package="com.tedu.cloudnote.aspect"/>
<!-- 开启AOP注解标记的使用,例如@Aspect,@Before,@After -->
<aop:aspectj-autoproxy />
</beans>
添加的功能,aop类
@Component // 扫描,等价于<bean>定义
@Aspect // 等价于<aop:aspect>定义
public class AspectJAdvice {
// 等价于<aop:before>定义
// 在Controller方法执行前,先执行logController处理
@Before("within(com.tedu.cloudnote.controller..*)")
public void logController() {
System.out.println("进入Controller处理请求");
}
@Around("within(com.tedu.cloudnote.service..*)")
public Object test(ProceedingJoinPoint jp) throws Throwable {
long t1 = System.currentTimeMillis();
Object val = jp.proceed();// 目标业务方法
long t2 = System.currentTimeMillis();
long t = t2 - t1;
// JoinPoint 对象可以获取目标业务方法的
// 详细信息: 方法签名, 调用参数等
Signature m = jp.getSignature();
// Signature: 签名, 这里是方法签名
System.out.println(m + "用时:" + t);
return val;
}
// e就是目标组件方法抛出的异常对象
@AfterThrowing(throwing = "e", pointcut = "within(com.tedu.cloudnote.controller..*)")
public void execute(Exception e) {
try {
// 将异常信息写入文件中
FileWriter fw = new FileWriter("D:\\note_error.log", true);
PrintWriter pw = new PrintWriter(fw);
// 利用pw对象写信息
Date time = new Date();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String timeStr = sdf.format(time);
pw.println("-----------------------------------------");
pw.println("*异常类型:*" + e);
pw.println("*发生时间:*" + timeStr);
pw.println("*异常详情: *");
e.printStackTrace(pw);
pw.close();
fw.close();
} catch (Exception ex) {
System.out.println("记录异常失败");
}
}
}
注解配置的
@Pointcut
声明切入点
**@Pointcut(value="切入点表达式", argNames = "参数名列表") **
public void pointcutName(……) {}
value:指定切入点表达式(在哪切);
argNames:指定命名切入点方法参数列表参数名字,可以有多个用“,”分隔,这些参数将传递给通知方法同名的参数,同时比如切入点表达式“args(param)”将匹配参数类型为命名切入点方法同名参数指定的参数类型。
pointcutName:切入点名字,可以使用该名字进行引用该切入点表达式。
@Pointcut(value="execution(* cn.javass..*.sayAdvisorBefore(..)) && args(param)", argNames = "param")
public void beforePointcut(String param) {}
定义了一个切入点,名字为“beforePointcut”,该切入点将匹配目标方法的第一个参数类型为通知方法实现中参数名为“param”的参数类型。
@Before
前置通知
**@Before(value = "切入点表达式或命名切入点", argNames = "参数列表参数名") **
value:指定切入点表达式或命名切入点;
argNames:与Schema方式配置中的同义。
@AfterReturnin
后置返回通知
**@AfterReturning(value="切入点表达式或命名切入点",
pointcut="切入点表达式或命名切入点",
argNames="参数列表参数名",
returning="返回值对应参数名") **
/实例
@AfterReturning(
value="execution(* cn.javass..*.sayBefore(..))",
pointcut="execution(* cn.javass..*.sayAfterReturning(..))",
argNames="retVal", returning="retVal")
public void afterReturningAdvice(Object retVal) {
System.out.println("===========after returning advice retVal:" + retVal);
@AfterThrowing
后置异常通知
**@AfterThrowing (
value="切入点表达式或命名切入点",
pointcut="切入点表达式或命名切入点",
argNames="参数列表参数名",
throwing="异常对应参数名")
**
@AfterThrowing(
value="execution(* cn.javass..*.sayAfterThrowing(..))",
argNames="exception", throwing="exception")
public void afterThrowingAdvice(Exception exception) {
System.out.println("===========after throwing advice exception:" + exception);
}
注:pointcut:同样是指定切入点表达式或命名切入点,如果指定了将覆盖value属性指定的,pointcut具有高优先级;
@After
后置最终通知:
**@After (
value="切入点表达式或命名切入点",
argNames="参数列表参数名")
**
@Around
环绕通知
**@Around (
value="切入点表达式或命名切入点",
argNames="参数列表参数名") **
@Around(value="execution(* cn.javass..*.sayAround(..))")
public Object aroundAdvice(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("===========around before advice");
Object retVal = pjp.proceed(new Object[] {"replace"});
System.out.println("===========around after advice");
return retVal;
}