SpringAOP03 项目脚手架、自定义注解、织入切面、引介增强

 

1 项目脚手架

  利用 Maven 进行创建

  1.1 利用IDEA创建一个Maven原型项目

    技巧01:原型Maven项目是没有webapp文件夹和resources项目文件夹的,需要自己手动创建;创建完后需要进行模块配置 file -> project structure -> modules

    

    

 

  1.2 配置 pom.xml 文件

    需要引入一些 spring 和 aop 相关的依赖

<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>cn.xiangxu.com</groupId>
  <artifactId>aop_base_demo</artifactId>
  <version>1.0-SNAPSHOT</version>

  <name>aop_base_demo</name>
  <!-- FIXME change it to the project's website -->
  <url>http://www.example.com</url>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>1.7</maven.compiler.source>
    <maven.compiler.target>1.7</maven.compiler.target>
    <file.encoding>UTF-8</file.encoding>
    <spring.version>4.2.2.RELEASE</spring.version>
    <testng.version>6.8.7</testng.version>
    <asm.version>4.0</asm.version>
    <cglib.version>3.0</cglib.version>
    <aspectj.version>1.8.1</aspectj.version>
    <aopalliance.version>1.0</aopalliance.version>
    <commons-codec.version>1.9</commons-codec.version>
    <slf4j.version>1.7.5</slf4j.version>
  </properties>

  <dependencies>
    <!-- spring 依赖-->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-beans</artifactId>
      <version>${spring.version}</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>${spring.version}</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context-support</artifactId>
      <version>${spring.version}</version>
    </dependency>


    <!-- asm/cglib依赖(spring依赖) -->
    <dependency>
      <groupId>org.ow2.asm</groupId>
      <artifactId>asm</artifactId>
      <version>${asm.version}</version>
    </dependency>
    <dependency>
      <groupId>org.ow2.asm</groupId>
      <artifactId>asm-util</artifactId>
      <version>${asm.version}</version>
    </dependency>
    <dependency>
      <groupId>cglib</groupId>
      <artifactId>cglib</artifactId>
      <version>${cglib.version}</version>
      <exclusions>
        <exclusion>
          <artifactId>asm</artifactId>
          <groupId>org.ow2.asm</groupId>
        </exclusion>
      </exclusions>
    </dependency>
    <dependency>
      <groupId>org.aspectj</groupId>
      <artifactId>aspectjrt</artifactId>
      <version>${aspectj.version}</version>
    </dependency>
    <dependency>
      <groupId>org.aspectj</groupId>
      <artifactId>aspectjweaver</artifactId>
      <version>${aspectj.version}</version>
    </dependency>
    <dependency>
      <groupId>aopalliance</groupId>
      <artifactId>aopalliance</artifactId>
      <version>${aopalliance.version}</version>
    </dependency>
    <dependency>
      <groupId>commons-codec</groupId>
      <artifactId>commons-codec</artifactId>
      <version>${commons-codec.version}</version>
    </dependency>



    <dependency>
      <groupId>org.testng</groupId>
      <artifactId>testng</artifactId>
      <version>${testng.version}</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-test</artifactId>
      <version>${spring.version}</version>
      <scope>test</scope>
    </dependency>
  </dependencies>

  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-surefire-plugin</artifactId>
        <version>2.7.2</version>
        <configuration>
          <forkMode>once</forkMode>
          <threadCount>10</threadCount>
          <argLine>-Dfile.encoding=UTF-8</argLine>
        </configuration>
      </plugin>
    </plugins>
  </build>

</project>
pom.xml

  1.3 刷新maven下载相关依赖

    技巧01:最好不要使用自带的maven仓库,使用自己修改过仓库地址的maven

    

    

 

2 自定义注解

  2.1 利用 @interface 创建

    技巧01:@Retention 用来指定注解有效范围,@Target 用来指定注解的使用范围

package cn.xiangxu.com.annotations;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface NeedTest {
    boolean value() default true;
}
/*
 * Copyright (c) 2003, 2004, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 *
 *
 *
 */

package java.lang.annotation;

