• 1. 依赖注入;(掌握)
  • 2. XML自动注入;(掌握)
  • 3. 全注解配置;(掌握)
  • 4. 代理模式;(掌握,难点)

依赖注入

构造参数注入

constructor-arg:构造器注入: index:顺序   name:行参的名称   type:类型    ref:关联另一个bean     id关联;

内部定义一个bean      value:值(普通属性的值-String,Integer,Long,...)

方案一:根据构造器参数的顺序(索引)

<!-- 按照索引注入,索引开始为0 -->

<bean id="MyBean" class="cn.itsource._01_.MyBean">

<constructor-arg index="0" value="666" />

<constructor-arg index="1" value="张二娃" />

</bean>

方案二:根据构造器参数的名称

<!-- 按照名称注入,名称必须一致 -->

<bean id="MyBean" class="cn.itsource._01_.MyBean">

<constructor-arg name="id" value="1" />

<constructor-arg name="name" value="张三娃" />

</bean>

方案三:根据构造器的类型注入

<!-- 按照类型注入,必须一一对应,不能有重复的类型-->

<bean id="MyBean" class="cn.itsource._01_.MyBean">

<constructor-arg type="java.lang.Long" value="1" />

<constructor-arg type="java.lang.String" value="张四娃" />

</bean>

如果有一个参数是我们自己的一个对象,怎么解决?

方案一:先在外面定义好

<bean id="otherBean" class="cn.itsource._01_.OtherBean"/>

<bean id="MyBean" class="cn.itsource._01_.MyBean">

<constructor-arg  value="1" />

<constructor-arg  value="张五娃" />

<constructor-arg ref="otherBean"/>

</bean>

方案二: 使用一个内部的Bean完成(不需要加id)

<bean id="MyBean" class="cn.itsource._01_.MyBean">

<constructor-arg  value="1" />

<constructor-arg  value="张六娃" />

<constructor-arg>

<bean class="cn.itsource._01_.OtherBean"/>

</constructor-arg>

</bean>

.其它简单、集合属性注入

// 简单属性

private Long id;

private String name;

private Boolean sex;

private BigDecimal salary;

// 对象属性

private List<String> list;

private List<OtherBean> otherBeanList;

private Set<String> set;

private Set<OtherBean> otherBeanSet;

private Map<String,Object> map;

//下面这个是重点

private Properties props1;

private Properties props2;

private String[] arrays;

数组(两种方案-掌握): 简写

<property name="arrays" value="A,B,C" />

完整写法

<property name="arrays">

<array>

<value>xxx</value>

<value>yyy</value>

<value>zzz</value>

</array>

</property>

List<String>(了解)

<property name="list">

<list>

<value>xxx</value>

<value>aaa</value>

<value>bbbb</value>

</list>

</property>

Set<String>(了解)

<property name="set">

<set>

<value>xxx</value>

<value>aaa</value>

<value>bbbb</value>

</set>

</property>

1.1. List<OtherBean>(了解)

<property name="otherBeanList">

<list>

<bean class="cn.itsource._01_.OtherBean" />

<bean class="cn.itsource._01_.OtherBean" />

<ref bean="otherBean" />

<ref bean="otherBean" />

</list>

</property>

1.1. Set<OtherBean>(了解)

<property name="otherBeanSet">

<set>

<bean class="cn.itsource._01_.OtherBean" />

<bean class="cn.itsource._01_.OtherBean" />

<ref bean="otherBean" />

<ref bean="otherBean" />

</set>

</property>

1.1. Map(了解)

<property name="map">

<map>

<entry key="key1" value-ref="otherBean"></entry>

<entry key="key2" value="1234"></entry>

</map>

</property>

怎么配置一个Properties对象:

方案1  不支持中文

<property name="props1">

<value>

<!-- <value>

url jdbc:mysql:///xxx

username  xxxx

</value> -->

Jpa.dialect=org.Jpa.dialect.HSQLDialect

Jpa.driverClassName=com.mysql.jdbc.Driver

</value>

</property>

方案二:支持中文

<property name="props2">

<props>

<prop key="Jpa.dialect">org.Jpa.dialect.HSQLDialect</prop>

