之前一直不理解ioc、di、aop到底是怎么工作的。只是知道spring具有这些功能。也只是知道spring是通过jdk的动态代理,通过反射来讲这些创建好的对象放在一个Map的容器里。但是一个程序到底是如何发现需要被放置到容器中的,一直没有眉目。
知道之前在腾讯课堂看到的一个关于手写mvc的视频。才知道一些比较底层的东西。
首先就是注解:
Annotation:他有两个最常用的属性:Target、Retention。
Target(常用属性):
/** 放置到类上面 */
TYPE,
/** 放置到类属性上 */
FIELD,
/** 放置到类方法上面 */
METHOD,
/** 放置到类方法的参数前面 */
PARAMETER,
Retention(常用的属性):
/**
运行时
*/
RUNTIME
Target是表示这个注解放置在什么地方,而Retention则是告知注解在什么时候有效。
这样就可以写出一个简单的注解了,如:
@Target(ElementType.Type)
@Retention(RetentionPolicy.RUNTIME)
public @interface test{
String value() default ""; //提供一个函数,默认为“”。
}
说了注解下面就说说这个注解有什么用,为什么推荐使用注解。
首先通过注解可以知道这个类、属性、函数、参数是有什么特殊的意义,比如某个类上标注了test注解。那就表示这个类是一个有着test表示的类。在后续的查询文件中如果发现这个类包含这个test注解,那么就表示这个类是需要被加入到ioc容器或是需要被其他操作的。通过检查类上面有没有这个注解可以很快的查找出想要的类,方法,属性或是参数。
使用注解个人觉得是方便。
下面就开始进一步的分析我的代码:
1.扫描部分
2.实例化部分
3.处理函数增强部分
4.处理自动注入部分
首先扫描部分:
其实就是将项目下的所有的不是目录的部分检查出来并将其放置到一个List容器中。
public void ScannerClassFile(String path){
URL resource = this.getClass().getClassLoader().getResource(path.replaceAll("\\.", "/"));
if (resource==null)
return;
String filename = resource.getFile();
File file = new File(filename);
if (file.isDirectory()){
String[] filenames = file.list();
if (filenames!=null) {
for (String s : filenames) {
File file1 = new File(filename +"\\"+ s);
if (file1.isDirectory()) {
//递归的查找
if (path.equals(""))
ScannerClassFile(s);
else
ScannerClassFile(path + "." + s);
} else {
//将文件放置到一个List列表中
if (path.equals(""))
filesname.add(s);
else
filesname.add(path + "." + s);
}
}
}
}
}
其次就是实例化部分:
在上面的扫描部分里就是将文件名放置到了list列表中。那么现在只需要根据之前讲的注解标识查找相应的类,并将其添加到Map容器中。
public void Instance() {
//遍历List列表
for (String filename : filesname) {
String fn = filename.replace(".class","");
try {
//通过文件名获取class
Class clazz = Class.forName(fn);
//通过检查类有没有包含Service注解,如果包含了,那么就实例化
if (clazz.isAnnotationPresent(Service.class)) {
Object object = clazz.newInstance();
//获取Service注解
Service service = (Service) clazz.getAnnotation(Service.class);
//将Service中保存的value值取出,并将期作为key
//object作为value放置到ico容器中
iocmap.put(service.value(),object);
//如果开启了aop的模式那么就进行增强
if (Aop_or_Ioc == 1) {
//判断这个类有没有函数有函数注解的
//有的话将其实例化的类放置到aop容器中
//表示这个类的这个函数是需要被增强的
if (iscreateaop(clazz)){
aopmap.put(service.value(), object);
}
}
}
//看看这类的属性有没有被标识,如果被标识了那么就创建
//实例并请其添加到ioc容器中
if (iscreateioc(clazz)){
Object object = clazz.newInstance();
String iockey = object.getClass().getName();
iocmap.put(iockey,object);
}
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {
e.printStackTrace();
}
}
}
下面就是处理函数增强的部分了:
//代码很简单,这样实现的原理就是对于同一个Object,
//在ioc和aop容器中使用相同的key.
if (Aop_or_Ioc == 1) {
JAutomatic jAutomatic = new JAutomatic();
for (Map.Entry<String, Object> entry : aopmap.entrySet()) {
Object o = entry.getValue();
String key = entry.getKey();
//通过jdk动态代理来生成行的类
Mcl_Invocation_Handler mcl_invocation_handler = new Mcl_Invocation_Handler(o);
Object newInstance = jAutomatic.createNewInstance(o, mcl_invocation_handler);
//刷新ioc容器
iocmap.put(key, newInstance);
}
}
处理自动注入部分
在java的反射中有一个函数叫做set()
public void set(Object obj, Object value)
//第一个参数是类,第二个参数为类中某个属性的值。
//如:
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Integer_{
String value() default "";
}
class test{
@Integer_
int i=0;
}
class test_main{
public static void main(String [] args) throws IllegalAccessException{
test t = new test();
Class clazz = t.getClass();
Field[] declaredFields = clazz.getDeclaredFields();
for (Field declaredField : declaredFields) {
if (declaredField.isAnnotationPresent(Integer_.class)){
declaredField.setAccessible(true);
declaredField.set(t,1);
}
}
System.out.print(t.i);
}
}
结果:
通过上面的现象可以看到1被赋值给了test中的变量i。
在下面的程序里也是一样,通过set函数将ioc容器中的Object对象赋值给了类中的被MAutowrite注释过的属性变量。这个就完成了变量自动赋值的过程。如果在上面的aop增强中经过增强了,那么此时属性拿到的就不是原始的对象了,而是经过增强的对象。
//遍历ioc容器
for(Map.Entry<String,Object> entry:iocmap.entrySet()){
//获取ioc中每一个实例
Object object = entry.getValue();
Class clazz = object.getClass();
//获取所有的属性
Field[] declaredFields = clazz.getDeclaredFields();
for (Field declaredField : declaredFields) {
if (declaredField.isAnnotationPresent(MAutowrite.class)){
//找到没一个注释
MAutowrite mAutowrite = (MAutowrite) declaredField.getAnnotation(MAutowrite.class);
try {
//通过获取keyget到具体的类。
Object object1 = iocmap.get(mAutowrite.value());
declaredField.setAccessible(true);
//加紧此实例注入到属性中去。
declaredField.set(object,object1);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
}
就此完成了对于从扫描到实例化再到aop增强最后自动注入的过程。
下面就是本小小小框架的重点。
就是可以实现增强函数的自定义。
如果只是上面的那一段那么就可以大体上的知道了Spring的ioc、di以及aop是如何的工作的,但是我们需要更多地挖掘细节。
首先诀窍就是注解+简单工厂模式
首先就是自定义了三个注解:before(前置)、after(后置)、looparound(环绕)。
样子类似于上面的Integer_注解
然后是针对于具体的增强函数的简单共产模式的说明:
InvocationHandler中的invoke函数:
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
其中method就是原始的类中的函数。但是这个method的修饰符与原来的类中的函数的修饰符不一样,一般为private。
获取原函数
1.通过InvocationHandler实现类中的Object(这个类就是动态代理时需要被代理的对象)类中的所有的method方法对比invoke中的参数method的函数名、返回值、参数类型可以获取唯一的一个函数。
2.通过对比可以确定这个代理类中的函数在原始函数中的定位。通过拿取到原始函数那么一切就都迎刃而解。
3.通过原始函数上的注解可以获取到给朕函数增强类型是什么,增强的函数是什么。
下面就是查找增强函数的部分:
通过注解中的value函数可以轻松的获取到增强函数的全限定名(所以在写注释的时候需要添加上全限定名)。如:
//Enhance_Functions.LoopAround.LoopAround_test1就是环绕接口的一个实现类
//通过填写具体的全限定名,后面的增强时就可以知道增强函数是哪一个了
@looparound("Enhance_Functions.LoopAround.LoopAround_test1")
public void query(String name) {
System.out.println("你的名字:"+name);
}
通过全限定名就可以创建这个类的实例了。由于所有的增强函数都实现了BSEInterface接口那么在查找的时候返回值都是BseInterface。将Invoke函数中method参数和InvocationHandler的实现类中的Object作为参数传给BseInterface。
通过接口中的excute方法就可以实现增强了。
FindEnhanceFunction fef = new FindEnhanceFunction();
public Mcl_Invocation_Handler(Object object){
//拿取是需要被代理的类
super(object);
}
//得到此时这个aop的状态
/*
在这里可以通过使用注释中的value方法提取出需要增强的方法
*/
public BseInterface getaopstate(Method method1){
//得到父类中的对应的方法
Method method = getOriginMethod(method1);
if (method!=null) {
//在这里区分不同的增强,before、after、looparound
if (method.isAnnotationPresent(looparound.class)) {
return fef.getlooparound(method);
}else if (method.isAnnotationPresent(after.class)){
return fef.getafter(method);
} else if (method.isAnnotationPresent(before.class)) {
return fef.getbefore(method);
}
}
return null;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
BseInterface bseInterface = getaopstate(method);
//执行增强函数
bseInterface.excute(method,this.object,args);
return null;
}
上面就是全部了:
下面来具体的测试一下:
public static void main(String[] args) {
new Dispatcher();
AutowriteTest.serviceInterfaceTest.query("毛从雷");
}
public static void main(String[] args) {
new Dispatcher(1);
AutowriteTest.serviceInterfaceTest.query("毛从雷");
}
这个是没有使用aop是的效果
这个是使用了aop的效果图,增强类型为环绕。
环绕的函数为:
public class LoopAround_test1 implements LoopAround{
@Override
public void excute(Method method, Object object, Object[] args) {
try {
before(method);
Object o = method.invoke(object,args);
after(o);
} catch (IllegalAccessException | InvocationTargetException e) {
e.printStackTrace();
}
}
public void before(Method method){
System.out.println(method.getName()+" "+method.getReturnType()+" "+method.getModifiers());
}
public void after(Object object){
if (object!=null) {
Class clazz = object.getClass();
System.out.println(clazz.getName());
}else System.out.println("函数执行完返回了一个null");
}
}
在具体的事务函数上的注释为:
@looparound("Enhance_Functions.LoopAround.LoopAround_test1")
public void query(String name) {
System.out.println("你的名字:"+name);
}
可以看见总体的效果还是不错的。由于篇幅问题不能所有的代码细节都一一列举,但是大致的原理都已经讲解完了。
具体的代码细节可以去我的github上去看看,上面有我的这个跟项目的源码。下载下来后可以直接的原型。没有任何的依赖包,原生的jdk1.8就可以了。
更新
1.添加了自定义注解的功能。
只是实现了class、method、field的自定义注解的部分,函数参数的注解没有写,希望后续可以添加。并且在自定义注解中目前只支持String value() default “”;这一个函数,其他的函数还没有支持。有读者可以的话欢迎来添加行的功能。
其工作原理也是痕迹简单,虽然没有看过其他人是怎么做的,但是我感觉我的做法还是比较中庸的。当效果出来后也是很欣喜。没有浪费我花时间去思考。
首先就是对于注解的查找。
在java的反射中有一个函数:
public Annotation[] getAnnotations()
通过这个函数可以获得lclass、field、Method等等的所有的注解。
然后在遍历一下注解,将不认识的注解就通过获取到这个注解的具体的class,然后获取其所包含的函数。将其中的定义好的value函数取出来。在通过method的invoke函数来获取value中的返回值:
if (method1!=null) {
String name = null;
try {
//通过method的invoke函数获取其返回值即value()
//的返回值
name = (String)method1.invoke(getmethod, null);
return name;
} catch (IllegalAccessException | InvocationTargetException e) {
e.printStackTrace();
}
}
对于不同类型的注释中的value值有不同的处理方法:
对于Class上的注释那么就是ElementType.TYPE那么其处理方式就是将其value值取出来作为ioc容器中的key值。
对于Method上的注释那么就是ElementType.METHOD那么处理方式就是将其value取出来,这个至一定是一个aop增强的注释,所以其中的值一定是全限定名所以通过全限定名去查找想一个的增强函数。
对于Field上的注释那么就是EllementType.FIELD那么处理方式就是即将其中的value值取出来作为key并从ioc中取出对应的对象,赋值给这个field。
所以最重要的就是对于注释的查找,通过上面的分类查找:class、method、field等等可以快速的查找到这些元素上的所有的注释,然后在对应取出这些注释中的value值,然后在去处理对应的事件。就可以实现自定义的注释。
还有一个就是增强部分的原理。
这个也是很简单的,更之前的一样通过查找到这个注释中的value值,取出来后就是全限定名。通过全限定名就可以查找到对应的增强函数。这里有一点需要强调的就是这个增强函数一定要实现BseInterface接口(这个是一个基本接口),否则程序会报错。
效果:
//新的自定义注释
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface newafter {
String value() default "";
}
//自定义的新的增强的函数
public class newafterExample implements BseInterface {
@Override
public void excute(Method method, Object object, Object[] args) {
try {
method.invoke(object,args);
newafter();
} catch (IllegalAccessException | InvocationTargetException e) {
e.printStackTrace();
}
}
public void newafter(){
System.out.println("this is new after Enhance Method");
}
}
//主函数
public class RunTest {
public static void main(String[] args) {
new Dispatcher(1);
AutowriteTest.serviceInterfaceTest.query("毛从雷");
}
}
效果: