JavaEE开发第二篇Spring-Aop

闲扯

离第一篇的spring到现在已经有将近一个星期了,迟迟没有写这篇文章的原因主要是自己还没有准备好第二篇,平时上班本来就有点忙,然后自己又想把知识点写的透彻然后最好能结合我们现在的项目(博主虽然不才,但是公司的项目好歹也是上千万的app用户量),如果能把学到spring的东西对应上项目,那就是再好不过了。然后对于spring接下来我觉得还有三个方面,第一个是spring的注解,这个其实比较简单一看就明白,我放到最后写。第二个是Aop相关的内容,我觉得东西还蛮多的。第三个是数据库操作相关的,结合基础的事务、jdbc我觉得内容也不少,数据库的部分我想合着ibatis、mybatis、数据库一起讲,最为基础知识说在最前面。那就直接来看看今天的正题Aop。

Aop原理-动态代理

静态代理

aop的原理其实就是我们的动态代理技术,动态代理也是设计模式的一种,了解动态代理前我们先来熟悉静态代理。举个简单的例子,现在我有一个接口

public interface Greeting {
    void sayHello();
}

他的实现类

 public class GreetingImpl implements Greeting {

    public void sayHello() {
        System.out.println("hello");
    }
}

现在我想让这个sayHello接口能做点别的事情,这个时候我就可以用代理来实现。来看下我的写法:

public class GreetingStaticProxy implements Greeting {

    GreetingImpl greetingImpl;

    public GreetingProxy(GreetingImpl greetingImpl) {
        this.greetingImpl = greetingImpl;
    }

    public void sayHello() {
        before();
        greetingImpl.sayHello();
        after();
    }

  private void after() {
        System.out.println("after");
    }

    private void before() {
    System.out.println("before");
    }
 }

这个代理类封装了sayhello方法,这样的话我用这个代理类就扩展了当前的功能,我们看下测试类:

   @Test
    public void TestStaticProxy(){
        GreetingStaticProxy greetingProxy=new GreetingStaticProxy(new GreetingImpl());
        greetingProxy.sayHello();
    }

打印的结果是

before
sayhello
after

可以看到我们通过GreetingStaticPorxy这个类扩展了GreetingImpl的功能,这就是静态代理。静态代理可以扩展类的功能,但是有一个问题是,如果接口变了,我们的代理类就咬跟着一起变,这样也太不灵活了,而且工程里会有一大堆的代理类。我们接下来看看动态代理。

动态代理

动态代理有两种方式,一个是jdk自带的代理方式,还有一个是用开源的一个库cglib最动态代理,我们分别都来看一下。

1、JDK动态代理

我们还是用上面的Greeting和GreetingImpl这两个类,看看如何实现这个类的JDK动态代理。

public class JdkDynamicProxy implements InvocationHandler{

    private Object object;

    public JdkDynamicProxy(Object object) {
        this.object = object;
    }


    public  T getProxy(){
        return (T)Proxy.newProxyInstance(getClass().
        getClassLoader(),object.getClass().getInterfaces(),this);
    }

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        before();
        Object result=method.invoke(object,args);
        after();
        return result;
    }

    private void after() {
        System.out.println("after");
    }

    private void before() {
        System.out.println("before");

    }
}

简单介绍下getProxy()是封装给外部获取代理对象的,里面传了3个参数
1、类加载器 ClassLoader
2、这个类的所有接口类(从这个参数可以看出来,JDK代理的局限性在于,目标类必须有接口才能被代理)
3、invocationHandler对象(本类即成了他,所以传了this)

然后再看下invoke方法,method.invoke()这个是代理目标类本身调用的方法,before和after就是我们给他增强的方法,invoke里面的参数一个是目标类,还一个是目标类方法的参数。最后看我们的测试类:

 @Test
    public void TestJdkDynamicProxy(){
        Greeting greetingProxy=new JdkDynamicProxy(new GreetingImpl()).getProxy();
        greetingProxy.sayHello();
    }

