spring 通过反射调用service方法

首先介绍一下一下spring ReflectionUtils方法

org.springframework.util.ReflectionUtils 是spring提供的反射工具类,使用它有以下好处:

  • 我们在使用Java反射的时候,需要处理大量的IllegalAccessException,而这个异常,我们在业务逻辑当中,绝大多数情况都不需要捕获,使用Spring工具类,可以直接将其转换为RuntimeException,在业务中无需直接捕获。
  • 使用Spring提供的接口,可以减少很多对Java类的字段方法操作带来的冗余,代码清晰简洁。

以下是 ReflectionUtils 源代码,不需要我们手写,直接在Spring框架中调用即可。

仅供参考:

public abstract class ReflectionUtils {

 //根据类和属性名称查找属性字段
//Attempt to find a {@link Field field} on the supplied {@link Class} with
//the supplied <code>name</code>. Searches all superclasses up to {@link Object}.
//@param clazz the class to introspect
//@param name the name of the field
//@return the corresponding Field object, or <code>null</code> if not found
//
 public static Field findField(Class clazz, String name) {
  return findField(clazz, name, null);
 }

 //根据类和属性名称查找属性字段
//Attempt to find a {@link Field field} on the supplied {@link Class} with
//the supplied <code>name</code> and/or {@link Class type}. Searches all
//superclasses up to {@link Object}.
//@param clazz the class to introspect
// @param name the name of the field (may be <code>null</code> if type is specified)
//@param type the type of the field (may be <code>null</code> if name is specified)
// @return the corresponding Field object, or <code>null</code> if not found
//
 public static Field findField(Class clazz, String name, Class type) {
  Assert.notNull(clazz, "Class must not be null");
  Assert.isTrue(name != null || type != null, "Either name or type of the field must be specified");
  Class searchType = clazz;
  while (!Object.class.equals(searchType) && searchType != null) {
   Field[] fields = searchType.getDeclaredFields();
   for (int i = 0; i < fields.length; i++) {
    Field field = fields[i];
    if ((name == null || name.equals(field.getName()))
      && (type == null || type.equals(field.getType()))) {
     return field;
    }
   }
   searchType = searchType.getSuperclass();
  }
  return null;
 }

 //根据属性字段和对象,设置对象的值
// Set the field represented by the supplied {@link Field field object} on
//the specified {@link Object target object} to the specified
//<code>value</code>. In accordance with
// {@link Field#set(Object, Object)} semantics, the new value is
// automatically unwrapped if the underlying field has a primitive type.
// <p>Thrown exceptions are handled via a call to
// {@link #handleReflectionException(Exception)}.
// @param field the field to set
// @param target the target object on which to set the field
// @param value the value to set; may be <code>null</code>
//
 public static void setField(Field field, Object target, Object value) {
  try {
   field.set(target, value);
  }
  catch (IllegalAccessException ex) {
   handleReflectionException(ex);
   throw new IllegalStateException(
     "Unexpected reflection exception - " + ex.getClass().getName() + ": " + ex.getMessage());
  }
 }

 //根据属性字段和对象,获取的对象中字段的值
 // Get the field represented by the supplied {@link Field field object} on
 // the specified {@link Object target object}. In accordance with
 // {@link Field#get(Object)} semantics, the returned value is
 // automatically wrapped if the underlying field has a primitive type.
 // <p>Thrown exceptions are handled via a call to
 // {@link #handleReflectionException(Exception)}.
 // @param field the field to get
 // @param target the target object from which to get the field
 // @return the field's current value
 public static Object getField(Field field, Object target) {
  try {
   return field.get(target);
  }
  catch (IllegalAccessException ex) {
   handleReflectionException(ex);
   throw new IllegalStateException(
     "Unexpected reflection exception - " + ex.getClass().getName() + ": " + ex.getMessage());
  }
 }

 //根据类,方法名称和参数类型查找方法
  // Attempt to find a {@link Method} on the supplied class with the supplied name
  // and no parameters. Searches all superclasses up to <code>Object</code>.
  // <p>Returns <code>null</code> if no {@link Method} can be found.
  // @param clazz the class to introspect
  // @param name the name of the method
  // @return the Method object, or <code>null</code> if none found
 public static Method findMethod(Class clazz, String name) {
  return findMethod(clazz, name, new Class[0]);
 }

 //根据类,方法名称和参数类型查找方法
 // Attempt to find a {@link Method} on the supplied class with the supplied name
 // and parameter types. Searches all superclasses up to <code>Object</code>.
 // <p>Returns <code>null</code> if no {@link Method} can be found.
 // @param clazz the class to introspect
 // @param name the name of the method
 // @param paramTypes the parameter types of the method
 // (may be <code>null</code> to indicate any signature)
 // @return the Method object, or <code>null</code> if none found
 public static Method findMethod(Class clazz, String name, Class[] paramTypes) {
  Assert.notNull(clazz, "Class must not be null");
  Assert.notNull(name, "Method name must not be null");
  Class searchType = clazz;
  while (!Object.class.equals(searchType) && searchType != null) {
   Method[] methods = (searchType.isInterface() ? searchType.getMethods() : searchType.getDeclaredMethods());
   for (int i = 0; i < methods.length; i++) {
    Method method = methods[i];
    if (name.equals(method.getName()) &&
      (paramTypes == null || Arrays.equals(paramTypes, method.getParameterTypes()))) {
     return method;
    }
   }
   searchType = searchType.getSuperclass();
  }
  return null;
 }
 //调用方法的应用
 // Invoke the specified {@link Method} against the supplied target object
 // with no arguments. The target object can be <code>null</code> when
 // invoking a static {@link Method}.
 // <p>Thrown exceptions are handled via a call to {@link #handleReflectionException}.
 // @param method the method to invoke
 // @param target the target object to invoke the method on
 // @return the invocation result, if any
 // @see #invokeMethod(java.lang.reflect.Method, Object, Object[])
 public static Object invokeMethod(Method method, Object target) {
  return invokeMethod(method, target, null);
 }