/**
 * Annotation retention policy.  The constants of this enumerated type
 * describe the various policies for retaining annotations.  They are used
 * in conjunction with the {@link Retention} meta-annotation type to specify
 * how long annotations are to be retained.
 *
 * @author  Joshua Bloch
 * @since 1.5
 */
public enum RetentionPolicy {
    /**
     * Annotations are to be discarded by the compiler.
     */
    SOURCE,

    /**
     * Annotations are to be recorded in the class file by the compiler
     * but need not be retained by the VM at run time.  This is the default
     * behavior.
     */
    CLASS,

    /**
     * Annotations are to be recorded in the class file by the compiler and
     * retained by the VM at run time, so they may be read reflectively.
     *
     * @see java.lang.reflect.AnnotatedElement
     */
    RUNTIME
}
RetentionPolicy.java
/*
 * Copyright (c) 2003, 2013, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 */

package java.lang.annotation;

/**
 * The constants of this enumerated type provide a simple classification of the
 * syntactic locations where annotations may appear in a Java program. These
 * constants are used in {@link Target java.lang.annotation.Target}
 * meta-annotations to specify where it is legal to write annotations of a
 * given type.
 *
 * <p>The syntactic locations where annotations may appear are split into
 * <em>declaration contexts</em> , where annotations apply to declarations, and
 * <em>type contexts</em> , where annotations apply to types used in
 * declarations and expressions.
 *
 * <p>The constants {@link #ANNOTATION_TYPE} , {@link #CONSTRUCTOR} , {@link
 * #FIELD} , {@link #LOCAL_VARIABLE} , {@link #METHOD} , {@link #PACKAGE} ,
 * {@link #PARAMETER} , {@link #TYPE} , and {@link #TYPE_PARAMETER} correspond
 * to the declaration contexts in JLS 9.6.4.1.
 *
 * <p>For example, an annotation whose type is meta-annotated with
 * {@code @Target(ElementType.FIELD)} may only be written as a modifier for a
 * field declaration.
 *
 * <p>The constant {@link #TYPE_USE} corresponds to the 15 type contexts in JLS
 * 4.11, as well as to two declaration contexts: type declarations (including
 * annotation type declarations) and type parameter declarations.
 *
 * <p>For example, an annotation whose type is meta-annotated with
 * {@code @Target(ElementType.TYPE_USE)} may be written on the type of a field
 * (or within the type of the field, if it is a nested, parameterized, or array
 * type), and may also appear as a modifier for, say, a class declaration.
 *
 * <p>The {@code TYPE_USE} constant includes type declarations and type
 * parameter declarations as a convenience for designers of type checkers which
 * give semantics to annotation types. For example, if the annotation type
 * {@code NonNull} is meta-annotated with
 * {@code @Target(ElementType.TYPE_USE)}, then {@code @NonNull}
 * {@code class C {...}} could be treated by a type checker as indicating that
 * all variables of class {@code C} are non-null, while still allowing
 * variables of other classes to be non-null or not non-null based on whether
 * {@code @NonNull} appears at the variable's declaration.
 *
 * @author  Joshua Bloch
 * @since 1.5
 * @jls 9.6.4.1 @Target
 * @jls 4.1 The Kinds of Types and Values
 */
public enum ElementType {
    /** Class, interface (including annotation type), or enum declaration */
    TYPE,

    /** Field declaration (includes enum constants) */
    FIELD,

    /** Method declaration */
    METHOD,

    /** Formal parameter declaration */
    PARAMETER,

    /** Constructor declaration */
    CONSTRUCTOR,

    /** Local variable declaration */
    LOCAL_VARIABLE,

    /** Annotation type declaration */
    ANNOTATION_TYPE,

    /** Package declaration */
    PACKAGE,

    /**
     * Type parameter declaration
     *
     * @since 1.8
     */
    TYPE_PARAMETER,

    /**
     * Use of a type
     *
     * @since 1.8
     */
    TYPE_USE
}
ElementType.java

  2.2 使用注解

    2.2.1 随便创建一个服务类

      技巧01:在该服务类中的方法中使用自定义的注解即可

package cn.xiangxu.com.service;