<prop key="Jpa.driverClassName">com.mysql.jdbc.Driver中文 </prop>

</props>

</property>

#xx.xml

{

  <bean id="person" class="x.xx.xx.Person">

    #此处必须与属性相同

    <property name="properties">

      #使用props进行属性描述

      <props>

        <prop key="name">zhangsan</prop>

        <prop key="age">16</prop>

        <prop key="sex">male</prop>

      <props>

    </property >

  </bean>

  ...

}

XML自动注入

使用XML自动注入,可以简化我们XML的配置

<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:schemaLocation="http://www.springframework.org/schema/beans

     http://www.springframework.org/schema/beans/spring-beans.xsd" default-autowire="byType">

<!--

beans标签(全局):

default-autowire:自动注入

byType : 字段类型必需和bean中的某一个类型相同

如果用了byType,不允许出来两个类型相同的bean

byName :  属性必需和bean的某一个id相同

bean标签(局部)

autowire(单独设置),有单独设置使用单独的,没有单独设置使用全局的

-->

<bean id="userDao" class="cn.itsource._04_autowire.UserDao"></bean>

<bean id="userServices" class="cn.itsource._04_autowire.UserService">

</bean>

<bean id="userServicexxx" class="cn.itsource._04_autowire.UserService">

</bean>

<bean id="userAction" class="cn.itsource._04_autowire.UserAction" autowire="byName">

</bean>

</beans>

1.1.1. byName : 按照属性的名:bean里提供定义一个属性,提供 setXxx方法

default-autowire="byName"

配置文件的bean的id必须和代码bean里的属性一致。

1.1.2. byType   按照注入对象的类型,要求:类型只能配置一个实例

default-autowire="byType"

setXxx方法   注入类的类型(Class)

和配置文件里面的类型进行比较

配置文件里面的类型只能是1个

根节点beans   default-autowire="byName" 对当前配置文件的所有bean都生效

子节点bean autowire="byType"只对当前bean生效

全注解配置

配置context命名空间

<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context"

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:schemaLocation="http://www.springframework.org/schema/beans

http://www.springframework.org/schema/beans/spring-beans.xsd

http://www.springframework.org/schema/context

http://www.springframework.org/schema/context/spring-context.xsd">

第一步:配置让Spring扫描类与支持注解

<!--

@Component  普通仓库

@Repository  仓库(其实就是dao层)

@Service service层

@Controller  控制层(servlet、action)

-->

<!-- 进行包的扫描,去看类上面是否有相应的标签配置 -->

<context:component-scan base-package="cn.itsource._03_anno" />

<!-- 这个不是必须的(spring3.2版本前使用) 配上后兼容性好 -->

<context:annotation-config />

1.1. 调用名称两套方案:

1.1.1. 方案一:使用@Autowired

@Service

public class UserService {

@Autowired

@Qualifier("userJdbcDao")

private IUserDao userDao;

public void save(){

userDao.save();

}

}

方案二:使用@Resource

@Service

public class UserService {

@Resource(name="userJpaDao")

private IUserDao userDao;

public void save(){

userDao.save();

}

}

AOP概述

AOP的使用只存在于一些特定的场合(具有横切逻辑的应用场合),横切逻辑这个解释可能比较抽象,咱们说得再具体一点,AOP可以用于事务管理,日志管理,性能监测等地方。

连接点(Joinpoint):程序执行的某一个特定位置,如类初始前后,方法的运行前后。而Spring只支持方法的连接点

切点(Pointcut):切点可以定位到相应的连接点,一个切点可以定位多个连接点。

增强(Advice):又被称为通知,完成逻辑的增强。

目标对象(Target):增强逻辑织入的目标类。

引介(Introduction):特殊的增强,为类添加一些属性和方法。

织入(Weaving): 将增强添加到目标类的具体连接点上的过程。Spring使用动态代理织入

代理(Proxy):一个类(原类)被织入增强(逻辑)后,就产生一个结果类,称为代理类。

切面(Aspect):由切点和增强组成

AOP联盟:众多开源AOP项目的联合组织,该组织定义了一套规范描述AOP标准。现在大部分的AOP实现都是使用AOP实现的标准。