 //调用方法的应用
 // Invoke the specified {@link Method} against the supplied target object
 // with the supplied arguments. The target object can be <code>null</code>
 // when invoking a static {@link Method}.
 // <p>Thrown exceptions are handled via a call to {@link #handleReflectionException}.
 // @param method the method to invoke
 // @param target the target object to invoke the method on
 // @param args the invocation arguments (may be <code>null</code>)
 // @return the invocation result, if any
 public static Object invokeMethod(Method method, Object target, Object[] args) {
  try {
   return method.invoke(target, args);
  }
  catch (Exception ex) {
   handleReflectionException(ex);
  }
  throw new IllegalStateException("Should never get here");
 }

// Invoke the specified JDBC API {@link Method} against the supplied
// target object with no arguments.
// @param method the method to invoke
// @param target the target object to invoke the method on
// @return the invocation result, if any
// @throws SQLException the JDBC API SQLException to rethrow (if any)
// @see #invokeJdbcMethod(java.lang.reflect.Method, Object, Object[])
 public static Object invokeJdbcMethod(Method method, Object target) throws SQLException {
  return invokeJdbcMethod(method, target, null);
 }

// Invoke the specified JDBC API {@link Method} against the supplied
// target object with the supplied arguments.
// @param method the method to invoke
// @param target the target object to invoke the method on
// @param args the invocation arguments (may be <code>null</code>)
// @return the invocation result, if any
// @throws SQLException the JDBC API SQLException to rethrow (if any)
// @see #invokeMethod(java.lang.reflect.Method, Object, Object[])
 public static Object invokeJdbcMethod(Method method, Object target, Object[] args) throws SQLException {
  try {
   return method.invoke(target, args);
  }
  catch (IllegalAccessException ex) {
   handleReflectionException(ex);
  }
  catch (InvocationTargetException ex) {
   if (ex.getTargetException() instanceof SQLException) {
    throw (SQLException) ex.getTargetException();
   }
   handleInvocationTargetException(ex);
  }
  throw new IllegalStateException("Should never get here");
 }

//处理异常的方法//使用反射处理异常的将异常信息输出但不抛出方法以外
// Handle the given reflection exception. Should only be called if
// no checked exception is expected to be thrown by the target method.
// <p>Throws the underlying RuntimeException or Error in case of an
// InvocationTargetException with such a root cause. Throws an
// IllegalStateException with an appropriate message else.
// @param ex the reflection exception to handle
 public static void handleReflectionException(Exception ex) {
  if (ex instanceof NoSuchMethodException) {
   throw new IllegalStateException("Method not found: " + ex.getMessage());
  }
  if (ex instanceof IllegalAccessException) {
   throw new IllegalStateException("Could not access method: " + ex.getMessage());
  }
  if (ex instanceof InvocationTargetException) {
   handleInvocationTargetException((InvocationTargetException) ex);
  }
  if (ex instanceof RuntimeException) {
   throw (RuntimeException) ex;
  }
  handleUnexpectedException(ex);
 }

//Handle the given invocation target exception. Should only be called if
//no checked exception is expected to be thrown by the target method.
// <p>Throws the underlying RuntimeException or Error in case of such
// a root cause. Throws an IllegalStateException else.
// @param ex the invocation target exception to handle
 public static void handleInvocationTargetException(InvocationTargetException ex) {
  rethrowRuntimeException(ex.getTargetException());
 }

//Rethrow the given {@link Throwable exception}, which is presumably the
//<em>target exception</em> of an {@link InvocationTargetException}.
//Should only be called if no checked exception is expected to be thrown by
//the target method.
//<p>Rethrows the underlying exception cast to an {@link RuntimeException}
//or {@link Error} if appropriate; otherwise, throws an
//{@link IllegalStateException}.
// @param ex the exception to rethrow
// @throws RuntimeException the rethrown exception
 public static void rethrowRuntimeException(Throwable ex) {
  if (ex instanceof RuntimeException) {
   throw (RuntimeException) ex;
  }
  if (ex instanceof Error) {
   throw (Error) ex;
  }
  handleUnexpectedException(ex);
 }

//Rethrow the given {@link Throwable exception}, which is presumably the
//<em>target exception</em> of an {@link InvocationTargetException}.
//Should only be called if no checked exception is expected to be thrown by
//the target method.
//<p>Rethrows the underlying exception cast to an {@link Exception} or
//{@link Error} if appropriate; otherwise, throws an
//{@link IllegalStateException}.
// @param ex the exception to rethrow
// @throws Exception the rethrown exception (in case of a checked exception)
 public static void rethrowException(Throwable ex) throws Exception {
  if (ex instanceof Exception) {
   throw (Exception) ex;
  }
  if (ex instanceof Error) {
   throw (Error) ex;
  }
  handleUnexpectedException(ex);
 }

//Throws an IllegalStateException with the given exception as root cause.
//@param ex the unexpected exception
 private static void handleUnexpectedException(Throwable ex) {
  // Needs to avoid the chained constructor for JDK 1.4 compatibility.
  IllegalStateException isex = new IllegalStateException("Unexpected exception thrown");
  isex.initCause(ex);
  throw isex;
 }

//Determine whether the given method explicitly declares the given exception
// or one of its superclasses, which means that an exception of that type
//can be propagated as-is within a reflective invocation.
//@param method the declaring method
//@param exceptionType the exception to throw
//@return <code>true</code> if the exception can be thrown as-is;
//<code>false</code> if it needs to be wrapped
 public static boolean declaresException(Method method, Class exceptionType) {
  Assert.notNull(method, "Method must not be null");
  Class[] declaredExceptions = method.getExceptionTypes();
  for (int i = 0; i < declaredExceptions.length; i++) {
   Class declaredException = declaredExceptions[i];
   if (declaredException.isAssignableFrom(exceptionType)) {
    return true;
   }
  }
  return false;
 }

//判断方法,属性字段等的修饰符的方法
//Determine whether the given field is a "public static final" constant.
// @param field the field to check
 public static boolean isPublicStaticFinal(Field field) {
  int modifiers = field.getModifiers();
  return (Modifier.isPublic(modifiers) && Modifier.isStatic(modifiers) && Modifier.isFinal(modifiers));
 }

//Determine whether the given method is an "equals" method.
//@see java.lang.Object#equals
 public static boolean isEqualsMethod(Method method) {
  if (method == null || !method.getName().equals("equals")) {
   return false;
  }
  Class[] paramTypes = method.getParameterTypes();
  return (paramTypes.length == 1 && paramTypes[0] == Object.class);
 }

//Determine whether the given method is a "hashCode" method.
//@see java.lang.Object#hashCode
 public static boolean isHashCodeMethod(Method method) {
  return (method != null && method.getName().equals("hashCode") &&
    method.getParameterTypes().length == 0);
 }

// Determine whether the given method is a "toString" method.
//@see java.lang.Object#toString()
 public static boolean isToStringMethod(Method method) {
  return (method != null && method.getName().equals("toString") &&
    method.getParameterTypes().length == 0);
 }