可以看到getProxy()返回的我们用接口来接受,因为代理对象就是会继承Greeting接口。这样打印结果就和我们预期一样了。上面也说了,JDK代理虽然能解决静态代理的问题,但是他的限制是没有接口的类没法代理,所以我们看看有木有更好的方案,这边我们来学习第二个动态代理的方式cglib。

CGLIB

cglib做动态代理没有任何限制,任何类都可以做代理目标类,我们还是以上面说的greeting和greetingimpl为例。

public class CglibDynamicProxy implements MethodInterceptor{
    public  T getProxy(Class cls){
        return (T) Enhancer.create(cls,this);
    }
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        before();
        Object result=methodProxy.invokeSuper(o,objects);
        after();
        return result;
    }
    private void before() {
        System.out.println("before");
    }
    private void after() {
        System.out.println("after");
    }
}

可以看到这就是我们cglib动态代理的写法。稍微解释下,首先我们需要maven依赖cglib,这个我们可以在网上找 直接搜索”cglib maven”就可以搜到maven仓库了。然后可以看大getproxy方法有两个参数分别是
1、class 目标类
2、methodinterceptor对象(本类继承了,所以传的this)

然后在intercept的方法里面,可以看到他是用了invokesuper这个方法,里面传了对象和参数,其实从我们这个方法可以看到一点,他为什么调用父类的方法?难道这个proxy继承了我们的目标类?是的,cglib代理的原理就是继承,他是继承了目标类去做的代理。那我们看看测试类,也比较简单

 @Test
    public void TestCglibDynamicProxy(){
        CglibDynamicProxy proxy=new CglibDynamicProxy();
        GreetingImpl greeting = proxy.getProxy(GreetingImpl.class);
        greeting.sayHello();
    }

如果我们不相信是继承的关系,可以去试试 proxy instanceOf greeting 如果是true那就说明是继承关系。
以上就是我们说的aop的原理,下面我们就来看看spring对aop做了哪些封装。

Spring AOP

AOP的概述

上面还没说aop是干啥的,我们这里先说说,有啥用这个东西。 aop叫面向切面编程,他的用途。举个例子,比如说我们想统计代码中某些方法的执行时常,那正常想法是在他执行前打印一下时间,执行后打印一下时间,写起来很简单。但是如果我们要打印的地方很多的话,我们需要一个一个的去加吗?能不能有一个东西,能如一把刀一样(我想到了庖丁解牛)可以横着把我们需要操作的方法都给他串联起来,让他们都具有这样的功能。这其实就是aop要做的事情,横向的去增强一些东西。项目中用到的有日志的打印,性能的监控、权限的控制等等。那aop在spring中其实有两种形式,一个是spring本身就自带的aop功能,还有一个是基于开源的Aspect的aop功能,当然后者更简便而且用的也更多。我们都来看下一。我们把spring自带的aop叫他传统aop。

传统AOP

首先理解下3种增强方式

  • 前置增强 也就是和我们before方法相似,在目标方法之前执行的
  • 后置增强 和after方法相似,在目标方法之后执行
  • 环绕增强 把before和after放在一起就是环绕增强了

前置增强和后置增强

public class GreetingBeforeAndAfter implements MethodBeforeAdvice,AfterReturningAdvice {
    public void afterReturning(Object result, Method method, Object[] objects, Object target) throws Throwable {
        System.out.println("after");
    }
    public void before(Method method, Object[] objects, Object o) throws Throwable {
        System.out.println("before");
    }
}

前置增强只要继承MethodBeforeAdvice 后置增强继承AfterReurnningAdvice并实现方法就就ok了。看看怎么用

 @Test
    public void test(){
        ProxyFactory proxyFactory = new ProxyFactory();
        proxyFactory.setTarget(new GreetingImpl());
        proxyFactory.addAdvice(new GreetingBeforeAndAfter());
        GreetingImpl greeting= (GreetingImpl) proxyFactory.getProxy();
        greeting.sayHello();
    }

settarget设置目标类,addadvice添加我们的增强就ok了。

环绕增强

环绕增强稍微有点不一样,一起来看一下

