AOP编程也就是面向切面编程,作为是对面向对象编程的一个重要补充,其应用场景活跃在我们日常开发的角角落落,比如对数据的处理,系统日志的记录,总之,需要在某些业务功能添加一些与业务本身又无关的功能时,你首先想到的应该是AOP。 在实际点的例子,作为java开发的首选框架--spring家族,aop概念就是其核心概念之一,其中的事务,异常以及其本身实现的切面注解都无不是aop的理论应用,如果没有这些东西,我无法想象我在编程时做的项目会是什么样子。
AOP其底层实现原理是利用注解和代理(动态代理),代理的实现原理则是反射。所以,如果想自己实现aop,反射的知识是必不可少的。有了知识储备,先聊聊我们等下实现aop的思路。这个我们可以借鉴spring对aop的功能介绍,然后针对这些功能,如果自己实现该怎么去完成。在spring中,aop中几个比较重要的概念:切面,通知,连接点。切面描述的是你关注的业务对象在spring中对应的大概是pointcut;通知则是你想做的处理处理嵌入到业务对象中的时机对应spring大概是before(业务进行前嵌入你想嵌入的处理),after(业务成功返回后嵌入想进行的处理),@afterReturn,@afterThrowing等注解;连接点则是触发你想要进行处理的事比如异常抛出,比如某个方法的执行。根据这些概念下面我们要实现这几步:
1,区分(找到)切面(要被代理的对象)
2,生成代理对象,并在代理对象里,加入我们想要添加的切面处理
很简单,不过这里我打算换一种方式,1,找到需要添加到切面的操作 2,找到切面 3,生成代理对象,并在代理对象中添加你想做的切面处理 其实就是想说,我们这里比spring的事务aop多做一些事。。。好吧,直接上源码
这里我想使用注解方式找到需要添加到切面的操作,所以先自定义一个注解吧。
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface AopAnnotation {
public String path() default "";
}
因为我们这里用jdk代理生成代理对象,所以,我们这里搞一个接口先(使用cglib其实也一样,就是不用声明接口了,spring中默认对实现接口的对象代理使用jdk代理)
public interface Service {
public void login1();
public void login2();
}
然后,建一个我们想要做特殊处理的切面类
public class LogsUser implements Service{
public void login1() {
System.out.println("user login in 1======");
}
public void login2() {
System.out.println("user login in 2======");
}
}
再然后,建一个我们想做的处理类并使用我们上面建好的自定义注解进行标注,我们给path赋值为刚建好的切面类,等下用这个找切面,并把找到的切面类进行代理,对其所有方法都在调用方法前加printlog日志,其实就相当于@Before注解(的jdk代理实现)
public class AopService {
@AopAnnotation(path="LogsUser")
public void printLog() {
System.out.println("++++++++++++++ before login logs===++++++++++");
}
}
再然后,就是我们经常看到spring启动时加载生成的一大堆代理类的过程了,主要是实现找到项目下所有使用自定义的切面注解,并生成相对应的代理。我简单的把他放在一个main中,主要也为了看效果。君可自行理解
public class AnnotationTest {
public static void main(String[] s) {
test2();
}
/**
* 实现aop
*/
public static void test2() {
String path = AnnotationTest.class.getResource("/").getPath();
System.out.println("项目根路径:"+path);
File bootFile = new File(path);
LinkedList<File> list = new LinkedList<File>();
Map<String,String> map= new HashMap<>();//存放文件对应的文件名
LinkedList<String> list1 = new LinkedList<>();//存放文件对应的包路径
if(bootFile.isDirectory()) {
File fs[] = bootFile.listFiles();
for(File file : fs) {
list.add(file);
list1.add("");
}
File tempFile = list.removeFirst();
String tempPath =list1.removeFirst();
while(tempFile!=null) {//如果是文件夹则遍历
if(tempFile.isDirectory()) {
File[] fa = tempFile.listFiles();
for(File file : fa) {
list.addLast(file);
if(tempPath!=null&&!tempPath.equals("")) {
list1.addLast(tempPath+"."+tempFile.getName());
if(!file.isDirectory()) {
map.put(file.getName().split("\\.")[0], tempPath+"."+tempFile.getName());
}
}else {
list1.addLast(tempFile.getName());
if(!file.isDirectory()) {
map.put(file.getName().split("\\.")[0], tempFile.getName());
}
}
}
}else {//是文件而不是文件夹,判断是否存在注解
String javaFile = tempFile.getName().split("\\.")[0];
try {
if(tempFile.getName().split("\\.")[1].equals("class")) {
System.out.println(tempFile.getName());
System.out.println(tempPath+"."+javaFile);
Class javaClass = Class.forName(tempPath+"."+javaFile);
// AopAnnotation aopAn = (AopAnnotation)javaClass.getDeclaredAnnotation(AopAnnotation.class);
// Class cl = Class.forName(aopAn.path());
Method[] methods = javaClass.getDeclaredMethods();
for(Method method : methods) {
// System.out.println("------------------");
AopAnnotation aopAN = null;
if(method.isAnnotationPresent(AopAnnotation.class)) {
aopAN = method.getAnnotation(AopAnnotation.class);//找到注解
Class cl = Class.forName(map.get(aopAN.path())+"."+aopAN.path());//找到切面类
Object proxyObject = null;
try {
proxyObject = cl.newInstance();
} catch (InstantiationException | IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
MyInvocationHandler handler = new MyInvocationHandler();
Annotation annatation = javaClass.getAnnotation(AopAnnotation.class);
try {
handler.setProxyObject(proxyObject, method, null, javaClass.newInstance());
} catch (InstantiationException | IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
Service proxyUser = (Service) Proxy.newProxyInstance(cl.getClassLoader(), proxyObject.getClass().getInterfaces(),
handler);
System.out.println("++++++++++++++++");
proxyUser.login1();
proxyUser.login2();
}
}
}
//暂时先不对class的filed进行设置..........................................
} catch (ClassNotFoundException e) {
System.out.println(e);
System.out.println("============查找class出错============");
}
}
if(list.size()>0) {
tempFile = list.removeFirst();
tempPath = list1.removeFirst();
}else {
tempFile=null;
}
}
}else {
System.out.println("no file behind the file");
}
}
既然是使用jdk代理,怎么少得了InvocationHandler呢。
class MyInvocationHandler implements InvocationHandler{
Object proxyObject;//要被代理的对象
Method preProxyMethod;//我们的特殊处理(这里就是加了我们自定义注解的方法)
Object[] args;//上面方法的参数
Object object;//上面方法所在的对象,或者在这里传一个class对象,然后你在自己生成其对象也行
public void setProxyObject(Object proxyObject,Method method,Object[] args,Object object) {
this.args=args;
this.proxyObject = proxyObject;
this.preProxyMethod = method;
this.object = object;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
preProxyMethod.invoke(object, this.args);//特殊处理织入
Object reObject = method.invoke(proxyObject, args);
return reObject;
}
}
ok,把这些代码,复制回去应该就是能运行出结果的,
虽然代码有点乱,但我相信上面的代码很简单。其实自己也是可以实现简单的aop的,aop说起来高大上,其实也没那么遥远,不过,我个人认为这种思想很重要,最起码,以后假如有一天让你自己去在非spring环境下处理问题,或者就是你自己写一个项目,脚本用不到spring时,你就可以用这招很明显的减少你的工作量了。