 //判断一个属性是否可以访问的方法
  //Make the given field accessible, explicitly setting it accessible if necessary.
 //The <code>setAccessible(true)</code> method is only called when actually necessary,
 //to avoid unnecessary conflicts with a JVM SecurityManager (if active).
 //@param field the field to make accessible
 //@see java.lang.reflect.Field#setAccessible
 public static void makeAccessible(Field field) {
  if (!Modifier.isPublic(field.getModifiers()) ||
    !Modifier.isPublic(field.getDeclaringClass().getModifiers())) {
   field.setAccessible(true);
  }
 }

 //判断一个方法是否可以访问的方法
 //Make the given method accessible, explicitly setting it accessible if necessary.
 //The <code>setAccessible(true)</code> method is only called when actually necessary,
 //to avoid unnecessary conflicts with a JVM SecurityManager (if active).
 //@param method the method to make accessible
 //@see java.lang.reflect.Method#setAccessible
 public static void makeAccessible(Method method) {
  if (!Modifier.isPublic(method.getModifiers()) ||
    !Modifier.isPublic(method.getDeclaringClass().getModifiers())) {
   method.setAccessible(true);
  }
 }

 //判断一个构造函数是否可以访问的方法
 //Make the given constructor accessible, explicitly setting it accessible if necessary.
 //The <code>setAccessible(true)</code> method is only called when actually necessary,
 //to avoid unnecessary conflicts with a JVM SecurityManager (if active).
 //@param ctor the constructor to make accessible
 //@see java.lang.reflect.Constructor#setAccessible
 public static void makeAccessible(Constructor ctor) {
  if (!Modifier.isPublic(ctor.getModifiers()) ||
    !Modifier.isPublic(ctor.getDeclaringClass().getModifiers())) {
   ctor.setAccessible(true);
  }
 }

// Perform the given callback operation on all matching methods of the
// given class and superclasses.
// <p>The same named method occurring on subclass and superclass will
// appear twice, unless excluded by a {@link MethodFilter}.
// @param targetClass class to start looking at
// @param mc the callback to invoke for each method
// @see #doWithMethods(Class, MethodCallback, MethodFilter)
 public static void doWithMethods(Class targetClass, MethodCallback mc) throws IllegalArgumentException {
  doWithMethods(targetClass, mc, null);
 }

//Perform the given callback operation on all matching methods of the
//given class and superclasses.
//<p>The same named method occurring on subclass and superclass will
//appear twice, unless excluded by the specified {@link MethodFilter}.
//@param targetClass class to start looking at
// @param mc the callback to invoke for each method
//@param mf the filter that determines the methods to apply the callback to
 public static void doWithMethods(Class targetClass, MethodCallback mc, MethodFilter mf)
   throws IllegalArgumentException {
  // Keep backing up the inheritance hierarchy.
  do {
   Method[] methods = targetClass.getDeclaredMethods();
   for (int i = 0; i < methods.length; i++) {
    if (mf != null && !mf.matches(methods[i])) {
     continue;
    }
    try {
     mc.doWith(methods[i]);
    }
    catch (IllegalAccessException ex) {
     throw new IllegalStateException(
       "Shouldn't be illegal to access method '" + methods[i].getName() + "': " + ex);
    }
   }
   targetClass = targetClass.getSuperclass();
  }
  while (targetClass != null);
 }

//获取类的所有的方法的
//Get all declared methods on the leaf class and all superclasses.
//Leaf class methods are included first.
 public static Method[] getAllDeclaredMethods(Class leafClass) throws IllegalArgumentException {
  final List list = new ArrayList(32);
  doWithMethods(leafClass, new MethodCallback() {
   public void doWith(Method method) {
    list.add(method);
   }
  });
  return (Method[]) list.toArray(new Method[list.size()]);
 }

 //调用字段时执行的回调的方法
 //Invoke the given callback on all fields in the target class,
 //going up the class hierarchy to get all declared fields.
 //@param targetClass the target class to analyze
 //@param fc the callback to invoke for each field
 public static void doWithFields(Class targetClass, FieldCallback fc) throws IllegalArgumentException {
  doWithFields(targetClass, fc, null);
 }

 //调用字段时执行的回调的并过滤的方法
 //Invoke the given callback on all fields in the target class,
 //going up the class hierarchy to get all declared fields.
 //@param targetClass the target class to analyze
 //@param fc the callback to invoke for each field
 //@param ff the filter that determines the fields to apply the callback to
 public static void doWithFields(Class targetClass, FieldCallback fc, FieldFilter ff)
   throws IllegalArgumentException {
  // Keep backing up the inheritance hierarchy.
  do {
   // Copy each field declared on this class unless it's static or file.
   Field[] fields = targetClass.getDeclaredFields();
   for (int i = 0; i < fields.length; i++) {
    // Skip static and final fields.
    if (ff != null && !ff.matches(fields[i])) {
     continue;
    }
    try {
     fc.doWith(fields[i]);
    }
    catch (IllegalAccessException ex) {
     throw new IllegalStateException(
       "Shouldn't be illegal to access field '" + fields[i].getName() + "': " + ex);
    }
   }
   targetClass = targetClass.getSuperclass();
  }
  while (targetClass != null && targetClass != Object.class);
 }

