先是一些基本概念。。感觉大段概念太多,实际的不如你去问个老司机,说的感觉比这个让人容易理解多了,所以一切都是实践为王,这些基础用法都是扯淡~
AOP(Aspect Oriented Programming),即面向切面编程,利用一种称为”横切”的技术,剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其命名为”Aspect”,即切面。所谓”切面”,简单说就是那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块之间的耦合度,并有利于未来的可操作性和可维护性。
使用”横切”技术,AOP把软件系统分为两个部分:核心关注点和横切关注点。业务处理的主要流程是核心关注点,与之关系不大的部分是横切关注点。横切关注点的一个特点是,他们经常发生在核心关注点的多处,而各处基本相似,比如权限认证、日志、事物。AOP的作用在于分离系统中的各种关注点,将核心关注点和横切关注点分离开来。
主要的几个概念
方面(Aspect):一个关注点的模块化,这个关注点实现可能另外横切多个对象。事务管理是J2EE应用中一个很好的横切关注点例子。方面用Spring的 Advisor或拦截器实现。
连接点(Joinpoint): 程序执行过程中明确的点,如方法的调用或特定的异常被抛出。
通知(Advice): 在特定的连接点,AOP框架执行的动作。各种类型的通知包括“around”、“before”和“throws”通知。通知类型将在下面讨论。许多AOP框架包括Spring都是以拦截器做通知模型,维护一个“围绕”连接点的拦截器链。Spring中定义了四个advice: BeforeAdvice, AfterAdvice, ThrowAdvice和DynamicIntroductionAdvice
切入点(Pointcut): 指定一个通知将被引发的一系列连接点的集合。AOP框架必须允许开发者指定切入点:例如,使用正则表达式。 Spring定义了Pointcut接口,用来组合MethodMatcher和ClassFilter,可以通过名字很清楚的理解, MethodMatcher是用来检查目标类的方法是否可以被应用此通知,而ClassFilter是用来检查Pointcut是否应该应用到目标类上
引入(Introduction): 添加方法或字段到被通知的类。 Spring允许引入新的接口到任何被通知的对象。例如,你可以使用一个引入使任何对象实现 IsModified接口,来简化缓存。Spring中要使用Introduction, 可有通过DelegatingIntroductionInterceptor来实现通知,通过DefaultIntroductionAdvisor来配置Advice和代理类要实现的接口
目标对象(Target Object): 包含连接点的对象。也被称作被通知或被代理对象。POJO
AOP代理(AOP Proxy): AOP框架创建的对象,包含通知。 在Spring中,AOP代理可以是JDK动态代理或者CGLIB代理。
织入(Weaving): 组装方面来创建一个被通知对象。这可以在编译时完成(例如使用AspectJ编译器),也可以在运行时完成。Spring和其他纯Java AOP框架一样,在运行时完成织入。
一、基于XML配置的Spring AOP
通过aop namespace下的一个标签aop:config来实现aop代理,这个也是用起来相当方便的一种配置方式
AOP配置标签描述
<aop:advisor>
| 定义AOP通知器
<aop:after>
| 定义AOP后置通知(不管该方法是否执行成功)
<aop:after-returning>
| 在方法成功执行后调用通知
<aop:after-throwing>
| 在方法抛出异常后调用通知
<aop:around>
| 定义AOP环绕通知
<aop:aspect>
| 定义切面
<aop:aspect-autoproxy>
| 定义
@AspectJ
注解驱动的切面
<aop:before>
| 定义AOP前置通知
<aop:config>
| 顶层的AOP配置元素,大多数的
第一种直接使用xml
使用方式,即直接使用xml配置这个例子是在网上找的,感觉很简单了,当然这里只写了核心用到的代码段~
首先是配置文件
<bean id="userManager" class="com.tgb.aop.UserManagerImpl"/>
<bean id="xmlHandler" class="com.tgb.aop.XMLAdvice" />
<aop:config>
<aop:aspect id="aspect" ref="xmlHandler">
<aop:pointcut id="pointUserMgr" expression="execution(* com.tgb.aop.*.find*(..))"/>
<aop:before method="doBefore" pointcut-ref="pointUserMgr"/>
<aop:after method="doAfter" pointcut-ref="pointUserMgr"/>
<aop:around method="doAround" pointcut-ref="pointUserMgr"/>
<aop:after-returning method="doReturn" pointcut-ref="pointUserMgr"/>
<aop:after-throwing method="doThrowing" throwing="ex" pointcut-ref="pointUserMgr"/>
</aop:aspect>
</aop:config>
下面是定义的你需要在切面前后做的事等
package com.tgb.aop;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
/**
* Advice通知类
* 测试after,before,around,throwing,returning Advice.
* @author Admin
*
*/
public class XMLAdvice {
/**
* 在核心业务执行前执行,不能阻止核心业务的调用。
* @param joinPoint
*/
private void doBefore(JoinPoint joinPoint) {
System.out.println("-----doBefore().invoke-----");
System.out.println(" 此处意在执行核心业务逻辑前,做一些安全性的判断等等");
System.out.println(" 可通过joinPoint来获取所需要的内容");
System.out.println("-----End of doBefore()------");
}
/**
* 手动控制调用核心业务逻辑,以及调用前和调用后的处理,
*
* 注意:当核心业务抛异常后,立即退出,转向After Advice
* 执行完毕After Advice,再转到Throwing Advice
* @param pjp
* @return
* @throws Throwable
*/
private Object doAround(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("-----doAround().invoke-----");
System.out.println(" 此处可以做类似于Before Advice的事情");
//调用核心逻辑
Object retVal = pjp.proceed();
System.out.println(" 此处可以做类似于After Advice的事情");
System.out.println("-----End of doAround()------");
return retVal;
}
/**
* 核心业务逻辑退出后(包括正常执行结束和异常退出),执行此Advice
* @param joinPoint
*/
private void doAfter(JoinPoint joinPoint) {
System.out.println("-----doAfter().invoke-----");
System.out.println(" 此处意在执行核心业务逻辑之后,做一些日志记录操作等等");
System.out.println(" 可通过joinPoint来获取所需要的内容");
System.out.println("-----End of doAfter()------");
}
/**
* 核心业务逻辑调用正常退出后,不管是否有返回值,正常退出后,均执行此Advice
* @param joinPoint
*/
private void doReturn(JoinPoint joinPoint) {
System.out.println("-----doReturn().invoke-----");
System.out.println(" 此处可以对返回值做进一步处理");
System.out.println(" 可通过joinPoint来获取所需要的内容");
System.out.println("-----End of doReturn()------");
}
/**
* 核心业务逻辑调用异常退出后,执行此Advice,处理错误信息
* @param joinPoint
* @param ex
*/
private void doThrowing(JoinPoint joinPoint,Throwable ex) {
System.out.println("-----doThrowing().invoke-----");
System.out.println(" 错误信息:"+ex.getMessage());
System.out.println(" 此处意在执行核心业务逻辑出错时,捕获异常,并可做一些日志记录操作等等");
System.out.println(" 可通过joinPoint来获取所需要的内容");
System.out.println("-----End of doThrowing()------");
}
}
这是main文件,
package com.tgb.aop;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Client {
public static void main(String[] args) {
BeanFactory factory = new ClassPathXmlApplicationContext("applicationContext.xml");
UserManager userManager = (UserManager)factory.getBean("userManager");
//可以查找张三
userManager.findUserById(1);
System.out.println(userManager.findUserById(1));
System.out.println("=====我==是==分==割==线=====");
try {
// 查不到数据,会抛异常,异常会被AfterThrowingAdvice捕获
userManager.findUserById(0);
} catch (IllegalArgumentException e) {
}
}
}
有上面这些代码就够了
各个配置实现不同的功能before可以在这个方法调用之前使用,从而实现诸如权限控制,登录控制等,而after则可以实现日志记录等等,我理解的就是实现了一些公共模块的重复调用,并且细粒度可以精确到方法级别。这里可以把这个方法理解成一个切面。
第二种是使用自动注解
下面这个例子是网上找的自己改了点。。。原谅没那么多时间写新的,而且感觉我实在没那水平写的比人家更通俗易懂。
使用配置注解,首先我们要将切面在spring上下文中声明成自动代理bean,我们需要在web层的web-inf/dispatcher-servlet.xml文件中配置如下一句话即可
当然我们需要在xml的根目录beans下引用aop的命名空间和xsi,这里不写了~
使用@Aspect注解
声明一个切面,只需要在类名上添加@Aspect属性即可,具体的连接点,我们用@Pointcut和@Before、@After等标注。
接口类
package com.cym.aop;
public interface Move {
public void up(int i);
public void down(int i);
public void left(int i);
public void right(int i);
}
切面类
package com.cym.aop;
import org.springframework.stereotype.Component;
@Component
public class MyMove implements Move {
@Override
public void up(int i) {
System.out.println("I'm moving up " + i + " steps!");
}
@Override
public void down(int i) {
System.out.println("I'm moving down " + i + " steps!");
}
@Override
public void left(int i) {
System.out.println("I'm moving left " + i + " steps!");
}
@Override
public void right(int i) {
System.out.println("I'm moving right " + i + " steps!");
}
}
编写打印日志类,注意此处在before注解后,需要加入通知的方法,并且括号中的参数一定要和方法的参数一致。例如下中,括号中int不能省略。可以用*表示所有含有一个int方法
package com.cym.aop;
import java.util.Arrays;
import java.util.List;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class LoggingMove {
//下面的()中int 不能省略 此处中类名为接口或者实现类都可以
@Before("execution(public void com.cym.aop.MyMove.up(int))")
public void beforeMove(JoinPoint joinPoint){
String name = joinPoint.getSignature().getName();
List<Object> args = Arrays.asList(joinPoint.getArgs());
System.out.println("I'm ready to move " + name +" " + args);
}
}
main类
package com.cym.aop;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Client {
public static void main( String[] args )
{
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext2.xml");
Move move = (Move) ctx.getBean("myMove");
move.up(3);
move.down(2);
}
}
然后是配置文件
<?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:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd ">
<context:component-scan base-package="com.cym.aop"></context:component-scan>
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>
其实配置文件重点就两行
<context:component-scan base-package="com.cym.aop"></context:component-scan>
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
结果是
I'm ready to move up [3]
I'm moving up 3 steps!
I'm moving down 2 steps!
上面是只将切面设置为up方法。也可以使用下面的配置,从而将切面设置为整个类中的方法
@Before("execution(public void com.cym.aop.MyMove.*(int))")
结果如下
I'm ready to move up [3]
I'm moving up 3 steps!
I'm ready to move down [2]
I'm moving down 2 steps!
在自己修改的时候进行配置时候的错误:
The prefix "context" for element "context:component-scan" is not bound.
这里可以参考http://blog.csdn.net/ytdxyhz/article/details/51520430
好了,先这样吧,实际的应用千变万化,这里写的只是1+1=2~实际项目中你会发现你需要做的是微积分~~