本文最后更新于:June 3, 2022 pm
Spring 是目前主流的 Java Web 开发框架,是 Java 世界最为成功的框架。Spring框架是由于软件开发的复杂性而创建的。Spring使用的是基本的JavaBean来完成以前只可能由EJB完成的事情。然而,Spring的用途不仅仅限于服务器端的开发。从简单性、可测试性和松耦合性角度而言,绝大部分Java应用都可以从Spring中受益。该框架是一个轻量级的开源框架,具有很高的凝聚力和吸引力。Spring 框架不局限于服务器端的开发。从简单性、可测试性和松耦合的角度而言,任何 Java 应用都可以从 Spring 中受益。Spring 框架还是一个超级粘合平台,除了自己提供功能外,还提供粘合其他技术和框架的能力。
目录 环境准备 使用AOP,需要添加一个依赖:
<dependency > <groupId > org.aspectj</groupId > <artifactId > aspectjweaver</artifactId > <version > 1.9.8.RC3</version > </dependency >
UserService.java
package com.tothefor.aopTest;public interface UserService { public void add () ; public void delete () ; public void update () ; public void query () ; }
UserServiceImpl.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 package com.tothefor.aopTest;public class UserServiceImpl implements UserService { @Override public void add () { System.out.println("增加-了一个用户" ); } @Override public void delete () { System.out.println("删除-了一个用户" ); } @Override public void update () { System.out.println("修改-了一个用户" ); } @Override public void query () { System.out.println("查询-了一个用户" ); } }
现在有一个需求:添加打印日志业务,但不能修改原有业务逻辑。
实现方式一 使用Spring的原生API接口。Spring中支持5种类型的Advice:
在方法前输出日志,LogBefore.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 package com.tothefor.aopTest;import org.springframework.aop.MethodBeforeAdvice;import java.lang.reflect.Method;public class LogBefore implements MethodBeforeAdvice { @Override public void before (Method method, Object[] args, Object target) throws Throwable { System.out.println("LogBefore: " +target.getClass().getName()+"的" +method.getName()+"方法,执行了" ); } }
在方法后输出日志,LogAfter.java
package com.tothefor.aopTest;import org.springframework.aop.AfterReturningAdvice;import java.lang.reflect.Method;public class LogAfter implements AfterReturningAdvice { @Override public void afterReturning (Object returnValue, Method method, Object[] args, Object target) throws Throwable { System.out.println("LogAfter: " +"执行了" +method.getName()+"方法,返回结果为" +returnValue); } }
配置文件:
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 <?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 https://www.springframework.org/schema/aop/spring-aop.xsd" > <bean id ="userService" class ="com.tothefor.aopTest.UserServiceImpl" /> <bean id ="logBefore" class ="com.tothefor.aopTest.LogBefore" /> <bean id ="logAfter" class ="com.tothefor.aopTest.LogAfter" /> <aop:config > <aop:pointcut id ="pointcut" expression ="execution(* com.tothefor.aopTest.UserServiceImpl.*(..))" /> <aop:advisor advice-ref ="logBefore" pointcut-ref ="pointcut" /> <aop:advisor advice-ref ="logAfter" pointcut-ref ="pointcut" /> </aop:config > </beans >
解释
首先,需要配置bean,包括在哪一个类(方法)切入,切入哪一个类(方法)。
<bean id ="userService" class ="com.tothefor.aopTest.UserServiceImpl" /> <bean id ="logBefore" class ="com.tothefor.aopTest.LogBefore" /> <bean id ="logAfter" class ="com.tothefor.aopTest.LogAfter" />
然后配置aop,但需要先导入aop的约束:(头部加入)
xmlns:aop="http://www.springframework.org/schema/aop" http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd
然后再配置切入点
<aop:config > <aop:pointcut id ="pointcut" expression ="execution(* com.tothefor.aopTest.UserServiceImpl.*(..))" /> </aop:config >
execution(访问权限 方法返回值 方法声明(参数) 异常类型) 可以简化为:execution(方法返回值 方法声明(参数))
上面这表示切入点是:com.tothefor.aopTest.UserServiceImpl.*(..)
。UserServiceImpl类中的所有方法。后面的(..)
表示方法的参数,这里写的是两个点(..
),表示任意参数。前面的星号(*)
表示所有的方法,这里的星号也可以改成固定的方法名。最前面的一个星号表示修饰符任意。
可以配置多个切入点。
既然在哪儿个位置切入(插入)确定,现在就应该配置(确定)应该切入(插入)什么了,
<aop:config > <aop:pointcut id ="pointcut" expression ="execution(* com.tothefor.aopTest.UserServiceImpl.*(..))" /> <aop:advisor advice-ref ="logBefore" pointcut-ref ="pointcut" /> <aop:advisor advice-ref ="logAfter" pointcut-ref ="pointcut" /> </aop:config >
advice-ref
表示应该切入(插入)什么,而pointcut-ref
表示在哪一个切入点切入。
切入点表达式别名
2022年6月3日
当有多个切入点相同时,可以使用别名。例如:
@Aspect public class aspectJAOP { @Before(value = "cutM()") public void log () { System.out.println("这里是附加信息" ); } @Pointcut(value = "execution(* *(..))") public void cutM () {} }
写一个空方法,使用@Pointcut
来设置切入点表达式,方法的名称即为别名。见上面代码示例。
测试 public static void main (String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml" ); UserService userService = (UserService) context.getBean("userService" ); userService.add(); }
注意:动态代理的是接口!不能写类!
输出:
LogBefore: com.tothefor.aopTest.UserServiceImpl的add方法,执行了 增加-了一个用户 LogAfter: 执行了add方法,返回结果为null
实现方式二(自定义类) 通过自定义类实现AOP。这种方式就是将所有的要插入的东西都写在一个类中,然后这个类就形成一个切入面。
Log.java
package com.tothefor.aopTest02;public class Log { public void before () { System.out.println("插在了前面" ); } public void after () { System.out.println("插在了后面" ); } }
配置文件
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 <?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 https://www.springframework.org/schema/aop/spring-aop.xsd" > <bean id ="userService" class ="com.tothefor.aopTest.UserServiceImpl" /> <bean id ="log" class ="com.tothefor.aopTest02.Log" /> <aop:config > <aop:aspect ref ="log" > <aop:pointcut id ="point" expression ="execution(* com.tothefor.aopTest.UserServiceImpl.*(..))" /> <aop:before method ="before" pointcut-ref ="point" /> <aop:after method ="after" pointcut-ref ="point" /> </aop:aspect > </aop:config > </beans >
其中,method需要写自定义类中的方法名。
测试 import com.tothefor.aopTest.UserService;import org.springframework.context.ApplicationContext;import org.springframework.context.support.ClassPathXmlApplicationContext;public class MyTest { public static void main (String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml" ); UserService userService = (UserService) context.getBean("userService" ); userService.add(); } }
输出:
实现方式三(注解) 也是用一个自定义类实现,只不过是使用注解的方式。
autoTest.java
package com.tothefor;import org.aspectj.lang.annotation.Aspect;import org.aspectj.lang.annotation.Before;@Aspect public class autoTest { @Before("execution(* com.tothefor.aopTest.UserServiceImpl.*(..))") public void before () { System.out.println("在方法之前执行" ); } }
@After使用同理。
配置文件
<?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 https://www.springframework.org/schema/aop/spring-aop.xsd" > <bean id ="userService" class ="com.tothefor.aopTest.UserServiceImpl" /> <bean id ="autot" class ="com.tothefor.autoTest" /> <aop:aspectj-autoproxy /> </beans >
测试 同上测试代码一样。
总结 切入分三步:
确定具体在哪个位置插入(切入)。
要插入(切入)什么。
指定关系。(谁插在谁前面或后面?)
两种方式的区别:
第一种方式中可以拿到相关方法的名称和参数等,至少和相关方法有关系。
第二种方式中就只是单纯的切入方法,和相关方法没有一点关系。