 //用于对象的拷贝的方法(类必须是同一类或子类)
 //Given the source object and the destination, which must be the same class
 //or a subclass, copy all fields, including inherited fields. Designed to
 //work on objects with public no-arg constructors.
 //@throws IllegalArgumentException if the arguments are incompatible
 public static void shallowCopyFieldState(final Object src, final Object dest) throws IllegalArgumentException {
  if (src == null) {
   throw new IllegalArgumentException("Source for field copy cannot be null");
  }
  if (dest == null) {
   throw new IllegalArgumentException("Destination for field copy cannot be null");
  }
  if (!src.getClass().isAssignableFrom(dest.getClass())) {
   throw new IllegalArgumentException("Destination class [" + dest.getClass().getName() +
     "] must be same or subclass as source class [" + src.getClass().getName() + "]");
  }

  doWithFields(src.getClass(), new FieldCallback() {
   public void doWith(Field field) throws IllegalArgumentException, IllegalAccessException {
    makeAccessible(field);
    Object srcValue = field.get(src);
    field.set(dest, srcValue);
   }
  }, COPYABLE_FIELDS);
 }

//Action to take on each method.
 public static interface MethodCallback {
   //Perform an operation using the given method.
   //@param method the method to operate on
  void doWith(Method method) throws IllegalArgumentException, IllegalAccessException;
 }

//Callback optionally used to method fields to be operated on by a method callback.
 public static interface MethodFilter {
   //Determine whether the given method matches.
   //@param method the method to check
  boolean matches(Method method);
 }

//Callback interface invoked on each field in the hierarchy.
 public static interface FieldCallback {
   //Perform an operation using the given field.
   //@param field the field to operate on
  void doWith(Field field) throws IllegalArgumentException, IllegalAccessException;
 }

//Callback optionally used to filter fields to be operated on by a field callback.
 public static interface FieldFilter {
   //Determine whether the given field matches.
   //@param field the field to check
  boolean matches(Field field);
 }

//Pre-built FieldFilter that matches all non-static, non-final fields.
 public static FieldFilter COPYABLE_FIELDS = new FieldFilter() {
  public boolean matches(Field field) {
   return !(Modifier.isStatic(field.getModifiers()) ||
     Modifier.isFinal(field.getModifiers()));
  }
 };
}


使用方法如下:

package cn.anycall.wx.service.impl;

import java.lang.reflect.Method;

import javax.transaction.Transactional;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.ReflectionUtils;

import cn.anycall.wx.dao.BaseDao;
import cn.anycall.wx.dao.ProcDao;
import cn.anycall.wx.kit.SpringContextsUtil;
import cn.anycall.wx.service.TestSerivce;
@Service("testService")
@Transactional
public class TestSerivceImpl implements TestSerivce {

    @Autowired
    private BaseDao baseDao;
    @Autowired
    private ProcDao procDao;

    public String testLock(String mob) throws Exception  {

                Method  mh = ReflectionUtils.findMethod(SpringContextsUtil.getBean("testService").getClass(), "testRelect",new Class[]{String.class} );
                Object obj = ReflectionUtils.invokeMethod(mh,  SpringContextsUtil.getBean("testService"),"13761036955");
                System.out.println("obj="+obj);
                baseDao.updateSQL("insert into test_liu(a,b) values(1,2,2)");
        return mob;
    }
    /**
     * 测试反射
     * @param eee
     * @return
     * @throws Exception
     * @throws Exception
     * @throws Exception
     */
    public String testRelect(String eee) throws Exception{
                baseDao.updateSQL("insert into test_liu(a,b) values(2,2)");
        return "111";
    }
}

其中用到 SpringContextsUtil 方法,这个是需要我们手写的;也有使用 SpringContextHolder 的,效果是一样的


import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
 
/**
 * 获取spring容器,以访问容器中定义的其他bean
 *   lyltiger
 *  MOSTsView 3.0 2009-11-16
 */
public class SpringContextUtil implements ApplicationContextAware{
 
    private static ApplicationContext   applicationContext;
 
    /**
     * 实现ApplicationContextAware接口的回调方法,设置上下文环境
     */
    public void setApplicationContext(ApplicationContext applicationContext){
        SpringContextUtil.applicationContext = applicationContext;
    }
 
    public static ApplicationContext getApplicationContext(){
        return applicationContext;
    }
 
    /**
     * 获取对象
     * @return  Object 一个以所给名字注册的bean的实例 (service注解方式,自动生成以首字母小写的类名为bean name)
     */
    public static Object getBean(String name) throws BeansException{
        return applicationContext.getBean(name);
    }
}

