zoukankan      html  css  js  c++  java
  • 框架学习之Spring 第三节 采用Spring实现AOP功能

    1.AOP中的概念

    Aspect(切面):指横切性关注点的抽象即为切面,它与类相似,只是两者的关注点不一样,类是对物体特征的抽象,而切面横切性关注点的抽象.

    joinpoint(连接点):所谓连接点是指那些被拦截到的点。在spring中,这些点指的是方法,因为spring只支持方法类型的连接点,实际上joinpoint还可以是field或类构造器)

    Pointcut(切入点):所谓切入点是指我们要对那些joinpoint进行拦截的定义.

    Advice(通知):所谓通知是指拦截到joinpoint之后所要做的事情就是通知.通知分为前置通知,后置通知,异常通知,最终通知,环绕通知

    Target(目标对象):代理的目标对象

    Weave(织入):指将aspects应用到target对象并导致proxy对象创建的过程称为织入.

    Introduction(引入):在不修改类代码的前提下, Introduction可以在运行期为类动态地添加一些方法或Field.

    代理方式:

    静态代理

    动态代理

    2.使用JDK的proxy技术实现AOP功能

    JDK 动态代理 (采用的是JDK 反射技术)

    public class JDKProxy implements InvocationHandler {
        private Object targetObject;//代理的目标对象
        public Object createProxyInstance(Object targetObject){
            this.targetObject = targetObject;
    /*
    * 第一个参数设置代码使用的类装载器,一般采用跟目标类相同的类装载器
    * 第二个参数设置代理类实现的接口
    * 第三个参数设置回调对象,当代理对象的方法被调用时,会委派给该参数指定对象的invoke方法
    */
            return Proxy.newProxyInstance(this.targetObject.getClass().getClassLoader(),
                    this.targetObject.getClass().getInterfaces(), this);
        }
        public Object invoke(Object proxy, Method method, Object[] args)
                throws Throwable {
            return method.invoke(this.targetObject, args);//把方法调用委派给目标对象
        }
    }
    
    当目标类实现了接口,我们可以使用jdk的Proxy来生成代理对象。
    3.使用CGLIB实现AOP功能
    public class CGLIBProxy implements MethodInterceptor {
        private Object targetObject;//代理的目标对象    
        public Object createProxyInstance(Object targetObject){
            this.targetObject = targetObject;
            Enhancer enhancer = new Enhancer();//该类用于生成代理对象
            enhancer.setSuperclass(this.targetObject.getClass());//设置父类
            enhancer.setCallback(this);//设置回调用对象为本身
            return enhancer.create();
        }
        public Object intercept(Object proxy, Method method, Object[] args,
                MethodProxy methodProxy) throws Throwable {
            return methodProxy.invoke(this.targetObject, args);
        }
    }
    
    CGLIB可以生成目标类的子类,并重写父类非final修饰符的方法。

    注:使用CGLIB之前要导入相应的jar包,在下载的spring文件包的cglib目录下

    测试:

    新建一个接口:

    package cn.itcast.service;
    
    public interface PersonService {
        public void save(String name);
        public void update(String name, Integer personid);
        public String getPersonName(Integer personid);
    }

    新建一个接口的 实现类:

    代码的目的是,在下面这个业务逻辑类中的方法执行之前都要被容器拦截,拦截了之后进行权限的判断,如果有权限才可以执行类中的方法

    本例中的有权限是指 user!=null

    package cn.itcast.service.impl;
    
    import cn.itcast.service.PersonService;
    
    public class PersonServiceBean implements PersonService{
        private String user = null;
        
        public String getUser() {
            return user;
        }
    
        public PersonServiceBean(){}
        
        public PersonServiceBean(String user){
            this.user = user;
        }
    
        public String getPersonName(Integer personid) {
            System.out.println("我是getPersonName()方法");
            return "xxx";
        }
    
        public void save(String name) {
            System.out.println("我是save()方法");
        }
    
        public void update(String name, Integer personid) {
            System.out.println("我是update()方法");
        }
    
    }

    使用JDK完成动态代理:

    package cn.itcast.aop;
    
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.lang.reflect.Proxy;
    
    import cn.itcast.service.impl.PersonServiceBean;
    
    public class JDKProxyFactory implements InvocationHandler{
        private Object targetObject;
        
        public Object createProxyIntance(Object targetObject){
            this.targetObject = targetObject;
            return Proxy.newProxyInstance(this.targetObject.getClass().getClassLoader(), 
                    this.targetObject.getClass().getInterfaces(), this);
        }
    
        public Object invoke(Object proxy, Method method, Object[] args)
                throws Throwable {//环绕通知
            PersonServiceBean bean = (PersonServiceBean) this.targetObject;
            Object result = null; 
            if(bean.getUser()!=null){
                //..... advice()-->前置通知
                try {
                    result = method.invoke(targetObject, args);
                    // afteradvice() -->后置通知
                } catch (RuntimeException e) {
                    //exceptionadvice()--> 例外通知
                }finally{
                    //finallyadvice(); -->最终通知
                }
            }
            return result;
        }
    
    }

    使用CGLIB实现AOP功能:

    package cn.itcast.aop;
    
    import java.lang.reflect.Method;
    
    import cn.itcast.service.impl.PersonServiceBean;
    
    import net.sf.cglib.proxy.Enhancer;
    import net.sf.cglib.proxy.MethodInterceptor;
    import net.sf.cglib.proxy.MethodProxy;
    
    public class CGlibProxyFactory implements MethodInterceptor{
        private Object targetObject;
        
        public Object createProxyIntance(Object targetObject){
            this.targetObject = targetObject;
            Enhancer enhancer = new Enhancer();
            enhancer.setSuperclass(this.targetObject.getClass());//非final
            enhancer.setCallback(this);
            return enhancer.create();
        }
    
        public Object intercept(Object proxy, Method method, Object[] args,
                MethodProxy  methodProxy) throws Throwable {
            PersonServiceBean bean = (PersonServiceBean) this.targetObject;
            Object result = null;
            if(bean.getUser()!=null){
                result = methodProxy.invoke(targetObject, args);
            }
            return result;
        }
    }

    新建一个单元测试:

    package junit.test;
    
    
    import org.junit.BeforeClass;
    import org.junit.Test;
    
    import cn.itcast.aop.CGlibProxyFactory;
    import cn.itcast.aop.JDKProxyFactory;
    import cn.itcast.service.PersonService;
    import cn.itcast.service.impl.PersonServiceBean;
    
    public class AOPTest {
    
        @BeforeClass
        public static void setUpBeforeClass() throws Exception {
        }
    
        @Test public void proxyTest(){
            JDKProxyFactory factory = new JDKProxyFactory();
            PersonService service = (PersonService) factory.createProxyIntance(new PersonServiceBean("xxx"));
            service.save("888");
        }
        
        @Test public void proxyTest2(){
            CGlibProxyFactory factory = new CGlibProxyFactory();
            PersonServiceBean service = (PersonServiceBean) factory.createProxyIntance(new PersonServiceBean("xxx"));
            service.save("999");
        }
    }

    两个测试结果都是:

    我是save()方法

    4.使用Spring框架实现AOP功能

    要进行AOP编程,首先我们要在spring的配置文件中引入aop命名空间:
    <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-2.5.xsd
               http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd">
    </beans>
    注意上面的 aop 命名空间 和 最后一行的两个schema文件路径

    Spring提供了两种切面声明方式,实际工作中我们可以选用其中一种:

    (1)基于注解方式声明切面。

    (2)基于XML配置方式声明切面。

    <1> 基于注解方式声明切面。

    首先启动对@AspectJ注解的支持(下划线部分):

    <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-2.5.xsd
               http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd">
         <aop:aspectj-autoproxy/>
        <bean id="orderservice" class="cn.itcast.service.OrderServiceBean"/>
        <bean id="log" class="cn.itcast.service.LogPrint"/>
    </beans>

    启用@AspectJ支持后,在application context中定义的任意带有一个@Aspect切面(拥有@Aspect注解)的bean都将被

    Spring自动识别并用于配置Spring AOP。

    注解方式的示例:

    @Aspect
    public class LogPrint {
        @Pointcut("execution(* cn.itcast.service..*.*(..))")
        private void anyMethod() {}//声明一个切入点    
        @Before("anyMethod() && args(userName)")//定义前置通知
        public void doAccessCheck(String userName) {
        }    
        @AfterReturning(pointcut="anyMethod()",returning="revalue")//定义后置通知
        public void doReturnCheck(String revalue) {
        }
        @AfterThrowing(pointcut="anyMethod()", throwing="ex")//定义例外通知
        public void doExceptionAction(Exception ex) {
        }
        @After("anyMethod()")//定义最终通知
        public void doReleaseAction() {
        }
        @Around("anyMethod()")//环绕通知
        public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable {
            return pjp.proceed();
        }
    }

    <2>基于XML配置方式声明切面。

    各种不同类型的通知

    public class LogPrint {
        public void doAccessCheck() {}定义前置通知
        public void doReturnCheck() {}定义后置通知
        public void doExceptionAction() {}定义例外通知
        public void doReleaseAction() {}定义最终通知
        public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable {
            return pjp.proceed();环绕通知
        }
    }

    XML配置:

    <bean id="orderservice" class="cn.itcast.service.OrderServiceBean"/>
    <bean id="log" class="cn.itcast.service.LogPrint"/>
    <aop:config>
      <aop:aspect id="myaop" ref="log">
          <aop:pointcut id="mycut" expression="execution(* cn.itcast.service..*.*(..))"/>
          <aop:before pointcut-ref="mycut" method="doAccessCheck"/>
          <aop:after-returning pointcut-ref="mycut" method="doReturnCheck "/>
          <aop:after-throwing pointcut-ref="mycut" method="doExceptionAction"/>
          <aop:after pointcut-ref="mycut" method=“doReleaseAction"/>
          <aop:around pointcut-ref="mycut" method="doBasicProfiling"/>
      </aop:aspect>
    </aop:config>

    测试:

    重新建立一个接口:

    package com.yinger.service;
    
    public interface PersonService2 {
    
        public void save();
        public void exTest() throws Exception;
        public String test(String name);
        
    }

    新建一个类,实现上面的接口:

    package com.yinger.service.impl;
    
    import com.yinger.service.PersonService2;
    
    public class PersonServiceBean4 implements PersonService2{
    
        //默认的构造器
        public PersonServiceBean4(){
            System.out.println("instance me");
        }
        
        //exTest 方法
        public void exTest() throws Exception {
            throw new Exception("发生了异常");
        }
        
        //test 测试方法
        public String test(String name){
            System.out.println("test");
            return name;
        }
        
        //save 方法
        public void save(){
            System.out.println("save");
        }
        
    }

    新建一个拦截器:

    package com.yinger.service.intercepter;
    
    import org.aspectj.lang.ProceedingJoinPoint;
    import org.aspectj.lang.annotation.After;
    import org.aspectj.lang.annotation.AfterReturning;
    import org.aspectj.lang.annotation.AfterThrowing;
    import org.aspectj.lang.annotation.Around;
    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.annotation.Before;
    import org.aspectj.lang.annotation.Pointcut;
    
    @Aspect
    public class PersonServiceIntercepter {
        
        @Pointcut("execution(* com.yinger.service.impl.PersonServiceBean4.*(..))")
        private void anyMethod(){}
    
        @Before("anyMethod()")
        public void doAccessCheck(){
            System.out.println("前置通知");
        }
        
        @AfterReturning(pointcut="anyMethod()",returning="reValue")
        public void doReturnCheck(String reValue){
            System.out.println("后置通知:"+reValue);
        }
        
        @AfterThrowing(pointcut="anyMethod()",throwing="ex")
        public void doExceptionAction(Exception ex) {
            System.out.println("例外通知:"+ex);
        }
        
        @After("anyMethod()")
        public void doReleaseAction(){
            System.out.println("最终通知");
        }
    
        @Around("anyMethod()")
        public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable {
            System.out.println("环绕通知");
            return pjp.proceed();
        }
    
    }

    beans.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: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-2.5.xsd
             http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd
             http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd">
    
        <aop:aspectj-autoproxy/>
        <!-- 注意记得要将自定义的拦截器交给Spring容器管理  -->
        <bean id="myIntercepter" class="com.yinger.service.intercepter.PersonServiceIntercepter"></bean>
        <bean id="personService4" class="com.yinger.service.impl.PersonServiceBean4"></bean>
        
    </beans>

    添加测试方法:

        @Test  //用于测试AOP功能的方法
        public void testAOP() throws Exception {
            AbstractApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml");
            System.out.println("--------");
            PersonService2 ps2 = (PersonService2)ctx.getBean("personService4");
            ps2.test("name");
    //        ps2.exTest();
    //        ps2.save();
            ctx.close();
        }

    首先测试 test 方法,结果:

    log4j:WARN No appenders could be found for logger (org.springframework.context.support.ClassPathXmlApplicationContext).
    log4j:WARN Please initialize the log4j system properly.
    instance me
    --------
    前置通知
    环绕通知
    test
    后置通知:name
    最终通知

    其次测试 save 方法:

    log4j:WARN No appenders could be found for logger (org.springframework.context.support.ClassPathXmlApplicationContext).
    log4j:WARN Please initialize the log4j system properly.
    instance me
    --------
    前置通知
    环绕通知
    save
    后置通知:null
    最终通知

    最后测试 exTest 方法:

    log4j:WARN No appenders could be found for logger (org.springframework.context.support.ClassPathXmlApplicationContext).
    log4j:WARN Please initialize the log4j system properly.
    instance me
    --------
    前置通知
    环绕通知
    例外通知:java.lang.Exception: 发生了异常
    最终通知

    从上面的例子中可以看出,使用通知可以得到方法返回的值,当发生了异常时可以知道异常的类型和信息

    并且通过给通知方法限制方法参数的类型,可以限制为指定的方法参数的方法

    如果是异常的话,就只有抛出的异常和例外通知中的异常一样时,异常通知方法才会执行

    参考文档中的内容:

    1011

    基于xml配置的例子略过。。。(和上面的示例代码差不多)

    5.aspectj的切入点的语法细节分析

    expression="execution(* cn.itcast.service..*.*(..))"

    第一个*表示方法的返回值可以是任何值

    接着 cn.itcast.service..* 表示 cn.itcast.service 包以及它的子包(两个点,如果是一个点就不包括子包)中的任何类 ,第二个*表示任何类

    接着 .* 表示类中的任何方法,第三个*表示任何方法

    最后的 (..)表示方法的参数可以是任意的,可以没有,也可以有一个或者多个

  • 相关阅读:
    springMVC 返回json 忽略类中属性的注解
    MySQL中函数CONCAT及GROUP_CONCAT
    ArrayList转成HashMap再转成LinkedHashMap 自己的解决方案
    easyui datebox 设置不可编辑
    js或jquery如何获取父级、子级、兄弟元素(包括祖级、孙级等)
    关于js中空值比较和传值的问题
    Tomcat报错:Failed to start component [StandardEngine[Catalina].StandardHost[localhost]]
    JQuery 阻止js事件冒泡 阻止浏览器默认操作
    visualstudio2017 +EF+Mysql生成实体数据模型闪退
    MVC错误:查询的结果不能枚举多次
  • 原文地址:https://www.cnblogs.com/yinger/p/2150627.html
Copyright © 2011-2022 走看看