问题:
在学习了SpringIOC之后,可以使用SpringIOC的知识完成责任链的解耦。方便了以后的代码的升级和维护,一个类的整体替换,我们只需要修改配置文件即可。但是因为业务需求只是升级一个方法的功能,保留原有功能的基础上增加新的功能处理,在没有掌握SpringAOP之前会直接修改方法的源码,这个时候要考虑能不能再不修改源码的基础上完成新功能的增加呢?因为有的时候无法直接对源码进行修改,比如升级mybatis中的方法。
方案一:
例:A类中有一个方法testA(),需要对testA()方法进行升级,可以创建一个类A2,里面也声明一个方法也叫testA(),然后再A2的testA()方法中声明的功能代码,同时将类A中的testA()调用一次。然后将调用A的testA()方法的代码替换为调用A2的testA()方法。
方案一的问题:
虽然实现了不修改原有代码的基础上完成了功能的升级,但是当升级的对象比较多时,就会造成一个问题:需要频繁的声明代理类和代理方法。而且还需要频繁的使用代理对象替换真实对象。而我们想要的只是写的扩展代码。这样造成了开发效率比较低。有没有更好的方法呢?
解决:
我们只需要写扩展功能的代码,代理对象和代理方法以及真实对象的替换都由其他人完成。这个(其他人)我们可以使用代码来声明。但是我们还是需要说给哪个类的哪个方法进行扩展,扩展代码增加到原有代码的那个地方?
实现:
SpringAOP
内容:
基于Schema-based的方式
基于AspectJ方式
AOP概念:面向切面的编程(动态创建代理对象和代理方法)
切点:要进行功能扩展的真实方法。
前置通知:在切点之前执行的扩展方法。
后置通知:在切点之后执行的扩展方法。
切面:前置通知+切点+后置通知形成的横向执行的面。
织入:形成切面的过程。
使用:
1.导入jar包
2.创建前置通知和后置通知的方法。
3.在配置文件中声明织入规则。
4.完成功能升级。
总结:
AOP是将代码的手动升级变成了半自动化升级,我们只需要提供升级的代码和升级规则,剩下的事情SpringAOP框架会自动完成,提高了开发效率。SpringAOP的使用是需要练习和记忆的。
基于Schema-based方式实现:
1.导入jar包:SpringIOC和SpringAOP的jar包
2.声明扩展代码:
(1)前置通知类:
public class BankServiceImplBefore implements MethodBeforeAdvice {
/**
* 前置通知类
* @param method 切点的方法对象
* @param objects 接受的实参的数组
* @param o 切点的真实对象
* @throws Throwable
*/
@Override
public void before(Method method, Object[] objects, Object o) throws Throwable {
System.out.println("我是前置方法:");
}
}
(2)后置通知类:
public class BankServiceImplAfter implements AfterReturningAdvice {
/**
* 后置通知:增加日志信息
* @param o 真实方法的返回值
* @param method 真是方法的对象
* @param objects 参数的Object的数组
* @param o1 真实对象
* @throws Throwable
*/
@Override
public void afterReturning(Object o, Method method, Object[] objects, Object o1) throws Throwable {
if((boolean)o){
Logger logger = Logger.getLogger(BankServiceImplAfter.class);
logger.debug(objects[0]+"给"+objects[1]+"转了"+objects[2]+"元");
}
}
}
(3)环绕通知类:
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
public class BankServiceImplAround implements MethodInterceptor {
/**
* 环绕通知
* @param methodInvocation :封存了所有相关信息
* methodInvocation.proceed() 放行执行真实方法
* methodInvocation.getArguments() 获得实参数组
* methodInvocation.getMethod() 获得切点的方法对象
* methodInvocation.getThis() 获取真实对象
* @return
* @throws Throwable
*/
@Override
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
System.out.println("环绕-前");
Object o = methodInvocation.proceed();//放行:调真实方法的切点(相当于过滤器)
System.out.println("环绕-后");
return o;
}
}
(4)异常通知类:
public class BankServiceImplException implements ThrowsAdvice {
/**
* 异常通知类
* @param e 接收代理方法的异常信息
* @throws Throwable
*/
public void aferThrowing(Exception e) throws Throwable{
System.out.println("我是异常通知:");
}
}
3.声明配置文件,并声明组装规则
(1)配置真实对象的bean
(2)配置前置通知的bean
(3)配置后置通知的bean
(4)配合组装规则
代码示例:
<!--配置业务层的bean:已使用注解的方式获取-->
<!--后置通知的bean-->
<bean id="after" class="com.bjsxt.advice.BankServiceImplAfter"></bean>
<bean id="before" class="com.bjsxt.advice.BankServiceImplBefore"></bean>
<bean id="around" class="com.bjsxt.advice.BankServiceImplAround"></bean>
<bean id="throw" class="com.bjsxt.advice.BankServiceImplException"></bean>
<!--配置注入规则-->
<aop:config>
<aop:pointcut id="myPointCut" expression="execution(* com.bjsxt.service.impl.BankServiceImpl.updateInfo(..))" ></aop:pointcut>
<aop:advisor advice-ref="after" pointcut-ref="myPointCut"></aop:advisor>
<aop:advisor advice-ref="before" pointcut-ref="myPointCut"></aop:advisor>
<aop:advisor advice-ref="around" pointcut-ref="myPointCut"></aop:advisor>
<aop:advisor advice-ref="throw" pointcut-ref="myPointCut"></aop:advisor>
</aop:config>
4.完成扩展:正常获取对象调用方法即可
基于AspectJ方式实现:
1.问题:Schema-based方式要求前置通知,后置通知等扩展代码,分别声明一个类型文件来实现对相应的接口来完成。比较麻烦。我们希望可以将扩展声明的代码在一个类中完成。
2.解决:Schema-based方式需要通过接口来区分扩展代码的执行顺序,所以每个扩展代码都需要独立声明类并实现相应的接口。我们也不可以将扩展功能代码声明在一个类文件中,在配置文件中配置好前置通知和后置通知等扩展代码。
3.实现:AspectJ方式
4.本质:Schema-based方式:接口区分,AspectJ方式:配置文件区分。
5.使用:
(1)导入jar包(同Schema-based的jar包)
(2)创建通知类,将所有的扩展代码声明在一个类中
public class TeacherAspectj {
public void before(String name,int age){
System.out.println("我是前置通知:");
}
public void after(){
System.out.println("我是后置通知:下面是老师");
}
public Object around(ProceedingJoinPoint p){
System.out.println("环绕通知--前");
Object proceed = null;
try {
proceed = p.proceed();
} catch (Throwable throwable) {
throwable.printStackTrace();
}
System.out.println("环绕通知--后");
return proceed;
}
public void afterThrowing(Exception e){
System.out.println("我是异常通知:");
}
}
(3)在配置文件中声明组装规则:需要声明代码的执行顺序
<!--为teacher类配置bean:真实方法-->
<bean id="teacher" class="com.bjsxt.pojo.Teacher"></bean>
<!--为通知类配置bean-->
<bean id="advice" class="com.bjsxt.advice.TeacherAspectj"></bean>
<!--配置aspectj的注入规则-->
<aop:config>
<aop:aspect ref="advice">
<aop:pointcut id="my" expression="execution(* com.bjsxt.pojo.Teacher.textTea(String,int)) and args(name,age)"></aop:pointcut>
<aop:before method="before" arg-names="name,age" pointcut-ref="my"></aop:before>
<aop:after method="after" pointcut-ref="my"></aop:after>
<aop:around method="around" pointcut-ref="my"></aop:around>
<aop:after-throwing method="afterThrowing" pointcut-ref="my" throwing="e"></aop:after-throwing>
</aop:aspect>
</aop:config>
4.正常获取容器中的对象,完成扩展代码的执行。
总结:
在使用AspectJ方式如果通知方法中需要获取参数的话,配置会比较麻烦,但是不涉及参数时,使用AspectJ方式会比较方便,层次比较清晰。建议涉及参数使用Schema-Based方式。
SpringAOP中AspectJ的注解:
1.注意:SpringAOP只针对AspectJ方式提供了注解。
2.使用:
(1)在配置文件中声明注解扫描。
<!--注解扫描-->
<context:component-scan base-package="com.bjsxt.advice,com.bjsxt.pojo"></context:component-scan>
(2)配置AOP注解的开启
<!--AOP注解开启-->
<aop:aspectj-autoproxy expose-proxy="true"></aop:aspectj-autoproxy>
(3)使用AspectJ的注解配置
@Component//作用:在类上声明,相当于配置bean标签
public class Teacher {
/**
* 作用:在切点方法上使用,声明切点
* 内容:切点的表达式
*/
@Pointcut("execution(* com.bjsxt.pojo.Teacher.textTea())")
public void textTea(){
//int i = 5/0;
System.out.println("真实方法:我是一名老师");
}
}
@Component//作用:在类上声明,相当于配置bean标签
@Aspect //作用:声明在通知类上,表示该类为通知类,用来功能扩展的
public class TeacherAspectj {
/**
* 作用:声明在前置通知方法上
* 内容:切点的全限定路径
*/
@Before("com.bjsxt.pojo.Teacher.testTea()")
public void before(){
System.out.println("我是前置通知:");
}
/**
* 作用:声明在后置通知方法上
* 内容:切点的全限定路径
*/
@After("com.bjsxt.pojo.Teacher.textTea()")
public void after(){
System.out.println("我是后置通知:下面是老师");
}
/**
* 作用:声明在环绕通知方法上
* 内容:切点的全限定路径
*/
@Around("com.bjsxt.pojo.Teacher.textTea()")
public Object around(ProceedingJoinPoint p){
System.out.println("环绕通知--前");
Object proceed = null;
try {
proceed = p.proceed();
} catch (Throwable throwable) {
throwable.printStackTrace();
}
System.out.println("环绕通知--后");
return proceed;
}
/**
* 作用:声明在异常通知方法上
* 内容:切点的全限定路径
*/
@AfterThrowing("com.bjsxt.pojo.Teacher.testTea()")
public void afterThrowing(){
System.out.println("我是异常通知:");
}
}