import cn.xiangxu.com.annotations.NeedTest;
import org.springframework.stereotype.Service;

/**
 * @author 王杨帅
 * @create 2018-05-04 10:28
 * @desc 自定义注解测试类
 **/
@Service
public class AnnotationTestService {
    @NeedTest
    public void deleteUser(Integer userId) {
        System.out.println("根据用户ID删除用户所有信息:" + userId);
    }

    @NeedTest(value = false)
    public void deleteUserAddress(String address) {
        System.out.println("删除用户地址信息:" + address);
    }
}

    2.2.2 创建一个测试类

      该测试类主要功能是获取注解对象信息

      技巧01:getClass() 获取运行时的class信息,getDeclaredMethods() 获取已经声明的方法,getAnnotation 用来获取注解对象信息

package cn.xiangxu.com.service;

import cn.xiangxu.com.annotations.NeedTest;
import org.springframework.beans.factory.annotation.Autowired;
import org.testng.annotations.Guice;
import org.testng.annotations.Test;

import javax.annotation.Resource;
import java.lang.reflect.Method;

import static org.testng.Assert.*;

public class AnnotationTestServiceTest {

    @Test
    public void tool() {
        // 01 获取到AnnotationTestService对象
        AnnotationTestService annotationTestService = new AnnotationTestService();

        // 02 获取到AnnotationTestService中所有的Method数组
        Method [] methods = annotationTestService.getClass().getDeclaredMethods();
        System.out.println(methods.length);

        // 03 获取到方法上的注解信息
        for (Method method : methods) {
            NeedTest needTest = method.getAnnotation(NeedTest.class);
            if (needTest != null) {
                if (needTest.value()) {
                    System.out.println(method.getName() + "()需要进行单元测试");
                } else{
                    System.out.println(method.getName() + "()不需要进行单元测试");
                }
            }
        }

    }

    @Test
    public void testDeleteUser() throws Exception {
    }

    @Test
    public void testDeleteUserAddress() throws Exception {
    }

}
AnnotationTestServiceTest.java
    /**
     * Returns the runtime class of this {@code Object}. The returned
     * {@code Class} object is the object that is locked by {@code
     * static synchronized} methods of the represented class.
     *
     * <p><b>The actual result type is {@code Class<? extends |X|>}
     * where {@code |X|} is the erasure of the static type of the
     * expression on which {@code getClass} is called.</b> For
     * example, no cast is required in this code fragment:</p>
     *
     * <p>
     * {@code Number n = 0;                             }<br>
     * {@code Class<? extends Number> c = n.getClass(); }
     * </p>
     *
     * @return The {@code Class} object that represents the runtime
     *         class of this object.
     * @jls 15.8.2 Class Literals
     */
    public final native Class<?> getClass();
getClass()
    /**
     *
     * Returns an array containing {@code Method} objects reflecting all the
     * declared methods of the class or interface represented by this {@code
     * Class} object, including public, protected, default (package)
     * access, and private methods, but excluding inherited methods.
     *
     * <p> If this {@code Class} object represents a type that has multiple
     * declared methods with the same name and parameter types, but different
     * return types, then the returned array has a {@code Method} object for
     * each such method.
     *
     * <p> If this {@code Class} object represents a type that has a class
     * initialization method {@code <clinit>}, then the returned array does
     * <em>not</em> have a corresponding {@code Method} object.
     *
     * <p> If this {@code Class} object represents a class or interface with no
     * declared methods, then the returned array has length 0.
     *
     * <p> If this {@code Class} object represents an array type, a primitive
     * type, or void, then the returned array has length 0.
     *
     * <p> The elements in the returned array are not sorted and are not in any
     * particular order.
     *
     * @return  the array of {@code Method} objects representing all the
     *          declared methods of this class
     * @throws  SecurityException
     *          If a security manager, <i>s</i>, is present and any of the
     *          following conditions is met:
     *
     *          <ul>
     *
     *          <li> the caller's class loader is not the same as the
     *          class loader of this class and invocation of
     *          {@link SecurityManager#checkPermission
     *          s.checkPermission} method with
     *          {@code RuntimePermission("accessDeclaredMembers")}
     *          denies access to the declared methods within this class
     *
     *          <li> the caller's class loader is not the same as or an
     *          ancestor of the class loader for the current class and
     *          invocation of {@link SecurityManager#checkPackageAccess
     *          s.checkPackageAccess()} denies access to the package
     *          of this class
     *
     *          </ul>
     *
     * @jls 8.2 Class Members
     * @jls 8.4 Method Declarations
     * @since JDK1.1
     */
    @CallerSensitive
    public Method[] getDeclaredMethods() throws SecurityException {
        checkMemberAccess(Member.DECLARED, Reflection.getCallerClass(), true);
        return copyMethods(privateGetDeclaredMethods(false));
    }
