自定义注解+动态代理+反射 实现简易AOP功能
简介
Spring的AOP功能非常强大,其核心技术主要用到的是动态代理,在这里,我通过自定义注解和动态代理,模拟Before和After的功能。
设计思路
主要思路:通过动态代理,增强被调用对象,使原对象新增一些原先没有的功能。那么,怎么样知道我增强了哪些功能呢?此时需要通过注解来定位,而这些功能的实现,则可以通过反射来执行。
1.为什么用注解?
注解是在JDK5引入的概念,通过@interface 关键字进行定义,它可以使配置更加简单。
2.为什么用动态代理?
在这个例子中,我们主要用到代理的增强特性,因为代理对象的包含了原对象的功能,并给与增强,我们可以简单理解为如下结构:
而为什么用动态代理而非静态代理,因为静态代理需要硬编码指定,动态代理可以在运行时自动生成,这样对代码没有侵入,没有耦合,与业务逻辑互不影响。
实现过程
一、pom配置
首先,我们创建父项目proxydemo和两个子项目:api和main。
proxydemo项目的pom.xml文件
<?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>com.mzsf.demo</groupId>
<artifactId>proxy-demo</artifactId>
<packaging>pom</packaging>
<version>1.0-SNAPSHOT</version>
<modules>
<module>main</module>
<module>api</module>
</modules>
<properties>
<version.slf4j>1.7.13</version.slf4j>
<version.commons-lang>3.3.2</version.commons-lang>
<version.commons-collections>4.0</version.commons-collections>
<version.spring>5.0.1.RELEASE</version.spring>
</properties>
<dependencyManagement>
<dependencies>
<!-- slf4j -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>${version.slf4j}</version>
</dependency>
<!-- Apache Commons Lang -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>${version.commons-lang}</version>
</dependency>
<!-- Apache Commons Collections -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
<version>${version.commons-collections}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${version.spring}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>${version.spring}</version>
<scope>test</scope>
</dependency>
</dependencies>
</dependencyManagement>
</project>
api项目的pom.xml文件(注:api项目的打包方式是jar,打包后可导入main项目中被引用)
<?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">
<parent>
<artifactId>proxy-demo</artifactId>
<groupId>com.mzsf.demo</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>api</artifactId>
<packaging>jar</packaging>
<dependencies>
<!-- slf4j -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
</dependency>
<!-- Apache Commons Lang -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<!-- Apache Commons Collections -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<!-- Compiler -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.2</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
</plugins>
</build>
</project>
main项目的pom.xml文件
<?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">
<parent>
<artifactId>proxy-demo</artifactId>
<groupId>com.mzsf.demo</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>main</artifactId>
<packaging>war</packaging>
<dependencies>
<dependency>
<groupId>com.mzsf.demo</groupId>
<artifactId>api</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<!-- Compiler -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.2</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
</plugins>
</build>
</project>
二、java实现
在api项目中,创建com.mzsf.demo.api.annotation包,创建CustomBefore和CustomAfter两个自定义注解。其中,代码里用到了@Target和@Retention这两个元注解,这里就不展开了,有兴趣的同学自行研究。
package com.mzsf.demo.api.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 用于注释:方法被执行之前,允许被执行的方法
*
*/
@Target(value=ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface CustomBefore {
//用于指定方法名称(格式:包名+类名+方法名),不能为空.
String[] value();
}
package com.mzsf.demo.api.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 用于注释:方法被执行之后,允许被执行的方法
*
*/
@Target(value=ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface CustomAfter {
//用于指定方法名称(格式:包名+类名+方法名),不能为空.
String[] value();
}
然后创建com.mzsf.demo.api.proxy包,在其下创建ProxyBeanFactory.java类,该类是一个创建代理对象的工厂类。
package com.mzsf.demo.api.proxy;
import com.mzsf.demo.api.annotation.CustomAfter;
import com.mzsf.demo.api.annotation.CustomBefore;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.lang.reflect.*;
import java.util.*;
public class ProxyBeanFactory{
//日志对象
private static final Logger LOGGER = LoggerFactory.getLogger(ProxyBeanFactory.class);
//声明被代理类对象
private Object interfaceObj;
public ProxyBeanFactory() {
}
/**
* 返回代理对象
*/
public <T> T getProxyBean(Object interfaceObj){
this.interfaceObj = interfaceObj;
return (T) Proxy.newProxyInstance(interfaceObj.getClass().getClassLoader(), interfaceObj.getClass().getInterfaces(), new InvocationHandler() {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return invokeMethod(proxy, method, args);
}
});
}
public Object invokeMethod(Object proxy, Method method, Object[] args) throws Throwable {
Object result;
if (method.isAnnotationPresent(CustomBefore.class)) {
CustomBefore customBefore = method.getAnnotation(CustomBefore.class);
if (null != customBefore && customBefore.value().length > 0){
List<String> list = Arrays.asList(customBefore.value());
list.stream().filter(e -> StringUtils.isNotEmpty(e)).forEach(e -> {
execute(e, method.getParameterTypes(), args);
});
}
}
//执行主方法
result = method.invoke(interfaceObj, args);
if (method.isAnnotationPresent(CustomAfter.class)) {
CustomAfter customAfter = method.getAnnotation(CustomAfter.class);
if (null != customAfter && customAfter.value().length > 0){
List<String> list = Arrays.asList(customAfter.value());
list.stream().filter(e -> StringUtils.isNotEmpty(e)).forEach(e -> {
execute(e, method.getParameterTypes(), args);
});
}
}
return result;
}
/**
* 执行方法
* @param serviceName
* @param args
* @throws Exception
*/
private void execute(String serviceName, Class<?>[] parameterTypes, Object[] args){
String[] c = parseServiceName(serviceName);
//获取类名和方法名
String className = c[0];
String methodName = c[1];
try {
//动态寻找类和方法
Object o = Class.forName(className).newInstance();
Method m = o.getClass().getMethod(methodName, parameterTypes);
m.invoke(o, args);
} catch (Exception e) {
e.printStackTrace();
LOGGER.error("serviceName:" + serviceName);
}
}
/**
* 解析serviceName
* @param serviceName
* @return
*/
private String[] parseServiceName(String serviceName){
String[] className = new String[2];
int index = serviceName.lastIndexOf(".");
if (index == -1){
return null;
}
//获取类名和方法名
className[0] = serviceName.substring(0, index);
className[1] = serviceName.substring(index + 1);
return className;
}
}
在main项目中,创建com.mzsf.demo.main.aspect包,创建TestServiceAspect.java,添加如下方法:
package com.mzsf.demo.main.aspect;
public class TestServiceAspect {
public void before1(String useId){
System.out.println(useId + " This is before1!");
}
public void before2(String useId){
System.out.println(useId + " This is before2!");
}
public void after(String useId){
System.out.println(useId + " This is after!");
}
}
创建com.mzsf.demo.main.service和com.mzsf.demo.main.service.impl包,分别添加TestService.java和TestServiceImpl.java文件:
package com.mzsf.demo.main.service;
import com.mzsf.demo.api.annotation.CustomAfter;
import com.mzsf.demo.api.annotation.CustomBefore;
public interface TestService {
@CustomBefore({"com.mzsf.demo.main.aspect.TestServiceAspect.before1","com.mzsf.demo.main.aspect.TestServiceAspect.before2"})
@CustomAfter({"com.mzsf.demo.main.aspect.TestServiceAspect.after"})
void doLogin(String userId);
}
package com.mzsf.demo.main.service.impl;
import com.mzsf.demo.main.service.TestService;
import org.springframework.stereotype.Service;
@Service("testService")
public class TestServiceImpl implements TestService {
@Override
public void doLogin(String userId) {
System.out.println("用户:" + userId + "登录成功");
}
}
创建com.mzsf.demo.main.controller包,加入测试Test.java类:
package com.mzsf.demo.main.controller;
import com.mzsf.demo.api.proxy.ProxyBeanFactory;
import com.mzsf.demo.main.service.TestService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Test {
public static void main(String[] args){
ApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");
//创建代理对象,但不执行代理对象中的Handler方法
TestService testService = new ProxyBeanFactory().getProxyBean(context.getBean("testService"));
//此时,testService已为代理对象,当该对象调用doLogin时,则执行代理对象中的新Handler方法
testService.doLogin("003");
}
}
最后在resources文件夹下面添加log4j.properties和spring.xml
log4j.rootLogger=ERROR,console
log4j.appender.console=org.apache.log4j.ConsoleAppender
log4j.appender.console.layout=org.apache.log4j.PatternLayout
log4j.appender.console.layout.ConversionPattern=%m%n
log4j.logger.com.mzsf=DEBUG
<?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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="com.mzsf.demo.main"/>
</beans>
Ok,编码完成,右击启动Test.java中的main方法,控制台输入如下:
003 This is before1!
003 This is before2!
用户:003登录成功
003 This is after!
总结
通过以上代码,我们模拟了一个简单的Before和After切面功能,demo非常简单,希望可以加深大家对注解和动态代理的理解。
大家有空多多交流,可以添加QQ群:915349845,或者关注我的公众号,里面会不定期发一些文章。
最后,双手奉上代码链接,有兴趣的可以下载
https://download.csdn.net/download/ppa3383794/12074761