一、AOP基础

1.基本需求

    

需求: 日志功能,在程序执行期间记录发生的活动。

ArithmeticCalculate.java

public interface ArithmeticCalculate{

	public int add(int a,int b);

	public int sub(int a,int b);

	public int mul(int a,int b);

	public int div(int a,int b);
}

ArithmeticCalculateImpl.java

public class ArithmeticCalculateImpl implements ArithmeticCalculate{

	@Override
public int add(int a,int b){
System.out.println("The method add.....begin");
int result = a + b;
System.out.println("The method add.....end");
return result;
} @Override
public int sub(int a,int b){
System.out.println("The method sub.....begin");
int result = a - b;
System.out.println("The method sub.....end");
return result;
} @Override
public int mul(int a,int b){
System.out.println("The method mul.....begin");
int result = a * b;
System.out.println("The method mul.....end");
return result;
} @Override
public int div(int a,int b){
System.out.println("The method div.....begin");
int result = a / b;
System.out.println("The method div.....end");
return result;
} }

以上这样写会出现两种问题。

(1)代码混乱

     越来越多的非业务需求加入后,原有的业务方法急剧膨胀。每个方法在处理核心逻辑的同时还必须兼顾其他多个关注点。    

(2)代码分散

     以日志需求为例,只是为了满足这个单一需求,就不得不在多个模块重复相同的代码日志,如果日志需求发生改变还得修改所有的需求。

 

使用动态代理

原理: 使用一个代理将对象包装起来,然后改代理对象取代原始对象。任何对原始对象的调用都要通过代理。代理对象决定是否以及何时将方法调用转到原始对象上。

ArithmeticCalculateProxy.java

public class ArithmeticCalculateProxy{

	//要代理的对象
private ArithmeticCalculate target; public ArithmeticCalculateProxy(){
} public ArithmeticCalculateProxy(ArithmeticCalculate target){
this.target = target;
} public ArithmeticCalculate getProxy(){
ArithmeticCalculate proxy = null; //代理对象由哪一个类加载器负责加载
ClassLoader loader = target.getClass().getClassLoader(); //代理对象的类型,即有哪些方法
Class[] interfaces = new Class[]{ArithmeticCalculate.class}; //当调用代理对象其中方法时,该执行的代码
InvocationHandler handler = new InvocationHandler(){ /*
* proxy: 正在返回的那个代理对象,一般情况下,在invoke方法中都不使用
* method: 正在被调用的方法
* args:调用方法时传入的参数
*/
@Override
public Object invoke(Object proxy,Method method,Object[] args) throws Throwable{ String methodName = method.getName(); //日志
System.out.println("The method " + methodName +" begin......");
//执行方法
Object result = method.invoke(target,args);
//日志
System.out.println("The method " + methodName +" end......");
return result;
}
}; proxy = (ArithmeticCalculate)Proxy.newProxyInstance(loader,interfaces,handler); return proxy;
}
}

Test.java

	@Test
public void testCalculate(){
ArithmeticCalculate target = new ArithmeticCalculateImpl();
ArithmeticCalculate proxy = new ArithmeticCalculateProxy(target).getProxy();
System.out.println(proxy.add(4,2));
System.out.println(proxy.sub(4,2));
}

结果:

      The method add begin......

      The method add end......

      6

      The method sub begin......

      The method sub end......

      2

 

2.AOP简介

AOP(Aspect-Oriented Programming): 面向切面编程,而切面模块化横切关注点。

在AOP编程中,仍然需要定义公共功能,但可以明确的定义这个功能在哪里,以什么方式应用,并且不必修改受影响的类。这样横切关注点就被模块化到特殊的对象(切面)里。

    

    AOP希望将这些分散在各个业务逻辑代码中的相同代码,通过横向切割的方式抽取到一个独立的模块中,还业务逻辑类一个清新的世界。我们知道将这些重复性的横切逻辑独立出来很容易,但是将这些独立的逻辑融合到业务逻辑中完成和原来一样的业务操作,这才是事情的关键,也是AOP要解决的主要问题。

