一、简介
Struts2的拦截器就是应用的AOP的思想,它用于拦截Action以进行一些预处理或结果处理。而spring的AOP是一种更通用的模式,可以拦截Spring管理的Bean,功能更强大,适用范围也更广,它是通过动态代理与反射机制实现的。
在AOP中,正是这种分散在各处且与对象核心功能无关的代码(横切代码)的存在,使得模块复用难度增加。AOP则将封装好的对象剖开,找出其中对多个对象产生影响的公共行为,并将其封装为一个可重用的模块,这个模块被命名为“切面”(Aspect),切面将那些与业务无关,却被业务模块共同调用的逻辑提取并封装起来,减少了系统中的重复代码,降低了模块间的耦合度,同时提高了系统的可维护性。
AOP可以通过预编译方式和运行期动态代理实现在不修改源代码的情况下给程序动态统一添加功能的一种技术。
设计模式孜孜不倦追求的是调用者和被调用者之间的解耦,提高代码的灵活性和可扩展性,AOP可以说也是这种目标的一种实现。
主要功能:日志记录、性能统计、安全控制,事务处理,日常处理
主要意图
将日志记录,性能统计,安全控制,事务处理,异常处理等代码从业务逻辑代码中划分出来,通过对这些行为的分离,我们希望可以将它们独立到非指导业务逻辑的方法中,进而改变这些行为的时候不影响业务逻辑的代码。
AOP 是一个概念,一个规范,本身并没有设定具体语言的实现,这实际上提供了非常广阔的发展的空间。
二、AOP的基本概念
(1)Aspect(切面):通常是一个类,里面可以定义切入点和通知
(2)JointPoint(连接点):程序执行过程中明确的点,一般是方法的调用
(3)Advice(通知):AOP在特定的切入点上执行的增强处理,有before,after,afterReturning,afterThrowing,around
(4)Pointcut(切入点):就是带有通知的连接点,在程序中主要体现为书写切入点表达式
(5)AOP代理:AOP框架创建的对象,代理就是目标对象的加强。Spring中的AOP代理可以使JDK动态代理,也可以是CGLIB代理,前者基于接口,后者基于子类
三、简单理解AOP实例
首先定义一个学生接口
StudentService.java
package com.java.service;
public interface StudentService {
//定义一个接口,接口实现类实现这个接口
public void addStudent(String name);
}
定义实现类实现这个接口里面的方法
StudentServiceImpl.java
package com.java.service.impl;
import com.java.service.StudentService;
public class StudentServiceImpl implements StudentService{
@Override
public void addStudent(String name) {
System.out.println("添加学生"+name);
}
}
bean.xml定义Bean
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd"
default-autowire="constructor">
<!-- 接口实现类的bean -->
<bean id="studentService" class="com.java.service.impl.StudentServiceImpl" >
</bean>
</beans>
实现类
package com.java.test;
import org.junit.Before;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.java.service.StudentService;
public class T {
private ApplicationContext ac;
@Before
public void setUp() throws Exception {
ac=new ClassPathXmlApplicationContext("beans.xml");
}
@Test
public void test1() {
StudentService studentService = (StudentService)ac.getBean("studentService");
studentService.addStudent("孙悟空");
}
}
结果
比如说我们现在有日志记录的需求,按照以前的方法这样写完成日志记录
但是这样写的话,是对原有逻辑代码的一个侵入,增加耦合度,程序维护困难
思想:所以需要面向切面的方法,
在System.out.println("添加学生"+name);执行之前切入日志
完成之后切入日志
切入的不影响业务逻辑的代码。
涉及到切面,要导入相应的jar包
Build path
bean.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">
</beans>
这样就可以操作aop了
1.前置通知
创建一个文件保存切面的类
StudentServiceAspect.java
package com.java.advice;
import org.aspectj.lang.JoinPoint;
public class StudentServiceAspect {
//前置通知:执行方法制定方法之前通知切面类
//通过JoinPoint可以获取执行方法的很多信息,类名、方法名,还有参数值
//jp.getArgs()[0]返回的是数组
public void doBefore(JoinPoint jp){
System.out.println("类名:"+jp.getTarget().getClass().getName());
System.out.println("方法名:"+jp.getSignature().getName());
System.out.println("前置日志:开始添加学生:"+jp.getArgs()[0]);
}
}
执行方法StudentServiceImpl.java
package com.java.service.impl;
import com.java.service.StudentService;
public class StudentServiceImpl implements StudentService{
@Override
public void addStudent(String name) {
System.out.println("添加学生"+name);
}
}
bean.java
<?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="studentServiceAspect" class="com.java.advice.StudentServiceAspect"></bean>
<!-- 接口实现类的bean -->
<bean id="studentService" class="com.java.service.impl.StudentServiceImpl" ></bean>
<aop:config>
<!--id随便取,ref指向通知类 -->
<aop:aspect id="studentServiceAspect" ref="studentServiceAspect">
<!-- pointcut定义切点 -->
<!-- 第一个*代表方法的返回值是任意的 -->
<!-- (..)代表方法的参数是任意的 -->
<aop:pointcut expression="execution(* com.java.service.*.*(..))" id="businessService"/>
<!-- 前置通知,methud是切面类写好的前置方法,pointcut-ref上面定义的切点 -->
<aop:before method="doBefore" pointcut-ref="businessService"/>
</aop:aspect>
</aop:config>
</beans>
执行代码
package com.java.test;
import org.junit.Before;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.java.service.StudentService;
public class T {
private ApplicationContext ac;
@Before
public void setUp() throws Exception {
ac=new ClassPathXmlApplicationContext("beans.xml");
}
@Test
public void test1() {
StudentService studentService = (StudentService)ac.getBean("studentService");
studentService.addStudent("孙悟空");
}
}
结果前置通知成功
2.后置通知
写后置方法
StudentServiceAspect.java
package com.java.advice;
import org.aspectj.lang.JoinPoint;
public class StudentServiceAspect {
//前置通知:执行方法制定方法之前通知切面类
//通过JoinPoint可以获取执行方法的很多信息,类名、方法名,还有获取参数值
//jp.getArgs()[0]返回的是数组
public void doBefore(JoinPoint jp){
System.out.println("类名:"+jp.getTarget().getClass().getName());
System.out.println("方法名:"+jp.getSignature().getName());
System.out.println("前置日志:开始添加学生:"+jp.getArgs()[0]);
}
public void doAfter(JoinPoint jp){
System.out.println("类名:"+jp.getTarget().getClass().getName());
System.out.println("方法名:"+jp.getSignature().getName());
System.out.println("后置日志:学生添加完成:"+jp.getArgs()[0]);
}
}
bean.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="studentServiceAspect" class="com.java.advice.StudentServiceAspect"></bean>
<!-- 接口实现类的bean -->
<bean id="studentService" class="com.java.service.impl.StudentServiceImpl" ></bean>
<aop:config>
<!--id随便取,ref指向通知类 -->
<aop:aspect id="studentServiceAspect" ref="studentServiceAspect">
<!-- pointcut定义切点 -->
<!-- 第一个*代表方法的返回值是任意的 -->
<!-- (..)代表方法的参数是任意的 -->
<aop:pointcut expression="execution(* com.java.service.*.*(..))" id="businessService"/>
<!-- 前置通知,methud是切面类写好的前置方法,pointcut-ref上面定义的切点 -->
<aop:before method="doBefore" pointcut-ref="businessService"/>
<!--后置通知-->
<aop:after method="doAfter" pointcut-ref="businessService"/>
</aop:aspect>
</aop:config>
</beans>
结果
3.环绕通知
StudentServiceAspect.java
package com.java.advice;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
public class StudentServiceAspect {
//前置通知:执行方法制定方法之前通知切面类
//通过JoinPoint可以获取执行方法的很多信息,类名、方法名,还有获取参数值
//jp.getArgs()[0]返回的是数组
public void doBefore(JoinPoint jp){
System.out.println("类名:"+jp.getTarget().getClass().getName());
System.out.println("方法名:"+jp.getSignature().getName());
System.out.println("前置日志:开始添加学生:"+jp.getArgs()[0]);
}
//后置通知
public void doAfter(JoinPoint jp){
System.out.println("类名:"+jp.getTarget().getClass().getName());
System.out.println("方法名:"+jp.getSignature().getName());
System.out.println("后置日志:学生添加完成:"+jp.getArgs()[0]);
}
//环绕通知
public Object doAround(ProceedingJoinPoint pjp) throws Throwable{
System.out.println("环绕:添加学生前");
Object retVal=pjp.proceed();//这里是调用执行方法,有返回值
System.out.println("环绕:添加学生后");
System.out.println(retVal);
return retVal;
}
}
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="studentServiceAspect" class="com.java.advice.StudentServiceAspect"></bean>
<!-- 接口实现类的bean -->
<bean id="studentService" class="com.java.service.impl.StudentServiceImpl" ></bean>
<aop:config>
<!--id随便取,ref指向通知类 -->
<aop:aspect id="studentServiceAspect" ref="studentServiceAspect">
<!-- pointcut定义切点 -->
<!-- 第一个*代表方法的返回值是任意的 -->
<!-- (..)代表方法的参数是任意的 -->
<aop:pointcut expression="execution(* com.java.service.*.*(..))" id="businessService"/>
<!-- 前置通知,methud是切面类写好的前置方法,pointcut-ref上面定义的切点 -->
<aop:before method="doBefore" pointcut-ref="businessService"/>
<aop:after method="doAfter" pointcut-ref="businessService"/>
<aop:around method="doAround" pointcut-ref="businessService"/>
</aop:aspect>
</aop:config>
</beans>
结果
因为因为执行方法是void ,没有返回值,所以环绕通知返回值为null
如果修改执行方法改成有返回值:
接口
接口实现类
4.返回通知
执行方法返回(return name;)数值之前通知
StudentServiceAspect.java
package com.java.advice;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
public class StudentServiceAspect {
//前置通知:执行方法制定方法之前通知切面类
//通过JoinPoint可以获取执行方法的很多信息,类名、方法名,还有获取参数值
//jp.getArgs()[0]返回的是数组
public void doBefore(JoinPoint jp){
System.out.println("类名:"+jp.getTarget().getClass().getName());
System.out.println("方法名:"+jp.getSignature().getName());
System.out.println("前置日志:开始添加学生:"+jp.getArgs()[0]);
}
//后置通知
public void doAfter(JoinPoint jp){
System.out.println("类名:"+jp.getTarget().getClass().getName());
System.out.println("方法名:"+jp.getSignature().getName());
System.out.println("后置日志:学生添加完成:"+jp.getArgs()[0]);
}
//环绕通知
public Object doAround(ProceedingJoinPoint pjp) throws Throwable{
System.out.println("环绕:添加学生前");
Object retVal=pjp.proceed();//这里是调用执行方法,有返回值
System.out.println("环绕:添加学生后");
System.out.println(retVal);
return retVal;
}
//返回通知
public void doAfterReturning(JoinPoint jp){
System.out.println("返回通知");
}
}
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="studentServiceAspect" class="com.java.advice.StudentServiceAspect"></bean>
<!-- 接口实现类的bean -->
<bean id="studentService" class="com.java.service.impl.StudentServiceImpl" ></bean>
<aop:config>
<!--id随便取,ref指向通知类 -->
<aop:aspect id="studentServiceAspect" ref="studentServiceAspect">
<!-- pointcut定义切点 -->
<!-- 第一个*代表方法的返回值是任意的 -->
<!-- (..)代表方法的参数是任意的 -->
<aop:pointcut expression="execution(* com.java.service.*.*(..))" id="businessService"/>
<!-- 前置通知,methud是切面类写好的前置方法,pointcut-ref上面定义的切点 -->
<aop:before method="doBefore" pointcut-ref="businessService"/>
<!-- 后置通知 -->
<aop:after method="doAfter" pointcut-ref="businessService"/>
<!-- 环绕通知 -->
<aop:around method="doAround" pointcut-ref="businessService"/>
<!-- 返回通知 -->
<aop:after-returning method="doAfterReturning" pointcut-ref="businessService"/>
</aop:aspect>
</aop:config>
</beans>
5.异常通知
假如接口实现的执行方法有异常,就会报错
例如(为了简洁把前面通知删除)
StudentServiceImpl.java
package com.java.service.impl;
import com.java.service.StudentService;
public class StudentServiceImpl implements StudentService{
@Override
public String addStudent(String name) {
//异常
System.out.println(1/0);
return name;
}
}
StudentServiceAspect.java
package com.java.advice;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
public class StudentServiceAspect {
//异常通知
public void doAfterThrowing(JoinPoint jp,Throwable ex){
System.out.println("异常通知");
System.out.println("异常信息:"+ex.getMessage());
}
}
配置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="studentServiceAspect" class="com.java.advice.StudentServiceAspect"></bean>
<!-- 接口实现类的bean -->
<bean id="studentService" class="com.java.service.impl.StudentServiceImpl" ></bean>
<aop:config>
<!--id随便取,ref指向通知类 -->
<aop:aspect id="studentServiceAspect" ref="studentServiceAspect">
<!-- pointcut定义切点 -->
<!-- 第一个*代表方法的返回值是任意的 -->
<!-- (..)代表方法的参数是任意的 -->
<aop:pointcut expression="execution(* com.java.service.*.*(..))" id="businessService"/>
<!--异常通知 -->
<aop:after-throwing method="doAfterThrowing" pointcut-ref="businessService" throwing="ex"/>
</aop:aspect>
</aop:config>
</beans>
结果: