AopOfSpring
本章内容:
-
面向切面编程的基本原理
-
通过POJO创建切面
-
使用@AspectJ注解
-
为AspectJ切面注入依赖
4.1什么是切面编程呢
切面能帮助我们模块化横切关注点,对于许多需要相同同的方法去增强的bean来说,一个个来将增强代码写入其中,显然是不可取的, 假如有一百个类需要增强,那么就得写一百次增强方法,再加上修改,这不得逆天,所以AOP面向切面编程出现了
4.1.1 定义AOP术语
不多说,上图
Spring切面可以有5种类型得通知:
-
前置通知:在目标方法调用之前调用通知
-
后置通知:在目标方法调用之后调用通知
-
返回通知:在目标方法成功执行后调用通知
-
异常通知:在目标方法抛出异常后调用通知
-
环绕通知(重点):通知包裹了被通知得方法,在被通知得方法调用之前和调用之后执行自定义得行为
4.1.2 Spring对Aop的支持
Spring提供了四种类型的AOP支持:
-
基于代理的经典SpringAOP(太过笨重复杂,不做介绍)
-
纯POJO切面
-
@AspectJ注解驱动的切面
-
注入式是AspectJ切面(适用于各个版本)
前三种都是SpringAOP的变体,需要构建在JDK动态代理上,所以Spring对AOP的支持局限于方法拦截
spring在运行时通知对象
当Spring在调用bean的方法前,并不是调用目标类的方法,而是执行代理对象的方法.即当代理类拦截到方法调用时,在调用目标方法之前,会执行切面逻辑
4.2 通过切点来选择连接点
这边主要会介绍有关于切点的编写
如下
package concert; //注意包名以及类名 public interface Performance { public void perform(); }
当我们使用AspectJ切点表达式选择Performance的perform()方法时
我们也可以使用within()指示器来限制匹配,使用方法如下
execution(* concert.Performance.perform(..)) && within(concert *)
&&符号表示的是and操作符,即必须是concert所在包下的任意类的方法
当然我们也可以使用'||'操作符来标识(or)关系,而使用'!'操作符来标识非(not)操作
在XML中'&'有特殊含义,我们可以是用and,or,not代替符号
4.2.2 在切点中选择bean
//执行concert.Performance类中perform方法,但是bean的id为woodstock execution(* concert.Performance.perform(..)) and bean('woodstock') //执行concert.Performance类中perform方法,但是bean的id不为woodstock execution(* concert.Performance.perform(..)) and !bean('woodstock')
4.3 使用注解创建切面
//切面类 @Aspect public class Audience { //来看演出需要静音手机 @Before("execution(* concert.Performance.perform(..))") public void silenceCellphones() { System.out.println("Silenciong cell phones"); } //静音手机 @Before("execution(* concert.Performance.perform(..))") public void takeSeats() { System.out.println("takingSeats"); } //表演的成功有掌声 @AfterReturning("execution(* concert.Performance.perform(..))") public void applause() { System.out.println("CLAP CLAP"); } //表演出现了异常 会要求退票 @AfterThrowing("execution(* concert.Performance.perform(..))") public void demanRefund() { System.out.println("Demanding a refund"); } }
Spring使用AspectJ注解来声明通知方法
注解 | 通知 |
---|---|
@After | 通知方法会在目标返回或抛出异常后调用 |
@AfterReturning | 通知方法会在目标方法返回后调用 |
@AfrterThrowing | 通知方法会在目标方法抛出异常后调用 |
@Around | 通知方法会将目标方法封装起来 |
@Before | 通知方法会在目标方法调用之前执行 |
从上面的代码我们发现,一个切点表达式重复了四次,这可不是什么好玩的事情,我们可以做一些改造
@Aspect public class Audience { //此方法不需要有任何执行,仅仅是一个表达式,让@PointCut去依附,仅仅是一个标识 @Pointcut("execution(* concert.Performance.perform(..))") public void performance() { } //来看演出需要静音手机 @Before("performance()") public void silenceCellphones() { System.out.println("Silenciong cell phones"); } //静音手机 @Before("performance()") public void takeSeats() { System.out.println("takingSeats"); } //表演的成功有掌声 @AfterReturning("performance())") public void applause() { System.out.println("CLAP CLAP"); } //表演出现了异常 会要求退票 @AfterThrowing("performance())") public void demanRefund() { System.out.println("Demanding a refund"); } }
当然Audience切面也可以像其他bean一样
@Bean public Audience audience(){ return new Audience(); }
在前面说过,我们调用的方法其实是个代理对象,即我们应该启动AspectJ自动代理
@ComponentScan @Configuration //启动AspectJ代理 @EnableAspectJAutoProxy public class JavaConfig { @Bean public Audience audience(){ return new Audience(); } }
也可以在XML声明中
<aop:aspectj-autoproxy />
带有参数得AOP
Aspect类
@Aspect public class Audience { //此方法不需要有任何执行,仅仅是一个表达式,让@PointCut去依附,仅仅是一个标识 @Pointcut("execution(** concert.ServiceTest.service3(Integer)) && args(param1)") public void performance(Integer param1) { } //来看演出需要静音手机 @Before("performance(param1)") public void silenceCellphones(Integer param1) { System.out.println("Silenciong cell phones" + " "+param1); } }
JavaConfig配置类
@ComponentScan @Configuration @EnableAspectJAutoProxy public class JavaConfig { @Bean public Audience audience() { return new Audience(); } }
Service类
@Service public class ServiceTest { public void service2(Integer param1, String param2) { System.out.println("I am services 2 and I have param:"+param1+" "+param2); } }
Test类
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes=JavaConfig.class) public class JavaAopTest { @Autowired private ServiceTest st; @Test public void test() { st.service3(6); } }
XML配置可自行尝试,示例事务管理配置(不是以上得使用)
<tx:advice id="txAdvice" transaction-manager="transactionManager"> <tx:attributes> <!-- 传播行为 --> <tx:method name="save*" propagation="REQUIRED"/> <tx:method name="delete*" propagation="REQUIRED"/> <tx:method name="insert*" propagation="REQUIRED"/> <tx:method name="update*" propagation="REQUIRED"/> <tx:method name="find*" propagation="SUPPORTS" read-only="true"/> <tx:method name="get*" propagation="SUPPORTS" read-only="true"/> <tx:method name="select*" propagation="SUPPORTS" read-only="true"/> </tx:attributes> </tx:advice> <!-- aop --> <aop:config> <aop:advisor advice-ref="txAdvice" pointcut="execution(* com.pjh.atcrowdfunding..*Service.*(..))"/> </aop:config>
接下来到了个重要环节即AOP底层实现,AOP得底层实现是使用了JDK动态代理,JDK动态代理需要被增强类有接口
下面来看下如何实现JDK的动态代理
Advice增加类
public class AdviceClass { public void advice1() { System.out.println("这是增强办法1111"); } public void advice2() { System.out.println("这是增强办法22222222222"); } public void advice3() { System.out.println("这是增强办法333333333333333333"); } }
目标类接口
/** * 目标类接口 * @author Administrator * */ public interface ItemsService { public void addItems(int i, int b)throws Exception; public void updateItems()throws Exception; public void deleteItems()throws Exception; }
目标类
public class ItemsServiceImpl implements ItemsService { @Override public void addItems(int i, int b) throws Exception { System.out.println("this is a: addItems"+i+"this is b "+b); } @Override public void updateItems() throws Exception { System.out.println("this is a: updateItems"); } @Override public void deleteItems() throws Exception { System.out.println("this is a: deleteItems"); } }
当然,有了目标类和增强类以后,我们需要将两者结合起来
代理工厂类
public class BeanFactory { public static ItemsService createService() { // 目标类 final ItemsService itemsService = new ItemsServiceImpl(); // 切面类 final AdviceClass advice = new AdviceClass(); /** * 调用Proxy.newProxyInstance((ClassLoader loader, 类<?>[] interfaces, InvocationHandler h))生成我们的代理类 * 参数: * loader 类加载器,动态代理类运行时创建,任何类的运行都需要类加载器 * interfaces 目标类的接口,jdk代理需要接口才可以实现 * h 处理类,接口,必须实现的,每一次执行一次方法,通过代理调用一次invoke * (Object proxy, Method method, Object[] args) * ivoke参数: * proxy:代理对象类 * method:调用的方法 * args:调用方法的实现类 */ ItemsService itemsServiceProxy = (ItemsService) Proxy.newProxyInstance(BeanFactory.class.getClassLoader(), itemsService.getClass().getInterfaces(), new InvocationHandler() { public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { advice.advice1(); Object obj = method.invoke(itemsService, args); advice.advice2(); advice.advice3(); return obj; } }); return itemsServiceProxy; } }
CGlib代理
和JDK动态代理不同,CGlib代理不需要实现接口,但是需要一个父接口,但我们都知道,Java默认继承Object类
这里只对BeanFactory做了更改
BeanFactory
public class BeanFactory { public static ItemsService createService() { // 目标类 final ItemsServiceImpl itemsService = new ItemsServiceImpl(); // 切面类 final AdviceClass advice = new AdviceClass(); // .代理类 ,采用cglib,底层创建目标类的子类 Enhancer enhancer = new Enhancer(); //确定父类 enhancer.setSuperclass(itemsService.getClass()); /* 设置回调函数 , MethodInterceptor接口 等效 jdk InvocationHandler接口 * intercept() 等效 jdk invoke() * 参数1、参数2、参数3:以invoke一样 * 参数4:methodProxy 方法的代理 */ enhancer.setCallback(new MethodInterceptor() { @Override public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { advice.advice1(); advice.advice2(); //执行目标类的方法 Object obj = method.invoke(itemsService, args); // * 执行代理类的父类 ,执行目标类 (目标类和代理类 父子关系) methodProxy.invokeSuper(proxy, args); //后 advice.advice3(); return obj; } }); //3.4 创建代理 ItemsServiceImpl proxService = (ItemsServiceImpl) enhancer.create(); return proxService; } }