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>
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
}
/* * 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 }
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 { } }
/** * 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();
/** * * 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)); }
/** * {@inheritDoc} * @throws NullPointerException {@inheritDoc} * @since 1.5 */ public <T extends Annotation> T getAnnotation(Class<T> annotationClass) { return super.getAnnotation(annotationClass); }
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); }
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..."); } }
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"); } }
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"); } }
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>
》测试类
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"); } }
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); }
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"); } }
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; }
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>
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", "王杨帅"); } }
5 本博文源代码