public class GreetingAroundAdvice implements MethodInterceptor {
    public Object invoke(MethodInvocation methodInvocation) throws Throwable {
        before();
        Object proceed = methodInvocation.proceed();
        after();
        return proceed;
    }

    private void after() {
        System.out.println("after");
    }

    private void before() {
        System.out.println("before");

    }
}

继承的和cglib一样,在invoke方法里面需要调用proceed方法。测试类和上面完全一样。
我们再来看看如果在spring的配置文件里面怎么写这些增强。

 
    <bean id="greetingProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
        <property name="target" ref="greetingImpl"/>
        <property name="interceptorNames" value="greetingAdvisor"/>
        <property name="proxyTargetClass" value="true"/>
    bean>

上面可以看到几个配置,
第一个是目标对象,这个对象需要在spring里面注册,可以用bean的方式注册,也可以用注解@component(“greetingimpl”)的方式。
第二个是增强的类的对象,这个也需要注册到spring
第三个是增强的类代理类,true表示cglib,false表示jdk的方式。
还有最上面有个注解叫织入增强,这个是相对于我们下面要说的这个引入增强来说的,织入增强是对方法的增强,而引入增强则是对类的增强。看看测试类的调用:

  @Test
    public void injectTest(){
    ApplicationContext ac=new ClassPathXmlApplicationContext("applicationContext.xml");
        GreetingImpl greeting= (GreetingImpl) ac.getBean("greetingProxy");
        greeting.sayHello();
    }

很容易理解,就不解释了。我们再来看看刚才说的引入增强,举个例子说明引入增强,比如一个a类,他在不想继承b类的情况下,却想去执行b类的方法,可以用引入增强去做。
现在我门增加一个类和他的实现类:

public interface Apology {
    void saySorry(String name);
}

public class ApologyImpl implements Apology{
    public  void saySorry(String name){
        System.out.println("say sorry"+name);
    }
}

看看引入增强类写法,让他去实现apology

public class GreetingIntroAdvice extends DelegatingIntroductionInterceptor implements Apology{
    @Override
    public Object invoke(MethodInvocation mi) throws Throwable {
        return super.invoke(mi);
    }
    public void saySorry(String name) {
        System.out.println("say sorry"+name);
    }
}

这个时候我们再来看看他的配置:

 
    <bean id="greetingProxy2" class="org.springframework.aop.framework.ProxyFactoryBean">
      ç
        <property name="interfaces" value="org.thinkingWalker.proxy.springproxy.yinru.Apology"/>
                                    
        <property name="target" ref="greetingImpl"/>

        <property name="interceptorNames" value="greetingIntroAdvice"/>
                                                            

    bean>

跟织入增强配置的区别是,这里的interfaces代表的意义是动态实现的接口。看看我们的调用方式:

  @Test
    public void injectTestDynamicIntro(){
        ApplicationContext ac=new ClassPathXmlApplicationContext("applicationContext.xml");
        GreetingImpl greeting= (GreetingImpl) ac.getBean("greetingProxy2");
        greeting.sayHello();
        Apology apology= (Apology) greeting;
        apology.saySorry("hafadf");
    }

可以看到代理对象可以被强转成aplogy对象,所以是可以调用apology对象的方法。

关于增强的方式其实还有一种没有写出来,就是异常增强,就是对异常的捕获,这个大家可以自己看一下。

#切面和切点

切面和切点的概念

我们上面可以回顾下,上面讲的增强方式已经可以解决对方法或者是类的横切,但是感觉他切的有点生硬,一刀下去全切掉了。比如说,我能不能对某个类的某些方法做一些增强?或者说对某个包下的某些类做些增强?那这里我们就要接触到我们的切面和切点的概念,切面其实是一个整体的概念,他包括我们上面说的增强,以及接下来要说的切点。这两个一起组成了切面,把切面配置到ProxyFactory中,就生成了能实现我们预期的代理类。

切点的实例

现在对Greeting增加两个方法,一个goodmorning,一个goodafternoon,现在greeting和greetingimpl的代码如下:

public interface Greeting {

    void sayHello();

