前言:
AOP:面向方面编程
● 理解AOP的编程思想及 原理(略)
● 掌握AOP的实现技术
AOP编程思想:
Spring框架的AOP机制:简单的解释是可以让开发者把业务流程中的通用功能抽取出来,单独编写功能代码。在业务流程执行过程中,Spring框架会根据业务流程要求,自动把独立编写的功能代码切入到流程的合适位置。
由此AOP的好处:
-每个事物逻辑位于一个位置,代码不分散,便于维护和升级。
-业务模板更简洁,只包含核心代码。
例如,在一个业务系统中,用户登录是基础功能,凡是涉及到用户的业务流程都要求用户进行系统登录。如果把用户登录功能代码写入到每个业务流程中,会造成代码冗余,维护也非常麻烦,当需要修改用户登录功能时,就需要修改每个业务流程的用户登录代码,这种处理方式显然是不可取的。比较好的做法是把用户登录功能抽取出来,形成独立的模块,当业务流程需要用户登录时,系统自动把登录功能切入到业务流程中。下图是用户登录功能切入到业务流程示意图。
正文:
AOP实现技术---切面必须完成的工作(即称为:通知)
引包:
● aopalliance-1.0.jar(底层用的是aspectj的jar包)
● aspectjweaver-1.5.3.jar (aspectj是aop框架)
1)简单演示前置通知类:
任务:
每当之前addStudent()之前 自动执行前置通知logBefore();
addStudent(); 业务方法(org.lanqiao.service.impl.StudentServiceImpl)
logBefore(); 自动执行的aop前置通知(org.lanqiao.aop.LogBefore)
编写业务类 和 通知类:
org.lanqiao.dao.impl.StudentDaoImpl类(Dao层是辅助演示可忽略):
public class StudentDaoImpl implements IStudentDao{
public void addStudent(Student student) {
System.out.println("增加学生...");
}
}
org.lanqiao.service.impl.StudentServiceImpl类(业务类):
public class StudentServiceImpl implements IStudentService{
IStudentDao studentDao ;//自行配置Dao层
public void setStudentDao(IStudentDao studentDao) {
this.studentDao = studentDao;
}
@Override
public void addStudent(Student student) {
studentDao.addStudent(student);
System.out.println("=====进行增加操作========");
}
}
org.lanqiao.aop.LogBefore类(通知类):
//普通类 变 前置通知实现此接口
public class LogBefore implements MethodBeforeAdvice {
//前置通知的具体类容
@Override
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.println("前置通知");
}
}
基于XML的配置
<!-- addStudent()方法 的依赖类 -->
<bean id="studentDao" class="org.lanqiao.dao.impl.StudentDaoImpl">
</bean>
<!-- addStudent()所在方法 -->
<bean id="studentService" class="org.lanqiao.service.impl.StudentServiceImpl">
<property name="studentDao" ref="studentDao"></property>
</bean>
<!-- 前置通知类 -->
<bean id="logBefore" class="org.lanqiao.aop.LogBefore">
</bean>
<!-- 将addStudent()方法与前置通知类关联 -->
<aop:config>
<!-- 配置切入点(在哪里) -->
<aop:pointcut expression="execution(execution(public void org.lanqiao.service.impl.StudentServiceImpl.addStudent(org.lanqiao.entity.Student))" id="poioncut"/>
<!-- 左:配置切面(各种通知) 和 右:切点与切面建立联系 -->
<aop:advisor advice-ref="logBefore" pointcut-ref="poioncut" />
</aop:config>
拓展1:配置切入点时的语法,表达式expression的常见示例如表所示:
expression="execution(…)"
举例 |
含义 |
public boolean addStudent(org.lanqiao.entity.Student)) |
所有返回类型为boolean、参数类型为org.lanqiao.entity.Student的addStudent()方法。 |
public boolean org.lanqiao.service.IStudentService. addStudent(org.lanqiao.entity.Student) |
org.lanqiao.service.IStudentService类(或接口)中的addStudent()方法,并且返回类型是boolean、参数类型是org.lanqiao.entity.Student |
public * addStudent(org.lanqiao.entity.Student) |
“*”代表任意返回类型 |
public void *( org.lanqiao.entity.Student) |
“*”代表任意方法名 |
public void addStudent(..) |
“..”代表任意参数列表 |
* org.lanqiao.service.*.*(..) |
org.lanqiao.service.IStudentService包中,包含的所有方法(不包含子包中的方法) |
* org.lanqiao.service..*.*(..) |
org.lanqiao.service.IStudentService包中,包含的所有方法(包含子包中的方法) |
测试:
ApplicationContext conext = new ClassPathXmlApplicationContext("applicationContext1.xml") ;
IStudentService studentService = (IStudentService)conext.getBean("studentService") ;
Student student=new Student();//自行创建该javabean类
studentService.addStudent(student);
打印:
前置通知
增加学生...
=====进行增加操作========
2)简单演示 前置通知类+后置通知类+业务类的多个切入点+execution表达式详解:
任务:
在准备执行addStudent(Student student),之前执行前置通知类LogBefore(),之后执行后置通知类LogAfter(),然后在准备执行 deleteStudentByNo(int stuNo),前执行前置通知类LogBefore(),执行完后执行后置通知类LogAfter()。
与上面示例基础之上的变化:
org.lanqiao.service.impl.StudentServiceImpl类(业务类):
注:此类在上个演示基础之上增加了deleteStudentByNo(int stuNo)方法,用于演示业务类的多个切入点。
切入点为:addStudent(Student student)和 deleteStudentByNo(int stuNo)
public class StudentServiceImpl implements IStudentService{
IStudentDao studentDao ;//自行配置Dao层
public void setStudentDao(IStudentDao studentDao) {
this.studentDao = studentDao;
}
@Override
public void addStudent(Student student) {
studentDao.addStudent(student);
System.out.println("=====进行增加操作========");
}
@Override
public void deleteStudentByNo(int stuNo) {
System.out.println("模拟删除...."); }
}
(新增)org.lanqiao.aop.LogAfter类(后置通知类):
public class LogAfter implements AfterReturningAdvice{
//target目标对象:哪个切入点(service内的方法)产生的此通知
@Override
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
System.out.println("**********后置通知:目标对象:"+target+",调用的方法名:"+method.getName()+",方法的参数个数:"+args.length+",方法的返回值:"+returnValue);
}
}
基于xml配置:
注:此配置在上个演示基础之上增加了 前置通知的deleteStudentByNo()也一同作为切点 + 后置通知且内部execution表达式的新语法。
<!-- addStudent()方法 的依赖类 -->
<bean id="studentDao" class="org.lanqiao.dao.impl.StudentDaoImpl">
</bean>
<!-- addStudent()所在方法 -->
<bean id="studentService" class="org.lanqiao.service.impl.StudentServiceImpl">
<property name="studentDao" ref="studentDao"></property>
</bean>
<!-- 前置通知类 -->
<bean id="logBefore" class="org.lanqiao.aop.LogBefore">
</bean>
<aop:config>
<aop:pointcut expression="execution(execution(public void org.lanqiao.service.impl.StudentServiceImpl.deleteStudentByNo(int)) or execution(public void org.lanqiao.service.impl.StudentServiceImpl.addStudent(org.lanqiao.entity.Student)))" id="poioncut"/>
<aop:advisor advice-ref="logBefore" pointcut-ref="poioncut" />
</aop:config>
<!-- 后置通知-->
<bean id="logAfter" class="org.lanqiao.aop.LogAfter">
</bean>
<aop:config>
<!-- 配置切入点(在哪里) --> <!-- *代表任意返回值类型 --> <!-- *(..)表示:任意方法名,参数为任意类型的方法 -->
<aop:pointcut expression="execution(public * org.lanqiao.service.impl.StudentServiceImpl.*(..)) " id="poioncut2"/>
<!-- 左:配置切面(各种通知) 和 右:切点与切面建立联系 -->
<aop:advisor advice-ref="logAfter" pointcut-ref="poioncut2" />
</aop:config>
测试:
ApplicationContext conext = new ClassPathXmlApplicationContext("applicationContext1.xml") ;
IStudentService studentService = (IStudentService)conext.getBean("studentService") ;
Student student=new Student();
studentService.addStudent(student);
studentService.deleteStudentByNo(1);
//打印:
前置通知
增加学生...
=====进行增加操作========
**********后置通知:目标对象:org.lanqiao.service.impl.StudentServiceImpl@441772e,调用的方法名:addStudent,方法的参数个数:1,方法的返回值:null
前置通知
模拟删除....
**********后置通知:目标对象:org.lanqiao.service.impl.StudentServiceImpl@441772e,调用的方法名:deleteStudentByNo,方法的参数个数:1,方法的返回值:null
小结:execution表达式:
execution( [修饰符] 返回值类型 包名.类名.方法名 (参数) )
参数:
()匹配一个无参方法
(..)参数列表可以使用..表示有无参数均可,有参数可以是任意类型
(*)参数列表可以使用*,表示参数可以是任意数据类型,但是必须有参数
(*,Integer)匹配一个接受两个参数的方法,第一个可以为任意类型,第二个必须为Integer。
符号作用:
*:代表一个任意类型的参数;
..:代表零个或多个任意类型的参数。
execution 表达式例子:
1、匹配指定包下所有类方法 :execution(* com.baidu.dao.*(..)) 不包含子包
2. 匹配指定包以及及指定包下面的子包所有类 : execution(* com.baidu.dao..*(..)) ..*表示当前包、子孙包下所有类
3、匹配指定类所有方法 ; execution(* com.baidu.service.UserService.*(..))
4、匹配实现特定接口所有类方法 : execution(* com.baidu.dao.GenericDAO+.*(..))
5、匹配所有save开头的方法 : execution(* save*(..))
3)异常通知(略)
在方法执行时出现异常时该发生,需实现ThrowsAdvice接口,
且必须实现此方法,不是重写!!!(是一种规定,必须实现此方法)
新增:异常通知类
public void afterThrowing(Method method, Object[] args ,Object target, NullPointerException ex)//只捕获NullPointerException类型的异常
{
System.out.println("00000000000异常通知:目标对象:"+target+",方法名:"+method.getName()+",方法的参数个数:"+args.length+",异常类型:"+ex.getMessage());
}
4)环绕通知
前置通知-->环绕通知---> 业务类 ---> 环绕通知-->后置通知 (注:业务类可能之后是 异常通知(略))
业务类、部分通知类、xml配置,不再重复演示(方法已经从上述例子说明到位了)
新增:环绕通知类
public class LogAround implements MethodInterceptor{
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
Object result = null ;
//方法体1...
try {
//方法体2...
System.out.println("用环绕通知实现的[前置通知]...");
//invocation.proceed() 之前的代码:前置通知
result = invocation.proceed() ;//控制着目标方法的执行 ,addStudent()。 如果注释点则不会执行此方法,但是环绕的前置 和后置 依然会执行!
//result 就是目标方法addStudent()方法的返回值,通过最后的return传递给后置通知的参数
//invocation.proceed() 之后的代码:后置通知
System.out.println("用环绕通知实现的[后置通知]...:");
System.out.println("-----------------目标对象target"+invocation.getThis()+",调用的方法名:"+invocation.getMethod().getName()+",方法的参数个数:"+invocation.getArguments().length+",返回值:"+result);
}catch(Exception e) {
//方法体3...
//异常通知
System.out.println("用环绕通知实现的[异常通知]...");
}
return result;//目标方法的返回值
}
}
测试:
ApplicationContext conext = new ClassPathXmlApplicationContext("applicationContext1.xml") ;
IStudentService studentService = (IStudentService)conext.getBean("studentService") ;
Student student=new Student();
studentService.addStudent(student);
studentService.deleteStudentByNo(1);
测试结果:
前置通知
用环绕通知实现的[前置通知]...
增加学生...
=====进行增加操作========
用环绕通知实现的[后置通知]...:
-----------------目标对象targetorg.lanqiao.service.impl.StudentServiceImpl@441772e,调用的方法名:addStudent,方法的参数个数:1,返回值:null
**********后置通知:目标对象:org.lanqiao.service.impl.StudentServiceImpl@441772e,调用的方法名:addStudent,方法的参数个数:1,方法的返回值:null
前置通知
用环绕通知实现的[前置通知]...
模拟删除....
用环绕通知实现的[后置通知]...:
-----------------目标对象targetorg.lanqiao.service.impl.StudentServiceImpl@441772e,调用的方法名:deleteStudentByNo,方法的参数个数:1,返回值:null
**********后置通知:目标对象:org.lanqiao.service.impl.StudentServiceImpl@441772e,调用的方法名:deleteStudentByNo,方法的参数个数:1,方法的返回值:null