在配置文件中加入:

    <!-- 用于持有ApplicationContext,可以使用 SpringContextUtil .getBean('xxxx')的静态方法得到spring bean对象 -->  
    <bean class="com.xxxxx.SpringContextUtil " lazy-init="false" />  


  • 1
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
你真的了解Ioc与AOP吗? 收藏 你真的了解Ioc与AOP吗?我现在还不是很了解,而且越学习越发现自己了解的很少,Ioc与AOP中蕴涵了大量的能量等待我们去开发。在这个系列 中,我仅仅利用Sping.net这个框架向大家展示一下Ioc与AOP的强大功能(呵呵,其实写这段话的目的是因为“文章题目”牛皮吹得有点大了,给自 己个台阶下罢了)。 在这个系列中一共包含6个案例,从简单到复杂,也是对问题分解、思考和解决的一个过程,它们分别是: (1)类之间的依赖; 降低 (2)接口依赖; (3)基 于配置文件和Reflection的工厂模式; (4)使用Spring.net实现Ioc; (5)Romoting; (6)利用Ioc在不动一行代码的情 况下实现Remoting。为了更好的理解文中的内容,最好顺序阅读。 作为一个应用系统,代码复用至关重要。如果在你的设计中,类与类存在很强的相互关联,那么你会发现在重用这些组件时就存在很严重的问题。在 Step1到Step3-Reflection的例子中,我们试图 利用“针对接口编程”以及自己设计的Ioc对系统进行解耦。在Step3到Step5的例子中,我们将利用Spring.net提供的Ioc框架,轻松完 成解耦以及系统改造等工作。 一、类之间的依赖 我们的第一个例子主要用于说明程序的基本构造,并且作为一个反面典型,引出为什么要解耦,以及如何下手。在这个例子中,我们将创建三个程序集,分别是MainApp.exe、HelloGenerator.dll以及SayHello.dll。它们之间的关系如下图所示: HelloGenerator类根据提供的姓名产生一个问候字符串,代码如下: using System; namespace IocInCSharp { public class EnHelloGenerator { public string GetHelloString(string name) { return String.Format("Hello, {0}", name); } } } SayHello类持有一个对EnHelloGenerator的引用,并负责将生成出来的问候字符串打印出来。 using System; namespace IocInCSharp { public class SayHello { private EnHelloGenerator _helloGen; public EnHelloGenerator HelloGenerator { get { return _helloGen; } set { _helloGen = value; } } public void SayHelloTo(string name) { if(_helloGen != null) Console.WriteLine(_helloGen.GetHelloString(name)); else Console.WriteLine("Client.hello is not initialized"); } } } MainApp.exe负责完成对象的创建、组装以及调用工作: using System; namespace IocInCSharp { public class MainApp { public static void Main() { SayHello sayHello = new SayHello(); sayHello.HelloGenerator = new EnHelloGenerator(); sayHello.SayHelloTo("zhenyulu"); } } } 在这个设计中,组件与组件之间、类与类之间存在着紧密的耦合关系。SayHello类中的_helloGen字段类型为 EnHelloGenerator,这将导致我们很难给它赋予一个其它的HelloGenerator(例如CnHelloGenerator,用于生成 中文问候语)。另外MainApp也严重依赖于SayHello.dll以及HelloGenerator.dll,在程序中我们可以看到类似new SayHello();new EnHelloGenerator();的命令。 这种紧密的耦合关系导致组件的复用性降低。试想,如果想复用SayHello组件,那么我们不得不连同HelloGenerator一同拷贝过去, 因为SayHello.dll是依赖与HelloGenerator.dll的。解决这个问题的办法就是“针对抽象(接口)”编程 (依赖倒置原则)。这里的抽象既包括抽象类也包括接口。我不想过多的去谈抽象类和接口的区别,在后续的例子中我们将使用接口。由于接口在进行“动态代理” 时仍能保持类型信息,而抽象类可能由于代理的原因导致继承关系的“截断”(如MixIn等)。除此之外,对于单继承的C#语言而言,使用接口可以拥有更大 的弹性。 二、接口依赖 既然类之间的依赖导致耦合过于紧密,按照《设计模式》的理论,我们要依赖于接口。但是人们往往发现,仅仅依赖于接口似乎并不能完全解决问题。我们从上面的例子中抽象出接口后,组件间的依赖关系可能变成如下图所示: 经过改造后,SayHello不再依赖于具体的HelloGenerator,而是依赖于IHelloGenerator接口,如此一来,我们可以 动态的将EnHelloGenerator或是CnHelloGenerator赋给SayHello,其打印行为也随之发生改变。接口的定义以及改造后 的SayHello代码如下(为了节省空间,将代码合并书写): using System; namespace IocInCSharp { public interface IHelloGenerator { string GetHelloString(string name); } public interface ISayHello { IHelloGenerator HelloGenerator{ get; set; } void SayHelloTo(string name); } public class SayHello : ISayHello { private IHelloGenerator _helloGen; public IHelloGenerator HelloGenerator { get { return _helloGen; } set { _helloGen = value; } } public void SayHelloTo(string name) { if(_helloGen != null) Console.WriteLine(_helloGen.GetHelloString(name)); else Console.WriteLine("Client.hello is not initialized"); } } } 但是我们的MainApp似乎并没有从接口抽象中得到什么好处,从图中看,MainApp居然依赖于三个组件:ICommon.dll、 HelloGenerator.dll以及SayHello.dll。这是由于MainApp在这里负责整体的“装配”工作。如果这三个组件中的任何一个 发生变化,都将导致MainApp.exe的重新编译和部署。从这个角度来看,似乎“针对接口编程”并没有为我们带来太多的好处。 如果能够将“组件装配”工作抽象出来,我们就可以将MainApp的复杂依赖关系加以简化,从而 进一步实现解耦。为此,我们引入“工厂”模式,并利用配置文件和反射技术,动态加载和装配相关组件。 三、基于配置文件和Reflection的工厂模式 为了消除MainApp对其它组件的依赖性,我们引入工厂模式,并且根据配置文件指定的装配规程,利用.net提供的反射技术完成对象的组装工作。 本部分代码仅仅提供一种功能演示,如果实际应用仍需进一步完善(建议使用一些成型的Ioc框架,例如Spring.net或Castle等)。经过改造后 的系统,组件间依赖关系如下图: 可以看出这次实现了真正的“针对接口编程”。所有的组件只依赖于接口。MainApp所需的对象是由工厂根据配置文件动态创建并组装起来的。当系统 需求发生变化时,只需要修改一下配置文件就可以了。而且MainApp、SayHello和HelloGenerator之间不存在任何的依赖关系,实现 了松耦合。 这是如何实现的呢?我们首先要能够解析配置文件中的信息,然后建立包含相关信息的对象。最后根据这些信息利用反射机制完成对象的创建。首先我们看一下配置文件所包含的内容: 从中我们可以看出,我们实现了一个IocInCSharp.ConfigHandler类,用来处理配置文件中IocInCSharp\ objects结点中的内容。ConfigHandler类将根据该结点下的内容处理并创建一ConfigInfo对象(关于ConfigInfo、 ObjectInfo以及PropertyInfo的代码可自行查看源代码,这里就不再赘述)。ConfigHandler类的代码实现如下: using System; using System.Configuration; using System.Xml; namespace IocInCSharp { public class ConfigHandler:IConfigurationSectionHandler { public object Create(object parent, object configContext, System.Xml.XmlNode section) { ObjectInfo info; PropertyInfo propInfo; ConfigInfo cfgInfo = new ConfigInfo(); foreach(XmlNode node in section.ChildNodes) { info = new ObjectInfo(); info.name = node.Attributes["name"].Value; info.assemblyName = node.Attributes["assembly"].Value; info.typeName = node.Attributes["typeName"].Value; foreach(XmlNode prop in node) { propInfo = new PropertyInfo(); propInfo.propertyName = prop.Attributes["name"].Value; propInfo.assemblyName = prop.Attributes["assembly"].Value; propInfo.typeName = prop.Attributes["typeName"].Value; info.properties.Add(propInfo); } cfgInfo.Objects.Add(info); } return cfgInfo; } } } 通过ConfigHandler的解析,我们最终得到一个ConfigInfo实例,Factory就是根据这个实例中所包含的配置信息,利用反射技术对所需对象生成并组装的。SayHelloFactory的代码如下: using System; using System.IO; using System.Configuration; using System.Reflection; namespace IocInCSharp { public class SayHelloFactory { public static object Create(string name) { Assembly assembly; object o = null; object p; string rootPath = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location) + Path.DirectorySeparatorChar; ConfigInfo cfgInfo = (ConfigInfo)ConfigurationSettings.GetConfig("IocInCSharp/objects"); ObjectInfo info = cfgInfo.FindByName(name); if(info != null) { assembly = Assembly.LoadFile(rootPath + info.assemblyName); o = assembly.CreateInstance(info.typeName); Type t = o.GetType(); for(int i=0; i<info.properties.Count; i++) { PropertyInfo prop = (PropertyInfo)info.properties[i]; assembly = Assembly.LoadFile(rootPath + prop.assemblyName); p = assembly.CreateInstance(prop.typeName); t.InvokeMember(prop.propertyName, BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.SetProperty, null, o, new Object[] {p}); } } return o; } } } 在上面这段代码中,重点注意三条命令的使用方法: assembly = Assembly.LoadFile(rootPath + prop.assemblyName); p = assembly.CreateInstance(prop.typeName); t.InvokeMember(prop.propertyName, BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.SetProperty, null, o, new Object[] {p}); Assembly.LoadFile()用于将外部文件装载进来;assembly.CreateInstance()根据装载进来的程序集创建一指定类型的对象;t.InvokeMember(prop.propertyName, ........BindingFlags.SetProperty, null, o, new Object[] {p})利用反射机制对创建出来的对象设置属性值。 我们的Factory就是利用这种方式根据配置文件动态加载程序集,动态创建对象并设置属性的。有了这个Factory,MainApp中的内容就很简单了: using System; namespace IocInCSharp { public class MainApp { public static void Main() { ISayHello sayHello = (ISayHello)SayHelloFactory.Create("SayHello"); if(sayHello != null) sayHello.SayHelloTo("zhenyulu"); else Console.WriteLine("Got an Error!"); } } } 现在,MainApp只依赖于接口,不再依赖于其它组件,实现了松耦合。在本例子中,大家可以尝试将配置文件中的IocInCSharp.CnHelloGenerator更改为IocInCSharp.EnHelloGenerator,看看是否输出内容由中文变为了英文。这便是“注入”的效果。 从上面这个例子我们可以看出,通过自定义配置文件和.net中的Reflection技术,我们自己就可以开发Ioc应用,根据配置文件的信息自行 组装相应的对象。但是Reflection编程的技术门槛还是比较高的,并且在实际应用中配置文件的格式、Handler的设计都不是象上面代码那样的简 单。不过幸好我们现在有很多的Ioc容器可供选择,它们都提供了完整的依赖注入方式,并且比自己写代码更加成熟、更加稳定。使用这些框架可以让程序员在三 两行代码里完成“注入”工作。在我们下一个案例中,我们将使用Spring.net实现依赖注入。我们会发现仅仅添加几行代码并更改一下配置文件就可轻松 实现依赖注入。 四、使用Spring.net实现依赖注入 Spring在Java界可是响当当的名字,现在也有.net平台下的Spring框架了,那就是Spring.net。用户可以从http://www.springframework.net/下载到Spring.net的最新版本。本例子中使用的版本为“Spring Interim Build August 15, 2005 ”,并对Spring.Services组件中的Remoting部分做了微小调整,删除了代码中用于输出的部分命令。 Spring.net为我们提供了一种基于配置文件的注入方式,目前Spring.net允许将值注入到属性,也允许将一个工厂绑定到属性,工厂的 产品将注入属性;除此之外,Spring.net还允许将一个方法的返回结果绑定到属性;它还可以在绑定之前强制进行初始化。另外Spring.net还 专门针对.net提供了Remoting以及Windows Service的“注入”方式。Spring.AOP允许完成横切(不过目前是调用的是AopAlliance的代码)。 尽管我对基于配置文件的注入方式仍然有些偏见(我认为它很难Debug、难于理解、没有编译时错误校验、编写效率比较低。另外它还存在安全隐患 ,恶意用户可以借助修改配置文件将恶意代码注入系统。因此,Spring.net在Web开发中应当更具优势),但这并不能掩盖Spring.net的光 芒(据说Castle比Spring.net要好,但目前我还没有尝试过使用Castle)。使用Spring.net,我们只需修改两三行代码,并提供 相应配置文件,就可以轻松实现Ioc。应用Spring.net后,我们的系统依赖关系如下图所示:   从图中可以看出,MainApp、SayHello、HelloGenerator之间并不存在任何依赖关系,它们都依赖于抽象出来的接口文件。除 此之外,MainApp还依赖于Spring.net,这使得MainApp可以借助Spring.net实现组件动态创建和组装。 对于原有代码,我们几乎不用作任何调整,唯一需要修改的就是MainApp中的调用方法,代码如下: using System; using System.Configuration; using Spring.Context; namespace IocInCSharp { public class MainApp { public static void Main() { try { IApplicationContext ctx = ConfigurationSettings.GetConfig("spring/context") as IApplicationContext; ISayHello sayHello = (ISayHello)ctx.GetObject("mySayHello"); sayHello.SayHelloTo("zhenyulu"); } catch (Exception e) { Console.WriteLine(e); } } } } 首先我们要添加对Spring.Context命名空间的引用,然后解析配置文件的"spring/context"结点,得到一 IApplicationContext对象(就好比在上一个例子中我们得到的ConfigInfo对象一样),剩下的事情就是向该Context索要相 关的对象了(ISayHello)ctx.GetObject("mySayHello"),其中"mySayHello"由配置文件指定生成方式和注入 方式。 配置文件的内容如下: 注意观察结点,就是由这里定义对象间相互依赖关系的。其中的结点定义了对什么属性执行注入,以及注入的内容是什么()。大家可以尝试将改为,看一看程序执行结果有什么变化来体会Spring.net的Ioc功能。 如果读者读到这里仍然觉得Ioc没有什么的话,那让我们再来看一个更为复杂的例子。在当前例子中,MainApp通过依赖注入调用了 HelloGenerator的功能,但所有的调用都发生在本地。当前程序是一个地地道道的本地应用程序。现在如果要求在不更改一行代码的情况下,将 HelloGenerator.dll放到另外一台计算机上,MainApp通过远程调用(Remoting)来访问HelloGenerator的功 能。这似乎就有一定的难度了。 这么作并不是没有任何依据,其实Ioc除了可以实现依赖注入外,我们还应当看到它可以将我们从复杂的物理架构中解脱出来,专心于业务代码的开发。系 统开发中关键是逻辑分层。在一个系统不需要Remoting时,开发的系统就是本地应用程序;当需要Remoting时,不用修改任何代码就可以将系统移 植为分布式系统。Ioc使这一切成为可能。Rod Johnson在他的《J2EE without EJB》一书中有着详细的论述,很值得一读。据说该书的中文译本今年九月份出版(呵呵,就是这个月,不过我还没有看到市面上有卖的)。 为了能够更深入的分析在“Remoting”改造过程中我们可能遇到的麻烦,在后续两部分内容中,我们将分别介绍不使用Ioc的Remoting改造以及使用Ioc的改造,并比较两者之间的区别。 五、使用Remoting对原有系统进行改造 如果使用Remoting技术对HelloGenerator进行改造,使其具有分布式远程访问能力,那么在不使用Ioc技术的情况下,我们将会作出如下调整: (1)让HelloGenerator继承自MarshalByRefObject类 如果要让某个对象具有分布式的功能,必须使其继承自MarshalByRefObject,这样才可以具有远程访问的能力。因此我们需要调整EnHelloGenerator和CnHelloGenerator的代码。这里以EnHelloGenerator为例: using System; namespace IocInCSharp { public class EnHelloGenerator : MarshalByRefObject, IHelloGenerator { public string GetHelloString(string name) { return String.Format("Hello, {0}", name); } } } (2)将修改后的HelloGenerator发布出去 在这一步中,我们创建了一个新的Console应用程序RemotingServer,并在其中注册了一个Channel,发布服务并进行监听。代码如下: using System; using System.Configuration; using System.Collections; using System.Runtime.Remoting; using System.Runtime.Remoting.Channels; using System.Runtime.Remoting.Channels.Http; using System.Runtime.Serialization.Formatters; namespace IocInCSharp { public class Server { public static void Main() { int port = Convert.ToInt32(ConfigurationSettings.AppSettings["LocalServerPort"]); try { BinaryServerFormatterSinkProvider serverProvider = new BinaryServerFormatterSinkProvider(); BinaryClientFormatterSinkProvider clientProvider = new BinaryClientFormatterSinkProvider(); serverProvider.TypeFilterLevel = TypeFilterLevel.Full; IDictionary props = new Hashtable(); props["port"] = port; props["timeout"] = 2000; HttpChannel channel = new HttpChannel(props, clientProvider, serverProvider); ChannelServices.RegisterChannel(channel); RemotingConfiguration.RegisterWellKnownServiceType( typeof(EnHelloGenerator), "HelloGenerator.soap", WellKnownObjectMode.Singleton); Console.WriteLine("Server started!\r\nPress ENTER key to stop the server..."); Console.ReadLine(); } catch { Console.WriteLine("Server Start Error!"); } } } } (3)全新的客户端调用代码 为了使得客户端MainApp能够调用到远程的对象,我们需要修改它的代码,注册相应的Channel并进行远程访问。修改后的MainApp代码如下: using System; using System.Configuration; using System.Collections; using System.Runtime.Remoting; using System.Runtime.Remoting.Channels; using System.Runtime.Remoting.Channels.Http; using System.Runtime.Serialization.Formatters; namespace IocInCSharp { public class MainApp { public static void Main() { //建立连接 BinaryServerFormatterSinkProvider serverProvider = new BinaryServerFormatterSinkProvider(); BinaryClientFormatterSinkProvider clientProvider = new BinaryClientFormatterSinkProvider(); serverProvider.TypeFilterLevel = TypeFilterLevel.Full; IDictionary props = new Hashtable(); props["port"] = System.Convert.ToInt32(ConfigurationSettings.AppSettings["ClientPort"]); HttpChannel channel = new HttpChannel(props, clientProvider, serverProvider); ChannelServices.RegisterChannel(channel ); //创建远程对象 ISayHello sayHello = new SayHello(); string RemoteServerUrl = ConfigurationSettings.AppSettings["RemoteServerUrl"]; sayHello.HelloGenerator = (IHelloGenerator)Activator.GetObject(typeof(IHelloGenerator), RemoteServerUrl); sayHello.SayHelloTo("zhenyulu"); } } } 在这段代码中,远程对象的创建是通过(IHelloGenerator)Activator.GetObject(typeof(IHelloGenerator), RemoteServerUrl)实现的。到此为止,我们就完成了对原有系统的Remoting改造。 经过调整后的系统,其组件间相互依赖关系如下图所示: 注意ICommon.dll文件在Client和Server端都有。 在整个调整过程中,我们修改了Server端的EnHelloGenerator以及CnHelloGenerator的代码,Client端的 MainApp也 作了修改,以加入了远程访问机制。那么能不能对原有代码不作任何修改就实现远程访问机制呢?当然可以!不过我们还要请出Sping.net帮助我们实现这 一切。 六、利用Ioc在不修改任何原有代码的情况下实现Remoting 上文我们提到,为了实现对HelloGenerator.dll的分布式调用,我们不得不修改了原有程序的多处代码。那么有没有可能在不动任何原有 代码的情况下,单纯靠添加组件、修改配置文件实现远程访问呢?当然可以。这次我们还是使用Spring.net完成这个工作。 经过调整后的系统组件构成如下图所示: 该方案没有修改“src\Step3”中的任何代码,仅仅通过修改配置文件和添加了若干个组件就实现了远程访问。修改方案如下: (1)使用Proxy模式代理原有HelloGenerator 如果要让某个对象具有分布式的功能,必须使其继承自MarshalByRefObject。但是由于不能修改任何原有代码,所以这次我们只能绕道而 行, 借助Proxy模式代理原有的HelloGenerator。在RemotingServer项目中,我们定义了一个新类 HelloGeneratorProxy继承自MarshalByRefObject,通过委派的方式对原有的HelloGenerator进行调用,代 码如下: using System; namespace IocInCSharp { public class HelloGeneratorProxy : MarshalByRefObject, IHelloGenerator { private IHelloGenerator _helloGen; public IHelloGenerator HelloGenerator { get { return _helloGen; } set { _helloGen = value; } } public string GetHelloString(string name) { if(_helloGen != null) return _helloGen.GetHelloString(name); return null; } } } 仔细观察,我们会发现HelloGeneratorProxy持有一个对IHelloGenerator的引用,该属性是可以Set的,因此我们可 以借助Ioc的威力,通过调整Sping.net的配置文件动态决定远程服务器究竟发布EnHelloGenerator还是 CnHelloGenerator。 (2)发布HelloGeneratorProxy 通过RemotingServer.exe,我们将HelloGeneratorProxy发布出去,客户端实际上调用的是Proxy对象(不用担心,由于“针对接口编程”,客户端只知道它是IHelloGenerator类型对象)。服务器端代码如下: using System; using System.Configuration; using System.Collections; using System.Runtime.Remoting; using System.Runtime.Remoting.Channels; using System.Runtime.Remoting.Channels.Http; using System.Runtime.Serialization.Formatters; using Spring.Context; namespace IocInCSharp { public class Server { public static void Main() { int port = Convert.ToInt32(ConfigurationSettings.AppSettings["LocalServerPort"]); try { BinaryServerFormatterSinkProvider serverProvider = new BinaryServerFormatterSinkProvider(); BinaryClientFormatterSinkProvider clientProvider = new BinaryClientFormatterSinkProvider(); serverProvider.TypeFilterLevel = TypeFilterLevel.Full; IDictionary props = new Hashtable(); props["port"] = port; props["timeout"] = 2000; HttpChannel channel = new HttpChannel(props, clientProvider, serverProvider); ChannelServices.RegisterChannel(channel); IApplicationContext ctx = ConfigurationSettings.GetConfig("spring/context") as IApplicationContext; HelloGeneratorProxy proxy = (HelloGeneratorProxy)ctx.GetObject("myHelloGeneratorProxy"); RemotingServices.Marshal(proxy, "HelloGenerator.soap"); Console.WriteLine("Server started!\r\nPress ENTER key to stop the server..."); Console.ReadLine(); } catch { Console.WriteLine("Server Start Error!"); } } } } 注意其中的几条命令: IApplicationContext ctx = ConfigurationSettings.GetConfig("spring/context") as IApplicationContext; HelloGeneratorProxy proxy = (HelloGeneratorProxy)ctx.GetObject("myHelloGeneratorProxy"); RemotingServices.Marshal(proxy, "HelloGenerator.soap"); 我们使用Ioc向HelloGeneratorProxy注入具体的HelloGenerator对象,并通过 RemotingServices.Marshal(proxy, "HelloGenerator.soap")命令将该实例发布出去。服务器端的配置文件如下: 用户可以尝试将配置文件中更改为,重新启动服务后看看客户端调用结果是什么? (3)客户端实现技术-1 客户端实现起来要麻烦一些。由于不允许修改MainApp中的任何代码,我们必须能够在合适的时机拦截代码运行并创建远程连接,同时确保在Ioc注 入时注入的是远程对象。所有这些工作Sping.net都考虑的很周到。它提供了depends-on属性,允许在执行某一操作前强制执行某段代码。在客 户端的配置文件中,我们可以看到如下的配置选项: ......... 这表示,当我们初始化mySayHello时,要先去调用ForceInit.dll文件中ForceInit类的Init方法。ForceInit是一个新编写的类,其主要目的就是创建并注册一个用于远程通讯的Channel。代码实现如下: using System; using System.Collections; using System.Runtime.Remoting; using System.Runtime.Remoting.Channels; using System.Runtime.Remoting.Channels.Http; using System.Runtime.Serialization.Formatters; namespace IocInCSharp { public class ForceInit { public static void Init() { //建立连接 BinaryServerFormatterSinkProvider serverProvider = new BinaryServerFormatterSinkProvider(); BinaryClientFormatterSinkProvider clientProvider = new BinaryClientFormatterSinkProvider(); serverProvider.TypeFilterLevel = TypeFilterLevel.Full; IDictionary props = new Hashtable(); props["port"] = 8199; props["name"] = "myHttp"; HttpChannel channel = new HttpChannel(props, clientProvider, serverProvider); //获得当前已注册的通道; IChannel[] channels = ChannelServices.RegisteredChannels; //关闭指定名为MyHttp的通道; foreach (IChannel eachChannel in channels) if (eachChannel.ChannelName == "myHttp") ChannelServices.UnregisterChannel(eachChannel); ChannelServices.RegisterChannel(channel); } } } (4)客户端实现技术-2 剩下的工作就是为mySayHello的HelloGenerator注入远程对象。通常情况下我们需要使用 Activator.GetObject方法调用远程对象,不过Spring.net已经将其封装起来,我们只需修改一下配置文件,就可以确保调用到远程 对象。配置文件对应部分如下: ...... IocInCSharp.IHelloGenerator, ICommon http://127.0.0.1:8100/HelloGenerator.soap 借助Spring.Remoting.SaoFactoryObject,我们轻松实现了远程对象访问,不必书写一行代码。(目前SAO在 Spring.net的实现尚不完整,按照Spring.net帮助手册上的做法,通过配置文件只能实现客户端访问远程对象,还做不到服务器端发布远程对 象) (5)使用AOP拦截调用 Sping.net目前已经实现AOP功能,我们可以很容易的对方法进行拦截和调用。需要做的工作就是设计相应的Interceptor,然后修改 配置文件。目前Sping.net使用的AOP功能是AopAlliance的实现,因此代码编写时命名空间引用让人感觉多少有些别扭,不是以Sping 开头。我编写的MethodInterceptor代码如下: using System; using AopAlliance.Intercept; namespace IocInCSharp { class MethodInterceptor : IMethodInterceptor { public object Invoke(IMethodInvocation invocation) { Console.WriteLine("Before Method Call..."); object returnValue = invocation.Proceed(); Console.WriteLine("After Method Call..."); return returnValue; } } } 在方法调用前打印"Before Method Call...",在方法调用后打印"After Method Call..."。剩下的工作就是修改配置文件,将其应用到相应的方法上。配置文件片断如下: ...... MethodAdvice 通过以上操作,我们在没有修改任何原有代码的情况下,让原有系统实现了远程分布式访问。 请大家访问示例代码的“bin\Step5"目录,下面有3个子目录:Server、Client、WithoutRemoting。首先运行 Server目录下的RemotingServer.exe,然后运行Client目录下的MainApp.exe进行远程调用。系统通过 Remoting完成远程调用。关闭所有程序后,进入到WithoutRemoting目录,里面有个Readme.txt文件,按照操作步骤将文件: ..\Server\HelloGenerator.dll ..\Client\MainApp.exe ..\Client\ICommon.dll ..\Client\SayHello.dll ..\Client\Spring.Core.dll ..\Client\log4net.dll   拷贝到该目录,再次运行MainApp.exe,你会发现它是一个地地道道的本地应用程序!本地与远程唯一的区别就是配置文件的不同以及增加了几个其它的DLL。这正式我们这个示例的价值体现。 到此为止,我们完成了对Ioc应用的一系列模拟。Ioc写得多一些,AOP写得少了点。欢迎大家批评指正。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值