使用AspectJ解决以上问题

LoggerAspect.java

/*
* 把这个类声明为一个切面
* 1.把该类放到IOC容器中
* 2.再声明为切面
*/
@Aspect
@Component
public class LoggerAspect{ /*
* 声明该方法是一个前置通知
* 在目标方法开始之前执行
*/
@Before("execution(* com.kiwi.aop.ArithmeticCalculate.*add(..))")
public void beforeMethod(JoinPoint joinPoint){
String methodName = joinPoint.getSignature().getName();
System.out.println("The method " + methodName +" begin......");
}
}

applicationContext.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:util="http://www.springframework.org/schema/util"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <!-- 指定Spring IOC容器扫描的包 -->
<context:component-scan base-package="com.kiwi"/> <!-- 使AspectJ注解起作用,为匹配的类生成代理对象 -->
<aop:aspectj-autoproxy />
</beans>

Test.java

	@Test
public void testAop(){
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
ArithmeticCalculate ac = context.getBean(ArithmeticCalculate.class);
System.out.println(ac.add(2,2));
}

结果:

      The method add begin......

      4

 

二、使用AspectJ注解声明切面

(1)要在Spring中声明AspectJ切面,只需要在IOC容器中将切面声明为Bean实例. 当在Spring IOC容器中初始化AspectJ切面之后, Spring IOC容器就会为那些与AspectJ切面相匹配的Bean创建代理。

(2)在AspectJ注解中,切面只是一个带有@Aspect注解的Java类。

(3)AspectJ 支持 5 种类型的通知注解:

    @Before: 前置通知,在方法执行之前执行。

   @After: 后置通知,在方法执行之后执行。

   @AfterRunning: 返回通知,在方法返回结果之后执行。

   @AfterThrowing: 异常通知,在方法抛出异常之后。

   @Around: 环绕通知,围绕着方法执行。

 

1.切点表达式函数

(1)切点表达式由关键字和操作参数组成。如:execution(* greetTo(..))

     execution为关键字,代表目标执行某一方法。

     * greetTo(..)为操作数,描述目标方法的匹配模式串。

    两者联合起来表示目标类greetTo()方法的连接点,为了描述方便面我们将前者称为函数,将匹配串称为入参。

    

      

(2)在函数入参中使用通配符

     * : 匹配任意字符,但它只能匹配上下文中一个元素。

     .. : 匹配任意字符,可以匹配上下文多个元素,但在表示类的时候,必须和*联合使用,在表示入参的时候单独使用。

     + : 表示按类型匹配指定所以类,必须跟在类名后面。

 

2.前置通知

(1)在方法执行之前的通知。

(2)前置通知使用@Before注解,并将切入点表达式的值作为注解值。

3.后置通知

     (1)后置通知是在连接点完成之后执行的,无论是否抛异常都会执行。

     (2)后置通知中不能访问目标方法的执行的结果。

	/*
* 后置通知
* 在目标方法执行后,无论是否发生异常,都执行的通知。
*/
@After("execution(* com.kiwi.aop.ArithmeticCalculate.*(..))")
public void afterMethod(JoinPoint joinPoint){
String name = joinPoint.getSignature().getName();
System.out.println("The method " + name +" end......");
}

 

4.返回通知

(1)无论连接点是正常返回还是抛出异常,后置通知都会执行。如果只想在连接点正常返回的时候执行,可以使用返回通知。

(2)在返回通知中,只要将returnning属性添加到@AfterReturning注解中,就可以访问连接点的返回值,该属性的值即为用来传入返回值的参数名称。

(3)必须在通知方法的签名中添加一个同名的参数,Spring AOP才会通过这个参数传递返回值。

	@AfterReturning(pointcut="execution(* com.kiwi.aop.ArithmeticCalculate.*(..))",returning="result")
public void afterReturningMethod(JoinPoint joinPoint,Object result){
String name = joinPoint.getSignature().getName();
System.out.println("The method " + name +" end......" + result);
}

 

