6.1 aop的概念
6.1.1
aop里面切面、切点的定义什么的我就不说,网上多如牛毛,我就记录一下自己对aop概念和流程的理解吧。
spring里的切面编程,浅显的讲就是你在调用某个方法的时候,程序会自动先执行某个方法,执行完你调用的方法之后再又自动的执行某个方法。
这样就完成了一次切面编程,其实过程很简单。
假设我们调用了A类里面的方法a,这时候程序会在执行a之前去调用B类里面的方法b,执行完a之后会去调用B类里面的d,我们用这个例子来看看aop里面的概念。这里的方法a就是一个pointcut(切入点),而b和d就是连个advice(通知),a是前置通知,b是后置通知。还有一个很重要的aspect(切面概念)是什么呢,我觉得是切入点和通知的整体,如下图,这整个是一个aspect 。
6.1.2 前后置的advice
现在的网站都能够统计在线的人数,这个是怎么做的呢?真实的项目中是怎么做的我不知道,但是我觉得用spring-aop可以很好的做到这个功能。
首先需要一个登陆的类(里面有登陆和登出的方法,先只用登陆的):
public interface ILogin {
void login(User user);
void logout(User user);
}
实现类:
public class Login implements ILogin{
public void login(User user){
System.out.println(user.getName()+"登陆");
}
public void logout(User user){
System.out.println(user.getName()+"登出");
}
}
还需要一个用来做切面的类,number用来统计登陆人数。
public class Count {
static int number = 0;
public void beforeuserlogin(){
number++;
System.out.println("当前在线人数:"+number);
}
public void afteruserlogin(){
System.out.println("login end");
}
}
配置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/aop http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="login" class="com.example.aop.Login"/>
<bean id="count" class="com.example.aop.Count"/>
<aop:config>
<aop:aspect id="logaspect" ref="count"> <!-- 我把这个ref理解为切面的制造者,就是拥有advice的类 -->
<aop:pointcut id="loginpointcut" expression="execution(* com.example.aop.ILogin.login(..))"/>
<aop:before method="beforeuserlogin" pointcut-ref="loginpointcut"/>
<aop:after method="afteruserlogin" pointcut-ref="loginpointcut"/>
</aop:aspect>
</aop:config>
</beans>
这个配置文件的意思如下图:
一旦调用login方法,就会执行前后置的advice,就能完成人数统计了。
测试代码:
ApplicationContext app = new ClassPathXmlApplicationContext("spring-main.xml");
ILogin login = (ILogin) app.getBean("login");
User user = new User();
user.setName("mj");
login.login(user);
User user1 = new User();
user1.setName("lili");
login.login(user1);
结果:
当前在线人数:1
mj登陆
login end
当前在线人数:2
lili登陆
login end
6.1.3 around advice
能统计登陆的人数,当然还得能统计登出的人数了,不然人数不是一直在增长了。现在使用一种新的advice-----around advice
使用这个advice可以同时实现前后置advice。
我们在Count里面增加方法aroundlogout作为around advice。(这里的ProceedingJoinPoint指的就是定义的切入点,
joinPoint.proceed()这个就是执行切入点函数)
public Object aroundlogout(ProceedingJoinPoint joinPoint){
System.out.println("logout start");
Object object = null;
try {
object = joinPoint.proceed();
} catch (Throwable throwable) {
throwable.printStackTrace();
}
number--;
System.out.println("logout end");
System.out.println("当前在线人数:"+number);
return object;
}
修改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/aop http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="login" class="com.example.aop.Login"/>
<bean id="count" class="com.example.aop.Count"/>
<aop:config>
<aop:aspect id="logaspect" ref="count"> <!-- 我把这个ref理解为切面的制造者,就是拥有advice的类 -->
<aop:pointcut id="loginpointcut" expression="execution(* com.example.aop.ILogin.login(..))"/>
<aop:before method="beforeuserlogin" pointcut-ref="loginpointcut"/>
<aop:after method="afteruserlogin" pointcut-ref="loginpointcut"/>
<aop:around method="aroundlogout" pointcut="execution(* com.example.aop.ILogin.logout(..))"/>
<!-- 上面这一句等于下面这两句的综合 -->
<!--<aop:pointcut id="logoutpointcut" expression="execution(* com.example.aop.ILogin.logout(..))"/>-->
<!--<aop:around method="aroundlogout" pointcut-ref="logoutpointcut"/>-->
</aop:aspect>
</aop:config>
</beans>
测试代码:
ApplicationContext app = new ClassPathXmlApplicationContext("spring-main.xml");
ILogin login = (ILogin) app.getBean("login");
User user = new User();
user.setName("mj");
login.login(user);
User user1 = new User();
user1.setName("lili");
login.login(user1);
login.logout(user);
结果:
当前在线人数:1
mj登陆
login end
当前在线人数:2
lili登陆
login end
logout start
mj登出
logout end
当前在线人数:1
6.1.4 afterreturn advice
这个advice是在切入点函数执行完之后执行,它可以获取到切入点函数的返回值,用来对返回值进行一定的格式化等,afterreturn advice在after advice之后执行。为了看看它的效果,修改logout接口,让它返回一个StringBuffer(本来想用String来演示的,但是发现String演示不了,如果logout函数返回一个String的话,不能被afterreturn advice改变,我想这个和String变量在java中的特殊性有关):
public class Login implements ILogin{
public void login(User user){
System.out.println(user.getName()+"登陆");
}
public StringBuffer logout(User user){
System.out.println(user.getName()+"登出");
return new StringBuffer("logout");
}
}
修改Count,增加一个方法,它会接收切入点返回的值,然后修改:
public void afterReturn(StringBuffer s){
s.append(" after return");
}
xml文件,就是加入了一个after-returning的配置(returning后面跟afterReturn函数的入参名称):
<aop:config>
<aop:aspect id="logaspect" ref="count"> <!-- 我把这个ref理解为切面的制造者,就是拥有advice的类 -->
<aop:pointcut id="loginpointcut" expression="execution(* com.example.aop.ILogin.login(..))"/>
<aop:before method="beforeuserlogin" pointcut-ref="loginpointcut"/>
<aop:after method="afteruserlogin" pointcut-ref="loginpointcut"/>
<aop:around method="aroundlogout" pointcut="execution(* com.example.aop.ILogin.logout(..))"/>
<!-- 上面这一句等于下面这两句的综合 -->
<!--<aop:pointcut id="logoutpointcut" expression="execution(* com.example.aop.ILogin.logout(..))"/>-->
<!--<aop:around method="aroundlogout" pointcut-ref="logoutpointcut"/>-->
<aop:after-returning method="afterReturn" returning="s" pointcut="execution(* com.example.aop.ILogin.logout(..))"/>
</aop:aspect>
</aop:config>
测试代码:
ApplicationContext app = new ClassPathXmlApplicationContext("spring-main.xml");
ILogin login = (ILogin) app.getBean("login");
User user = new User();
user.setName("mj");
login.login(user);
User user1 = new User();
user1.setName("lili");
login.login(user1);
System.out.println(login.logout(user));
从代码上看最后一行是不是应该输出 logout ?
结果:
当前在线人数:1
mj登陆
login end
当前在线人数:2
lili登陆
login end
logout start
mj登出
logout end
当前在线人数:1
logout after return
6.2 在通知函数里面获取参数
注意,这里的参数似乎只能是切入点函数的参数或者切入点所在的那个类
6.2.1 利用JoinPoint类进行传参
在上面的例子中我们用到了ProceedingJoinPoint这个类,这是个JoinPoint的子类,利用它我们就可以获取切入点相关的信息,我们修改aroundlogout这个函数,在里面获取切入点的信息(其它都不变)
public Object aroundlogout(ProceedingJoinPoint joinPoint){
System.out.println("logout start");
System.out.println("获取切入点的信息,切入点所在类:"+joinPoint.getThis());
Object object = null;
try {
object = joinPoint.proceed();
} catch (Throwable throwable) {
throwable.printStackTrace();
}
number--;
System.out.println("logout end");
System.out.println("当前在线人数:"+number);
return object;
}
结果:
当前在线人数:1
mj登陆
login end
当前在线人数:2
lili登陆
login end
logout start
获取切入点的信息,切入点所在类:[email protected]470f1802
mj登出
logout end
当前在线人数:1
logout after return
我们如果想要用JoinPoint进行传参的话,只要把advice函数的第一个参数设置为JoinPoint就可以了,必须是第一个参数!
比如我们修改beforelogin:
public void beforeuserlogin(JoinPoint joinPoint){
number++;
System.out.println("获取切入点的信息,切入点所在类:"+joinPoint.getThis());
System.out.println("当前在线人数:"+number);
}
修改测试代码:
ApplicationContext app = new ClassPathXmlApplicationContext("spring-main.xml");
ILogin login = (ILogin) app.getBean("login");
User user = new User();
user.setName("mj");
login.login(user);
结果:
获取切入点的信息,切入点所在类:com.example.aop.Login@470f1802
当前在线人数:1
mj登陆
login end
JoinPoint还提供了很多方法,可以自己试验一下。
6.2.2 利用配置文件传参
假设我们在afteruserlogin这个函数里面要用到切入点login的参数user,我们可以用joinpoint提供的getArgs方法,也可以利用配置文件单独的将user传入到afteruserlogin。
修改afteruserlogin:
public void afteruserlogin(User user){
System.out.println(user.getName()+" login end");
}
修改xml:
<aop:after method="afteruserlogin" pointcut="execution(* com.example.aop.ILogin.login(..)) and args(user)"/>
运行6.2.1中的测试代码:
获取切入点的信息,切入点所在类:com.example.aop.Login@3e92efc3
当前在线人数:1
mj登陆
mj login end
(如果参数很多也没问题,在args里面用“,”隔开就行了。)
end:
spring还提供了AspectJ 形式的切面编程,用的都是注解的形式,还提供了一种名为Introductions的切面编程(可以把一个类做一个切面,向里面添加接口),这些以后慢慢来吧。
时间: 2024-10-10 16:26:40