1.1. Spring实现AOP的方案

Spring实现Aop有两种方案:JDKCGLIB

若目标对象实现了若干接口

spring使用JDK的java.lang.reflect.Proxy类代理。

. 若目标对象没有实现任何接口,

spring使用CGLIB库生成目标对象的子类。

1.对接口创建代理优于对类创建代理,因为会产生更加松耦合的系统。

对类代理是让遗留系统或无法实现接口的第三方类库同样可以得到通知,

这种方案应该是备用方案。

2.标记为final的方法不能够被通知。spring是为目标类产生子类。任何需要

被通知的方法都被复写,将通知织入。final方法是不允许重写的。

1.1.1. 添加aop命名空间

xmlns:aop="http://www.springframework.org/schema/aop"

http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd

<aop:config>

<aop:pointcut expression="execution(* cn.itsource.aop.I*Service.*(..))" id="pointcut" />

<aop:aspect  ref="txManager">

<!-- 前置通知 -->

<aop:before method="begin" pointcut-ref="pointcut"/>

<!-- 后置通知 -->

<aop:after-returning method="commit" pointcut-ref="pointcut"/>

<!-- 异常通知 -->

<aop:after-throwing method="rollback" pointcut-ref="pointcut"/>

<!-- 最终通知 -->

<aop:after method="close" pointcut-ref="pointcut" />

</aop:aspect>

</aop:config>

环绕增强(前面的4个增强就不用了)

<!--

环绕通知:有了环绕,就不需要用另外几个通知(会重复)

如果有两个以上的通知,建议使用环绕

-->

<aop:around method="around" pointcut-ref="pointcut" />

环绕方法

import org.aspectj.lang.ProceedingJoinPoint;

public Object around(ProceedingJoinPoint joinPoint){

//System.out.println(joinPoint.getTarget()); //调用的类

//System.out.println(Arrays.asList(joinPoint.getArgs()));//参递的参数

//System.out.println(joinPoint.getSignature()); //方法签名

Object object = null;

try {

begin();

object = joinPoint.proceed(); //执行相应的代码

commit();

} catch (Throwable e) {

rollback(e);

}finally{

close();

}

return object;

}

1.1. 注解版

1.1.1. 配置文件

<!-- 组件搜索 -->

<context:component-scan base-package="cn.itsource.aopanno" />

<!-- 支持aop注解 -->

<aop:aspectj-autoproxy />

1.1.1. 事务管理器

import org.aspectj.lang.ProceedingJoinPoint;

import org.aspectj.lang.annotation.After;

import org.aspectj.lang.annotation.AfterReturning;

import org.aspectj.lang.annotation.AfterThrowing;

import org.aspectj.lang.annotation.Aspect;

import org.aspectj.lang.annotation.Before;

import org.aspectj.lang.annotation.Pointcut;

import org.springframework.stereotype.Component;

@Component

@Aspect //AOP的类注解

public class TxManager {

//设置切点

@Pointcut("execution(* cn.itsource.aopanno.I*Service.*(..))")

public void pointcut(){}

//前置通知

@Before("pointcut()")

public void begin(){

System.out.println("开启事务....");

}

//后置通知

@AfterReturning("pointcut()")

public void commit(){

System.out.println("提交事务...");

}

//异常通知

@AfterThrowing(pointcut="pointcut()",throwing="e")

public void rollback(Throwable e){

System.out.println("回滚事务....");

System.out.println(e.getMessage());

}

//最终通知

@After("pointcut()")

public void close(){

System.out.println("关闭资源....");

}

}

//温馨提醒:如果要使用环绕通知的话,把其它几个通知的注解去掉(不然会出现重复)

@Around("pointcut()")

public Object around(ProceedingJoinPoint joinPoint){

Object object = null;

try {

begin();

object = joinPoint.proceed(); //执行相应的代码

commit();

} catch (Throwable e) {

rollback(e);

}finally{

close();

}

return object;

}

AOP是面向切面编程,是对咱们OOP的一个补充,Spring的Aop允许咱们在方法的前后加上相应的功能增强。

