概述
AOP是aspect-oriented programming的缩写,译为面向切面编程。利用 AOP 可以对业务逻辑的各个部分进行隔离,从而使得 业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。简单来说,AOP就是不修改源代码在主干功能里面添加新功能。
底层原理
AOP底层使用了动态代理:在有接口的时候使用JDK 动态代理、在没有接口的时候使用CGLIB字节码动态代理 。
JDK动态代理
简介
使用JDK 动态代理需要用到JDK中的Proxy类里面的newProxyInstance方法创建代理对象。方法如下:
1 static Object newProxyInstance (ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) ;
newProxyInstance方法的三个参数说明:
loader 定义代理类的类加载器
interfaces 代理类要实现的接口列表(可以多个)
h 指派方法调用的处理程序(要增强的功能在这里面实现)
newProxyInstance方法返回一个指定接口的代理类实例。
InvocationHandler接口中有个invoke方法,用于实现增强的功能:
1 2 public Object invoke (Object proxy, Method method, Object[] args) throws Throwable;
invoke方法的三个参数说明:
proxy表示代理对象
method表示被增强的方法
args是方法的参数 若没有则为null
示例代码
代码结构如下:
1 2 3 4 5 6 └─src └─com └─spring5 JDKProxy.java UserDao.java UserDaoImpl.java
UserDao接口:
1 2 3 4 public interface UserDao { int add (int a,int b) ; String update (String id) ; }
UserDaoImpl类:
1 2 3 4 5 6 7 8 9 10 11 12 public class UserDaoImpl implements UserDao { @Override public int add (int a, int b) { System.out.println("I am add..." ); return a+b; } @Override public String update (String id) { return id; } }
JDKProxy类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 public class JDKProxy { public static void main (String[] args) { Class[] interfaces = {UserDao.class}; UserDaoImpl userDao = new UserDaoImpl (); UserDao dao = (UserDao)Proxy.newProxyInstance(JDKProxy.class.getClassLoader(), interfaces, new UserDaoProxy (userDao)); int result = dao.add(1 , 2 ); System.out.println("result:" +result); } } class UserDaoProxy implements InvocationHandler { private Object obj; public UserDaoProxy (Object obj) { this .obj = obj; } @Override public Object invoke (Object proxy, Method method, Object[] args) throws Throwable { System.out.println("方法之前执行...." + method.getName() + " :传递的参数..." + Arrays.toString(args)); Object res = method.invoke(obj, args); System.out.println("方法之后执行...." + obj); return res; } }
运行结果:
方法之前执行…add :传递的参数…[1, 2]
I am add…
方法之后执行…com.spring5.UserDaoImpl@355da254
result:3
CGLIB字节码动态代理
简介
使用CGLIB字节码动态代理不受代理类必须实现接口的限制,其底层采用ASM字节码生成框架。CGLIB动态代理的优缺点:
使用字节码技术生产代理类比JAVA反射效率高
不能对声明为final的类和方法进行代理,因为其原理是动态生成被代理类的子类
需要实现接口MethodInterceptor,然后重写intercept方法:
1 Object intercept (Object proxy, Method method, Object[] args, MethodProxy arg3) throws Throwable;
intercept方法的参数说明:
proxy CGLIB生成的代理类实例 ,也是目标对象的子类
相当于重写父类方法
method 被代理方法
args 方法参数
为生成的代理类对方法的代理引用
intercept方法返回
参考链接
另外用到了Enhancer类 ,它是Cglib中的一个字节码增强器,先调它的setSuperclass()将被代理类设置成父类、再调setCallback函数执行intercept方法,最后调create()生成代理类。
示例代码
代码结构如下:
1 2 3 4 5 └─src └─com └─spring5 CglibProxy.java User.java
User类:
1 2 3 4 5 public class User { public void sleep () { System.out.println("我想睡觉..." ); } }
CglibProxy类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 public class CglibProxy { public static void main (String[] args) { UserProxy userProxy = new UserProxy (); User base=(User) userProxy.getProxy(new User ()); base.sleep(); } } class UserProxy implements MethodInterceptor { public Object getProxy (Object object) { Enhancer e=new Enhancer (); e.setSuperclass(object.getClass()); e.setCallback(this ); return e.create(); } @Override public Object intercept (Object proxy, Method method, Object[] args, MethodProxy arg3) throws Throwable { System.out.println("睡觉前脱衣服" ); Object object = arg3.invokeSuper(proxy, args); System.out.println("起床穿衣服" ); return object; } }
运行结果:
睡觉前脱衣服
我想睡觉…
起床穿衣服
AOP操作
概述
AOP相关的几个术语:
连接点
类里面哪些方法可以被增强,这些方法称为连接点
切入点
实际被真正增强的方法称为切入点
通知
实际增强的逻辑部分称为通知,分为前置通知、后置通知、环绕通知、异常通知和最终通知五种类型,其中最终通知相当于JAVA的finally 。
切面
把通知应用到切入点过程
AspectJ
AspectJ不是 Spring 组成部分,是一个独立的AOP 框架,一般把 AspectJ 和 Spirng 框架一起使 用,进行 AOP操作。增强就是代理的意思 。
准备工作
在进行AOP操作的时候需要先引入下面四个Jar包
1 2 3 4 com.springsource.net.sf.cglib-2.2 .0 .jar com.springsource.org.aopalliance-1.0 .0 .jar com.springsource.org.aspectj.weaver-1.6 .8 .RELEASE.jar spring-aspects-5.2 .6 .RELEASE.jar
引入包后的所有包如下:
com.springsource.net.sf.cglib-2.2.0.jar
com.springsource.org.aopalliance-1.0.0.jar
com.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar
spring-aspects-5.2.6.RELEASE.jar
commons-logging-1.1.1.jar
spring-aop-5.2.6.RELEASE.jar
spring-beans-5.2.6.RELEASE.jar
spring-context-5.2.6.RELEASE.jar
spring-core-5.2.6.RELEASE.jar
spring-expression-5.2.6.RELEASE.jar
AspectJ的切入点表达式语法说明
1 execution([权限修饰符] [返回类型] [类全路径] [方法名称]([参数列表]) )
1 execution (* com.sample.service.impl..*. *(..))
1、execution(): 表达式主体。
2、第一个*号:表示返回类型, 号表示所有的类型。
3、包名:表示需要拦截的包名,后面的两个句点表示当前包和当前包的所有子包,com.sample.service.impl包、子孙包下所有类的方法。
4、第二个 号:表示类名,号表示所有的类。
5、 (…):最后这个星号表示方法名,*号表示所有的方法,后面括弧里面表示方法的参数,两个句点表示任何参数
1 2 3 4 5 6 7 8 9 10 execution(* com.dao.BookDao.add(..)) execution(* com.dao.BookDao.add(..)) || excution(* com.dao.BookDao.delete(..)) @AfterReturning(value = "execution(* com.dao.BookDao.add(..)) || execution(* com.dao.BookDao.delete(..))") execution(* com.dao.BookDao.* (..)) execution(* com.dao.*.* (..))
基于注解实现
主要步骤
主要步骤如下:
在Spring配置文件中,开启注解扫描
需要在XML中引入context和aop的上下文空间。
使用注解创建 User 和 UserProxy 对象
在增强类上面添加注解 @Aspect
在Spring配置文件中开启生成代理对象
1 <aop:aspectj-autoproxy > </aop:aspectj-autoproxy >
开启Aspectj生成对象后,会去代码中扫描@aspect注解
配置不同类型的通知
在通知方法 上面使用@Before、@AfterReturning、@Around、@AfterThrowing和@After注解,结合切入点表达式配置。
@after是在方法执行之后执行(有异常也执行),@afterReturning是在返回值之后执行(有异常不执行)。
示例代码
代码结构:
1 2 3 4 5 6 7 8 9 └─src │ bean.xml │ └─com └─spring5 └─aopanno Test.java User.java UserProxy.java
User类:
1 2 3 4 5 6 7 @Component public class User { public void add () { int a = 10 /0 ; System.out.println("I am add" ); } }
UserProxy类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 @Component @Aspect public class UserProxy { @Before(value = "execution(* com.spring5.aopanno.User.add(..))") public void before () { System. out .println( "前置通知 before" ); } @AfterReturning(value = "execution(* com.spring5.aopanno.User.add(..))") public void afterReturning () { System.out.println("后置通知(返回通知)afterReturning" ); } @After(value = "execution(* com.spring5.aopanno.User.add(..))") public void after () { System.out.println("最终通知 after" ); } @AfterThrowing(value = "execution(* com.spring5.aopanno.User.add(..))") public void afterThrowing () { System.out.println("异常通知 afterThrowing" ); } @Around(value = "execution(* com.spring5.aopanno.User.add(..))") public void around (ProceedingJoinPoint proceedingJoinPoint) throws Throwable { System.out.println("Around 环绕之前" ); proceedingJoinPoint.proceed(); System.out.println("Around 环绕之后" ); } }
Test类:
1 2 3 4 5 6 7 8 9 public class Test { @org .junit.Test public void testAopAnno () { ApplicationContext context = new ClassPathXmlApplicationContext ("bean.xml" ); User user = context.getBean("user" ,User.class); user.add(); } }
无异常返回结果:
Around 环绕之前
前置通知 before
I am add
Around 环绕之后
最终通知 after
后置通知(返回通知)afterReturning
有异常时返回结果:
Around 环绕之前
前置通知 before
最终通知 after
异常通知 afterThrowing
java.lang.ArithmeticException: / by zero
相同切入点提取
用@Pointcut标签
1 2 3 4 5 6 7 8 9 10 11 12 13 14 @Pointcut(value = "execution(* com.spring5.aopanno.User.*(..))") public void pointTest () {} @Before(value = "pointTest()") public void before () { System. out .println( "前置通知 before" ); } @AfterReturning(value = "pointTest()") public void afterReturning () { System.out.println("后置通知(返回通知)afterReturning" ); }
多个增强类对同一个方法进行增强
用@Order注解设置增强类优先级,数字类型值越小表示优先级越高。
1 2 3 4 @Component @Aspect @Order(1) public class PersonProxy { }
完全注解开发
在启动配置类中添加@EnableAspectJAutoProxy注解:
1 2 3 4 5 @Configuration @ComponentScan(basePackages = { "com.spring5"}) @EnableAspectJAutoProxy(proxyTargetClass = true) public class ConfigAop {}
基于配置文件实现
具体步骤
创建增强类和被增强类,创建相关方法
在Spring配置文件中配置两个类对象
在Spring配置文件中配置AOP
示例代码
代码结构如下:
1 2 3 4 5 6 7 8 9 └─src │ bean.xml │ └─com └─spring5 └─aopxml Student.java StudentProxy.java Test.java
Student类:
1 2 3 4 5 public class Student { public void buy () { System.out.println("I want to buy a book..." ); } }
StudentProxy类:
1 2 3 4 5 6 7 8 public class StudentProxy { public void before () { System.out.println("I am before..." ); } public void afterReturn () { System.out.println("I am afterReturn..." ); } }
Test类:
1 2 3 4 5 6 7 8 9 public class Test { @org .junit.Test public void testAopXml () { ApplicationContext context = new ClassPathXmlApplicationContext ("bean.xml" ); Student student = context.getBean("student" ,Student.class); student.buy(); } }
bean.xml:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 <?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 ="student" class ="com.spring5.aopxml.Student" > </bean > <bean id ="studentProxy" class ="com.spring5.aopxml.StudentProxy" > </bean > <aop:config > <aop:pointcut id = "p" expression = "execution(* com.spring5.aopxml.Student.buy(..))" /> <aop:aspect ref = "studentProxy" > <aop:before method = "before" pointcut-ref = "p" /> </aop:aspect > <aop:aspect ref = "studentProxy" > <aop:after-returning method = "afterReturn" pointcut-ref = "p" /> </aop:aspect > </aop:config > </beans >
运行结果:
I am before…
I want to buy a book…
I am afterReturn…
AspectJ获取方法参数和返回值
示例代码:
1 2 3 4 5 6 7 8 9 10 11 12 @AfterReturning(value = "execution(* com.xxxImpl.doBusiness(..))") private void secondProposition (JoinPoint joinPoint) { PropositionApprovalBo bo = (PropositionApprovalBo) joinPoint.getArgs()[1 ]; } @AfterReturning(value = "execution(* com.xxxImpl.addPropositionList(..))", returning="returnValue") public void addPropositionList (JoinPoint joinPoint, Object returnValue) { List<PropositionCollect> propositionCollectList = (List<PropositionCollect>) returnValue; }
只使用返回值时,joinPoint可以省略。
AOP失效场景
1、类内部调用被代理方法,此时被代理方法并不会被增强。
2、被代理方法非public修饰 (未验证,最好改为public)。
示例代码:
1 2 3 4 5 6 7 8 @Service public class PropositionManageServiceImpl implements PropositionManageService { public void BatchImport (MultipartFile file, ISessionUser sessionUser) { this .addPropositionList(); } public List<PropositionCollect> addPropositionList () { } }
由于addPropositionList方法是在PropositionManageServiceImpl类内部调用的,所以AOP代理会失效。不会执行这个切面方法。
1 2 3 4 5 @AfterReturning(value = "execution(* com.xxxImpl.addPropositionList(..))", returning="returnValue") public void addPropositionList (JoinPoint joinPoint, Object returnValue) { List<PropositionCollect> propositionCollectList = (List<PropositionCollect>) returnValue; }
解决方法:使用service调用需要代理的方法。
1 2 3 4 5 6 7 8 9 10 @Service public class PropositionManageServiceImpl implements PropositionManageService { @Autowired private PropositionManageService propositionManageService; public void BatchImport (MultipartFile file, ISessionUser sessionUser) { propositionManageService.addPropositionList(); } public List<PropositionCollect> addPropositionList () { } }
Spring AOP切面方法出现异常,会影响主程序运行的解决方法
采用@AfterReturning,在主程序走完后执行
try catch住异常代码,并且不要抛出新异常
第二步是必须的。
Spring AOP切面中启用新事务 (可以用@After注解代替)
Spring AOP切面中启用新事务,解决不管主程序是否回滚,都会执行切面方法(比如记录日志) 。
因为@Transactional也是声明式事务,本身就是AOP实现的,在AOP的代码中使用不起作用。
所以就只能使用spring的编程式事务了,需要引入TransactionTemplate。
示例代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 @Autowired private TransactionTemplate transactionTemplate;@AfterReturning(value = "execution(* com.rewardinnovation..addAchievementApplication(..))") public void addAchievement (JoinPoint joinPoint) { log.info("AchievementAspect.addAchievement 开始执行新增成果切面逻辑,joinPoint:{}" ,joinPoint); transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW); transactionTemplate.execute(new TransactionCallback <T>() { @Override public T doInTransaction (TransactionStatus transactionStatus) { return null ; } }); }
这种方式只适合于"主程序异常但需要切面正常执行"的场景。完全可以使用@After注解代替
参考链接