Aspect Oriented Programing,面向切面编程。
利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
AOP主要用于日志记录,性能统计,安全控制(权限控制),事务处理,异常处理等。将日志记录,性能统计,安全控制,事务处理,异常处理等代码从业务逻辑代码中划分出来,通过对这些行为的分离,我们希望可以将它们独立到非指导业务逻辑的方法中,进而改变这些行为的时候不影响业务逻辑的代码。
像Spring AOP一样,我们要的AOP也需要依赖于IoC容器。
我们的IoC容器实现:http://blog.csdn.net/jrainbow/article/details/50680914
本文就是通过JDK提供的反射机制来实现类似基于@AspectJ的Spring 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中的切点表达式是结合正则表达式及它本身自有的一些规则产生的,我们这里不要太复杂,也不需要方法参数列表参数名字这个属性,直接是先考虑使用正则表达式来表示。
确定了以上的内容后,下面我们以前置增强为例开始代码实现。
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();
}
因为我们的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 {
我们这里通过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;
}
});
}
}
在获取到所有的切面类后,我们对切面类里的增强方法进行分析处理。我们判断所有托管的普通类对象中是否有符合切点表达式的方法。并通过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;
}
}
把拥有代理对象的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);
}
}
与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方法。
scanPackage=xyz.letus.demo
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方法也成功织入了一个增强。
https://github.com/benben-ren/wheel/tree/36e8a8d84896947349291675c9db37ce8701f590
注:源码中只有IoC与AOP构架源码,不包含使用源码。