从服务端转到Android,最大的遗憾是不能用绿色健康的Spring了。
虽然依稀记得有spring for android,但是看了官网才发现根本没有IOC 和 AOP这些功能,只有http客户端功能
大失所望之下,自己动手搞了个最简单的demo,遂有创建个开源项目android下的spring的念头...
诸君可能会大大拍砖 性能啦之类的 确实值得考虑,但是随着Android项目日益庞大,代码量稳步上升,结构越来越混乱,
维护成本也越来越高,相比之下使用Spring的服务端好很多。有兴趣的看看demo代码吧
android-spring 项目:(作为android lib 项目)
项目地址: https://code.google.com/p/spring-android/
package android.springframework;
/**
* Bean上下文
*/
public interface BeanContext {
String tag = "springframework";
<T> T getBean(Class<T> clazz);
<T> T getBean(String name, Class<T> clazz);
Object getBean(String name);
}
package android.springframework;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 组件注解
*
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Component {
String name() default "";
boolean prototype() default true;
}
package android.springframework;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Resource 注入注解<p>
* 目前只支持字段
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Resource {
String name() default "";
}
/**
*
*/
package android.springframework;
import static org.ow2.asmdex.Opcodes.*;
import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.net.URL;
import java.util.HashMap;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import org.ow2.asmdex.AnnotationVisitor;
import org.ow2.asmdex.ApplicationReader;
import org.ow2.asmdex.ApplicationVisitor;
import org.ow2.asmdex.ClassVisitor;
import org.ow2.asmdex.FieldVisitor;
import android.content.Context;
import android.util.Log;
/**
* 基于注解的BeanContext
*
*/
public class AnnotaionBeanContext implements BeanContext {
/**
* 类扫描的基础包
*/
private final String basePkgToScan;
private HashMap<String, Object> namedBeans = new HashMap<String, Object>();
private HashMap<Class, Object> classBeans = new HashMap<Class, Object>();
private HashMap<String, Class> descToClass = new HashMap<String, Class>();
/**
* Android Application Context - 供所有使用Context的非Android组件类公共使用
*/
private final Context context;
private static final String COMPONNET_ANNO_DESC = "Landroid/springframework/Component;";
private static final String RESOURCE_ANNO_DESC = "Landroid/springframework/Resource;";
private static final int API_ASM = ASM4;
public AnnotaionBeanContext(String basePkgToScan, Context context) {
Log.d(tag, "AnnotaionBeanContext 初始化 ");
this.basePkgToScan = basePkgToScan;
this.context = context;
try {
initApplication();
} catch (Exception e) {
Log.e(tag, Log.getStackTraceString(e));
}
}
private void initApplication() throws Exception {
URL moduleResUrl = AnnotaionBeanContext.class.getClassLoader()
.getResource("AndroidManifest.xml");
String path = moduleResUrl.getFile();
String apkPath = path.substring(path.indexOf('/'), path.indexOf("!/"));
Log.d(tag, "apkPath = " + apkPath);
ZipFile zipFile = new ZipFile(apkPath);
ZipEntry classesEntry = zipFile.getEntry("classes.dex");
InputStream classesStream = zipFile.getInputStream(classesEntry);
ApplicationReader ar = new ApplicationReader(API_ASM, classesStream);
AppVisitor appVistor = new AppVisitor();
appVistor.setModulePackage(basePkgToScan);
ar.accept(appVistor, 0);
classesStream.close();
zipFile.close();
}
@SuppressWarnings("unchecked")
@Override
public <T> T getBean(Class<T> clazz) {
return (T) classBeans.get(clazz);
}
@Override
public <T> T getBean(String name, Class<T> clazz) {
// TODO Auto-generated method stub
return null;
}
@Override
public Object getBean(String name) {
// TODO Auto-generated method stub
return null;
}
class AppVisitor extends ApplicationVisitor {
private String modulePackage;
private BeanClassVisitor beanClassVisitor = new BeanClassVisitor(api);
public AppVisitor() {
super(API_ASM);
}
public void setModulePackage(String modulePackage) {
if (modulePackage.startsWith("L")) {
this.modulePackage = modulePackage;
} else {
this.modulePackage = 'L' + modulePackage.replace('.', '/');
}
}
@Override
public ClassVisitor visitClass(int access, String name,
String[] signature, String superName, String[] interfaces) {
if (name.startsWith(modulePackage)) {
return beanClassVisitor;
}
return null;
}
}
class BeanClassVisitor extends ClassVisitor {
public BeanClassVisitor(int api) {
super(api);
}
String className;
BeanFieldVistor beanFieldVistor = new BeanFieldVistor();
boolean isComponent;
Object bean;
@Override
public void visit(int version, int access, String name,
String[] signature, String superName, String[] interfaces) {
Log.i(tag, "*** className = " + name);
className = name;
beanFieldVistor.reset();
}
@Override
public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
Log.i(tag, "@Annotation : desc = " + desc + ", visible = "
+ visible);
if (COMPONNET_ANNO_DESC.equals(desc)) {
isComponent = true;
bean = initBean(className);
// for (Field f : bean.getClass().getDeclaredFields()) {
// Resource resAnn = f.getAnnotation(Resource.class);
// if (resAnn == null) {
// continue;
// }
// f.setAccessible(true);
// Object fval = initBean(f.getType());
// if (fval != null) {
// try {
// f.set(bean, fval);
// } catch (Exception e) {
// e.printStackTrace();
// }
// }
// }
}
return null;
}
@Override
public FieldVisitor visitField(int access, String name, String desc,
String[] signature, Object value) {
if (isComponent) {
beanFieldVistor.set(bean, access, name, desc, signature);
return beanFieldVistor;
}
return null;
}
}
class BeanFieldVistor extends FieldVisitor {
public BeanFieldVistor() {
super(API_ASM);
}
void reset() {
access = 0;
fieldName = "";
fieldDesc = "";
signature = null;
ownerBean = null;
}
int access;
String fieldName;
String fieldDesc;
String[] signature;
Object ownerBean;
void set(Object bean, int access, String name, String desc,
String[] signature) {
this.ownerBean = bean;
this.access = access;
this.fieldName = name;
this.fieldDesc = desc;
this.signature = signature;
}
@Override
public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
if (RESOURCE_ANNO_DESC.equals(desc)) {
try {
Field field = ownerBean.getClass().getDeclaredField(
fieldName);
field.setAccessible(true);
Object fval = initBean(field.getType());
if (fval != null) {
field.set(ownerBean, fval);
}
} catch (Exception e) {
e.printStackTrace();
}
}
return super.visitAnnotation(desc, visible);
}
}
private Object initBean(String className) {
if (className.endsWith(";")) {
className = className.substring(1, className.length() - 1);
}
if (className.contains("/")) {
className = className.replace('/', '.');
}
Object ins = null;
Class<?> clazz = descToClass.get(className);
if (clazz == null) {
try {
return initBean(clazz = Class.forName(className));
} catch (Exception e) {
Log.e(tag, Log.getStackTraceString(e));
}
} else {
ins = classBeans.get(clazz);
}
return ins;
}
private Object initBean(Class<?> clazz) {
if (clazz == null) {
return null;
}
Object ins = classBeans.get(clazz);
if (ins != null) {
return ins;
}
descToClass.put(clazz.getName(), clazz);
try {
Constructor<?> defCons = clazz.getConstructor();
ins = defCons.newInstance();
classBeans.put(clazz, ins);
} catch (NoSuchMethodException e) {
try {
Constructor<?> cons = clazz.getConstructor(Context.class);
ins = cons.newInstance(context);
classBeans.put(clazz, ins);
} catch (Exception e1) {
Log.e(tag, Log.getStackTraceString(e1));
}
} catch (Exception e) {
Log.e(tag, Log.getStackTraceString(e));
}
return ins;
}
}
项目依赖了ASMdex, 下载地址为:http://download.forge.ow2.org/asm/asmdex-1.0.jar
清单文件为空即可(AndroidManifest.xml):
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="android.springframework"
android:versionCode="1"
android:versionName="1.0" >
</manifest>
project.properties:
# Project target.
target=android-19
android.library=true
一个简单的Spring android 实现完事,下面是一个demo项目,依赖spring-android项目
package springdemo.modules.app;
import android.content.Context;
import android.springframework.Component;
/**
*
*/
@Component
public class AppManager {
private Context context;
public AppManager(Context context) {
this.context = context;
}
public String getApps() {
String apps = "apps:" + context.getPackageName() + ";com.facebook";
return apps;
}
}
package springdemo.modules.push;
import springdemo.modules.app.AppManager;
import android.springframework.Component;
import android.springframework.Resource;
/**
*
*/
@Component
public class PushManager {
@Resource
private AppManager appManager;
public String push() {
return "push: appManager.getApps()=" + appManager.getApps();
}
}
package springdemo;
import android.content.Context;
import android.springframework.AnnotaionBeanContext;
import android.springframework.BeanContext;
/**
*
*/
public class SpringApplicationHelper {
private BeanContext beanContext;
private Context context;
private SpringApplicationHelper(Context context) {
Context applicationCtx = context.getApplicationContext();
beanContext = new AnnotaionBeanContext(getClass().getPackage()
.getName() + ".modules", applicationCtx);
this.context = applicationCtx;
}
private static SpringApplicationHelper instance;
public static SpringApplicationHelper getInstance(Context context) {
if (instance == null && context != null) {
instance = new SpringApplicationHelper(context);
}
return instance;
}
public BeanContext getBeanContext() {
return beanContext;
}
}
package spring.android.demo;
import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import spring.android.demo.R;
import springdemo.SpringApplicationHelper;
import springdemo.modules.push.PushManager;
public class MainActivity extends Activity {
private static final String tag = "spring-test";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
findViewById(R.id.btn_spring_test).setOnClickListener(
new View.OnClickListener() {
@Override
public void onClick(View v) {
try {
testSpringBean_pushMgr();
} catch (Throwable e) {
Log.e(tag, Log.getStackTraceString(e));
}
}
});
}
private void testSpringBean_pushMgr(){
SpringApplicationHelper springHelper = SpringApplicationHelper
.getInstance(MainActivity.this);
PushManager pushManager = springHelper
.getBeanContext()
.getBean(PushManager.class);
String msg = "pushManager.push() = "
+ pushManager.push();
Log.i(tag, msg);
}
}
布局文件(main/layout.xml):
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<Button
android:id="@+id/btn_spring_test"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/btn_text" />
</LinearLayout>
project.properties文件:
# Project target.
target=android-19
android.library.reference.1=../spring-android
AndroidManifest.xml 文件:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="spring.android.demo"
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk
android:minSdkVersion="10"
android:targetSdkVersion="19" />
<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
>
<activity
android:name="spring.android.demo.MainActivity"
android:label="@string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
界面效果:
日志输出结果:
11-09 20:14:46.566: I/springframework(12149): *** className = Lspringdemo/modules/app/AppManager;
11-09 20:14:46.566: I/springframework(12149): @Annotation : desc = Landroid/springframework/Component;, visible = true
11-09 20:14:46.566: I/springframework(12149): *** className = Lspringdemo/modules/push/PushManager;
11-09 20:14:46.566: I/springframework(12149): @Annotation : desc = Landroid/springframework/Component;, visible = true
11-09 20:14:46.576: I/spring-test(12149): pushManager.push() = push: appManager.getApps()=apps:spring.android.demo;com.facebook