5.异常通知

(1)只有在连接点抛出异常时才执行的异常通知。

(2)将throwing属性添加到@AfterThrowing注解中,也可以以访问连接点抛出的异常。

(3)如果只对某种特殊的异常类型感兴趣, 可以将参数声明为其他异常的参数类型. 然后通知就只在抛出这个类型及其子类的异常时才被执行。

	@AfterThrowing(pointcut="execution(* com.kiwi.aop.ArithmeticCalculate.*(..))",throwing="ex")
public void afterReturningMethod(JoinPoint joinPoint,Exception ex){
String name = joinPoint.getSignature().getName();
System.out.println("The method " + name +" end......" + ex);
}

 

6.环绕通知

(1)环绕通知需要携带ProceedingJoinPoint类型的参数。

(2)在环绕通知中需要明确调用 ProceedingJoinPoint 的proceed()方法来执行被代理的方法. 如果忘记这样做就会导致通知被执行了, 但目标方法没有被执行。

(3)环绕通知的方法需要返回目标方法执行之后的结果, 即调用 joinPoint.proceed()的返回值。

	/*
* 1.环绕通知需要携带ProceedingJoinPoint类型的参数
* 2.在环绕通知中需要明确调用 ProceedingJoinPoint 的 proceed()
* 方法来执行被代理的方法. 如果忘记这样做就会导致通知被执行了, 但目标方法没有被执行.
* 3.环绕通知的方法需要返回目标方法执行之后的结果, 即调用 joinPoint.proceed()的返回值
*/
@Around("execution(* com.kiwi.aop.ArithmeticCalculate.*(..))")
public Object aroundMethod(ProceedingJoinPoint point){
String name = point.getSignature().getName(); //执行目标方法,返回值为目标方法的返回值
Object result = null;;
try{
//前置通知
System.out.println("The method " + name +" begin......" + Arrays.asList(point.getArgs()));
result = point.proceed();
//返回通知
System.out.println("The method " + name +" ends With......" + result);
}catch(Throwable e){
//异常通知
System.out.println("The method " + name +" occur Exception......");
throw new RuntimeException();
}
//后置通知
System.out.println("The method " + name +" end......"); return result;
}

 

7.切面的优先级

(1)在同一个连接点上应用不止一个切面时, 除非明确指定, 否则它们的优先级是不确定的。

(2)切面的优先级可以通过实现Ordered接口或利用@Order注解指定。

(3)实现 Ordered 接口,getOrder() 方法的返回值越小,优先级越高。

(4)若使用 @Order 注解,序号出现在注解中。

    

 

8.重用切点

(1)编写AspectJ切面时, 可以直接在通知注解中书写切入点表达式. 但同一个切点表达式可能会在多个通知中重复出现。

(2)在AspectJ切面中, 可以通过@Pointcut 注解将一个切入点声明成简单的方法. 切入点的方法体通常是空的, 因为将切入点定义与应用程序逻辑混在一起是不合理的。

    