Spring的Aop是使用代理模式完成的,如果是有接口的类,使用JDK代理模式,如果没有接口的类,使用CGLIB代理模式。

咱们自己使用AOP太麻烦,Spring让我们通过简单的配置即可完成AOP。

第一种配置:XML    第二种配置:注解

配置的时候注意找到何时(方法前后,异常),何地(哪些类的哪些方法),做什么(安全中是添加事务)

1.1. 代理模式定义

代理模式的英文叫做Proxy或Surrogate

① 抽象主题角色:

声明了真实主题和代理主题的共同接口,这样一来在任何可以使用真实主题的地方都可以是使用代理主题。

② 代理主题(Proxy)角色:

代理主题角色内部含有对真实主题的引用,从而可以在任何时候操作真实主题对象;

代理主题角色提供一个与真实主题角色相同的接口,以便可以在任何时候都可以替代真实主题控制对真实主题的引用,负责在需要的时候创建真实主题对象(和删除真实主题对象);

代理角色通常在将客户端调用传递给真实的主题之前或之后(前置增强/通知,后置增强/通知),都要执行某个操作,而不是单纯地将调用传递给真实主题对象。

③ 真实主题角色:

定义了代理角色所代表地真实对象

 保存的对象User:
public class User {
private Long id;
private String name;
// getter,setter略
}
模拟一个假的事务管理器TxManager:
public class TxManager {
public void begin(){
System.out.println("开启事务....");
}
public void commit(){
System.out.println("提交事务...");
}
public void rollback(){
System.out.println("回滚事务....");
}
public void close(){
System.out.println("关闭资源....");
}
} 7.3..抽象主题角色
public interface IEmployeeService {
void save(User user);
} 7.3..真实主题角色
public class EmployeeServiceImpl implements IEmployeeService {
@Override
public void save(User user) {
System.out.println("保存用户...");
}
} 7.3..代理主题角色
public class EmployeeServiceProxyImpl implements IEmployeeService {
//声明一个变量(代表这个类所代理的真实主题角色)
/*
* 留一个思考题:这里为什么使用接口声明,而不直接使用类?
*/
private IEmployeeService employeeService;
//在构造器中传入真实主题角色(必需要有真实主题角色,代理类才有意义)
public EmployeeServiceProxyImpl(IEmployeeService employeeService) {
this.employeeService = employeeService;
}
/**
* 在真实主题角色执行代码的前后增加额外的事务代码
*/
@Override
public void save(User user) {
TxManager txManager = new TxManager();
try {
txManager.begin();
employeeService.save(user);
txManager.commit();
} catch (Exception e) {
txManager.rollback();
e.printStackTrace();
}finally{
txManager.close();
}
}
} 7.3..调用方
@Test
public void testProxy() throws Exception {
User user = new User();
user.setName("张三");
//创建真实主题对象
IEmployeeService employeeService = new EmployeeServiceImpl();
//创建代理主题对象
employeeService = new EmployeeServiceProxyImpl(employeeService);
//调用方法
employeeService.save(user);
}

1.1.1. JDK动态代理

注:JDK动态代理只能代理有接口的类

①.准备条件

保留静态代理的结构(将代理类删除掉)

下面是完成JDK代理的主要类:

java.lang.reflect.Proxy (可以到jdk文档中找到这个类)