getDeclaredMethods()
    /**
     * {@inheritDoc}
     * @throws NullPointerException  {@inheritDoc}
     * @since 1.5
     */
    public <T extends Annotation> T getAnnotation(Class<T> annotationClass) {
        return super.getAnnotation(annotationClass);
    }
getAnnotation()

 

3 切面样例

  3.1 创建所需的服务接口和服务类

    说明:Waiter 是一个接口利用有两个方法,其中greetTo方法有@NeedTest注解;NaiveWaiter是Waiter接口的实现类

package cn.xiangxu.com.service;

import cn.xiangxu.com.annotations.NeedTest;

public interface Waiter {
    @NeedTest
    public void greetTo(String clientName);    
    public void serveTo(String clientName);
}
Waiter.java
package cn.xiangxu.com.service;


public class NaiveWaiter implements Waiter {
    public void greetTo(String clientName) {
        System.out.println("NaiveWaiter:greet to "+clientName+"...");
    }    
    public void serveTo(String clientName){
        System.out.println("NaiveWaiter:serving "+clientName+"...");
    }
    public void smile(String clientName,int times){
        System.out.println("NaiveWaiter:smile to  "+clientName+ times+"times...");
    }    
}
NaiveWaiter.java

  3.2 创建切面类

    技巧01:一个完整的切面类必须包含: 切点、增强、横切逻辑

package cn.xiangxu.com.aops.example;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

/**
 * @author 王杨帅
 * @create 2018-05-04 11:09
 * @desc
 **/
@Aspect
public class PreGreetingAspect {
    @Before("execution(* greetTo(..))")
    public void beforeGreeting() {
        System.out.println("How are you");
    }
}
PreGreetingAspect.java

  3.3 织入切面

    3.3.1 通过编程方式

      利用 AspectJProxyFactory 织如基于 @AspectJ注解的类

package cn.xiangxu.com.aops.example;

import cn.xiangxu.com.service.NaiveWaiter;
import cn.xiangxu.com.service.Waiter;
import org.springframework.aop.aspectj.annotation.AspectJProxyFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.testng.annotations.Test;

import javax.annotation.Resource;

import static org.testng.Assert.*;

public class PreGreetingAspectTest {
    @Test
    public void testBeforeGreeting() throws Exception {
        // 01 生成目标对象实例
        Waiter target = new NaiveWaiter();
        // 02 实例化工厂
        AspectJProxyFactory factory = new AspectJProxyFactory();
        // 03 设置目标对象
        factory.setTarget(target);
        // 04 添加切面类
        factory.addAspect(PreGreetingAspect.class);

        // 05 生成植入切面的代理对象
        Waiter proxy = factory.getProxy();
        // 06 利用代理对象调用方法
        proxy.greetTo("Warrior");
        System.out.println("parting line");
        proxy.serveTo("Warrior");

    }

}
PreGreetingAspectTest.java

    3.3.2 利用Spring配置的方式

      》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-4.0.xsd
           http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd">
    <aop:aspectj-autoproxy/>
    <!--bean class="org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator"/-->
    <bean id="waiter" class="cn.xiangxu.com.service.NaiveWaiter" />
    <bean class="cn.xiangxu.com.aops.example.PreGreetingAspect" />
</beans>
View Code

      》测试类

package cn.xiangxu.com.aops.example;

import cn.xiangxu.com.service.NaiveWaiter;
import cn.xiangxu.com.service.Waiter;
import javafx.application.Application;
import org.springframework.aop.aspectj.annotation.AspectJProxyFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.testng.annotations.Test;

