首先看一下Spring AOP的相关知识
1)通知(Advice):
定义了切面是什么以及何时使用,描述切面要完成的工作和何时需要执行这个工作
2)连接点(JOINPOINT)
程序能够应用通知的一个时机,这些时机就是连接点,例如方法被调用时,异常被抛出时。
3)切入点(PointCut)
通知定义了切面要发生的故事和时间,那么切入点则通知了故事发生的地点,例如某个类或某个方法
4)切面(Aspect)
通知和切点共同定义了切面:时间,地点和要发生的故事
5)引入(Introduction)
引入运行我们向现有的类添加新的方法(Spring提供了一个方法注入的功能)
6)代理(Proxy)
应用通知的对象
7)织入(Weaving)
8)目标(Target)
目标,即被通知的对象,如果没有AOP,那么它的逻辑将要交叉别的事务逻辑,有了AOP之后它可以只关注自己要做的事。
Spring Aop的核心技术其实也是动态代理,动态代理主要的作用是方法的增强,在一些业务限制中,我们可能不能改变某些方法的签名以及方法体内的原有代码,我们就可以动态代理的方法增强方法,可以在方法执行前后做任何你想做的事情(甚至根本不去执行这个方法),因为在InvocationHandler的invoke方法中,你可以直接获取正在调用方法对应的Method对象,具体应用的话,比如可以添加调用日志,做事务控制等。因为在InvocationHandler的invoke方法中,你可以直接获取正在调用方法对应的Method对象,具体应用的话,比如可以添加调用日志,做事务控制等。
对于JDK动态代理,只能针对接口实现动态代理,无法对于类直接实现动态代理,这是由于java单继承特性留下的天然桎梏。但是对于cglib代理来说,cglib直接改变字节码,可以直接对类进行动态代理。直接改变字节码,打破了java无法对类实现动态代理的天然限制。
下面再解释下AOP
AOP(Aspect Oriented Programming),即面向切面编程,可以说是OOP(Object Oriented Programming,面向对象编程)的补充和完善。OOP引入封装、继承、多态等概念来建立一种对象层次结构,用于模拟公共行为的一个集合。不过OOP允许开发者定义纵向的关系,但并不适合定义横向的关系,例如日志功能。日志代码往往横向地散布在所有对象层次中,而与它对应的对象的核心功能毫无关系对于其他类型的代码,如安全性、异常处理和透明的持续性也都是如此,这种散布在各处的无关的代码被称为横切(cross cutting),在OOP设计中,它导致了大量代码的重复,而不利于各个模块的重用。
AOP技术恰恰相反,它利用一种称为”横切”的技术,剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其命名为”Aspect”,即切面。所谓”切面”,简单说就是那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块之间的耦合度,并有利于未来的可操作性和可维护性。
使用”横切”技术,AOP把软件系统分为两个部分:核心关注点和横切关注点。业务处理的主要流程是核心关注点,与之关系不大的部分是横切关注点。横切关注点的一个特点是,他们经常发生在核心关注点的多处,而各处基本相似,比如权限认证、日志、事物。AOP的作用在于分离系统中的各种关注点,将核心关注点和横切关注点分离开来。
二.实现AOP有两种方式,一种是全注解,一种是用配置文件实现。
1)首先是全注解方式
先看配置文件
<?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:p="http://www.springframework.org/schema/p"
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.5.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="wangcc"></context:component-scan>
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>
在使用Spring实现注解时,必须添加Spring-context.jar,
<context:component-scan base-package="wangcc"></context:component-scan>
base-package=”wangcc”代表wangcc这个包以及旗下的包里的注解都会被扫描 aop命名空间的声明自动为spring容器中那些配置@aspectJ切面的bean创建代理,织入切面。当然,spring在内部依旧采用AnnotationAwareAspectJAutoProxyCreator进行自动代理的创建工作,但具体实现的细节已经被隐藏起来了 有一个proxy-target-class属性,默认为false,表示使用jdk动态代理织入增强,当配为时,表示使用CGLib动态代理技术织入增强。不过即使proxy-target-class设置为false,如果目标类没有声明接口,则spring将自动使用CGLib动态代理。
再看AOP实现类
package wangcc.aop;
import java.util.Arrays;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
/**
* @ClassName: AroundTest
* @Description: TODO(这里用一句话描述这个类的作用)
* @author wangcc
* @date 2017-1-6 下午1:33:19
** Before 和After属于消极处理,他不会对Pointcut切入点方法有影响,典型的用于记录日志
* 只是在方法调用前后进行一些处理,不会影响方法中的逻辑体本身
* 而Around是积极处理,他可以影响方法中的逻辑处理,切人点方法的处理与否由Around通知来决定。
*/
@Aspect
@Component
public class AroundTest {
@Pointcut("execution(* wangcc.service..*.update(..))")
public void test(){};
@Before("test()")
public void authority(JoinPoint jp){
System.out.println("Before增强:模拟执行权限检查");
// 返回被织入增强处理的目标方法
System.out.println("Before增强:被织入增强处理的目标方法为:"
+ jp.getSignature().getName());
// 访问执行目标方法的参数
System.out.println("Before增强:目标方法的参数为:"
+ Arrays.toString(jp.getArgs()));
// 访问被增强处理的目标对象
System.out.println("Before增强:被织入增强处理的目标对象为:"
+ jp.getTarget());
}
@Around("test()")
public Object doTranscation(ProceedingJoinPoint pjp) throws Throwable{
System.out.println("Around增强:执行目标方法之前,模拟开始事务...");
Object[] args=pjp.getArgs();
if(args!=null&&args[0].getClass()==Integer.class)
{
args[0]=(Integer)args[0]+10;
}
Object id=pjp.proceed(args);
System.out.println("Around增强:执行目标方法之后,模拟结束事务...");
if(id!=null&&id instanceof Integer)
{
id=(Integer) id* (Integer) id;
}
return id;
}
@After("test()")
public void release(JoinPoint jp)
{
System.out.println("After增强:模拟方法结束后的释放资源...");
// 返回被织入增强处理的目标方法
System.out.println("After增强:被织入增强处理的目标方法为:"
+ jp.getSignature().getName());
// 访问执行目标方法的参数
System.out.println("After增强:目标方法的参数为:"
+ Arrays.toString(jp.getArgs()));
// 访问被增强处理的目标对象
System.out.println("After增强:被织入增强处理的目标对象为:"
+ jp.getTarget());
}
@AfterReturning(pointcut="test()",returning="retVal")
public void log(JoinPoint jp,Object retVal){
System.out.println("AfterReturning增强:获取目标方法返回值:"
+ retVal);
System.out.println("AfterReturning增强:模拟记录日志功能...");
// 返回被织入增强处理的目标方法
System.out.println("AfterReturning增强:被织入增强处理的目标方法为:"
+ jp.getSignature().getName());
// 访问执行目标方法的参数
System.out.println("AfterReturning增强:目标方法的参数为:"
+ Arrays.toString(jp.getArgs()));
// 访问被增强处理的目标对象
System.out.println("AfterReturning增强:被织入增强处理的目标对象为:"
+ jp.getTarget());
}
}
业务代码
package wangcc.service;
import org.springframework.stereotype.Component;
@Component("userService")
public class UserService {
public void addUser(){
System.out.println("add user");
}
public Integer update(int id){
Integer realId=id+1;
return realId;
}
public Integer insertUser(){
System.out.println("insert user");
return 20;
}
}
``
测试代码
package wangcc.test;
import org.junit.Test;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import wangcc.service.UserService;
public class TestAspectJ {
@Test
public void testUpdate(){
ClassPathXmlApplicationContext ctx=new ClassPathXmlApplicationContext("applicationContext.xml");
UserService userService=(UserService) ctx.getBean("userService");
userService.update(10);
}
@Test
public void testAdd(){
ClassPathXmlApplicationContext ctx=new ClassPathXmlApplicationContext("applicationContext.xml");
UserService userService=(UserService) ctx.getBean("userService");
userService.addUser();
}
@Test
public void testInsert(){
ClassPathXmlApplicationContext ctx=new ClassPathXmlApplicationContext("applicationContext.xml");
UserService userService=(UserService) ctx.getBean("userService");
userService.insertUser();
}
}
2)使用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"
xmlns:p="http://www.springframework.org/schema/p"
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.5.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<bean id="userManager" class="wangcc.dao.UserManagerImpl">
</bean>
<bean id="xmlHandler" class="wangcc.aop.AdviseHandler"></bean>
<aop:config>
<aop:aspect id="aspect" ref="xmlHandler">
<aop:pointcut expression="execution(* wangcc.dao.*.find*(..))" id="pointUser"/>
<!-- 注意 before after around 三者配置的先后顺序将会影响程序的执行顺序
以pjp.proceed();为界
当before after around 顺序时
after 对应函数会在pjp.proceed();之后的代码块之前执行
当before around after 顺序时
正常流转
当after before around 顺序时
after 对应函数会在pjp.proceed();之后的代码块之前执行
当after around before 顺序时
before 对应函数会在pjp.proceed();执行之前执行
before 对应函数会在pjp.proceed();之后的代码块执行之前执行
-->
<aop:after method="doAfter" pointcut-ref="pointUser"/>
<aop:around method="doAround" pointcut-ref="pointUser"/>
<aop:before method="doBefore" pointcut-ref="pointUser"/>
<aop:after-returning method="doAfterReturn" pointcut-ref="pointUser"/>
<aop:after-throwing method="doAfterThrow" pointcut-ref="pointUser" throwing="ex"/>
</aop:aspect>
</aop:config>
</beans>
aop实现类
package wangcc.aop;
import java.util.Arrays;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
public class AdviseHandler {
private void doBefore(JoinPoint jp){
System.out.println("-----doBefore().invoke-----");
System.out.println(" 此处意在执行核心业务逻辑前,做一些安全性的判断等等");
System.out.println(" 可通过joinPoint来获取所需要的内容");
System.out.println("目标方法的参数列表:"+Arrays.toString(jp.getArgs()));
System.out.println(":被织入处理的目标对象为"+jp.getTarget());
System.out.println("Before增强:被织入增强处理的目标方法为:"
+ jp.getSignature().getName());
System.out.println("-----End of doBefore()------");
}
private void doAfter(JoinPoint jp){
System.out.println("--doAfter().invoke--");
System.out.println(" 此处意在执行核心业务逻辑之后,做一些日志记录操作等等");
System.out.println(" 可通过joinPoint来获取所需要的内容");
System.out.println("-----End of doAfter()------");
}
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;
}
private void doAfterReturn(JoinPoint jp){
System.out.println("-----doReturn().invoke-----");
System.out.println(" 此处可以对返回值做进一步处理");
System.out.println(" 可通过joinPoint来获取所需要的内容");
System.out.println("-----End of doReturn()------");
}
private void doAfterThrow(JoinPoint jp,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()------");
}
}
业务代码
package wangcc.dao;
public interface UserManager {
public String findUserById(int id);
}
package wangcc.dao;
public class UserManagerImpl implements UserManager {
@Override
public String findUserById(int id) {
// TODO Auto-generated method stub
System.out.println("---------UserManagerImpl.findUserById()--------");
if (id <= 0) {
throw new IllegalArgumentException("该用户不存在!");
}
return "Kobe Bryant";
}
}
package wangcc.service;
import org.springframework.stereotype.Component;
@Component("userService")
public class UserService {
public void addUser(){
System.out.println("add user");
}
public Integer update(int id){
Integer realId=id+1;
return realId;
}
public Integer insertUser(){
System.out.println("insert user");
return 20;
}
}
测试代码
package wangcc.test;
import org.junit.Test;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import wangcc.dao.UserManager;
import wangcc.service.UserService;
public class TestAspectJ {
@Test
public void test(){
BeanFactory factory=new ClassPathXmlApplicationContext("applicationContext.xml");
UserManager userManager=(UserManager) factory.getBean("userManager");
userManager.findUserById(1);
System.out.println("---------分割线---------");
try {
userManager.findUserById(0);
} catch (IllegalArgumentException e) {
// TODO: handle exception
}
}
}