java.lang.reflect.InvocationHandler:代理调用处理程序的接口

 创建JDKProxy类:
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* InvocationHandler:是代理实例的调用处理程序 实现的接口。
*/
public class JdkProxy implements InvocationHandler{
//定义真实主题角色:目标对象
private Object targetObject;
//传入事务管理器
private TxManager txManager;
public JdkProxy(Object targetObject,TxManager txManager) {
this.targetObject = targetObject;
this.txManager = txManager;
} /**
* proxy:经过jdk的代理对象(基本上没有作用)
* method:实际执行的方法
* args:方法中的参数
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result = null; //返回的结果
try {
txManager.begin();
result = method.invoke(targetObject, args); //执行直接对象的方法
txManager.commit();
} catch (Exception e) {
txManager.rollback();
e.printStackTrace();
}finally{
txManager.close();
}
return result;
} /**
* 创建一个代理对象
* @return
*/
public Object createProxy(){
return Proxy.newProxyInstance(
this.getClass().getClassLoader(), //类加载器,只要拿到一个即可
targetObject.getClass().getInterfaces() , //实现类的接口集合(因为一个类可以实现多个接口)
this //代理实例的调用处理程序(InvocationHandler的实现类)
);
}
} 测试功能:
@Test
public void testProxy() throws Exception {
User user = new User();
//真实主题角色对象
IEmployeeService employeeService = new EmployeeServiceImpl();
//事务管理器
TxManager txManager = new TxManager();
//创建咱们自定义的一个处理代理功能类(这个类中我们加了一个方法可以直接创建代理对象)
JdkProxy jdkProxy = new JdkProxy(employeeService,txManager);
//获取代理对象
IEmployeeService proxy = (IEmployeeService)jdkProxy.createProxy();
proxy.save(user);
}

1.1.1. CGLIB动态代理

Cglib类似于javassist-3.18.1-GA.jar功能字节码增强,

原来Hibernate3.2之前就是使用cglib来进行字节码增强

①.下面是完成CGLIB的类:

org.springframework.cglib.proxy.Enhancer; 增强器

org.springframework.cglib.proxy.MethodInterceptor; 方法切面(代理实例处理方法功能的接口)

org.springframework.cglib.proxy.MethodProxy;

 CGLIBProxy类代码:
import java.lang.reflect.Method;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy; public class CglibProxy implements MethodInterceptor{ //定义参数,接收真实的目标对象
private Object targetObject;
//事务对象
private TxManager txManager; public CglibProxy(Object targetObject,TxManager txManager) {
this.targetObject = targetObject;
this.txManager = txManager;
} /**
* proxyObject:CGLIB代理后的对象,一般不用
* method:真实对象的方法
* args:方法的参数
* methodProxy:CGLIB代理后的方法,一般不用
*/
@Override
public Object intercept(Object proxyObject, Method method, Object[] args,MethodProxy methodProxy) throws Throwable {
Object result = null; //返回的结果
try {
txManager.begin();
result = method.invoke(targetObject, args); //执行直接对象的方法
txManager.commit();
} catch (Exception e) {
txManager.rollback();
e.printStackTrace();
}finally{
txManager.close();
}
return result;
} /**
* 创建一个代理对象
* @return
*/
public Object createProxy(){
//创建增强器
Enhancer enhancer = new Enhancer();
//创建的代理就是咱们真实目标对象的子类
enhancer.setSuperclass(targetObject.getClass());
//MethodInterceptor就是一个Callback回调
enhancer.setCallback(this);
//创建一个代理对象并返回
return enhancer.create();
}
} 测试代码
@Test
public void testProxy() throws Exception {
User user = new User();
//真实主题角色对象
EmployeeServiceImpl employeeService = new EmployeeServiceImpl();
//事务管理器
TxManager txManager = new TxManager();
//创建Cglib代理对象
CglibProxy cglibProxy = new CglibProxy(employeeService, txManager);
//拿到代理对象
EmployeeServiceImpl proxy = (EmployeeServiceImpl)cglibProxy.createProxy();
proxy.save(user);
}
  1. 常见异常

只需要一个类型的Bean,但是出现了2个

org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type [cn.itsource._03_autowire.UserDao] is defined: expected single matching bean but found 2: userDao,userJdbcDao

