1.AspectJ简介
AspectJ是一个基于Java语言的AOP框架。
Spring2.0以后新增了对AdpectJ切点表达式的支持。
@AspectJ是AspectJ1.5新增功能,通过JDK5注解技术,允许直接在Bean类中定义切面。
新版本Spring框架,建议使用AspectJ方式来开发AOP。
使用AspectJ需要导入Spring AOP和AspectJ相关jar包。
2.语法简介
(1)@AspectJ提供不同的通知类型
@Before 前置通知,相当于BeforeAdvice
@AfterReturning 后置通知,相当于AfterReturningAdvice
@Around 环绕通知,相当于MethodInterceptor
@AfterThrowing异常抛出通知,相当于ThrowAdvice
@After 最终final通知,不管是否异常,该通知都会执行
@DeclareParents 引介通知,相当于IntroductionInterceptor
(2)在通知中通过value属性定义切点
通过execution函数,可以定义切点的方法切入。
语法:
execution(<访问修饰符>?<返回类型><方法名>(<参数>)<异常>)
例如:
匹配所有类public方法 execution(public * *(..)) 第一个*任意方法返回值,第二个*任意参数 ..表示任意参数
匹配指定包下所有类方法(不包含子包) execution(* com.ikidana.dao.*(..)) 访问修饰符可以没有,第一个*返回值类型 第二个*方法名称 ..表示任意参数
匹配指定包下所有类方法(包含子包) 第一个..*表示包、子孙包下所有类
匹配指定类所有方法 execution(* com.ikidana.service.UserService.*(..))
匹配实现特定接口所有类方法 execution(* com.imooc.dao.GenericDAO+.*(..))
匹配所有save开头的方法 execution(* save*(..))
3.简单案例
导入依赖包:
1<dependency> 2 <groupId>junit</groupId> 3 <artifactId>junit</artifactId> 4 <version>4.1.12</version> 5</dependency> 6<!--引入Spring的基本开发包--> 7<dependency> 8 <groupId>org.springframework</groupId> 9 <artifactId>spring-core</artifactId> 10 <version>4.2.4.RELEASE</version> 11</dependency> 12<dependency> 13 <groupId>org.springframework</groupId> 14 <artifactId>spring-context</artifactId> 15 <version>4.2.4.RELEASE</version> 16</dependency> 17<dependency> 18 <groupId>org.springframework</groupId> 19 <artifactId>spring-beans</artifactId> 20 <version>4.2.4.RELEASE</version> 21</dependency> 22<dependency> 23 <groupId>org.springframework</groupId> 24 <artifactId>spring-expression</artifactId> 25 <version>4.2.4.RELEASE</version> 26</dependency> 27<dependency> 28 <groupId>aopalliance</groupId> 29 <artifactId>aopalliance</artifactId> 30 <version>1.0</version> 31</dependency> 32<dependency> 33 <groupId>org.springframework</groupId> 34 <artifactId>spring-aop</artifactId> 35 <version>4.2.4.RELEASE</version> 36</dependency> 37<dependency> 38 <groupId>org.aspectj</groupId> 39 <artifactId>aspectjweaver</artifactId> 40 <version>1.8.9</version> 41</dependency> 42<dependency> 43 <groupId>org.springframework</groupId> 44 <artifactId>spring-aspects</artifactId> 45 <version>4.2.4.RELEASE</version> 46</dependency> 47<dependency> 48 <groupId>org.springframework</groupId> 49 <artifactId>spring-test</artifactId> 50 <version>4.2.4.RELEASE</version> 51</dependency> 52<dependency> 53 <groupId>junit</groupId> 54 <artifactId>junit</artifactId> 55 <version>4.12</version> 56 <scope>compile</scope> 57</dependency>
创建XML配置文件,开启自动代理:
<aop:aspectj-autoproxy/>
1)前置通知
a.创建一个实例类,并创建许多方法,现在我需要增强这个类中的方法
1public class ProductDAO { 2 public void save(){ 3 System.out.println("ProductDAO save"); 4 } 5 public void delete(){ 6 System.out.println("ProductDAO delete"); 7 } 8 public void update(){ 9 System.out.println("ProductDAO update"); 10 } 11 public void find(){ 12 System.out.println("ProductDAO find"); 13 } 14}
b.添加一个切面
1@Aspect //代表一个切面 2public class MyAspectAnno { 3 @Before(value = "execution(* com.imooc.aspectJ.demo1.ProductDAO.*(..))") //ProductDao所有方法 4 public void before(){ 5 System.out.println("前置通知"); 6 } 7}
c.在XML中声明注册
1<!--目标类,属性注入--> 2<bean id="productDao" class="com.imooc.aspectJ.demo1.ProductDAO"/> 3<!--定义切面,不需要引用,所以不需要方法--> 4<bean class="com.imooc.aspectJ.demo1.MyAspectAnno"/>
d.属性注入、目标注入、通知测试
1@RunWith(SpringJUnit4ClassRunner.class) 2@ContextConfiguration("classpath:applicationContext.xml") 3public class SpringDemo1 { 4 @Resource(name="productDao") //目标注入 5 private ProductDAO productDAO; 6 7 @Test 8 public void demo1(){ 9 productDAO.save(); 10 productDAO.delete(); 11 productDAO.update(); 12 productDAO.find(); 13 } 14}
e.测试结果
前置通知
ProductDAO save
前置通知
ProductDAO delete
前置通知
ProductDAO update
前置通知
ProductDAO find
我们可以发现,ProductDAO类下面的所有方法,都添加了前置通知。
如果我们这样定义切面:
@Before(value = "execution(* com.imooc.aspectJ.demo1.ProductDAO.save(..))")
那么只会在save前面添加前置通知
可以在方法中传入JoinPoint对象,用来获得切点信息。
1@Before(value = "execution(* com.imooc.aspectJ.demo1.ProductDAO.*(..))") //ProductDao所有方法 2public void before(JoinPoint joinPoint){ 3 System.out.println("前置通知" + joinPoint) ; 4}
切点信息类似如下:
execution(void com.imooc.aspectJ.demo1.ProductDAO.save())
2)后置通知
添加一个切面:
1@AfterReturning(value = "execution(* com.imooc.aspectJ.demo1.ProductDAO.update(..))",returning = "ret") 2public void afterReturing(Object ret){ 3 System.out.println("后置通知" + ret); 4} 5 6结果: 7ProductDAO save 8ProductDAO delete 9ProductDAO update 10后置通知 ProductDAO update 返回值 11ProductDAO find
通过returning属性,可以定义方法返回值。
3)环绕通知
around方法的返回值就是目标代理方法执行的返回值。
可以通过ProceedingJoinPoint可以调用拦截目标方法执行。
a.添加一个环绕通知的切面
1public Object around(ProceedingJoinPoint joinPoint) throws Throwable { 2 System.out.println("环绕前通知"); 3 Object obj = joinPoint.proceed(); //执行目标方法,如果不调用这句话,那么目标方法将不会执行 4 System.out.println("环绕后通知"); 5 return obj; 6} 7//结果: 8ProductDAO save 9环绕前通知 10ProductDAO delete 11环绕后通知 12ProductDAO update 13ProductDAO find
4)异常抛出通知
通过设置throwing属性,可以设置发生异常对象参数。
1@AfterThrowing(value = "execution(* com.imooc.aspectJ.demo1.ProductDAO.find(..))") 2public void afterThrowing(){ 3 System.out.println("异常抛出通知" ); 4}
当然我们还可以打印异常:
1@AfterThrowing(value = "execution(* com.imooc.aspectJ.demo1.ProductDAO.find(..))",throwing = "e") 2public void afterThrowing(Throwable e){ 3 System.out.println("异常抛出通知" + " " + e); 4}
5)最终通知
无论是否出现异常,最终通知总是会被执行的。
1@After(value = "execution(* com.imooc.aspectJ.demo1.ProductDAO.find(..))") 2public void after(){ 3 System.out.println("最终通知"); 4}
4.切点命中
通过@Pointcut为切点命名。
在每个通知内定义切点,会造成工作量大,不易维护,对于重复的切点,可以使用@Pointcut进行定义。
切点方法:private void 无参方法,方法名为切点名。
当通知多个切点时,可以使用||进行连接。
大概意思就是,如果同一个切点value被很多地方引用,改起来就不太方便。
1@Pointcut(value = "execution(* com.imooc.aspectJ.demo1.ProductDAO.save(..))") 2private void myPointcut1(){} 3 4@AfterReturning(value = "myPointcut1()") 5public void afterReturing(){ 6 System.out.println("后置通知"); 7}
相当于创建了别名,修改了别名,就相当于修改了所有的引用。
5.基于AspectJ的XML方式的AOP开发
1)前置通知
1//创建接口类和实例类 2public interface CustomerDao { 3 public void save(); 4 public void update(); 5 public void delete(); 6 public void find(); 7} 8 9public class CustomerDaoImpl implements CustomerDao { 10 public void save() { 11 System.out.println("c-save"); 12 } 13 14 public void update() { 15 System.out.println("c-update"); 16 } 17 18 public void delete() { 19 System.out.println("c-delete"); 20 } 21 22 public void find() { 23 System.out.println("c-find"); 24 } 25} 26 27//创建通知 28public class MyAspectXml { 29 //前置通知 30 public void before(){ 31 System.out.println("XML方式的前置通知"); 32 } 33} 34 35//配置增强 36<!--XML配置的方式完成AOP开发--> 37<!--配置目标类--> 38<bean id="customerDao" class="com.imooc.aspectJ.demo2.CustomerDaoImpl"/> 39<!--配置切面类--> 40<bean id="myAspectXml" class="com.imooc.aspectJ.demo2.MyAspectXml"/> 41<!--AOP相关配置--> 42<aop:config> 43 <!--定义切入点:那些类的那些方法需要应用增强--> 44 <aop:pointcut id="pointcut1" expression="execution(* com.imooc.aspectJ.demo2.CustomerDao.save(..))"/> 45 <!--配置AOP切面:使用那些增强--> 46 <aop:aspect ref="myAspectXml"> 47 <!--配置前置增强 pointcut-ref哪个切点增强 method使用那个增强方法 aop:before增强类型--> 48 <aop:before method="before" pointcut-ref="pointcut1"/> 49 </aop:aspect> 50</aop:config> 51 52//测试 53@RunWith(SpringJUnit4ClassRunner.class) 54@ContextConfiguration(value = "classpath:applicationContext2.xml") 55public class SpringDemo2 { 56 @Resource(name = "customerDao") 57 private CustomerDao customerDao; 58 59 @Test 60 public void demo1(){ 61 customerDao.save(); 62 customerDao.delete(); 63 customerDao.find(); 64 customerDao.update(); 65 } 66} 67 68//结果 69XML方式的前置通知 70c-save 71c-delete 72c-find 73c-update
2)其他通知
1public class MyAspectXml { 2 //前置通知 3 public void before(){ //一样可以打印JoinPoint连接点 4 System.out.println("XML方式的前置通知"); 5 } 6 7 //后置通知 8 public void afterReturing(){ 9 System.out.println("XML方式的后置通知"); 10 } 11 12 //环绕通知 13 public Object around(ProceedingJoinPoint joinPoint) throws Throwable { 14 System.out.println("环绕前"); 15 Object obj = joinPoint.proceed(); //执行目标方法 16 System.out.println("环绕后"); 17 return obj; 18 } 19} 20 21<aop:config> 22 <!--定义切入点:那些类的那些方法需要应用增强--> 23 <aop:pointcut id="pointcut1" expression="execution(* com.imooc.aspectJ.demo2.CustomerDao.save(..))"/> 24 <aop:pointcut id="pointcut2" expression="execution(* com.imooc.aspectJ.demo2.CustomerDao.find(..))"/> 25 <aop:pointcut id="pointcut3" expression="execution(* com.imooc.aspectJ.demo2.CustomerDao.delete(..))"/> 26 27 <!--配置AOP切面:使用那些增强--> 28 <aop:aspect ref="myAspectXml"> 29 <!--配置前置增强 pointcut-ref哪个切点增强 method使用那个增强方法 aop:before增强类型--> 30 <aop:before method="before" pointcut-ref="pointcut1"/> 31 <!--配置后置通知--> 32 <aop:after method="afterReturing" pointcut-ref="pointcut2"/> 33 <!--环绕通知--> 34 <aop:around method="around" pointcut-ref="pointcut3"/> 35 </aop:aspect> 36</aop:config>
在现在企业中,spring在进行AOP开发的时候,都不会使用传统方式,都会基于AspectJ的AOP开发。
传统AOP开发需要手动开发代理层,这样工作量会稍大。