1.传统AOP编程
传统SpringAOP的Advice 必须实现对应的接口!
- 前置通知 org.springframework.aop.MethodBeforeAdvice
在目标方法执行前实施增强 - 后置通知 org.springframework.aop.AfterReturningAdvice
在目标方法执行后实施增强 - 环绕通知 org.aopalliance.intercept.MethodInterceptor
在目标方法执行前后实施增强 - 异常抛出通知 org.springframework.aop.ThrowsAdvice
在方法抛出异常后实施增强 - 引介通知 org.springframework.aop.IntroductionInterceptor
在目标类中添加一些新的方法和属性
1.1 测试:通过AOP,记录Controller层方法的运行时间
流程:
步骤一.编写advice通知,根据需要实现对应的接口
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
public class RuntimeAdvice implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
//方法调用之前记录时间
long beginTime = System.currentTimeMillis();
//目标对象原来的方法的调用,返回目标对象方法的返回值。
Object invoke = methodInvocation.proceed();//类似于invoke
//方法调用之后记录时间
long endTime = System.currentTimeMillis();
//计算运行时间(毫秒)
long runTime=endTime-beginTime;
System.out.println(methodInvocation.getMethod().getName() + "方法执行时间为:" + runTime + "毫秒");
return invoke;
}
}
步骤二.将通知注册到Spring容器中
applicationContext.xml
<bean id="runtimeAdvice" class="demo.advice.RuntimeAdvice"></bean>
步骤三.配置切入点和切面
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
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/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<bean id="runtimeAdvice" class="demo.advice.RuntimeAdvice"></bean>
<aop:config>
<aop:pointcut expression="execution(* demo.controller..*.*(..))" id="myPointcut"/>
<aop:advisor advice-ref="runtimeAdvice" pointcut-ref="myPointcut"/>
</aop:config>
测试:
@Controller
public class UserController {
@RequestMapping(value = "test")
public String test(HttpServletRequest request) {
return "test";
}
}
问题:
启动tomcat服务器后,我们访问 localhost:8080/test,我们发现并没有走到RuntimeAdvice中的环绕方法中,这是为什么呢?
原因:
Spring和SpringMVC两个容器,它们的配置文件分别为applicationContext.xml
和xx-servlet.xml
。Spring是根容器,SpringMVC是其子容器。SpringMVC容器可以访问Spring容器中的Bean,Spring容器不能访问SpringMVC容器的Bean
SPRING容器与SPRINGMVC容器的区别与联系
解决方式:
在SpringMVC的配置文件中定义
<bean id="runtimeAdvice" class="demo.advice.RuntimeAdvice"></bean>
<aop:config>
<aop:pointcut expression="execution(* demo.controller..*.*(..))" id="myPointcut"/>
<aop:advisor advice-ref="runtimeAdvice" pointcut-ref="myPointcut"/>
</aop:config>
2.AspectJ 切面编程(xml方式)
spring2.0 支持AspectJ 语法 ,简化AOP开发。
开发方法还是三步:
- 确定目标对象(bean)
- 编写通知,对目标对象增强(advice)
- 配置切入点(pointcut)、切面(aspect)
2.1 AspectJ 提供Advice类型
普通的pojo即可。(不需要实现接口)
- Before 前置通知,相当于BeforeAdvice
- AfterReturning 后置通知,相当于AfterReturningAdvice
- Around 环绕通知,相当于MethodInterceptor
- AfterThrowing抛出通知,相当于ThrowAdvice
- After 最终final通知,不管是否异常,该通知都会执行
- DeclareParents 引介通知,相当于IntroductionInterceptor (不要求掌握)
2.2 分析各种通知应用
1.Before前置通知
案例应用: 实现权限控制 (即:权限不足的时候,抛出异常)、 记录方法调用信息日志
第一步:配置MyAspect类(切面),配置before方法(通知)
//aspectj的advice通知增强类,无需实现任何接口
public class MyAspect {
//前置通知的:方法运行之前增强
//应用: 权限控制 (权限不足,抛出异常)、 记录方法调用信息日志
//参数:org.aspectj.lang.JoinPoint
//参数:连接点对象(方法的包装对象:方法,参数,目标对象)
public void before(JoinPoint joinPoint){
//分析:抛出异常拦截的
//当前登录用户
String loginName = "Rose";
System.out.println("方法名称:"+joinPoint.getSignature().getName());
System.out.println("目标对象:"+joinPoint.getTarget().getClass().getName());
System.out.println("代理对象:"+joinPoint.getThis().getClass().getName());
//判断当前用户有没有执行方法权限
if(joinPoint.getSignature().getName().equals("save")){
if(!loginName.equals("admin")){
//只有超级管理员admin有权限,其他人不能执行某个方法,比如查询方法
throw new RuntimeException("您没有权限执行方法:"+joinPoint.getSignature().getName()+",类型为:"+joinPoint.getTarget().getClass().getName());
}
}
}
}
第二步:Spring容器中配置,配置applicationContext-aspectJ.xml(通知)
<!-- 1.确定了要增强的target对象 -->
<!-- 对于spring来说,目标对象:就是bean对象 -->
<!-- 基于接口类 -->
<bean id="customerService" class="cn.itcast.spring.service.CustomerServiceImpl"/>
<!-- 基于一般类的 -->
<bean id="productService" class="cn.itcast.spring.service.ProductService"/>
<!-- 2.配置advice通知增强 -->
<bean id="myAspectAdvice" class="cn.itcast.spring.aspect.MyAspect"/>
<!-- 3:配置aop -->
<aop:config>
<aop:aspect ref="myAspectAdvice">
<aop:pointcut expression="bean(*Service)" id="myPointcut"/>
<!-- 前置通知 -->
<aop:before method="before" pointcut-ref="myPointcut" />
</aop:aspect>
</aop:config>
2.AfterReturing 后置通知
应用场景:与业务相关的,如网上营业厅查询余额后,自动下发短信功能。
分析: 后置通知可以获取到目标方法返回值,如果想对返回值进行操作,使用后置通知(但不能修改目标方法返回 )
第一步:配置MyAspect类(切面),配置afterReturing方法(通知)
//aspectj的advice通知增强类,无需实现任何接口
public class MyAspect {
//应用场景:与业务相关的,如网上营业厅查询余额后,自动下发短信。
//后置通知:会在目标方法执行之后调用通知方法增强。
//参数1:连接点对象(方法的包装对象:方法,参数,目标对象)
//参数2:目标方法执行后的返回值,类型是object,“参数名”随便,但也不能太随便,一会要配置
public void afterReturing(JoinPoint joinPoint,Object returnVal){
//下发短信:调用运行商的接口,短信猫。。。
System.out.println("-++++++++-后置通知-当前下发短信的方法"+"-尊敬的用户,您调用的方法返回余额为:"+returnVal);
}
}
第二步:Spring容器中配置,配置applicationContext-aspectJ.xml
<!-- 1.确定了要增强的target对象 -->
<!-- 对于spring来说,目标对象:就是bean对象 -->
<!-- 基于接口类 -->
<bean id="customerService" class="cn.itcast.spring.service.CustomerServiceImpl"/>
<!-- 基于一般类的 -->
<bean id="productService" class="cn.itcast.spring.service.ProductService"/>
<!-- 2.配置advice通知增强 -->
<bean id="myAspectAdvice" class="cn.itcast.spring.aspect.MyAspect"/>
<!-- 3:配置aop -->
<aop:config>
<aop:aspect ref="myAspectAdvice">
<aop:pointcut expression="bean(*Service)" id="myPointcut"/>
<!-- 后置通知
returning:配置方法中的参数名字,与通知方法的第二个参数的名字,名字必须对应。
在运行的时候,spring会自动将返回值传入该参数中。
-->
<aop:after-returning method="afterReturing" returning="returnVal" pointcut-ref="myPointcut"/>
</aop:aspect>
</aop:config>
3.Around 环绕通知
应用场景:日志、缓存、权限、性能监控、事务管理
第一步:配置MyAspect类(切面),配置around方法(通知)
//aspectj的advice通知增强类,无需实现任何接口
public class MyAspect {
//应用场景:日志、缓存、权限、性能监控、事务管理
//环绕通知:在目标对象方法的执行前+后,可以增强
//参数:可以执行的连接点对象ProceedingJoinPoint(方法),特点是调用proceed()方法可以随时随地执行目标对象的方法(相当于目标对象的方法执行了)
//必须抛出一个Throwable
public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable{
//目标:事务的控制:
//开启事务:
System.out.println("-----开启了事务。。。。。。。。。");
//执行了目标对象的方法
Object resultObject = proceedingJoinPoint.proceed();
//结束事务
System.out.println("-----提交了事务。。。。。。。。。");
return resultObject;//目标对象执行的结果
}
}
第二步:Spring容器中配置,配置applicationContext-aspectJ.xml
<!-- 1.确定了要增强的target对象 -->
<!-- 对于spring来说,目标对象:就是bean对象 -->
<!-- 基于接口类 -->
<bean id="customerService" class="cn.itcast.spring.service.CustomerServiceImpl"/>
<!-- 基于一般类的 -->
<bean id="productService" class="cn.itcast.spring.service.ProductService"/>
<!-- 2.配置advice通知增强 -->
<bean id="myAspectAdvice" class="cn.itcast.spring.aspect.MyAspect"/>
<!-- 3:配置aop -->
<aop:config>
<aop:aspect ref="myAspectAdvice">
<aop:pointcut expression="bean(*Service)" id="myPointcut"/>
<!-- 环绕通知 -->
<aop:around method="around" pointcut-ref="myPointcut"/>
</aop:aspect>
</aop:config>
4.AfterThrowing 抛出通知
作用:目标代码出现异常,通知执行。记录异常日志、通知管理员(短信、邮件)
应用场景:处理异常(一般不可预知),记录日志
第一步:配置MyAspect类(切面),配置aterThrowing方法(通知)
//aspectj的advice通知增强类,无需实现任何接口
public class MyAspect {
//作用:目标代码出现异常,通知执行。记录异常日志、通知管理员(短信、邮件)
//只有目标对象方法抛出异常,通知才会执行
//参数1:静态连接点(方法对象)
//参数2:目标方法抛出的异常,参数名随便,但也不能太随便
public void afterThrowing(JoinPoint joinPoint,Throwable ex){
//一旦发生异常,发送邮件或者短信给管理员
System.out.println("++管理员您好,"+joinPoint.getTarget().getClass().getName()+"的方法:"
+joinPoint.getSignature().getName()+"发生了异常,异常为:"+ex.getMessage());
}
}
第二步:Spring容器中配置,配置applicationContext-aspectJ.xml
<!-- 1.确定了要增强的target对象 -->
<!-- 对于spring来说,目标对象:就是bean对象 -->
<!-- 基于接口类 -->
<bean id="customerService" class="cn.itcast.spring.service.CustomerServiceImpl"/>
<!-- 基于一般类的 -->
<bean id="productService" class="cn.itcast.spring.service.ProductService"/>
<!-- 2.配置advice通知增强 -->
<bean id="myAspectAdvice" class="cn.itcast.spring.aspect.MyAspect"/>
<!-- 3:配置aop -->
<aop:config>
<aop:aspect ref="myAspectAdvice">
<aop:pointcut expression="bean(*Service)" id="myPointcut"/>
<!-- 抛出通知
throwing:通知中的方法的第二个参数,异常类型的参数的名字,在运行的时候,spring会自动将异常传入该参数中。-->
<aop:after-throwing method="aterThrowing" throwing="ex" pointcut-ref="myPointcut"/>
</aop:aspect>
</aop:config>
5.After 最终通知
作用:不管目标方法是否发生异常,最终通知都会执行(类似于finally代码功能)
应用场景:释放资源 (关闭文件、 关闭数据库连接、 网络连接、 释放内存对象 )
第一步:配置MyAspect类(切面),配置after方法(通知)
//aspectj的advice通知增强类,无需实现任何接口
public class MyAspect {
//应用场景:释放资源 (关闭文件、 关闭数据库连接、 网络连接、 释放内存对象 )
//最终通知:不管是否有异常都会执行
public void after(JoinPoint joinPoint){
//释放数据库连接
System.out.println("数据库的connection被释放了。。。。。,执行的方法是:"+joinPoint.getSignature().getName());
}
}
第二步:Spring容器中配置,配置applicationContext-aspectJ.xml
<!-- 1.确定了要增强的target对象 -->
<!-- 对于spring来说,目标对象:就是bean对象 -->
<!-- 基于接口类 -->
<bean id="customerService" class="cn.itcast.spring.service.CustomerServiceImpl"/>
<!-- 基于一般类的 -->
<bean id="productService" class="cn.itcast.spring.service.ProductService"/>
<!-- 2.配置advice通知增强 -->
<bean id="myAspectAdvice" class="cn.itcast.spring.aspect.MyAspect"/>
<!-- 3:配置aop -->
<aop:config>
<aop:aspect ref="myAspectAdvice">
<aop:pointcut expression="bean(*Service)" id="myPointcut"/>
<!-- 最终通知 -->
<aop:after method="after" pointcut-ref="myPointcut"/>
<!-- 以上代码也可以写成:pointcut切入点表达式:只能给一个通知方法来用,相当于省略了<aop:pointcut expression="bean(*Service)" id="myPointcut"/>
<aop:after method="after" pointcut="bean(*Service)"/>-->
</aop:aspect>
</aop:config>
五种通知小结:
只要掌握Around(环绕通知)
通知类型,就可实现其他四种通知效果。
因为你可以在环绕通知的方法中编写如下代码:
try {
//前置通知
Object result = proceedingJoinPoint.proceed(); // 环绕通知
//后置通知
}catch(Exception){
//抛出通知
}finally{
//最终通知
}
2.3 各种Advice方法可接收的参数和返回值小结(参考)
通知类型 | 输入参数(可选) | 返回值类型 | 其他 |
---|---|---|---|
Before前置通知 | JoinPoint(静态连接点信息) | void | |
AfterReturning后置通知 | JoinPoint, Object | void | |
Around环绕通知 | ProceedingJoinPoint(可执行的连接点信息) | Object | throws Throwable |
AfterThrowing抛出通知 | JJoinPoint, Throwable | void | |
After最终通知 | JoinPoint(静态连接点信息) | void |
3.@AspectJ 注解配置切面编程
3.1 编写目标对象 (bean)、spring容器、测试类
3.1.1 编写目标对象(bean)
1.创建接口CustomerService.java
//接口
public interface CustomerService {
//保存
public void save();
//查询
public int find();
}
2.创建接口的实现类,CustomerServiceImpl
//实现类
/**
* @Service("customerService")
* 相当于spring容器中定义:
* <bean id="customerService" class="cn.itcast.spring.a_aspectj.CustomerServiceImpl">
*/
@Service("customerService")
public class CustomerServiceImpl implements CustomerService{
public void save() {
System.out.println("客户保存了。。。。。");
}
public int find() {
System.out.println("客户查询数量了。。。。。");
return 100;
}
}
3.创建类ProductService.java,不需要实现接口
//没有接口的类
/**
* @Service("productService")
* 相当于spring容器中定义:
* <bean id="productService" class="cn.itcast.spring.a_aspectj.ProductService">
*/
@Service("productService")
public class ProductService {
public void save() {
System.out.println("商品保存了。。。。。");
}
public int find() {
System.out.println("商品查询数量了。。。。。");
return 99;
}
}
3.1.2 配置applicationContext.xml
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
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/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
使用bean注解的扫描(自动开启注解功能)
<!-- 1。确定目标 -->
<!-- 扫描bean组件 -->
<context:component-scan base-package="cn.itcast.spring"/>
3.1.3 测试代码SpringTest.java
//springjunit集成测试
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations="classpath:applicationContext.xml")
public class SpringTest {
//注入要测试bean
@Autowired
private CustomerService customerService;
@Autowired
private ProductService productService;
//测试
@Test
public void test(){
//基于接口
customerService.save();
customerService.find();
//基于类的
productService.save();
productService.find();
}
}
3.2 编写通知,配置切面
1.编写通知类,在通知类添加@Aspect 注解,代表这是一个切面类,并将切面类交给spring管理(能被spring扫描到@Component)。
- @Component(“myAspect”):将增强的类交给spring管理,才可以增强
- @Aspect:将该类标识为切面类(这里面有方法进行增强),相当于<aop:aspect ref=”myAspect”>
//advice通知类增强类
@Component("myAspect")//相当于<bean id="myAspect" class="cn.itcast.spring.a_aspectj.MyAspect"/>
@Aspect//相当于<aop:aspect ref="myAspect">
public class MyAspect {
}
2.在切面的类,通知方法上添加
@AspectJ提供不同的通知类型
- @Before 前置通知,相当于BeforeAdvice
- @AfterReturning 后置通知,相当于AfterReturningAdvice
- @Around 环绕通知,相当于MethodInterceptor
- @AfterThrowing抛出通知,相当于ThrowAdvice
- @After 最终final通知,不管是否异常,该通知都会执行
- @DeclareParents 引介通知,相当于IntroductionInterceptor (不要求掌握)
3.在spring容器中开启AspectJ 注解自动代理机制
使用<aop:aspectj-autoproxy/>
作用:能自动扫描带有@Aspect的bean,将其作为增强aop的配置,有点相当于:<aop:config>
<!-- 1。确定目标 -->
<!-- 扫描bean组件 -->
<context:component-scan base-package="cn.itcast.spring"/>
<!-- 2:编写通知 -->
<!-- 3:配置aop的aspectj的自动代理:
自动扫描bean组件中,含有@Aspect的bean,将其作为aop管理,开启动态代理 -->
<aop:aspectj-autoproxy/>
3.2.1 前置通知
在切面的类MyAspect.java类中添加通知方法@Before(),
方案一:可以直接将切入点的表达式写到@Before()中
//前置通知
//相当于:<aop:before method="before" pointcut="bean(*Service)"/>
//@Before("bean(*Service)"):参数值:自动支持切入点表达式或切入点名字
@Before("bean(*Service)")
public void before(JoinPoint joinPoint){
System.out.println("=======前置通知。。。。。");
}
方案二:可以使用自定义方法,使用@Pointcut 定义切入点
切入点方法的语法要求:
切点方法:private void 无参数、无方法体的方法,方法名为切入点的名称
一个通知方法@Before可以使用多个切入点表达式,中间使用“||”符合分隔,用来表示多个切入点
//自定义切入点
//方法名就是切入点的名字
//相当于<aop:pointcut expression="bean(*Service)" id="myPointcut"/>
@Pointcut("bean(*Service)")
private void myPointcut(){}
//自定义切入点
//方法名就是切入点的名字
//相当于<aop:pointcut expression="bean(*Service)" id="myPointcut2"/>
@Pointcut("bean(*Service)")
private void myPointcut2(){}
//前置通知
//相当于:<aop:before method="before" pointcut-ref="myPointcut"/>
//相当于:<aop:before method="before" pointcut-ref="myPointcut2"/>
@Before("myPointcut()||myPointcut2()")
public void before(JoinPoint joinPoint){
System.out.println("=======前置通知。。。。。");
}