AOP(面对切面编程)
引例
若要求对方法进行日志记录,而我们采取每个方法单独写日志代码,将会导致
原有业务急剧膨胀,每个方法处理必须兼顾多个关注点;并且导致代码分散,若
日志条件发生改变则必须修改所有模块
为了解决这个问题,使用AOP编程(面向切面编程)
AOP术语:
切面(Aspect): 横切关注点(跨越应用程序多个模块的功能)被模块化的特殊对象
通知(Advice): 切面必须要完成的工作
目标(Target): 被通知的对象
代理(Proxy): 向目标对象应用通知之后创建的对象
连接点(Joinpoint):程序执行的某个特定位置:如类某个方法调用前、调用后、
方法抛出异常后等。连接点由两个信息确定:方法表示的程序执行点;相对点表示
的方位。例如 ArithmethicCalculator#add() 方法执行前的连接点,执行点为
ArithmethicCalculator#add(); 方位为该方法执行前的位置
切点(pointcut):每个类都拥有多个连接点:例如 ArithmethicCalculator
的所有方法实际上都是连接点,即连接点是程序类中客观存在的事务。AOP 通过切
点定位到特定的连接点。类比:连接点相当于数据库中的记录,切点相当于查询条
件。切点和连接点不是一对一的关系,一个切点匹配多个连接点,切点通过
org.springframework.aop.Pointcut 接口进行描述,它使用类和方法作为连接点的查询条件。
实现方法
一.使用动态代理
好处:-每个事物逻辑位于一个位置,代码不分散,便于维护和升级
-业务模块更简洁,只包含核心业务代码
方法执行需要进行日志记录的类:算数类,实现的自定义接口中包含加减乘除四个方法
public class ArithmeticCalculatorImpl implements ArithmeticCalculator {
@Override
public int add(int i, int j) {
return i+j;
}
@Override
public int sub(int i, int j) {
return i-j;
}
@Override
public int mul(int i, int j) {
return i*j;
}
@Override
public int div(int i, int j) {
return i/j;
}
}
动态代理类:
public class ArithmeticCalculatorLoggingProxy {
private ArithmeticCalculator target;
public ArithmeticCalculatorLoggingProxy(ArithmeticCalculator target) {
this.target = target;
}
public ArithmeticCalculator getLoggingProxy() {
ArithmeticCalculator proxy=null;
//代理对象由哪一个类加载器负责加载
ClassLoader loader=target.getClass().getClassLoader();
//代理对象的类型,即其中有哪些方法
Class [] interfaces = new Class[]{ArithmeticCalculator.class};
//当调用代理对象其中的方法时,该执行的代码
InvocationHandler h=new InvocationHandler() {
/**
* proxy:正在返回的那个代理对象,一般情况下,在invoke方法中都不使用该对象
使用该对象的方法会再次调用invoke,导致死循环。
* method:正在被调用的方法
* args:调用方法时,传入的参数
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String methodName=method.getName();
//记录日志
//执行方法
try {
//前置通知
System.out.println("The method"+methodName+" begins with "+ Arrays.asList(args));
Object result = method.invoke(target, args);
//返回通知,此时可以访问返回值
System.out.println("The method"+methodName+" ends with "+ result);
return result;
}catch (Exception e){
e.printStackTrace();
//异常通知,可以访问到方法出现的异常
}
System.out.println("The method"+methodName+" ends with ");
//后置通知,因为方法可能会出现异常,所以访问不到方法的返回值
//记录日志
return null;
}
};
proxy= (ArithmeticCalculator) Proxy.newProxyInstance(loader,interfaces,h);
return proxy;
}
}
spring配置文件
<!--配置自动扫描的包-->
<context:component-scan base-package="com.itlc.spring.aop.impl"></context:component-scan>
<!--使Aspject注解起作用:自动为匹配的类生成代理对象-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
一.spring AOP
步骤:
1.加入jar包,在idea中还需要导入aopalliance-1.0.jar和aspectjweaver.jar
这两个jar包下载,密码:iqc5
2.在配置文件中加入aop的命名空间
xmlns:aop="http://www.springframework.org/schema/aop"
3.基于注解的方式
(1).使Aspject注解起作用:自动为匹配的类生成代理对象
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
(2).把横切关注点的代码抽象到切面的类中
1).切面首先是一个IOC中的bean,即加入@Component注解
2).切面还需要加入@Aspect注解
(3)在类中加入各种通知
1).申请一个方法
2).在方法前加入各种通知注解
使用方法为书写完整方法,包括路径与参数类型,用*表示上一个路径中的所有文件,也可用*代替
表示任意申请符,方法中参数的申请符,可以用..来代替所有
@Before: 前置通知, 在方法执行之前执行
@After: 后置通知, 在方法执行之后执行
@AfterRunning: 返回通知, 在方法返回结果之后执行
@AfterThrowing: 异常通知, 在方法抛出异常之后
@Around: 环绕通知, 围绕着方法执行
通知内部绑定对应方法
例如:@Before("execution(public int com.itlc.spring.aop.impl.ArithmeticCalculator.*(int ,int ))")
@Before("execution(public int com.itlc.spring.aop.impl.*.*(int ,int ))")
@Before("execution(* int com.itlc.spring.aop.impl.*.*(..))")
(4)可以在通知方法中声明一个类型为JoinPoint的参数,然后就能访问链接细节,如方法名称和参数值
(5)当有多个切面类绑定一个方法的时候,课用@Order(n),n为阿拉伯数字,表示切面执行的优先级,值越小优先级越高
(6) 在通知标签里中存在大量重复信息
1)此时定义一个方法,用于作为切入点表达式,一般地,该方法再不需要添加其他的代码
2)使用@Pointcut来声明切入点表达式
3)后面的其他通知直接使用方法名来引用当前的切入点表达式
例:@Pointcut("execution(public int com.itlc.spring.aop.impl.ArithmeticCalculator.*(.. ))")
public void declareJountPointExpression(){}
则通知的注解可改为:@Before("declareJountPointExpression()")
方法执行需要进行日志记录的类:算数类,实现的自定义接口中包含加减乘除四个方法
public class ArithmeticCalculatorImpl implements ArithmeticCalculator {
@Override
public int add(int i, int j) {
return i+j;
}
@Override
public int sub(int i, int j) {
return i-j;
}
@Override
public int mul(int i, int j) {
return i*j;
}
@Override
public int div(int i, int j) {
return i/j;
}
}
切面类:
//把这个类声明为一个切面:需要把该类放入IOC容器中,再声明为一个切面
@Aspect
@Component
public class LoggingAspect {
/*
* 定义一个方法,用于作为切入点表达式,一般地,该方法再不需要添加其他的代码
* 使用@Pointcut来声明切入点表达式
* 后面的其他通知直接使用方法名来引用当前的切入点表达式
* */
@Pointcut("execution(public int com.itlc.spring.aop.impl.ArithmeticCalculator.*(.. ))")
public void declareJountPointExpression(){}
/*声明该方法是一个前置通知:在目标方法开始之前执行
使用方法为书写完整方法,包括路径与参数类型,用*表示上一个路径中的所有文件,也可用*代替
表示任意申请符*/
@Before("declareJountPointExpression()")
public void beforeMethod(JoinPoint joinPoint){
//传入的参数,包括调用的方法名都在joinPoint这个对象中
String methodName = joinPoint.getSignature().getName();
List<Object> args= Arrays.asList(joinPoint.getArgs());
System.out.println("The method "+methodName+" begins with" + args);
}
//声明该方法是一个后置通知:无论是否发生异常,在目标方法开始之后执行
//注意:不能在后置通知中访问目标方法执行的结果,因为方法可能出现异常
@After("declareJountPointExpression()")
public void afterMethod(JoinPoint joinPoint){
//传入的参数,包括调用的方法名都在joinPoint这个对象中
String methodName = joinPoint.getSignature().getName();
List<Object> args= Arrays.asList(joinPoint.getArgs());
System.out.println("The method "+methodName+" ends");
}
/*
* 返回通知
* 在方法正常结束受执行的代码
* 返回通知是可以访问到方法的返回值的
* */
@AfterReturning(value="declareJountPointExpression()",returning = "result")
public void afterReturningMethod(JoinPoint joinPoint,Object result){
//传入的参数,包括调用的方法名都在joinPoint这个对象中
String methodName = joinPoint.getSignature().getName();
List<Object> args= Arrays.asList(joinPoint.getArgs());
System.out.println("The method "+methodName+" ends with" + result);
}
/*
* 在目标方法方法出现异常时会执行的代码
* 可以访问到异常对象;
* 且可以指定在出现特定异常时再执行通知代码:将形参列表中的Exception换成特定的异常类名即可
* */
@AfterThrowing(value="declareJountPointExpression()",throwing = "ex")
public void afterThrowingMethod(JoinPoint joinPoint,Exception ex){
//传入的参数,包括调用的方法名都在joinPoint这个对象中
String methodName = joinPoint.getSignature().getName();
List<Object> args= Arrays.asList(joinPoint.getArgs());
System.out.println("The method "+methodName+" occurs exception" + ex);
}
/*
* 环绕通知需要携带ProceedingJoinPint
* 环绕通知类似于动态代理的全过程:ProceedingJoinPint 类型的参数可以决定是否执行目标方法
* 且环绕通知必须有返回值,返回值即为目标方法的返回值
* 环绕通知功能最强,但并不是最常用的
* */
@Around("execution(public int com.itlc.spring.aop.impl.ArithmeticCalculator.*(..))")
public Object aroundMethod(ProceedingJoinPoint pjd){
Object result=null;
String methodName=pjd.getSignature().getName();
try {
//前置通知
System.out.println("The method "+methodName+" begins with" + Arrays.asList(pjd.getArgs()));
result=pjd.proceed();
//返回通知
System.out.println("The method "+methodName+" ends with "+result);
} catch (Throwable throwable) {
//异常通知
System.out.println("The method "+methodName+" occur exception:"+throwable);
}
//后置通知
System.out.println("The method "+methodName+" ends");
return result;
}
}
spring配置:
<!--配置自动扫描的包-->
<context:component-scan base-package="com.itlc.spring.aop.impl"></context:component-scan>
<!--使Aspject注解起作用:自动为匹配的类生成代理对象-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
Main:
public class Main {
public static void main(String[] args) {
ApplicationContext ctx=new ClassPathXmlApplicationContext("applicationContext.xml");
ArithmeticCalculator arithmeticCalculator=ctx.getBean(ArithmeticCalculator.class);
int result=arithmeticCalculator.add(3,6);
System.out.println(result);
result=arithmeticCalculator.div(4,0);
System.out.println(result);
}
}
三.通过配置文件来实现AOP
原理类似切面类来实现,不过不用注解标注
方法执行需要进行日志记录的类:算数类,实现的自定义接口中包含加减乘除四个方法
public class ArithmeticCalculatorImpl implements ArithmeticCalculator {
@Override
public int add(int i, int j) {
return i+j;
}
@Override
public int sub(int i, int j) {
return i-j;
}
@Override
public int mul(int i, int j) {
return i*j;
}
@Override
public int div(int i, int j) {
return i/j;
}
}
切面类1:
public class LoggingAspect {
public void beforeMethod(JoinPoint joinPoint){
//传入的参数,包括调用的方法名都在joinPoint这个对象中
String methodName = joinPoint.getSignature().getName();
List<Object> args= Arrays.asList(joinPoint.getArgs());
System.out.println("The method "+methodName+" begins with" + args);
}
public void afterMethod(JoinPoint joinPoint){
//传入的参数,包括调用的方法名都在joinPoint这个对象中
String methodName = joinPoint.getSignature().getName();
List<Object> args= Arrays.asList(joinPoint.getArgs());
System.out.println("The method "+methodName+" ends");
}
public void afterReturningMethod(JoinPoint joinPoint,Object result){
//传入的参数,包括调用的方法名都在joinPoint这个对象中
String methodName = joinPoint.getSignature().getName();
List<Object> args= Arrays.asList(joinPoint.getArgs());
System.out.println("The method "+methodName+" ends with" + result);
}
public void afterThrowingMethod(JoinPoint joinPoint,Exception ex){
//传入的参数,包括调用的方法名都在joinPoint这个对象中
String methodName = joinPoint.getSignature().getName();
List<Object> args= Arrays.asList(joinPoint.getArgs());
System.out.println("The method "+methodName+" occurs exception" + ex);
}
public Object aroundMethod(ProceedingJoinPoint pjd){
Object result=null;
String methodName=pjd.getSignature().getName();
try {
//前置通知
System.out.println("The method "+methodName+" begins with" + Arrays.asList(pjd.getArgs()));
result=pjd.proceed();
//返回通知
System.out.println("The method "+methodName+" ends with "+result);
} catch (Throwable throwable) {
//异常通知
System.out.println("The method "+methodName+" occur exception:"+throwable);
}
//后置通知
System.out.println("The method "+methodName+" ends");
return result;
}
}
切面类2:
public class VlidationAspect {
public void beforeMethod(JoinPoint joinPoint){
//传入的参数,包括调用的方法名都在joinPoint这个对象中
String methodName = joinPoint.getSignature().getName();
List<Object> args= Arrays.asList(joinPoint.getArgs());
System.out.println("VlidationAspect--> The method "+methodName+" begins with" + args);
}
public void afterMethod(JoinPoint joinPoint){
//传入的参数,包括调用的方法名都在joinPoint这个对象中
String methodName = joinPoint.getSignature().getName();
List<Object> args= Arrays.asList(joinPoint.getArgs());
System.out.println("VlidationAspect--> The method "+methodName+" ends");
}
public void afterReturningMethod(JoinPoint joinPoint,Object result){
//传入的参数,包括调用的方法名都在joinPoint这个对象中
String methodName = joinPoint.getSignature().getName();
List<Object> args= Arrays.asList(joinPoint.getArgs());
System.out.println("VlidationAspect--> The method "+methodName+" ends with" + result);
}
public void afterThrowingMethod(JoinPoint joinPoint,Exception ex){
//传入的参数,包括调用的方法名都在joinPoint这个对象中
String methodName = joinPoint.getSignature().getName();
List<Object> args= Arrays.asList(joinPoint.getArgs());
System.out.println("VlidationAspect--> The method "+methodName+" occurs exception" + ex);
}
}
spring配置文件
<!--配置bean-->
<bean id="arithmeticCalculator"
class="com.itlc.spring.aop.impl.ArithmeticCalculatorImpl"></bean>
<!--配置切面的bean-->
<bean id="LoggingAspect" class="com.itlc.spring.aop.impl.LoggingAspect"></bean>
<bean id="vlidationAspect" class="com.itlc.spring.aop.aopByBean.VlidationAspect"></bean>
<!--配置AOP-->
<aop:config>
<!--配置切点表达式-->
<aop:pointcut id="pointcut" expression="execution(* com.itlc.spring.aop.impl.ArithmeticCalculator.*(..))"/>
<!--配置切面及通知-->
<aop:aspect ref="LoggingAspect" order="2">
<aop:before method="beforeMethod" pointcut-ref="pointcut"></aop:before>
<aop:after method="afterMethod" pointcut-ref="pointcut"></aop:after>
<aop:after-returning method="afterReturningMethod" pointcut-ref="pointcut" returning="result"/>
<aop:after-throwing method="afterThrowingMethod" pointcut-ref="pointcut" throwing="ex"></aop:after-throwing>
<!--<aop:around method="aroundMethod" pointcut-ref="pointcut"></aop:around>-->
</aop:aspect>
<aop:aspect ref="vlidationAspect" order="1">
<aop:before method="beforeMethod" pointcut-ref="pointcut"></aop:before>
</aop:aspect>
</aop:config>
Main:
public class Main {
public static void main(String[] args) {
ApplicationContext ctx=new ClassPathXmlApplicationContext("aopByBean.xml");
ArithmeticCalculator arithmeticCalculator= (ArithmeticCalculator) ctx.getBean("arithmeticCalculator");
int result=arithmeticCalculator.add(3,6);
System.out.println(result);
result=arithmeticCalculator.div(4,0);
System.out.println(result);
}
}
上一篇: php调用DLL(COM)注意事项
下一篇: htaccess 伪静态跳转有关问题