    void goodMorning();

    void goodAfternoon();
}

public class GreetingImpl implements Greeting {

    public void sayHello() {
        System.out.println("hello");
    }
    public void goodMorning() {
        System.out.println("morning");
    }
    public void goodAfternoon() {
        System.out.println("afternoon");

    }
}

那现在我们来配置一个切面,达到的目的是我只想对greetingimpl中以good开头的方法做增强。我们来一起看看配置,首先需要配置一个符合我们要求的切面:

  
    <bean id="greetingAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
 <property name="advice" ref="greetingAfterAdvice"/>
<property name="pattern"value="org.thinkingWalker.proxy.Greeting.good.*"/>
            
    bean>

//代理类  把切点配置为interceptorNames
 <bean id="greetingPointCutProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
        <property name="target" value="greetingImpl"/>
        <property name="interceptorNames" value="greetingAdvisor"/>
        <property name="proxyTargetClass" value="true"/>
    bean>

可以看到我们匹配的是在greeting类中以good开头的方法,这样他就只会对goods开头的方法增强。
这个关于切面的配置还是挺多的,所以项目里会采用Aspect来做aop,我们一起来看看aspect.

Aspect Spring

Aspect是一个开源的用来做aop的插件,我们来看用了aspect做增强有多简单。先看一个例子:

@Aspect
@Component
public class GreetingAspect {
    /**
     * 第一个*表示返回值任意  最后一个*表示切点的方法,后面的两个。。表示参数随意
     * @param pjp
     * @return
     * @throws Throwable
     */
    @Around("execution(* org.thinkingWalker.proxy.GreetingImpl.good*(..))")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        before();
        Object result = pjp.proceed();
        after();
        return result;
    }

    private void after() {
        System.out.println("after");
    }
    private void before() {
        System.out.println("before");
    }
}

上面就是一个环绕增强
@Aspect表示他是一个advisor(增强)
@around表示他是环绕增强,里面的参数表示他的切点。非常简单直观。
然后需要在配置文件里配置:

    <aop:aspectj-autoproxy proxy-target-class="true"/>

这样配置后就可以增强方法了。

aspect还可以基于注解去做切点我们一起看下:
先定一个注解

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Tag {

}

看看切面类怎么写:

@Aspect
@Component
public class GreetingAspect2 {
    /**
     * 把Tag注解做为切点
     * @param pjp
     * @return
     * @throws Throwable
     */
    @Around("@annotation(org.thinkingWalker.proxy.springaspect.Tag)")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        before();
        Object result = pjp.proceed();
        after();
        return result;
    }

    private void after() {
        System.out.println("after");
    }

    private void before() {
        System.out.println("before");

    }
}

是不是写起来很爽,比传统那个简单很多呀。

aspect的引入增强

再来看下引入增强

@Aspect
@Component
public class GreetingAspect3 {
   @DeclareParents(value = "org.thinkingWalker.proxy.GreetingImpl", defaultImpl = ApologyImpl.class)

    private Apology apology;
}

value表示要增强的类,defaultimpl表示引入接口的默认实现类。所以我们需要对引入的类写一个实现类。测试类和上面的一样,这里就不重复写了。

总结

Aop我暂时能写的也就这么多,上面写的主要参考吴勇的《从零开始写javaweb框架》,其实我写的这些只是停留在用的阶段。而他的书主要的思想是先学会怎么用,然后自己写一个这样的轻量的框架,所以书上有介绍关于如何自己写aop框架,水平和精力有限,暂时自己还达不到完全消化完然后分享给大家,所以这个留着自己以后钻研吧。
水平有限,如果有错误的地方,请大家包含。

又瞎扯

喝了一杯苦茶,听着五月天的倔强,还能再奋斗一个小时。希望各位小伙伴都能在该奋斗的年纪好好奋斗,学习态度真的很重要,我就是放松了自己将近一年,把自己最引以为傲的学习态度都快丢了。每天学习,有节制的玩,这样既能保持自己的学习态度又可以经历量变到质变,加油!!!

你可能感兴趣的:(javaee)