该系列为imooc Spring从入门到进阶笔记,跟随课程加入自己见解,同时也为项目中碰到一些问题做了解答
大纲
1、AspectJ简介
- AspectJ是一个基于Java语言的AOP框架
- Spring2.0以后新增了对AspectJ切点表达式支持
- @AspectJ 是AspectJ1.5新增功能,通过JDK5注解技术,允许直接在Bean类中定义切面
- 新版本Spring框架,建议使用AspectJ方式来开发AOP
- 使用AspectJ 需要导入Spring AOP和 AspectJ相关jar包
- spring-aop-4.2.4.RELEASE.jar
- com.springsource.org.aopalliance-1.0.0.jar
- spring-aspects-4.2.4.RELEASE.jar
- com.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar
2、AspectJ的注解开发AOP
2.1、开发环境准备
在Spring配置文件配置约束
<?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">
<!-- 开启AspectJ自动代理-->
<aop:aspectj-autoproxy />
</beans>
2.2、AspectJ提供不同的通知类型
- @Before 前置通知,相当于BeforeAdvice
- @AfterReturning 后置通知,相当于AfterReturningAdvice
- @Around 环绕通知,相当于MethodInterceptor
- @AfterThrowing异常抛出通知,相当于ThrowAdvice
- @After 最终final通知,不管是否异常,该通知都会执行
- @DeclareParents 引介通知,相当于IntroductionInterceptor (不要求掌握)
2.3、切入点表达式的定义
通过execution函数,可以定义切点的方法切入
-
语法:
- execution(<访问修饰符>?<返回类型><方法名>(<参数>)<异常>)
-
例如
-
匹配所有类public方法
(第一个*
表示任意返回值类型,第二个*
表示任意名称,..
表示任意参数)
execution(public * *(..))
-
匹配指定包下所有类方法
execution(* com.imooc.dao.*(..)) //不包含子包
execution(* com.imooc.dao..*(..)) ..*//表示包、子孙包下所有类
-
匹配指定类所有方法
execution(* com.imooc.service.UserService.*(..))
-
匹配实现特定接口所有类方法
execution(* com.imooc.dao.GenericDAO+.*(..))
-
匹配所有save开头的方法
execution(* save*(..))
-
注意:以上表达式第一个*后有空格
2.4、入门案例
演示实现前置通知案例
- 使用idea Maven创建Web项目
- 配置项目pom文件
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<!--引入Spring的基本开发包-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>4.2.4.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.2.4.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>4.2.4.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-expression</artifactId>
<version>4.2.4.RELEASE</version>
</dependency>
<dependency>
<groupId>aopalliance</groupId>
<artifactId>aopalliance</artifactId>
<version>1.0</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>4.2.4.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.9</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>4.2.4.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>4.2.4.RELEASE</version>
</dependency>
</dependencies>
- 在java文件夹下>新建com.imooc.aspectJ.demo1包>新建ProductDao类
package com.imooc.aspectJ.demo1;
public class ProductDao {
public void save(){
System.out.println("保存商品...");
}
public String update(){
System.out.println("修改商品...");
return "hello";
}
public void delete(){
System.out.println("删除商品...");
}
public void findOne(){
System.out.println("查询一个商品...");
//int i = 1/0;
}
public void findAll(){
System.out.println("查询所有商品...");
//int j = 1/0;
}
}
- 在java文件夹下>com.imooc.aspectJ.demo1包>新建MyAspectAnno类(切面类)
针对ProductDao 的save()方法做前置通知
定义切面
@Aspect
public class MyAspectAnno {
}
package com.imooc.aspectJ.demo1;
import org.aspectj.lang.annotation.*;
/**
* 切面类
*/
@Aspect//定义切面
public class MyAspectAnno {
@Before(value="execution(* com.imooc.aspectJ.demo1.ProductDao.save(..))")
public void before(){
System.out.println("前置通知==================");
}
}
- 在resources下>新建applicationContext.xml(Spring配置文件)
<?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">
<!-- 开启AspectJ自动代理-->
<aop:aspectj-autoproxy />
<!--定义被代理的目标类-->
<bean id="productDao" class="com.imooc.aspectJ.demo1.ProductDao"/>
<!--定义切面-->
<bean class="com.imooc.aspectJ.demo1.MyAspectAnno"/>
</beans>
- 在java文件夹下>com.imooc.aspectJ.demo1包>新建SpringDemo1 类(测试类)
package com.imooc.aspectJ.demo1;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import javax.annotation.Resource;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class SpringDemo1 {
@Resource(name = "productDao")
private ProductDao productDao;
@Test
public void demo1(){
productDao.save();
productDao.update();
productDao.delete();
productDao.findAll();
productDao.findOne();
}
}
启动测试
可见使用AspectJ的注解方式实现前置通知案例成功
2.5、通知类型
2.5.1、@Before 前置通知
可以在方法中传入JoinPoint对象,用来获得切点信息
修改MyAspectAnno类before方法
//需要增强的代码
@Before(value="execution(* com.imooc.aspectJ.demo1.ProductDao.save(..))")
public void before(JoinPoint joinPoint){
System.out.println("前置通知=================="+joinPoint);
}
启动测试
可见已经获取到了切点的信息
2.5.2、@AfterReturing 后置通知
通过returning属性 可以获得 被代理的目标类的 切入点方法的返回值,作为参数
在MyAspectAnno类中新增afterReturing方法
@AfterReturning(value = "execution(* com.imooc.aspectJ.demo1.ProductDao.update(..))",returning = "result")
public void afterReturing(Object result){
System.out.println("后置通知=================="+result);
启动测试
可见已经对update方法进行了后置通知,并获取到了该方法的返回值
2.5.3、@Around 环绕通知
- around方法的返回值就是被代理的目标的 切入点方法的返回值
- 调用ProceedingJoinPoint的proceed方法,被代理的目标类的切入点方法才会执行
在MyAspectAnno类中新增around方法
@Around(value = "execution(* com.imooc.aspectJ.demo1.ProductDao.delete(..))")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("环绕前通知==================");
Object obj=joinPoint.proceed();
System.out.println("环绕后通知==================");
return obj;
}
启动测试
可见已经对delete方法进行了环绕通知
2.5.4、@AfterThrowing 异常抛出通知
- 通过设置throwing属性,可以获取被代理的目标类 切入点方法发生异常时异常对象参数
在MyAspectAnno类中新增afterThrowing方法
@AfterThrowing(value ="execution(* com.imooc.aspectJ.demo1.ProductDao.findOne(..))",throwing = "e")
public void afterThrowing(Throwable e){
System.out.println("异常抛出通知=================="+e.getMessage());
}
修改Product类中的findOne方法 ,制造异常
public void findOne(){
System.out.println("查询一个商品...");
int i = 1/0;
}
启动测试代码
可见已经对findOne方法进行了异常抛出通知
2.5.5、@After 最终通知
无论被代理的目标类 切入点方法是否出现异常,最终通知总是会被执行的
在MyAspectAnno类中新增after方法
@After(value = "execution(* com.imooc.aspectJ.demo1.ProductDao.findAll(..))")
public void after(){
System.out.println("最终通知==================");
}
启动测试
可见已经对findAll方法进行了最终通知
2.6、通过@Pointcut为切点命名
在每个通知内定义切点,会造成工作量大,不易维护,对于重复的切点,可以使用@Pointcut进行定义来定义一个方法,该方法无任何实际操作,无参数无返回值,方法名可自定义,然后在@Pointcut注解中通过execution表达式(匹配被代理类中的方法)来配置value属性
定义切点方法的方式:
@Pointcut(value="execution(表达式内容)")
private void 名称自定义(){}
当多个切点使用一个通知时可有以下三种方式
- 为多个切点都配置切点方法,然后 在通知的注解value属性中使用
||
为切点方法名进行链接(最实用) - 将多个切点定义在一个切点方法中,在@Pointcut注解value属性中使用
||
为execution表达式进行链接,多个切点共用一个方法名 - 直接看代码,觉得不太实用
@Pointcut("execution(* com.savage.aop.MessageSender.*(..))")
private void logSender(){}
@Pointcut("execution(* com.savage.aop.MessageReceiver.*(..))")
private void logReceiver(){}
@Pointcut("logSender() || logReceiver()")
private void logMessage(){}
代码演示
在MyAspectAnno类中定义切点方法
package com.imooc.aspectJ.demo1;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
/**
* 切面类
*/
@Aspect
public class MyAspectAnno {
//演示多个切点使用同一个增强的情况
@Before(value="myPointcut2()||myPointcut1()")
public void before(JoinPoint joinPoint){
System.out.println("前置通知=================="+joinPoint);
}
@AfterReturning(value = "myPointcut2()",returning = "result")
public void afterReturing(Object result){
System.out.println("后置通知=================="+result);
}
@Around(value = "myPointcut3()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("环绕前通知==================");
Object obj=joinPoint.proceed();
System.out.println("环绕后通知==================");
return obj;
}
@AfterThrowing(value ="myPointcut4()",throwing = "e")
public void afterThrowing(Throwable e){
System.out.println("异常抛出通知=================="+e.getMessage());
}
@After(value = "myPointcut5()")
public void after(){
System.out.println("最终通知==================");
}
@Pointcut(value="execution(* com.imooc.aspectJ.demo1.ProductDao.save(..))")
private void myPointcut1(){}
@Pointcut(value="execution(* com.imooc.aspectJ.demo1.ProductDao.update(..))")
private void myPointcut2(){}
@Pointcut(value="execution(* com.imooc.aspectJ.demo1.ProductDao.delete(..))")
private void myPointcut3(){}
@Pointcut(value="execution(* com.imooc.aspectJ.demo1.ProductDao.findOne(..))")
private void myPointcut4(){}
@Pointcut(value="execution(* com.imooc.aspectJ.demo1.ProductDao.findAll(..))")
private void myPointcut5(){}
}
启动测试
可见通过@Pointcut注解来注解方法定义切点的方式成功
3、AspectJ的XML方式开发AOP
直接看代码
在理解注解方式的基础上可通过以下案例理解AspectJ的XML方式开发AOP
- 引入的依赖与注解方式相同
- 在java文件夹下>新建com.imooc.aspectJ.demo2包>新建CustomerDao类(接口)
package com.imooc.aspectJ.demo2;
public interface CustomerDao {
public void save();
public String update();
public void delete();
public void findOne();
public void findAll();
}
- 在java文件夹下>com.imooc.aspectJ.demo2包>新建CustomerDao类(接口实现)
package com.imooc.aspectJ.demo2;
public class CustomerDaoImpl implements CustomerDao{
@Override
public void save() {
System.out.println("保存客户...");
}
@Override
public String update() {
System.out.println("修改客户...");
return "Spring";
}
@Override
public void delete() {
System.out.println("删除客户...");
}
@Override
public void findOne() {
System.out.println("查询一个客户...");
//int a=1/0;
}
@Override
public void findAll() {
System.out.println("查询所有客户...");
}
}
- 在java文件夹下>com.imooc.aspectJ.demo2包>新建MyAspectXml类(通知类)
package com.imooc.aspectJ.demo2;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
public class MyAspectXml {
// 前置通知
public void before(JoinPoint joinPoint){
System.out.println("XML方式的前置通知=============="+joinPoint);
}
// 后置通知
public void afterReturing(Object result){
System.out.println("XML方式的后置通知=============="+result);
}
// 环绕通知
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("XML方式的环绕前通知==============");
Object obj = joinPoint.proceed();
System.out.println("XML方式的环绕后通知==============");
return obj;
}
// 异常抛出通知
public void afterThrowing(Throwable e){
System.out.println("XML方式的异常抛出通知============="+e.getMessage());
}
// 最终通知
public void after(){
System.out.println("XML方式的最终通知=================");
}
}
- 在resources下新建applicationContext2.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">
<!--XML的配置方式来完成AOP的开发===================-->
<!--配置被代理的目标类-->
<bean id="customerDaoImpl" class="com.imooc.aspectJ.demo2.CustomerDaoImpl"/>
<!--配置切面类-->
<bean id="myAspectXml" class="com.imooc.aspectJ.demo2.MyAspectXml"/>
<!--植入的相关配置-->
<aop:config>
<!--配置切入点-->
<aop:pointcut id="pointcut1" expression="execution(* com.imooc.aspectJ.demo2.CustomerDao.save(..))"/>
<aop:pointcut id="pointcut2" expression="execution(* com.imooc.aspectJ.demo2.CustomerDao.update(..))"/>
<aop:pointcut id="pointcut3" expression="execution(* com.imooc.aspectJ.demo2.CustomerDao.delete(..))"/>
<aop:pointcut id="pointcut4" expression="execution(* com.imooc.aspectJ.demo2.CustomerDao.findOne(..))"/>
<aop:pointcut id="pointcut5" expression="execution(* com.imooc.aspectJ.demo2.CustomerDao.findAll(..))"/>
<!--配置AOP的切面-->
<aop:aspect ref="myAspectXml">
<!--配置前置通知-->
<aop:before method="before" pointcut-ref="pointcut1"/>
<!--配置后置通知-->
<aop:after-returning method="afterReturing" pointcut-ref="pointcut2" returning="result"/>
<!--配置环绕通知-->
<aop:around method="around" pointcut-ref="pointcut3"/>
<!--配置异常抛出通知-->
<aop:after-throwing method="afterThrowing" pointcut-ref="pointcut4" throwing="e"/>
<!--配置最终通知-->
<aop:after method="after" pointcut-ref="pointcut5"/>
</aop:aspect>
</aop:config>
</beans>
- 在java文件夹下>com.imooc.aspectJ.demo2包>新建SpringDemo2 类(测试类)
package com.imooc.aspectJ.demo2;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import javax.annotation.Resource;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(value="classpath:applicationContext2.xml")
public class SpringDemo2 {
@Resource(name="customerDaoImpl")
private CustomerDao customerDao;
@Test
public void demo1(){
customerDao.save();
customerDao.update();
customerDao.delete();
customerDao.findOne();
customerDao.findAll();
}
}
启动代码