1 前言
Aspect Oriented Programing,面向切面编程。
利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
AOP主要用于日志记录,性能统计,安全控制(权限控制),事务处理,异常处理等。将日志记录,性能统计,安全控制,事务处理,异常处理等代码从业务逻辑代码中划分出来,通过对这些行为的分离,我们希望可以将它们独立到非指导业务逻辑的方法中,进而改变这些行为的时候不影响业务逻辑的代码。
像Spring AOP一样,我们要的AOP也需要依赖于IoC容器。
我们的IoC容器实现:http://blog.csdn.net/jrainbow/article/details/50680914
本文就是通过JDK提供的反射机制来实现类似基于@AspectJ的Spring AOP的使用。
2 AOP的实现需求
基于@Aspect的Spring AOP使用:http://blog.csdn.net/jrainbow/article/details/9268637
我们通过一个基于@Aspect的Spring AOP的使用模板来看看我们需要实现哪些功能。
/*
* 通过@Aspect把这个类标识管理一些切面
*/
@Aspect
@Component
public class FirstAspect {
/*
* 定义切点及增强类型
*/
@Before("execution(* save(..))")
public void before(){
System.out.println("我是前置增强...");
}
}
从Spirng AOP的@Aspect使用代码可以看到,我们如果想实现类似这样的功能,需要对最基本的几个声明进行解析:切面、切点、增强类型。
@Before(value= "切入点表达式或命名切入点",argNames = "指定命名切入点方法参数列表参数名字,可以有多个用“,”分隔")
切面与增强类型都是annotation。
而切点,从@Before注解看来,是一个表达式加上方法参数列表参数名字的形式。
Spring AOP中的切点表达式是结合正则表达式及它本身自有的一些规则产生的,我们这里不要太复杂,也不需要方法参数列表参数名字这个属性,直接是先考虑使用正则表达式来表示。
确定了以上的内容后,下面我们以前置增强为例开始代码实现。
3 实现过程
3.1 切面及增强类型注解
package xyz.letus.framework.aop.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 切面
* @ClassName: Aspect
* @Description: TODO
* @author 潘广伟(笨笨)
* @date 2016年3月7日
*
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Aspect {
String value() default "";
}
package xyz.letus.framework.aop.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 前置增强注解
* @ClassName: Before
* @Description: TODO
* @author 潘广伟(笨笨)
* @date 2016年3月7日
*
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Before {
/**
* 切点表达式
*
* @Title: value
* @Description: TODO
* @param @return
* @return String
* @throws
*/
String value();
}
3.2 扫描@Aspect的托管
因为我们的AOP也需要依赖于IoC容器,我们这里也使用ClassFactory类在描述basePackage包下的类时,把@Aspect描述的类找出来。
/**
* 解析路径,并获取所要的类
* @Title: parse
* @Description: TODO
* @return void
* @throws
*/
public void parse(){
for (String packagePath : packages) {
Set<Class<?>> classes = ClassLoader.getClassSet(packagePath);
for(Class<?> clazz : classes){
if(clazz.isAnnotationPresent(Component.class)){
//普通类托管
...
}else if(clazz.isAnnotationPresent(Aspect.class)){
//切面类托管
aspectClasses.put(clazz.getSimpleName(), clazz);
}
}
}
}
完整的ClassFactory类:
package xyz.letus.framework.util;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import xyz.letus.framework.aop.annotation.Aspect;
import xyz.letus.framework.ioc.annotation.Component;
/**
* 类操作助手
* @ClassName: ClassHelper
* @Description: TODO
* @author 潘广伟(笨笨)
* @date 2015年9月19日
*
*/
public class ClassFactory {
private Map<String, Class<?>> componentClasses;
private Map<String, Class<?>> aspectClasses;
private List<String> packages;
public ClassFactory(List<String> packages){
this.packages = packages;
componentClasses = new HashMap<String, Class<?>>();
aspectClasses = new HashMap<String, Class<?>>();
this.parse();
}
/**
* 解析路径,并获取所要的类
* @Title: parse
* @Description: TODO
* @return void
* @throws
*/
public void parse(){
for (String packagePath : packages) {
Set<Class<?>> classes = ClassLoader.getClassSet(packagePath);
for(Class<?> clazz : classes){
if(clazz.isAnnotationPresent(Component.class)){
//普通类托管
Component component = clazz.getAnnotation(Component.class);
String name = clazz.getSimpleName();
String value = component.value();
if(value.length() > 0){
name = value;
}
componentClasses.put(name, clazz);
}else if(clazz.isAnnotationPresent(Aspect.class)){
//切面类托管
aspectClasses.put(clazz.getSimpleName(), clazz);
}
}
}
}
public Map<String, Class<?>> getComponentClasses() {
return componentClasses;
}
public Map<String, Class<?>> getAspectClasses() {
return aspectClasses;
}
}
注:Spring可能为了不修改原有的IoC实现,托管的注解和切面的注解是分开的,而我们这里是放一起的。
Spring要使用切面的时候,需要使用两个注解:
@Aspect
@Component
public class FirstAspect {
3.4 代理工具类
我们这里通过JDK提供的java.lang.reflect.Proxy来创建代理对象。不过Proxy比较局限的地方在于,代理对象的目标对象必须是基于接口的实现。当然这也是本文中基于JDK的AOP实现的不足之一。
package xyz.letus.framework.aop;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.List;
/**
* 代理管理器
* @ClassName: ProxyManager
* @Description: TODO
* @author 潘广伟(笨笨)
* @date 2016年3月7日
*
*/
public class ProxyManager {
/**
* 创建一个前置增强的代理对象
* @Title: createBeforeProxy
* @Description: TODO
* @param @param aspect 切面对象
* @param @param target 目标对象
* @param @param matchMethods 匹配的方法名
* @param @param before 前置增强方法
* @param @return
* @return T
* @throws
*/
@SuppressWarnings("unchecked")
public static <T> T createBeforeProxy(final Object aspect,final Object target,final List<String> matchMethods,final Method before){
return (T) Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new InvocationHandler() {
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
/**
* 增加前置增强
*/
if(matchMethods.contains(method.getName())){
before.invoke(aspect, args);
}
Object result = method.invoke(target, args);
return result;
}
});
}
}
3.4 切面管理
在获取到所有的切面类后,我们对切面类里的增强方法进行分析处理。我们判断所有托管的普通类对象中是否有符合切点表达式的方法。并通过ProxyManager来为有符合切点方法的类对象织入增强,创建代理对象,并替换掉原有的对象,并由IoC容器管理。
/**
* 前置增强处理
* @Title: beforeHandle
* @Description: TODO
* @param @param aspect 切面对象
* @param @param beforeMethod 增强方法
* @return void
* @throws
*/
private void beforeHandle(Object aspect,Method beforeMethod){
Before before = beforeMethod.getAnnotation(Before.class);
String execution = before.value();
for (Entry<String,Object> entry : beanMap.entrySet()) {
String name = entry.getKey();
Object target = entry.getValue();
List<String> list = getMatchMethod(execution, target);
if(list.size() > 0){
Object obj = ProxyManager.createBeforeProxy(aspect, target, list, beforeMethod);
beanMap.put(name, obj);
}
}
}
符合切点表达式的解析比较简单,就是完整类名加方法名的正则匹配。
这里要注意的是,我们需要对目标对象的接口方法进行判断,而不是目标对象的方法进行匹配。因为如果一个对象需要织入多个增强,我们需要进行多次代理对象的替换,而代理对象的路径并不是原有路径,不能匹配之前定义的正则表达式。
/**
* 获取匹配的方法名
*
* 现只做完整类名加方法名的解析
* @Title: getMatchMethod
* @Description: TODO
* @param @param before
* @param @param target
* @param @return
* @return boolean
* @throws
*/
private List<String> getMatchMethod(String execution,Object target){
List<String> list = new ArrayList<String>();
Class<?>[] classes = target.getClass().getInterfaces();
for(Class<?> clazz : classes){
Method[] methods = clazz.getDeclaredMethods();
Pattern pattern = Pattern.compile(execution);
for(Method method : methods){
StringBuffer methodName = new StringBuffer(clazz.getName());
methodName.append(".").append(method.getName());
if(pattern.matcher(methodName).matches()){
list.add(method.getName());
}
}
}
return list;
}
完整的切面管理类:
package xyz.letus.framework.aop;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.regex.Pattern;
import xyz.letus.framework.aop.annotation.Before;
/**
* 切面管理
* @ClassName: AspectManager
* @Description: TODO
* @author 潘广伟(笨笨)
* @date 2016年3月7日
*
*/
public class AspectManager {
private Map<String, Object> beanMap;
public AspectManager(Map<String, Object> beanMap){
this.beanMap = beanMap;
}
/**
* 对所有切面进行解析
* @Title: parse
* @Description: TODO
* @param @param aspectClasses
* @return void
* @throws
*/
public void parse(Map<String, Class<?>> aspectClasses) {
for (Entry<String, Class<?>> entry : aspectClasses.entrySet()) {
parse(entry.getValue());
}
}
/**
* 解析一个切面
* @Title: parse
* @Description: TODO
* @param @param clazz
* @return void
* @throws
*/
private void parse(Class<?> clazz) {
try {
Method[] methods = clazz.getMethods();
Object aspect = clazz.newInstance();
for(Method method : methods){
if(method.isAnnotationPresent(Before.class)){
beforeHandle(aspect,method);
}
}
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
/**
* 获取匹配的方法名
*
* 现只做完整类名加方法名的解析
* @Title: getMatchMethod
* @Description: TODO
* @param @param before
* @param @param target
* @param @return
* @return boolean
* @throws
*/
private List<String> getMatchMethod(String execution,Object target){
List<String> list = new ArrayList<String>();
Class<?>[] classes = target.getClass().getInterfaces();
for(Class<?> clazz : classes){
Method[] methods = clazz.getDeclaredMethods();
Pattern pattern = Pattern.compile(execution);
for(Method method : methods){
StringBuffer methodName = new StringBuffer(clazz.getName());
methodName.append(".").append(method.getName());
if(pattern.matcher(methodName).matches()){
list.add(method.getName());
}
}
}
return list;
}
/**
* 前置增强处理
* @Title: beforeHandle
* @Description: TODO
* @param @param aspect 切面对象
* @param @param beforeMethod 增强方法
* @return void
* @throws
*/
private void beforeHandle(Object aspect,Method beforeMethod){
Before before = beforeMethod.getAnnotation(Before.class);
String execution = before.value();
for (Entry<String,Object> entry : beanMap.entrySet()) {
String name = entry.getKey();
Object target = entry.getValue();
List<String> list = getMatchMethod(execution, target);
if(list.size() > 0){
Object obj = ProxyManager.createBeforeProxy(aspect, target, list, beforeMethod);
beanMap.put(name, obj);
}
}
}
public Map<String, Object> getBeanMap() {
return beanMap;
}
}
3.5 替换BEAN_MAP
把拥有代理对象的map替换到BeanFactory中去。
完整的BeanFactory:
package xyz.letus.framework.ioc;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import xyz.letus.framework.aop.AspectManager;
import xyz.letus.framework.util.ClassFactory;
import xyz.letus.framework.util.ReflectionFactory;
/**
* Bean助手类
* @ClassName: BeanHelper
* @Description: TODO
* @author 潘广伟(笨笨)
* @date 2015年9月19日
*
*/
public class BeanFactory {
private static Map<String, Object> BEAN_MAP = new HashMap<String, Object>();
/**
* 创建所有托管的实例
* @Title: createInstance
* @Description: TODO
* @param @param packages
* @return void
* @throws
*/
public static void createInstance(List<String> packages){
ClassFactory classFactory = new ClassFactory(packages);
Map<String, Class<?>> componentClasses = classFactory.getComponentClasses();
Map<String, Class<?>> aspectClasses = classFactory.getAspectClasses();
//对普通的托管类进行处理
for (Entry<String, Class<?>> entry : componentClasses.entrySet()) {
Object obj = ReflectionFactory.newInstance(entry.getValue());
BEAN_MAP.put(entry.getKey(), obj);
}
//依赖注入
IocHelper.inject(BEAN_MAP);
//切面处理
AspectManager aspectManager = new AspectManager(BEAN_MAP);
aspectManager.parse(aspectClasses);
BEAN_MAP = aspectManager.getBeanMap();
}
/**
* 获取Bean实例
* @Title: getBean
* @Description: TODO
* @param @param name
* @param @return
* @return T
* @throws
*/
@SuppressWarnings("unchecked")
public static <T> T getBean(String name){
if(!BEAN_MAP.containsKey(name)){
throw new RuntimeException("can not get bean by className:"+name);
}
return (T) BEAN_MAP.get(name);
}
}
4 框架使用
与IoC容器框架的使用类似。
我们对之前的Service类,让它实现一个接口。
package xyz.letus.demo.service;
public interface IService {
public void say();
public void play();
}
package xyz.letus.demo.service;
import xyz.letus.demo.dao.Dao;
import xyz.letus.framework.ioc.annotation.Component;
import xyz.letus.framework.ioc.annotation.Inject;
@Component("service")
public class Service implements IService{
@Inject("a")
private Dao dao;
public void say(){
dao.say();
System.out.println("Service say something.");
}
public void play() {
System.out.println("Service playing.");
}
}
- 通过框架注解定义一个切面
package xyz.letus.demo.aspect;
import xyz.letus.framework.aop.annotation.Aspect;
import xyz.letus.framework.aop.annotation.Before;
@Aspect
public class BeforeAspect {
@Before("xyz.letus.demo.service.*.*")
public void beforeA(){
System.out.println("beforeA");
}
@Before("xyz.letus.demo.service.*.say")
public void beforeB(){
System.out.println("beforeB");
}
}
切面中定义了两个前置增强,一个增强是针对xyz.letus.demo.service包下的所有接口方法,另一个是针对xyz.letus.demo.service包下的所有接口的say方法。
- 资源文件context.properties没变化,依然只需配置需扫描的主包。
scanPackage=xyz.letus.demo
- 启动IoC容器,并获取service实例
ApplicationContext context = ApplicationContext.getContext("context.properties");
IService service = context.getBean("service");
service.say();
service.play();
- 输出结果
beforeA
beforeB
Dao say something.
Service say something.
====================
beforeA
Service playing.
可以看到say方法成功织入了两个前置增强,而play方法也成功织入了一个增强。
5 不足
- 通过JDK提供的java.lang.reflect.Proxy来创建代理对象的目标对象必须是基于接口的实现。
- 如果有多个增强针对同一个类的话,需要多次创建代理对象。
- 由于为了快速开发的关系,之前的IoC容器和本文的AOP框架没有解耦。
6 源码下载
https://github.com/benben-ren/wheel/tree/36e8a8d84896947349291675c9db37ce8701f590
注:源码中只有IoC与AOP构架源码,不包含使用源码。