Spring增强代理模式的更多相关文章

  1. Spring AOP /代理模式/事务管理/读写分离/多数据源管理

    参考文章: http://www.cnblogs.com/MOBIN/p/5597215.html http://www.cnblogs.com/fenglie/articles/4097759.ht ...

  2. Java进阶知识20 Spring的代理模式

    本文知识点(目录): 1.概念  2.代理模式      2.1.静态代理      2.2.动态代理      2.3.Cglib子类代理 1.概念 1.工厂模式  2. 单例模式 代理(Proxy ...

  3. Spring AOP代理模式

    代理模式 代理模式是一种设计模式,提供了对目标对象的另外的访问方式.即通过代理访问目标对象. 好处:可以再目标对象实现的基础上,增加额外的功能的操作.扩展目标对象的功能,而不改变现有的功能逻辑. 1. ...

  4. spring的代理模式

    静态代理: 首先定义一个接口,随便写一个方法 定义2个实现接口的方法 (被代理的对象) (代理对象) 需要将接口 定义get set 方法 代理增强的方法 然后实现 输出结果如下: 动态代理(jdk动 ...

  5. Spring的代理模式(静态,JDK,CGLIB)

    一.静态代理   1.定义业务接口 public interface Subject { void doSomeThing(); }   2.真实业务类实现接口 public class RealSu ...

  6. Spring中常见的设计模式——代理模式

    一.代理模式的应用场景 生活中的中介,黄牛,等一系列帮助甲方做事的行为,都是代理模式的体现.代理模式(Proxy Pattern)是指为题对象提供一种代理,以控制对这个对象的访问.代理对象在客户端和目 ...

  7. &lt;四&gt;读&lt;&lt;大话设计模式&gt;&gt;之代理模式

    代理模式我想大家即便不熟悉也都听过吧,从字面意思上看就是替别人干活的,比方代理商.在项目的实际应用中也有非常多地方用到.比方spring通过代理模式生成对象等. 代理模式的书面定义:为其它对象提供一种 ...

  8. Spring代理模式及AOP基本术语

    一.代理模式: 静态代理.动态代理 动态代理和静态代理区别?? 解析:静态代理需要手工编写代理类,代理类引用被代理对象. 动态代理是在内存中构建的,不需要手动编写代理类 代理的目的:是为了在原有的方法 ...

  9. Spring 代理模式及AOP基本术语

    一.代理模式: 静态代理.动态代理 动态代理和静态代理区别?? 解析:静态代理需要手工编写代理类,代理类引用被代理对象. 动态代理是在内存中构建的,不需要手动编写代理类 代理的目的:是为了在原有的方法 ...

随机推荐

  1. 会话技术、Cookie技术与Session技术

    一.会话技术  1. 存储客户端状态 会话技术是帮助服务器记住客户端状态(区分客户端)的.  2. 会话技术 从打开一个浏览器访问某个站点,到关闭这个浏览器的整个过程,称为一次会话.会话技术就是记录这 ...

  2. [CF662C] Binary Table(FWT)

    题意: https://www.cnblogs.com/cjyyb/p/9065801.html 题解:

  3. python全栈开发day102-django rest-framework框架

    1.频次访问组件 1) 手写版本 # class VisitThrottle(BaseThrottle): # # def __init__(self): # self.history = None ...

  4. Linux安装Tomcat-Nginx-FastDFS-Redis-Solr-集群——【第九集-补充-之安装mariadb】

    由于也是第一次安装,再此不必献丑了,贴上参考链接: 1,指导我为什么使用mariadb而不是用mysql:https://blog.csdn.net/liumiaocn/article/details ...

  5. django——视图层

    1. 视图函数 一个视图函数,简称视图,是一个简单的Python 函数,它接受Web请求并且返回Web响应.响应可以是一张网页的HTML内容,一个重定向,一个404错误,一个XML文档,或者一张图片. ...

  6. 明天开始学习 Hibernate

    明天开始学 Hibernate ,想简单的了解一下 Hibernate,也借此机会了解一下 ORM 计划用 5 天的时间看完,包括整理笔记 加油吧,少年~~

  7. css 制作圆角、圆形图形布局

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  8. react-native获取设备信息组件(react-native-device-info)

    转载链接:http://www.ncloud.hk/%E6%8A%80%E6%9C%AF%E5%88%86%E4%BA%AB/react-native-acquisition-device-infor ...

  9. python的array初识

    from array import array """ 'b' signed char int 1 'B' unsigned char int 1 'u' Py_UNIC ...

  10. php 识别二维码(转载)

    近段需要写一个通过PHP来识别二维码的功能,在网上查了很久才解决问题.以此来记录下来解决问题的方法. 最开始找的方法是一个叫 php-zbarcode 的扩展,自己照着网上的安装步骤安装了 Image ...