一、理解:
AOP(Aspect Oriented Programming),即面向切面编程,可以说是OOP(Object Oriented Programming,面向对象编程)的补充和完善。
OOP引入封装、继承、多态等概念来建立一种对象层次结构,用于模拟公共行为的一个集合。
AOP技术恰恰相反,它利用一种称为"横切"的技术,剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其命名为"Aspect",即切面。所谓"切面",简单说就是那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块之间的耦合度,并有利于未来的可操作性和可维护性。
使用"横切"技术,AOP把软件系统分为两个部分:核心关注点和横切关注点。业务处理的主要流程是核心关注点,与之关系不大的部分是横切关注点。横切关注点的一个特点是,他们经常发生在核心关注点的多处,而各处基本相似,比如权限认证、日志、事物。AOP的作用在于分离系统中的各种关注点,将核心关注点和横切关注点分离开来。
二、面向切面编程:
(1)通知(增强)Advice
通知定义了切面是什么以及何时使用,应该应用在某个方法被调用之前?之后?还是抛出异常时?等等。
(2)连接点 Join point
连接点是在应用执行过程中能够插入切面的一个点。这个点可以是调用方法时,抛出异常时,甚至修改一个字段时。切面代码可以利用这些点插入到应用的正常流程中,并添加新的行为。
(3)切点 Pointcut
切点有助于缩小切面所通知的连接点的范围。如果说通知定义了切面的“什么”和“何时”的话,那么切点就定义了“何处”,切点会匹配通知所要织入的一个或多个连接点,一般常用正则表达式定义所匹配的类和方法名称来指定这些切点。
(4)切面 Aspect
切面是通知和切点的结合。通知和切点定义了切面的全部内容——它是什么,在何时何处完成其功能。
(5)引入 Introduction
引入允许我们向现有的类添加新方法或属性,从而无需修改这些现有类的情况下,让他们具有新的行为和状态。
(6)织入 Weaving
在过去我常常把织入与引入的概念混淆,我是这样来辨别的,“引入”我把它看做是一个定义,也就是一个名词,而“织入”我把它看做是一个动作,一个动词,也就是切面在指定的连接点被织入到目标对象中。
总结一下:
通知包含了需要用于多个应用对象的横切行为;连接点是程序执行过程中能够应用通知的所有点;切点定义了通知被应用的具体位置(在哪些连接点)。其中关键的概念是切点定义了哪些连接点会得到通知(增强)。创建切点来定义切面所织入的连接点是AOP框架的基本功能。
另外,Spring是基于动态代理的,所以Spring只支持方法连接点,而像AspectJ和JBoss除了方法切点,它们还提供字段和构造器接入点。如果需要方法拦截之外的连接点拦截功能,则可以利用AspectJ来补充SpringAOP的功能。
三、在Spring中的使用方式:
注:如果spring配置文件xml中存在
<!-- 扫描注解组件并且自动的注入spring beans中. 例如,他会扫描@Controller 和@Service下的文件.所以确保此base-package设置正确. -->
<!-- 扫描controller(controller层注入) -->
<context:component-scan base-package="com.spring"/>
则在配置beans.xml时不需要再添加
<bean id="userService" class="com.spring.service.impl.UserServiceImpl"/>
测试时
package com.spring.test; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import com.spring.service.UserService; public class Test {
@Resource
private UserService userService; public static void main(String[] args) { userService.update(2); userService.add(); } }
1、通过Spring的API实现AOP:
第一步:
public interface UserService { public void add(); public void update(int a); public void delete(); public void search(); }
第二步:
public class UserServiceImpl implements UserService { @Override public void add() { System.out.println("增加用户"); } @Override public void update(int a) { System.out.println("修改用户"); } @Override public void delete() { System.out.println("删除用户"); } @Override public void search() { System.out.println("查询用户"); }
第三步:实现MethodBeforeAdvice,AfterReturningAdvice的接口,Spring框架当中为我们提供了很多中通知。
public class Log implements MethodBeforeAdvice{ /** * @param method 被调用方法对象 * @param args 被调用的方法的参数 * @param target 被调用的方法的目标对象 * */ @Override public void before(Method method, Object[] args, Object target) throws Throwable { System.out.println(target.getClass().getName()+"的"+method.getName()+"方法被执行"); } }
public class AfterLog implements AfterReturningAdvice{ /** * 目标方法执行后执行的通知 * returnValue--返回值 * method 被调用的方法对象 * args 被调用的方法对象的参数 * target 被调用的方法对象的目标对象 * */ @Override public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable { System.out.println(target.getClass().getName()+"的"+method.getName()+"被成功执行,返回值是:"+returnValue); } }
import java.lang.reflect.Method; import org.springframework.aop.ThrowsAdvice; public class ExceptionLog implements ThrowsAdvice { public void afterThrowing(Method method,Exception ex) throws Throwable { } }
第四步:配置beans.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:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <bean id="userService" class="com.spring.service.impl.UserServiceImpl"/> <!-- 这个切面也要配置成bean--> <bean id="log" class="com.spring.advice.Log"/><bean id="afterLog" class="com.spring.advice.AfterLog"></bean>
<aop:config>
<!--切入点,需要告诉方法在什么去执行 expression="execution(* com.spring.service.impl.*.*(..))" 第一个* 表示所有的返回值,然后就是包名 第二个*表示所有的类对象 第三个*表示类对象所有的方法 第四个*表示所有方法下面的带参数的方法或者是不带参数的方法 --> <aop:pointcut expression="execution(* com.spring.service.impl.*.*(..))" id="pointcut"/> <!-- 在所有的方法中都切入前置通知--> <aop:advisor advice-ref="log" pointcut-ref="pointcut"/><aop:advisor advice-ref="afterLog" pointcut-ref="pointcut"/>
</aop:config>
</beans>
第五步:测试:
package com.spring.test; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import com.spring.service.UserService; public class Test { public static void main(String[] args) { ApplicationContext ac = new ClassPathXmlApplicationContext("beans.xml"); UserService userService = (UserService)ac.getBean("userService"); userService.update(2); userService.add(); } }
第二种方式:自定义类来实现AOP,不实现spring的自带的通知
第一步:重新通知
public class Log { public void before(){ System.out.println("方法执行前"); } public void after(){ System.out.println("方法执行后"); } }
第二步:重新写配置文件
<?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" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <bean id="userService" class="com.spring.service.impl.UserServiceImpl"/> <!-- 这个切面也要配置成bean--> <bean id="log" class="com.spring.advice.Log"/> <aop:config> <!--切入点,需要告诉方法在什么去执行 expression="execution(* com.spring.service.impl.*.*(..))" 第一个* 表示所有的返回值,然后就是包名 第二个*表示所有的类对象 第三个*表示类对象所有的方法 第四个*表示所有方法下面的带参数的方法或者是不带参数的方法 --> <aop:aspect ref="log"> <aop:pointcut expression="execution(* com.spring.service.impl.*.*(..))" id="pointcut"/> <aop:before method="before" pointcut-ref="pointcut"/> <aop:after method="after" pointcut-ref="pointcut"/> </aop:aspect> </aop:config> </beans>
第三种方式:通过注解实现AOP
第一步:修改log
package com.spring.advice; import java.lang.reflect.Method; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.After; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.springframework.aop.MethodBeforeAdvice; @Aspect public class Log { @Before("execution(* com.spring.service.impl.*.*(..))") public void before(){ System.out.println("方法执行前"); } @After("execution(* com.spring.service.impl.*.*(..))") public void after(){ System.out.println("方法执行后"); } @Around("execution(* com.spring.service.impl.*.*(..))") public Object around(ProceedingJoinPoint jp) throws Throwable{ System.out.println("环绕前"); System.out.println("方法"+jp.getSignature()); Object result=jp.proceed(); System.out.println("环绕后"); return result; } }
第二步:修改beans.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:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <bean id="userService" class="com.spring.service.impl.UserServiceImpl"/> <!-- 这个切面也要配置成bean--> <bean id="log" class="com.spring.advice.Log"/> <aop:aspectj-autoproxy/> </beans>