import javax.annotation.Resource;

import static org.testng.Assert.*;

public class PreGreetingAspectTest {

    /**
     * 利用 编码的方式织入切面
     * @throws Exception
     */
    @Test
    public void testBeforeGreeting() throws Exception {
        // 01 生成目标对象实例
        Waiter target = new NaiveWaiter();
        // 02 实例化工厂
        AspectJProxyFactory factory = new AspectJProxyFactory();
        // 03 设置目标对象
        factory.setTarget(target);
        // 04 添加切面类
        factory.addAspect(PreGreetingAspect.class);

        // 05 生成植入切面的代理对象
        Waiter proxy = factory.getProxy();
        // 06 利用代理对象调用方法
        proxy.greetTo("Warrior");
        System.out.println("parting line");
        proxy.serveTo("Warrior");

    }

    /**
     * 利用Spring配置的方式织入切面
     */
    @Test
    public void testBeforeGreeting02() {
        // 01 配置文件路径
        String configPath = "aops/example/beans.xml";

        // 02 获取应用上下文
        ApplicationContext ac = new ClassPathXmlApplicationContext(configPath);

        // 03 利用应用上下文获取对象
        Waiter waiter = (Waiter)ac.getBean("waiter");

        waiter.greetTo("fury");
        System.out.println("===parting line===");
        waiter.serveTo("warrior");

    }

}
PreGreetingAspectTest.java

 

4 引介增强

  引介增强最主要的目的是为类A添加一个需要实现的接口B,并指定实现类C(解释:C是B的实现类);简而言之,类A可以通过引介增强来实现接口B,实现的方式是利用了类C

  4.1 创建接口B

package cn.xiangxu.com.service;

public interface Seller {
  int sell(String goods, String clientName);
}
Seller.java

  4.2 创建接口B的实现类C

package cn.xiangxu.com.service;

public class SmartSeller implements Seller {

    public int sell(String goods,String clientName) {
        System.out.println("SmartSeller: sell "+goods +" to "+clientName+"...");
        return 100;
    }
    
    public void checkBill(int billId){
        if(billId == 1) throw new IllegalArgumentException("iae Exception");
        else throw new RuntimeException("re Exception");
    }
}
SmartSeller.java

  4.3 创建切面类

    该切面类主要实现引介增强

package cn.xiangxu.com.aops.basic;

import cn.xiangxu.com.service.Seller;
import cn.xiangxu.com.service.SmartSeller;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.DeclareParents;

@Aspect
public class EnableSellerAspect {
    @DeclareParents(value="cn.xiangxu.com.service.NaiveWaiter",
            defaultImpl=SmartSeller.class)
    public  Seller seller;
}
EnableSellerAspect.java

  4.4 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-4.0.xsd
           http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd">
    <aop:aspectj-autoproxy/>
    <bean id="waiter" class="cn.xiangxu.com.service.NaiveWaiter"/>
    <bean class="cn.xiangxu.com.aops.basic.EnableSellerAspect"/>
</beans>
View Code

  4.5 编写测试类

package cn.xiangxu.com.aops.basic;

import cn.xiangxu.com.service.Seller;
import cn.xiangxu.com.service.Waiter;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.testng.annotations.Test;

public class EnableSellerAspectTest {

    @Test
    public void test01() {
        // 01 配置文件路径
        String configPath = "aops/basic/beans.xml";
        // 02 实例化应用上下文
        ApplicationContext ac = new ClassPathXmlApplicationContext(configPath);
        // 03 获取bean对象
        Waiter waiter = (Waiter)ac.getBean("waiter");

        // 04 调用实例方法
        waiter.greetTo("fury");

        // 05 类型强转
        Seller seller = (Seller)waiter;
        seller.sell("hotpot", "王杨帅");
    }

}
EnableSellerAspectTest.java

   

5 本博文源代码

  点击前往

    

  

 

 

 

 

 

 

 

    

 

posted @ 2018-05-04 15:02  寻渝记  阅读(290)  评论(0编辑  收藏  举报