说到spring,java开发人员完全不会陌生,因为开发spring已经整合了各种主流框架,可以说是垄断了java程序员的所有框架,说到spring,就离不开它的俩大核心IOC和AOP,IOC控制反转,顾名思义就是把创建对象的控制权从程序员手里反转到了框架,利用java反射特性去构建对象,然后自动注入到属性里,spring实现IOC用三种方式配置xml、annotation、javaconfig。AOP面向切面编程,就是在不改函数的基础上在调用函数或者方法开始、中间、结束当切入点,将切面逻辑放进去。简单来说就是对函数进行增强。spring 实现AOP主要技术是用JDK动态代理和Cglib动态代理,就是在java编译期不知道目标类是什么,而再执行的时候给目标类生成代理子类,子类会重写目标类中所有非final的方法。
既然要山寨spring的俩大核心技术,就先制定山寨方案,因为spring实现这俩大技术有多种方式,山寨就选择一种即可。ioc的实现用annotation,aop就用cglib动态代理。
首先需要定义俩个annotation,一个是用来实现IOC的BeanAnnoation(山寨@Bean),一个是用来实现AOP的LogAnnotation(模拟方法调用打印日志的注解)。然后@Autowried就用java原生的@Resource去实现
//@Retention(value = RetentionPolicy.RUNTIME)必写,否则在运行的时候无法读取到该注解
@Documented
@Retention(value = RetentionPolicy.RUNTIME)
public @interface LogAnnotation {
}
@Documented
@Retention(value = RetentionPolicy.RUNTIME)
public @interface BeanAnnoation {
}
然后需要一个可以创建cglib动态代理对象的类
public class CGlibProxy implements MethodInterceptor {
private Object obj;
public CGlibProxy(String className){
try {
this.obj=Class.forName(className).newInstance();
} catch (InstantiationException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
}
public CGlibProxy(Object obj){
this.obj=obj;
}
public Object buildProxyObject(){
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(obj.getClass());
enhancer.setCallback(this);
return enhancer.create();
}
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
String className=obj.getClass().getName().split("\\$")[0];
if(method.isAnnotationPresent(LogAnnotation.class)){
System.out.println(className+"."+method.getName()+"方法开始执行");
}
Object returnObj=proxy.invokeSuper(obj,args);
if(method.isAnnotationPresent(LogAnnotation.class)){
System.out.println(className+"."+method.getName()+"方法结束执行");
}
return returnObj;
}
}
obj是想生成动态代理对象的原对象。再构造器中加上className参数,支持用字符串反射构建obj对象。
需要实现MethodInterceptor接口,实现intercept方法。
buildProxyObject方法是通过obj原对象构建动态代理对象的方法,并将构建好的代理对象返回。
intercept是动态代理对象的拦截方法,再调用动态代理对象的方法都会该调用该方法,第一个参数是原对象,第二个参数是调用的方法(原对象的方法),第三个参数是调用的参数数组,第四个参数是调用的动态代理方法。咱需要再该方法中加上开始调用的日志和结束的日志。用method.isAnnotationPresent()去判断是该方法否存在LogAnnotation注解(要用method不要用proxy,否则读取不到注解)。如果存在该注解。则再方法调用前后输出日志。 Object returnObj=proxy.invokeSuper(obj,args);是调用代理对象的方法,然后得到返回值,一定要注意这里用proxy不要用method,如果用mehod则又会进入该拦截方法,然后无限递归报错。
好了,动态代理解决了,AOP也就实现了。然后咱开始实现IOC。第一步是需要扫描包里所有的类,第二部是利用反射去构建类中所有带@Bean注解的类对象,然后再循环遍历所有的对象,找到带有@Resource的属性。然后将相同类型的对象set到属性里。
public class MyContainer {
private List<Object> objectList = new ArrayList<>();
public MyContainer(String packageName) throws Exception {
List<String>classPaths= new ArrayList<String>();
//先把包名转换为路径,首先得到项目的classpath
String classpath = this.getClass().getResource("/").getPath();
//然后把我们的包名basPach转换为路径名
packageName = packageName.replace(".", File.separator);
//然后把classpath和basePack合并
String searchPath = classpath + packageName;
doPath(new File(searchPath),classPaths);
//这个时候我们已经得到了指定包下所有的类的绝对路径了。我们现在利用这些绝对路径和java的反射机制得到他们的类对象
List<String>classPaths1= new ArrayList<String>();
for (String s : classPaths) {
//把 D:\work\code\20170401\search-class\target\classes\com\baibin\search\a\A.class 这样的绝对路径转换为全类名com.baibin.search.a.A
s = s.replace(classpath.replace("/","\\").replaceFirst("\\\\",""),"").replace("\\",".").replace(".class","");
if(Class.forName(s).isAnnotationPresent(BeanAnnoation.class)){
objectList.add(new CGlibProxy(s).buildProxyObject());
}
}
objectList.stream().forEach(e->{
try {
ioc(e);
} catch (Exception ex) {
throw new RuntimeException(ex);
}
});
//System.out.println(classPaths1.size());
}
private void ioc(Object o) throws Exception{
Class c = Class.forName(o.getClass().getName().split("\\$")[0]);
for(Field field:c.getDeclaredFields()){
if(field.isAnnotationPresent(Resource.class)){
field.setAccessible(true);
field.set(o,getObjectByClassName(field.getType().getName()));
}
}
}
public Object getObjectByClassName(String className){
for(Object o:objectList){
if(o.getClass().getName().split("\\$")[0].equals(className)){
return o;
}
}
return null;
}
private static void doPath(File file, List<String>classPaths) {
if (file.isDirectory()) {//文件夹
//文件夹我们就递归
File[] files = file.listFiles();
for (File f1 : files) {
doPath(f1,classPaths);
}
} else {//标准文件
//标准文件我们就判断是否是class文件
if (file.getName().endsWith(".class")) {
//如果是class文件我们就放入我们的集合中。
classPaths.add(file.getPath());
}
}
}
public List<Object> getObjectList() {
return objectList;
}
public void setObjectList(List<Object> objectList) {
this.objectList = objectList;
}
}
编写一个MyContainer去实现所有对象的IOC
再MyContainer的构造器中加入包名参数,在构造器里去实现对包的扫描和objectList的创建。
构建完objectList再遍历objectList去调用ioc,寻找Resource注解的属性,然后用反射将相同类型的object set到属性里。
好了,现在AOP和IOC已经实现了。然后写个controller和service和dao去测试吧。
@BeanAnnoation
public class TestService {
@LogAnnotation
public void test(){
System.out.println("执行service过程");
testDao.test();
}
@Resource
private TestDao testDao;
}
@BeanAnnoation
public class TestDao {
@LogAnnotation
public void test(){
System.out.println("数据库执行");
}
}
@BeanAnnoation
public class TestController {
@Resource
private TestService testService;
public void test(){
testService.test();
}
/**
执行结果:
aop.test.TestService.test方法开始执行
执行service过程
aop.test.TestDao.test方法开始执行
数据库执行
aop.test.TestDao.test方法结束执行
aop.test.TestService.test方法结束执行
*/
public static void main(String[] args) throws Exception {
String packagemName=TestController.class.getPackage().getName();
MyContainer myContainer =new MyContainer(packagemName);
TestController testController =(TestController)myContainer.getObjectByClassName(TestController.class.getName());
testController.test();
}
}
再三个类上加入@BeanAnnoation告诉容器这三个类需要被反射构建,@Resource告诉容器该属性需要被set,@LogAnnotation告诉生成的代理对象该方法需要打印日志。
main函数中再TestController的test里调用TestService的test,然后TestService的test在调用TestDao的test。main函数的执行结果已经在注释里写好。