恒美微站
首页
关于我们
建站服务
主题模板
案例展示
资讯中心
联系我们
Seasar DI容器实战:从依赖注入到AOP与数据库访问的完整教程
首页
资讯中心
/
Seasar DI容器实战:从依赖注入到AOP与数据库访问的完整教程
Seasar DI容器实战:从依赖注入到AOP与数据库访问的完整教程
发布时间:2026/10/15 1:40:01
简介Seasar教程资料面向具备一定Java Web基础的开发者定位是快速上手 Seasar2——一款在日本企业中使用广泛的轻量级Java框架可视为Struts、Spring与Hibernate的简化整合方案。压缩包共92个文件约8.14MB其中包含32个jar依赖库、20个dicon依赖注入配置文件、10个java源文件与6个sql脚本另有class、properties、html、xml等辅助资源基本覆盖框架学习与运行所需。已有431人学习下载。包内以完整的示例项目为主线带有工程配置文件、属性资源及示例页面可在IDE中直接对照调试从HTML/JSP页面到Java类、再到数据库操作的典型调用链以及dicon配置文件的写法、项目目录结构与配套库的组织方式都适合逐层拆解。读者可借助此包快速搭建一个Seasar2JSF风格的演示环境理解其核心的依赖注入与AOP思想适合想拓展框架视野、了解海外企业技术栈的Java学习者。1. 为什么还在用 Seasar一套被低估的 Java DI 容器教程资源提到 Java 开发框架大多数人第一时间想到 Spring。但如果你接手过遗留系统或者翻过一些海外软件项目的技术栈大概率会撞见另一个名字——Seasar。我这次拆的这份 seasar 教程资源不是把官方文档翻译一遍而是直接从一个可运行的示例项目入手把 DI 容器的启动、组件注入、AOP 切面和数据库访问完整串起来。Seasar 的容器比 Spring 更轻它的 dicon 配置文件在调试依赖问题时非常直观。适合两类人一类是维护老系统的工程师另一类是刚学 Spring 但想搞懂“容器到底怎么把对象连起来”的新手这份资源刚好可以把黑匣子打开给你看。2. Seasar 的核心机制从容器初始化到依赖注入的完整链路Seasar 的容器叫 S2Container它解决的还是那个老问题对象由谁创建、对象之间的依赖怎么连、什么时候销毁。和 Spring 相比它的核心设计全部围绕一个“注册表”展开。你用 dicon 文件或者注解把组件告诉容器容器在启动时解析这些描述然后通过构造器或字段把依赖填进去。理解这一层后面所有配置都是顺理成章的事。2.1 从 dicon 文件到对象图Seasar 容器启动时做了什么常见做法是在一个 XML 格式的 .dicon 文件里声明组件。下面这段是我在教程资源里经常看到的入门配置把它放到资源包的 src/main/resources 下命名为 app.dicon?xml version1.0 encodingUTF-8? !DOCTYPE components PUBLIC -//SEASAR//DTD S2Container 2.4//EN http://www.seasar.org/dtd/components.dtd components namespaceapp component nameuserDao classcom.example.UserDaoImpl/ component nameuserService classcom.example.UserServiceImpl arg namedao componentNameuserDao/ /component /components这段配置干了这么几件事先注册一个叫 userDao 的组件实现类是 UserDaoImpl再注册 userService并且在创建实例时把名为 userDao 的组件作为构造器参数传进去。注意arg的 componentName 属性它指向的是容器里另一个组件的名字不是 class 名。如果你写成arg namedao而构造器参数名不是 dao容器会在启动时报依赖不匹配。我在第一次跑的时候就在这里卡过后面避坑章节会细说。初始化容器的方式非常简单。在 Java 入口里写import org.seasar.framework.container.S2Container; import org.seasar.framework.container.factory.SingletonS2ContainerFactory; public class Boot { public static void main(String[] args) { SingletonS2ContainerFactory.setConfigPath(app.dicon); SingletonS2ContainerFactory.init(); S2Container container SingletonS2ContainerFactory.getContainer(); UserService service (UserService) container.getComponent(userService); service.sayHello(); SingletonS2ContainerFactory.destroy(); } }逻辑说明第一步指定配置文件路径第二步初始化第三步获取容器实例第四步按名字取出组件第五步销毁容器。参数说明setConfigPath里只要传类路径下的文件名如果配置文件在子目录里需要带路径比如config/app.dicon。这里我用 SingletonS2ContainerFactory 而不是每次 new S2Container是因为在同一个 JVM 内复用同一个容器实例避免重复加载配置。如果你在写单元测试更推荐每次都新建容器不然上一个测试的组件状态会泄漏到下一个用例里。2.2 注解式注入Component 与 Resource 的使用边界dicon 文件适合管理全局组件但项目里类多起来之后注解的方式更省事。Seasar 支持在类上标Component在字段上标Resource。比如package com.example; import org.seasar.framework.container.annotation.tiger.Component; import javax.annotation.Resource; Component public class UserServiceImpl implements UserService { Resource private UserDao userDao; public String sayHello() { return userDao.queryName(); } }这段代码的关键在于容器启动时会扫描指定包下的类发现Component就自动注册然后扫描字段上的Resource如果字段类型能在容器里找到唯一匹配的组件就注入进去。这里有个使用边界Resource是按字段名字和类型双重匹配的。如果容器里有两个实现 UserDao 接口的组件比如 UserDaoImpl 和 MockUserDaoImpl字段名 userDao 只能和其中一个组件名匹配否则会抛 TooManyBean 异常。而Inject之类的注解在这套框架里要求更严格所以我一般在教程资源里直接告诉学员字段名要和组件名保持一致能省很多麻烦。拿表格对比一下两种方式方式声明位置优点缺点dicon 文件外部 XML配置集中替换实现不需要改代码写多了容易手滑标签大小写敏感注解式类/字段就近查看依赖关系组件多时隐式匹配反而难查这里的内容还属于原理层。真正让 Seasar 和 Spring 拉开差距的是它对“元数据”的态度。Spring 更强调注解与自动配置Seasar 的注解则更像一种显式声明没有复杂的自动装配规则。所以教程资源里给出的示例基本都是先写 dicon 文件再过渡到注解让新手先看到连接的过程。2.3 作用域与生命周期singleton 和 prototype 的实际差异组件注册时如果不指定作用域默认是 singleton也就是容器里只有一个实例所有调用方共享。dicon 文件里可以显式声明component namecounter classcom.example.Counter instanceprototype/instance 属性有两个取值singleton和prototype。singleton 在容器第一次获取组件时创建之后每次 getComponent 都返回同一个对象。prototype 则每次 getComponent 都创建新实例。这个区别在实际业务里直接影响状态管理。比如你做一个请求级的 Service内部持有可变的临时字段如果用了 singleton并发请求之间会互相污染。教程资源里有一个购物车的例子就是因为误用 singleton 导致 A 用户能看到 B 用户的购物车数据改成 prototype 之后才正常。生命周期钩子也是一个容易忽略的配置。在 dicon 中指定component nameconnectionPool classcom.example.ConnectionPool initMethod nameinit/ destroyMethod nameclose/ /component容器初始化时调用 init 方法销毁时调用 close 方法。这里有个很隐蔽的坑如果组件是 prototype 作用域每次新实例不会自动执行 destroy 方法必须由调用方自己管理资源释放。所以我通常建议带资源打开的操作尽量用 singleton 加 destroyMethod避免泄漏连接。3. 把教程资源跑起来环境搭建与第一个 Hello World 的详细步骤拿到手的 tutorial 资源包通常不会直接给你一个 Spring Boot 那样的一键工程。它是传统 Java Web 项目结构你可能需要手动把 lib 下的 jar 放进 classpath。我第一次跑的时候被这一步卡住了二十分钟所以这一章按实际顺序走一遍。3.1 资源包里的目录结构与运行顺序我拆的这份 seasar 教程压缩包解开后大概是这样的seasar-tutorial/ ├── lib/ │ ├── s2container-xxx.jar │ ├── s2framework-xxx.jar │ └── ... ├── src/ │ ├── main/java/com/example/ │ │ ├── UserService.java │ │ ├── UserServiceImpl.java │ │ ├── UserDao.java │ │ └── Boot.java │ └── main/resources/ │ └── app.dicon └── web/ └── WEB-INF/ └── web.xml注意我这里没有写具体版本号因为这类教程资源经常是“集齐一版能跑就行”的旧 jar。老老实实用资源里自带的 lib 目录别去仓库里搜高版本高版本可能改过包名。先把 lib 下的 jar 全部加入项目的 Build Path。如果你用的是 Maven常见做法是把本地 jar 安装进本地仓库mvn install:install-file -Dfilelib/s2container-xxx.jar \ -DgroupIdorg.seasar -DartifactIds2container \ -Dversion2.4 -Dpackagingjar这样 pom.xml 里就可以统一管理依赖了。参数说明-Dfile指向 jar 的位置-DgroupId和-DartifactId是你在代码里引用时用的坐标-Dversion自己定一个稳定的值。装完之后在 pom.xml 里写dependency groupIdorg.seasar/groupId artifactIds2container/artifactId version2.4/version /dependency3.2 用 main 方法启动容器不依赖 Web 容器的最小验证为了快速验证容器本身可以跳过 Tomcat直接从 main 方法启动。建立 Boot.java代码和第 2 章类似但多了一个细节如果 dicon 文件引用了外部 DTD而电脑离线启动会报错。import org.seasar.framework.container.factory.SingletonS2ContainerFactory; public class Boot { public static void main(String[] args) { SingletonS2ContainerFactory .setConfigPath(app.dicon); SingletonS2ContainerFactory.init(); System.out.println(container started); SingletonS2ContainerFactory.destroy(); } }这一步跑通说明类路径没问题。然后再加上业务调用。如果启动时报FileNotFound原因基本是配置文件路径不对。教程资源中 app.dicon 放在 resources 根目录所以 setConfigPath 参数就是 app.dicon。如果放到 config 目录下参数要变成config/app.dicon。接下来我们用一个用户登录的 Demo。UserServiceImpl 里调用 UserDao 查密码然后做校验public class UserServiceImpl implements UserService { private UserDao userDao; public UserServiceImpl(UserDao userDao) { this.userDao userDao; } public String login(String name) { return userDao.findPassword(name); } }注意这里我改用构造器注入和上面的 dicon 文件里arg对应。如果 dicon 里没有配置arg容器会尝试调用无参构造器然后 userDao 就是 null后面调用时必然空指针。所以跑 Demo 前检查两处dicon 的arg是不是写了以及构造器参数名是否和name属性一致。3.3 部署到 Tomcat监听器初始化与 classpath 的坑独立 main 方法验证之后要把 Demo 放到 Web 容器里跑。传统做法是在 web.xml 里配置一个监听器让容器随 Web 应用启动listener listener-class org.seasar.framework.container.servlet.SingletonS2ContainerContextListener /listener-class /listener这个监听器会在应用启动时读取WEB-INF/classes/app.dicon创建全局容器。很多新人在这一步遇到 classpath 问题配置文件明明在 src/main/resources 下部署后却没有出现在 WEB-INF/classes 里。检查你的打包配置确保 resources 目录被包含。如果同时需要暴露 Seasar 组件给 JSP 或 Servlet可以配置filter filter-classorg.seasar.framework.container.filter.S2ContainerFilter/filter-class /filter它的作用是在请求进来时从容器里获取对应的业务组件。这里不需要额外参数但顺序要注意监听器在 filter 之前执行。刚学的时候别写反否则 filter 会拿到一个还没初始化的容器启动直接抛空指针。4. AOP 与数据库模板Seasar 的实践节选与参数说明教程资源除了 DI还通常包含 AOP 和数据库访问。Seasar 的 AOP 是典型的拦截器模型和 Spring AOP 思路类似但配置位置在 dicon 文件里。数据库部分用的是 S2Dao它把 SQL 文件和 Java 接口绑定非常符合“SQL 只写 SQLJava 只写接口”的分层。4.1 切面注册用拦截器实现日志与事务先写一个拦截器实现MethodInterceptor接口package com.example.aspect; import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; public class LogInterceptor implements MethodInterceptor { public Object invoke(MethodInvocation invocation) throws Throwable { long start System.currentTimeMillis(); Object result invocation.proceed(); System.out.println(invocation.getMethod().getName() cost (System.currentTimeMillis() - start)); return result; } }然后在 dicon 文件里给 userService 组件挂上这个拦截器component nameuserService classcom.example.UserServiceImpl arg namedao componentNameuserDao/ aspect namelog componentNamelogInterceptor pointcutlogin|check/ /component逻辑说明invocation.proceed()是链路的核心调用它才会执行被拦截的方法。pointcut指定方法名正则只有匹配的方法才被拦截。参数说明aspect的 name 属性任意起名componentName 指向容器里已经注册的 logInterceptor 组件。如果没定义 pointcut默认拦截所有 public 方法。这里有个陷阱Seasar 的拦截器是基于字节码增强的私有方法不会经过代理所以写了也拦不到。事务切面也是同一个套路。教程资源里一般会提供一个S2TransactionInterceptor把它配置在 service 层组件上方法抛异常时自动回滚。事务切面必须配置在业务层组件的公开方法上如果配置在 private 方法或者从同类内部调用另一个被增强方法事务不会生效。这种同类调用绕过代理的问题在用 Spring 时也存在不是 Seasar 特有的玄学。4.2 S2Dao 的 SQL 即代码协作接口与 SQL 文件约定S2Dao 的用法很特别。你只需要写一个 Dao 接口然后把 SQL 写在同包下的类名同名 SQL 文件中。比如package com.example.dao; public interface UserDao { public String findPassword(String name); }对应的UserDao.sqlselect password from user where name /*name*/dummyS2Dao 会把接口里的方法名findPassword和 SQL 文件里的对应条目绑定。/*name*/dummy是替换参数后面的dummy是给 SQL 编辑器看的假值运行时会被传入的 name 替换。参数说明/*name*/是注释形式中间的 name 必须和方法参数名一致。如果参数名对不上框架会报“parameter not found”。这个方法只支持单参数如果两个参数就得写成/*param.name*/的形式。教程资源里这段代码我会重点标记因为很多人拿 MyBatis 的习惯套以为#{}能用结果报 SQL 错误。再配合实体查询select * from user where age /*age*/0 order by id返回的结果会自动映射为ListUserEntity前提是字段名和实体属性名使用下划线到驼峰的映射。这个约定在资源里有一张映射表总之记住数据库字段是 user_name实体属性就是 userName。如果你不想遵守映射也可以在 SQL 里用别名直接对属性名select id as id, user_name as userName from user但这会破坏统一风格我一般只在字段差异很大的时候用。4.3 事务切面与异常回滚参数与顺序的细节把事务拦截器挂到 service 上的完整片段component nametransactionInterceptor classorg.seasar.framework.aop.interceptors.TransactionInterceptor/ component nameuserService classcom.example.UserServiceImpl arg namedao componentNameuserDao/ aspect nametx componentNametransactionInterceptor/ /component这样 userService 的所有 public 方法都被事务切面包裹。默认情况下只有 RuntimeException 会触发回滚。如果你希望受检异常也回滚需要配置事务拦截器的 rollback 规则。常见做法是继承 TransactionInterceptor 覆写isRollbackThrowable方法。事务切面与日志切面的配置顺序也有讲究。如果日志切面在外层事务切面在内层日志切面会先进入然后进入事务。反过来事务先开始然后记录日志。推荐的顺序是事务在更外层这样日志打印的结果是事务提交后的状态。配置顺序就是aspect出现的顺序踩过坑的人都知道这个顺序改起来比写代码还容易错。5. 常见问题与避坑指南Seasar 教程里没写清楚的四个雷区我每次带人跑这类老框架翻车点很集中。这一章直接把现象、原因、解决写清楚省得到处搜。5.1 坑一dicon 文件放在 resources 目录下仍然报 FileNotFound现象setConfigPath(app.dicon)抛出文件找不到异常但 IDE 里 resources/app.dicon 明明存在。原因IDE 的 classpath 没有包含 src/main/resources或者配置文件没有被编译到 target/classes。旧项目经常出现只有 java 目录被标记为资源目录。解决检查 Build Path 中资源目录是否被排除或者在 pom.xml 的 build 资源里显式加入resources resource directorysrc/main/resources/directory /resource /resources加入之后重新执行mvn clean compile然后到 target/classes 下确认 app.dicon 存在。这个排查步骤看着简单却能挡掉一半的启动问题。5.2 坑二Resource 注入的字段是 null容器却启动正常现象容器正常启动日志没有错误但调用 service 方法时 userDao 为 null空指针。原因Seasar 的注解式扫描需要明确开启。如果你只是把Component写在类上而没有在初始化时告诉容器“去哪个包扫描注解”注解不会生效。这不是注解写错是扫描机制没有被激活。解决在初始化之前加一行ContainerFactory.setAutoRegistration(true);或者更好的是在 S2Container 上设置注册 include 包名。我更推荐的方式是一开始用 dicon 文件显式注册组件先跑通再切成注解式。这样可以把“是不是扫描没开”这个变量去掉。5.3 坑三AOP 切面没有拦截到私有方法现象日志切面只打印了 public 方法私有方法没有输出。原因Seasar 的 AOP 是基于代理的private 方法无法被动态代理覆盖。这不是配置问题是 JVM 机制限制。解决把需要拦截的方法改成 public然后通过接口调用。如果必须拦截私有方法需要把逻辑移到单独组件里用 public 包一层。教程资源里有一个权限校验的例子就是先把检查逻辑抽取成独立的 PermissionChecker再在 service 方法里调用这样切面能生效职责也更清晰。5.4 坑四与 Spring 混合使用时组件被重复初始化现象同时引入 Spring 和 Seasar 时同一个 Service 对象被创建了两次事务边界完全混乱。原因两套容器各自管理自己的组件。如果代码里既有 Spring 的Autowired又有 Seasar 的Resource同一个实例可能被两套代理包装导致一个调用栈里出现两个不同的对象。解决在一个模块里只选一套容器。如果历史项目已经混用那么让 Seasar 作为主容器Spring 的 Bean 通过桥接接口暴露给 Seasar反过来配置很容易死循环。这个坑我通常会建议直接在架构层面统一而不是打补丁。补丁也许能跑过测试但上线后组件初始化顺序一旦变化问题就会重新冒出来。5.5 坑五改 dicon 配置后热部署不生效现象修改了 app.dicon 里的组件名重新编译后容器还是按旧配置初始化。原因SingletonS2ContainerFactory 在同一个 ClassLoader 里只会初始化一次热部署工具可能没有触发新的容器创建。解决开发时把配置改为每次都重新创建容器或者调用SingletonS2ContainerFactory.destroy()后再 init。在 Servlet 容器中需要确保监听器正确触发容器销毁。有些热部署插件会直接替换 class 文件但不会重新执行监听器这时候就需要你手动重启。这个问题很烦但确实不是玄学。从那以后我每次改 dicon 之后第一件事就是检查容器是不是旧实例。6. 进阶自己封装一个 Seasar 风格的注入注解处理器这一章做一个可以亲手敲出来的小实验用反射和注解实现一个微型容器模仿 Seasar 的Component和Resource。这能帮你真正理解教程资源里那些配置背后做的事也方便你自己写工具的时候设计 API。先定义两个注解package com.example.micro; 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 MyComponent {} Target(ElementType.FIELD) Retention(RetentionPolicy.RUNTIME) public interface MyResource {}然后写一个简单的容器类package com.example.micro; import java.lang.reflect.Field; import java.util.HashMap; import java.util.Map; public class MyContainer { private MapString, Object beans new HashMap(); public void register(Class? clazz) throws Exception { if (clazz.isAnnotationPresent(MyComponent.class)) { Object instance clazz.getDeclaredConstructor().newInstance(); beans.put(clazz.getName(), instance); } } public void injectDependencies() throws Exception { for (Object instance : beans.values()) { for (Field field : instance.getClass().getDeclaredFields()) { if (field.isAnnotationPresent(MyResource.class)) { Object dependency beans.get(field.getType().getName()); field.setAccessible(true); field.set(instance, dependency); } } } } public T T getComponent(ClassT clazz) { return (T) beans.get(clazz.getName()); } }逻辑说明第一步先注册所有组件把实例存进 Map第二步遍历每个实例的字段看到MyResource就按字段类型从 Map 里找依赖并注入。这里我刻意把注册和注入拆成两个方法是因为真实容器必须先把所有组件建好再统一解析依赖否则 A 需要 B、B 需要 A 时就会死循环。参数说明beans的 key 使用类全限定名而不是简单名避免不同包下同名类互相覆盖。如果你要更贴近 Seasar可以改成按组件名匹配但那样需要额外维护一个 name 属性。这个微型容器能跑起来之后你可以再给它加上循环依赖检测、prototype 作用域、接口匹配。我那时候就是靠写这个玩具容器才彻底明白 Seasar 的 dicon 文件和 Spring 的 bean 工厂到底在做什么。从那以后我每次接手老项目都先看一眼容器配置再动手改业务代码这个习惯帮我少踩了很多底层坑希望帮到你。本文还有配套的精品资源点击获取