Spring基础学习(四)—AOP的更多相关文章

  1. Python基础学习四

    Python基础学习四 1.内置函数 help()函数:用于查看内置函数的用途. help(abs) isinstance()函数:用于判断变量类型. isinstance(x,(int,float) ...

  2. Spring基础学习,附例子代码讲解

    什么是Spring.IOC.AOP.DI?     Spring是一个基于IOC和AOP的结构J2EE系统的框架.     IOC(Inversion Of Control)控制反转(Spring的基 ...

  3. spring基础学习01

    spring基础 Spring是一个开放源代码的设计层面框架,他解决的是业务逻辑层和其他各层的松耦合问题,因此它将面向接口的编程思想贯穿整个系统应用 IOC控制反转 把创建对象和维护对象之间的关系权利 ...

  4. spring框架学习(四)——注解方式AOP

    注解配置业务类 使用@Component("s") 注解ProductService 类 package com.how2java.service; import org.spri ...

  5. spring深入学习(四)-----spring aop

    AOP概述 aop其实就是面向切面编程,举个例子,比如项目中有n个方法是对外提供http服务的,那么如果我需要对这些http服务进行响应时间的监控,按照传统的方式就是每个方法中添加相应的逻辑,但是这些 ...

  6. spring基础学习---aop

    1:无参aop下面为项目结构 2:通知类.MyAdvice package cn.edu.aop; import org.aspectj.lang.ProceedingJoinPoint; //通知类 ...

  7. Spring学习笔记(一) Spring基础IOC、AOP

    1.       注入类型 a)       Spring_0300_IOC_Injection_Type b)       setter(重要) c)       构造方法(可以忘记) d)     ...

  8. spring基础学习

    ClassXmlAplicationContext和FileSystemXmlApplicationContext的区别      https://www.cnblogs.com/sxdcgaq808 ...

  9. Spring基础学习笔记

    1. Spring入门 1. 1 Spring的体系结构 1.2 HelloWorld 1.2.1 Maven的使用 1)maven 的javase工程目录结构: 2)maven的settings.x ...

随机推荐

  1. JAVA 在程序中存储和修改信息

    1.语句和表达式 计算机程序是一组告诉计算机什么的指令,每一个指令称为语句. 2.指定变量类型 变量名.变量存储的信息类型 整型int(-2.14*109~2.14*109).浮点型float(38位 ...

  2. 手把手教你用Mysql-Cluster-7.5搭建数据库集群

    前言 当你的业务到达一定的当量,肯定需要一定数量的数据库来负载均衡你的数据库请求,我在之前的博客中已经说明了,如何实现负载均衡,但是还有一个问题就是数据同步,因为负载均衡的前提就是,各个服务器的数据库 ...

  3. 关于Storm tick

    关于Storm tick 1. tick的功能 Apache Storm中内置了一种定时机制——tick,它能够让任何bolt的所有task每隔一段时间(精确到秒级,用户可以自定义)收到一个来自__s ...

  4. 使用postfix在debian上配置邮件服务器

    如果debian中安装了exim4,先卸载exim4: apt-get remove exim4 安装postfix apt-get install postfix 安装完成后就可以测试下,PHP代码 ...

  5. (原创)我对未来的人类的发展,以及AI技术发展的一些思考。

    最近AI非常的火,不仅仅是阿尔法狗的成功,因为它击败了人类最强的大脑,颠覆了人类几千年来的对传统的认识,也让人类意识 到了一个问题:天外有天,人外有AI. 那么AI究竟会对人类的未来造成什么深远的影响 ...

  6. Java设计模式之《享元模式》及应用场景

    原创作品,可以转载,但是请标注出处地址:http://www.cnblogs.com/V1haoge/p/6542449.html 享元模式:"享"就是分享之意,指一物被众人共享, ...

  7. 小白该怎么学《马哥Linux从入门到精通》

    首先,必须说明我已经不是小白啦~现在的我在国内某独角兽担任运维工程师,带着一个四人小团队,在运维方面也算是有些心得,勉强过来回答一下这个问题,就算抛砖引玉了. 所有人都是从小白阶段过来的,我=也经历过 ...

  8. 初写Python

    #!/usr/bin/env python (指定用什么解释器运行脚本以及解释器所在的位置) # -*- coding:utf-8 -*- (用来指定文件编码为utf-8的)   # 无效的内容,只做 ...

  9. Sql Server优化---统计信息维护策略

    本位出处:http://www.cnblogs.com/wy123/p/5748933.html 首先解释一个概念,统计信息是什么: 简单说就是对某些字段数据分布的一种描述,让SQL Server大概 ...

  10. [原创]HBase学习笔记(1)-安装和部署

    HBase安装和部署 使用的HBase版本是1.2.4 1.安装步骤(默认hdfs已安装好) # 下载并解压安装包 cd tools/ tar -zxf hbase-1.2.4-bin.tar.gz ...