首页 文章 精选 留言 我的

精选列表

搜索[boot],共8303篇文章
优秀的个人博客,低调大师

Pepper Metrics - Spring/Spring Boot应用性能监控利器

关于项目 Pepper Metrics是我与同事开发的一个开源工具(https://github.com/zrbcool/pepper-metrics),其通过收集jedis/mybatis/httpservlet/dubbo/motan的运行性能统计,并暴露成prometheus等主流时序数据库兼容数据,通过grafana展示趋势。其插件化的架构也非常方便使用者扩展并集成其他开源组件。 请大家给个star,同时欢迎大家成为开发者提交PR一起完善项目。 Architecture Pepper Metrics项目从核心上来说,基于Tom Wilkie的RED理论,即对每个服务(这里的服务特指进程中的某种调用,比如调用一次数据库查询)进行RED指标收集,包括: Rate (请求速率一般指QPS) Errors (错误数或单位时间窗口内的错误率) Duration (请求消耗的时间一般以PXX的百分位时间表示,比如P99=100ms代表百分之九十九的请求耗时在X毫秒内) 上面简述了Pepper Metrics项目的核心思想及方法论依据,而从技术上来说,Pepper Metrics项目构建了一套完整的可插拔插件体系,使应用可以基于选用的组件(如RPC通信框架dubbo,motan、ORM对象模型关系映射框架mybatis、标准的HTTP Servlet组件、Redis操作库jedis、等)选择现有的插件扩展直接具备上述指标的: 收集 打印(基于标准格式设计并基于slf4j定时输出于日志) 输出(针对多种数据库,默认以prometheus实现,将指标输出到prometheus中) 可视化(基于grafana开发的dashboard,默认以prometheus作为数据源) Concept Architecture 各个组件说明 Profiler, 核心部分,用于启动定期调度任务,并通过ExtensionLoad加载所有的ScheduledRun扩展,按照指定周期发起调度。同时内部维护Stats的构造器Profiler.Builder Scheduler, 虚拟概念,在Profiler作为一个定时任务存在 ExtensionLoader, 非常重要的组件,通过Java SPI机制加载插件,使项目的各个模块可以灵活插拔,也是项目架构的基石 ScheduledRun, 扩展点:pepper metrics core会定时调度,传递所有的Stats,实现插件可以使用Stats当中收集到的性能数据,目前已实现的为scheduled printer组件 MeterRegistryFactory,扩展点:基于不同的micrometer的Registry实现抽象并屏蔽各个数据库的差异 Pepper Metrics X, 具体的集成,我们的目标是度量一切,目前计划实现的为:jedis,motan,dubbo,servlet,mybatis等最常用组件 写在最后 项目WIKI https://github.com/zrbcool/pepper-metrics/wiki 快速入门 https://github.com/zrbcool/pepper-metrics/wiki/ZH-Quick-Start 快速DEMO(十分钟本地启动全套模拟应用+grafana+prometheus+内置dashboard) https://github.com/zrbcool/pepper-metrics-demo 在线DEMO http://pepper-metrics.zrbcool.top

优秀的个人博客,低调大师

spring boot 使用 web Socket 的两种方式

方法一 使用 @ServerEndpoint 注解方式 添加 ServerEndpointExporter 到 IOC 容器中 ServerEndpointExporter serverEndpointExporter() {return new ServerEndpointExporter();} 使用@ServerEndpoint package cn.qqhxj; import com.alibaba.fastjson.JSONObject; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; import javax.websocket.OnClose; import javax.websocket.OnOpen; import javax.websocket.Session; import javax.websocket.server.ServerEndpoint; import java.io.IOException; import java.util.concurrent.CopyOnWriteArraySet; /** * @author han xinjian * @date 2018-11-20 17:29 **/ @Component @Slf4j @ServerEndpoint(value = "/socket") public class SocketHandler { private static CopyOnWriteArraySet<SocketHandler> webSocketSet = new CopyOnWriteArraySet<SocketHandler>(); private Session session; @OnClose public void onClose() { webSocketSet.remove(this); //从set中删除 } @OnOpen public void onOpen(Session session) { this.session = session; webSocketSet.add(this); //加入set中 log.info("欢迎id={}你的加入成功" , session.getId()); } public void sendMessage(String string) throws IOException { this.session.getBasicRemote().sendText(JSONObject.toJSONString(string)); //this.session.getAsyncRemote().sendText(message); } public static <T> void sendMessageToAll(String str) throws IOException { for (SocketHandler item : webSocketSet) { try { item.sendMessage(str); } catch (IOException e) { continue; } } } } 方法二 继承TextWebSocketHandler 配置 @Configuration class WebSocketConfig implements WebSocketMessageBrokerConfigurer { @Override public void registerStompEndpoints(StompEndpointRegistry registry) { registry.addEndpoint("/api/v1/socket") //添加允许跨域访问 .setAllowedOrigins("*") .withSockJS(); } @Override public void configureMessageBroker(MessageBrokerRegistry registry) { //推送消息前缀 registry.enableSimpleBroker("/api/v1/socket/send"); //应用请求前缀 registry.setApplicationDestinationPrefixes("/api/v1/socket/req"); //推送用户前缀 registry.setUserDestinationPrefix("/user"); } } 2. ### 继承`TextWebSocketHandler` package cn.qqhxj.wsn.modules.wsn.socket; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; import org.springframework.web.socket.CloseStatus; import org.springframework.web.socket.TextMessage; import org.springframework.web.socket.WebSocketSession; import org.springframework.web.socket.handler.TextWebSocketHandler; import java.util.concurrent.CopyOnWriteArraySet; /** * @author han xinjian * @date 2018-12-26 13:06 **/ @Slf4j @Component public class BaseWebSocketHandler extends TextWebSocketHandler { private static CopyOnWriteArraySet<WebSocketSession> socketSessions = new CopyOnWriteArraySet<>(); @Override public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception { socketSessions.add(session); } @Override public void afterConnectionEstablished(WebSocketSession session) throws Exception { socketSessions.remove(session); } @Override protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception { System.out.println(message.getPayload()); } }

优秀的个人博客,低调大师

spring boot多数据源、读写分离( AOP动态)

项目地址 springboot读写分离 应用场景 读写分离,多数据源,主从库 具体实现 1、配置文件application.yml ##多数据源 datasource: #主库 master: jdbcUrl:jdbc:mysql://localhost:3306/pa_yqs_game?useUnicode=true&characterEncoding=utf-8 username:root password:root driver-class-name:com.mysql.jdbc.Driver #从库 slave: #并非url而是jdbcUrl(因为这个在获取数据源时一直报错,看了DataSource的属性才知道是jdbcUrl) jdbcUrl:jdbc:mysql://localhost:3306/data_count?useUnicode=true&characterEncoding=utf-8 username:root password:root driver-class-name:com.mysql.jdbc.Driver ##mybatis mybatis: mapper-locations:classpath:mapper/*.xml type-aliases-package:com.example.*.domain.pojo check-config-location:true config-location:classpath:mybatis-config.xml 2、Application启动类 /** ***在最外侧,即包含所有子包 *@ClassNameApplication *@Description入口,启动类 *@authorlide *@date2018年2月9日下午2:47:04 */ @SpringBootApplication(exclude={ DataSourceAutoConfiguration.class }) @EnableTransactionManagement(order=2) //设置事务执行顺序(需要在切换数据源之后,否则只走默认库) @MapperScan(basePackages="com.example.*.domain.mapper") publicclassApplication{ publicstaticvoidmain(String[]args){ SpringApplication.run(Application.class,args); } } 1. 需要设置事务(本质也是AOP)执行的顺序,否则事务的执行顺序高于后续的AOP,会导致动态切换数据源失效 2. exclude = {DataSourceAutoConfiguration.class}用于禁用掉默认的数据源获取方式,默认会读取配置文件的据源(spring.datasource.*) 3. MapperScan()指向的是mapper 3、代码实现 3.1 DataSourceType数据源枚举 publicenumDataSourceType{ //主表 Master("master"), //从表 Slave("slave"); privateStringname; privateDataSourceType(Stringname){ this.name=name; } publicStringgetName(){ returnname; } publicvoidsetName(Stringname){ this.name=name; } } 3.2 数据源上下文JdbcContextHolder(ThreadLocal) publicclassJdbcContextHolder{ privatefinalstaticThreadLocallocal=newThreadLocal<>(); publicstaticvoidputDataSource(Stringname){ local.set(name); } publicstaticStringgetDataSource(){ returnlocal.get(); } } 3.3 AbstractRoutingDataSource实现类DynamicDataSource(关键) AbstractRoutingDataSource抽象类知识,实现AOP动态切换的关键 AbstractRoutingDataSource中determineTargetDataSource()方法中获取数据源 Object lookupKey = determineCurrentLookupKey(); DataSource dataSource = this.resolvedDataSources.get(lookupKey); 根据determineCurrentLookupKey()得到Datasource,并且此方法是抽象方法,应用可以实现 2.resolvedDataSources的值根据targetDataSources所得 afterPropertiesSet()方法中(在@Bean所在方法执行完成后,会调用此方法): Map.Entry 3.然后在xml中使用 4.利用自定义注解,AOP拦截动态的设置ThreadLocal的值 5.在DAO层与数据库建立连接时会根据ThreadLocal的key得到数据源 代码:getConnection() determineTargetDataSource().getConnection();(determineTargetDataSource返回的是DataSource) publicclassDynamicDataSourceextendsAbstractRoutingDataSource{ privateLoggerlogger=LoggerFactory.getLogger(this.getClass()); @Override protectedObjectdetermineCurrentLookupKey(){ logger.info("数据源为{}",JdbcContextHolder.getDataSource()); returnJdbcContextHolder.getDataSource(); } } 3.4 AOP切换数据源 DataSourceAspect @Aspect @Order(1) //设置AOP执行顺序(需要在事务之前,否则事务只发生在默认库中) @Component publicclassDataSourceAspect{ privateLoggerlogger=LoggerFactory.getLogger(this.getClass()); //切点 @Pointcut("execution(*com.example.*.service..*.*(..)))") publicvoidaspect(){} @Before("aspect()") privatevoidbefore(JoinPointpoint){ Objecttarget=point.getTarget(); Stringmethod=point.getSignature().getName(); Classclassz=target.getClass(); Class[]parameterTypes=((MethodSignature)point.getSignature()) .getMethod().getParameterTypes(); try{ Methodm=classz.getMethod(method,parameterTypes); if(m!=null&&m.isAnnotationPresent(MyDataSource.class)){ MyDataSourcedata=m.getAnnotation(MyDataSource.class); JdbcContextHolder.putDataSource(data.value().getName()); logger.info("===============上下文赋值完成:{}",data.value().getName()); } }catch(Exceptione){ e.printStackTrace(); } } } 3.5 自定义注解MyDataSource @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public@interfaceMyDataSource{ DataSourceTypevalue()defaultDataSourceType.Master; //默认主表 } 3.6 数据源配置 注意@Primary标注多数据源,否则会产生实现类冲突。 @Primary和@Qualifier这两个注解的意思: @Primary: 意思是在众多相同的bean中,优先使用用@Primary注解的bean. @Qualifier : 这个注解则指定某个bean有没有资格进行注入。 @Configuration publicclassDataSourceConfig{ @Bean(name="master") @ConfigurationProperties(prefix="datasource.master") publicDataSourcedataSource1(){ System.out.println("主配"); returnDataSourceBuilder.create().build(); } @Bean(name="slave") @ConfigurationProperties(prefix="datasource.slave") publicDataSourcedataSource2(){ System.out.println("从配"); returnDataSourceBuilder.create().build(); } @Bean(name="dynamicDataSource") @Primary //优先使用,多数据源 publicDataSourcedataSource(){ DynamicDataSourcedynamicDataSource=newDynamicDataSource(); DataSourcemaster=dataSource1(); DataSourceslave=dataSource2(); //设置默认数据源 dynamicDataSource.setDefaultTargetDataSource(master); //配置多数据源 Mapmap=newHashMap<>(); map.put(DataSourceType.Master.getName(),master); //key需要跟ThreadLocal中的值对应 map.put(DataSourceType.Slave.getName(),slave); dynamicDataSource.setTargetDataSources(map); returndynamicDataSource; } }

优秀的个人博客,低调大师

《Netkiller Spring Cloud 手札》Spring boot 2.0 mongoTemplate 操作范例

本文节选自 《Netkiller Spring Cloud 手札》 Netkiller Spring Cloud 手札 Spring Cloud Cookbook Mr.NeoChan,陈景峯(BG7NYT) 中国广东省深圳市望海路半岛城邦三期 518067 +8613113668890 <netkiller@msn.com> $Id: book.xml 606 2013-05-29 09:52:58Z netkiller $ 版权 © 2015-2018 Neo Chan 版权声明 转载请与作者联系,转载时请务必标明文章原始出处和作者信息及本声明。 http://www.netkiller.cn http://netkiller.github.io http://netkiller.sourceforge.net 微信订阅号 netkiller-ebook (微信扫描二维码) QQ:13721218 请注明“读者” QQ群:128659835 请注明“读者” 2017-11 我的系列文档 编程语言 Netkiller Architect 手札 Netkiller Developer 手札 Netkiller Java 手札 Netkiller Spring 手札 Netkiller PHP 手札 Netkiller Python 手札 Netkiller Testing 手札 Netkiller Cryptography 手札 Netkiller Perl 手札 Netkiller Docbook 手札 Netkiller Project 手札 Netkiller Database 手札 5.2.4.mongoTemplate 导入与模板相关的包 import org.springframework.data.mongodb.core.MongoTemplate; import org.springframework.data.mongodb.core.query.Criteria; import org.springframework.data.mongodb.core.query.Query; import org.springframework.data.mongodb.core.query.Update; 注入 MongoTemplate 对象 @Autowired private MongoTemplate mongoTemplate; 5.2.4.1.Save 保存 User user = new User(); user.setName("Netkiller"); mongoTemplate.save(user, "user"); 更新数据 user = mongoTemplate.findOne(Query.query(Criteria.where("name").is("Jam")), User.class); user.setName("Neo"); mongoTemplate.save(user, "user"); 5.2.4.2.Insert User user = new User(); user.setName("Neo"); mongoTemplate.insert(user, "user"); BSONObject personBsonObj = BasicDBObjectBuilder.start() .add("name","Neo Chen") .add("age",27) .add("address",null).get(); mongoTemplate.insert(personBsonObj,"personCollection"); document in the db: db.personCollection.findOne().pretty(); {"age":21,"name":"John Doe";"address":null}* 5.2.4.3.更新第一条 Query query = new Query(); query.addCriteria(Criteria.where("name").is("Neo")); Update update = new Update(); update.set("name", "Netkiller"); mongoTemplate.updateFirst(query, update, User.class); 5.2.4.4.更新所有数据 Query query = new Query(); query.addCriteria(Criteria.where("name").is("Neo")); Update update = new Update(); update.set("name", "Jerry"); mongoTemplate.updateMulti(query, update, User.class); 5.2.4.5.查找并保存 Query query = new Query(); query.addCriteria(Criteria.where("name").is("Luck")); Update update = new Update(); update.set("name", "Lisa"); User user = mongoTemplate.findAndModify(query, update, User.class); 5.2.4.6.upsert Query query = new Query(); query.addCriteria(Criteria.where("name").is("Green")); Update update = new Update(); update.set("name", "Tom"); mongoTemplate.upsert(query, update, User.class); 5.2.4.7.删除 User user = new User(); user.setId("5bbf091efd9557069c4a25c5") mongoTemplate.remove(user, "user"); 5.2.4.8.查找一条数据 public Person findOneByName(String name) { Query query = new Query(); query.addCriteria(Criteria.where("name").is(name)); return mongoTemplate.findOne(query, Person.class); } 5.2.4.9.查找所有数据 public List<Person> findByName(String name) { Query query = new Query(); query.addCriteria(Criteria.where("name").is(name)); return mongoTemplate.find(query, Person.class); } 5.2.4.10.Query 5.2.4.10.1.翻页 public List<Person> getAllPersonPaginated(int pageNumber, int pageSize) { Query query = new Query(); query.skip(pageNumber * pageSize); query.limit(pageSize); return mongoTemplate.find(query, Person.class); } 5.2.4.10.2.between 实现一个区间条件 new Criteria("createdDate").gte(beginDate).lte(endDate) public boolean AccountDeposit(Date beginDate, Date endDate) { MatchOperation matchOperation = match(new Criteria("createdDate").gte(beginDate).lte(endDate)); GroupOperation groupOperation = group("loginname").sum("amount").as("amount"); SortOperation sortOperation = sort(new Sort(Direction.ASC, "loginname")); Aggregation aggregation = newAggregation(matchOperation, groupOperation, sortOperation); AggregationResults<AccountSettlementDetails> results = mongoTemplate.aggregate(aggregation, AccountSettlementDetails.class, AccountSettlementDetails.class); if (results.getMappedResults() != null) { log.info(results.getRawResults().get("result").toString()); for (AccountSettlementDetails settlementDetails : results.getMappedResults()) { log.info("{}", settlementDetails.toString()); } } return true; } 5.2.4.11.Criteria 5.2.4.11.1.is Query query = new Query(); query.addCriteria(Criteria.where("name").is("Neo")); List<User> users = mongoTemplate.find(query, User.class); 5.2.4.11.2.Regex 正则表达式搜索 查询以N开头的名字 Query query = new Query(); query.addCriteria(Criteria.where("name").regex("^N")); List<User> users = mongoTemplate.find(query,User.class); 查询以o结尾的名字 Query query = new Query(); query.addCriteria(Criteria.where("name").regex("o$")); List<User> users = mongoTemplate.find(query, User.class); 5.2.4.11.3.lt 和 gt 查询年龄小于 < 30 并 > 20 的用户 Query query = new Query(); query.addCriteria(Criteria.where("age").lt(30).gt(20)); List<User> users = mongoTemplate.find(query,User.class); 查找日期范围 Date start = DateUtil.convertStringToDateTime("2014-02-10 20:38:44"); Date end = DateUtil.convertStringToDateTime("2014-02-10 20:38:50"); Query query = new Query(); Criteria criteria = Criteria.where("delflag").is(false); criteria.and("modifyDate").gte(start).lte(end); query.addCriteria(criteria); query.limit(10); 5.2.4.11.4. <programlisting> <![CDATA[ Query query = new Query(); query.addCriteria( new Criteria().andOperator( Criteria.where("field1").exists(true), Criteria.where("field1").ne(false) ) ); List<Foo> result = mongoTemplate.find(query, Foo.class); System.out.println("query - " + query.toString()); for (Foo foo : result) { System.out.println("result - " + foo); } 5.2.4.11.5.包含 public List<Person> findByFavoriteBooks(String favoriteBook) { Query query = new Query(); query.addCriteria(Criteria.where("favoriteBooks").in(favoriteBook)); return mongoTemplate.find(query, Person.class); } 5.2.4.12.Update 5.2.4.12.1.set Update update = new Update(); update.set("name", "Netkiller"); 5.2.4.12.2.追加数据 Query query = Query.query(Criteria.where("id").is("5bbf091efd9557069c4a25c5")); Update update = new Update().push("author", new Author("neo", "chen")); mongoTemplate.updateFirst(query, update, Article.class); 5.2.4.12.3.更新数据 Query query = Query.query(Criteria.where("classId").is("1").and("Students.studentId").is("1")); Update update = Update.update("Students.$.name", "lisa"); mongoTemplate.upsert(query, update, "class"); 5.2.4.12.4.删除数据 Query query = Query.query(Criteria.where("classId").is("1").and("Students.studentId").is("3")); Update update = new Update(); update.unset("Students.$"); mongoTemplate.updateFirst(query, update, "class"); 5.2.4.12.5.inc public void updateMultiplePersonAge() { Query query = new Query(); Update update = new Update().inc("age", 1); mongoTemplate.findAndModify(query, update, Person.class);; } 5.2.4.12.6.update.addToSet Query query = Query.query(Criteria.where("classId").is("1")); Student student = new Student("1", "lisa", 3, "girl"); Update update = new Update(); update.addToSet("Students", student); mongoTemplate.upsert(query, update, "class"); 5.2.4.13.Sort 按照年龄排序 Query query = new Query(); query.with(new Sort(Sort.Direction.ASC, "age")); List<User> users = mongoTemplate.find(query,User.class); 5.2.4.14.Query + PageRequest final Pageable pageableRequest = new PageRequest(0, 2); Query query = new Query(); query.with(pageableRequest); 5.2.4.15.newAggregation MultilevelDirectSellingAccountRewardsSettlementDetails multilevelDirectSellingAccountRewardsSettlementDetails = new MultilevelDirectSellingAccountRewardsSettlementDetails(); multilevelDirectSellingAccountRewardsSettlementDetails.setLoginname("111"); multilevelDirectSellingAccountRewardsSettlementDetails.setPhone("111"); multilevelDirectSellingAccountRewardsSettlementDetails.setRecommenderLoginname("111"); multilevelDirectSellingAccountRewardsSettlementDetails.setRecommenderPhone("111"); multilevelDirectSellingAccountRewardsSettlementDetails.setRecommenderName("Neo"); multilevelDirectSellingAccountRewardsSettlementDetails.setRecommenderType("客户"); multilevelDirectSellingAccountRewardsSettlementDetails.setAmount(5.02); multilevelDirectSellingAccountRewardsSettlementDetails.setCreatedDate(new Date()); multilevelDirectSellingAccountRewardsSettlementDetailsRepository.save(multilevelDirectSellingAccountRewardsSettlementDetails); Date beginDate = this.getToday("00:00:00"); Date endDate = this.getToday("23:59:59"); log.info(beginDate.toString() + " ~ " + endDate.toString()); GroupOperation groupOperation = group("loginname").sum("amount").as("amount"); MatchOperation matchOperation = match(new Criteria("createdDate").gte(beginDate).lte(endDate)); SortOperation sortOperation = sort(new Sort(Direction.ASC, "loginname")); Aggregation aggregation = newAggregation(matchOperation, groupOperation, sortOperation); AggregationResults<MultilevelDirectSellingAccountRewardsSettlementDetails> results = mongoTemplate.aggregate(aggregation, MultilevelDirectSellingAccountRewardsSettlementDetails.class, MultilevelDirectSellingAccountRewardsSettlementDetails.class); System.out.println(results.getRawResults().get("result").toString()); 5.2.4.16.创建索引 mongoOps.indexOps(User.class).ensureIndex(new Index().on("name", Direction.ASC)); 5.2.4.17.子对象操作 5.2.4.17.1.List 类型 package cn.netkiller.api.domain; import java.util.List; import javax.persistence.Id; import org.springframework.data.mongodb.core.mapping.Document; @Document public class Article { @Id private String id; private String title; private String description; List<Author> author; public static class Author { private String id; private String firstname; private String lastname; public Author(String firstname, String lastname) { this.firstname = firstname; this.lastname = lastname; } } } 更新 db.getCollection('foo').update({"author.firstname":"neo"},{"$set":{"author.$.firstname":"netkiller"}}) 更新数据 Query query = Query.query(Criteria.where("author.firstname").is("neo")); Update update = new Update().set("author.$.firstname", "netkiller"); mongoTemplate.updateFirst(query, update, Article.class); 追加数据 Query query = Query.query(Criteria.where("id").is("5bbf091efd9557069c4a25c5")); Update update = new Update().push("author", new Author("neo", "chen")); mongoTemplate.updateFirst(query, update, Article.class); 删除数据 Query query = Query.query(Criteria.where("id").is("5bbf091efd9557069c4a25c5")); Update update = new Update().pull("author", new Author("jerry", "lee")); mongoTemplate.updateFirst(query, update, Article.class);

资源下载

更多资源
优质分享App

优质分享App

近一个月的开发和优化,本站点的第一个app全新上线。该app采用极致压缩,本体才4.36MB。系统里面做了大量数据访问、缓存优化。方便用户在手机上查看文章。后续会推出HarmonyOS的适配版本。

Nacos

Nacos

Nacos /nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service 的首字母简称,一个易于构建 AI Agent 应用的动态服务发现、配置管理和AI智能体管理平台。Nacos 致力于帮助您发现、配置和管理微服务及AI智能体应用。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据、流量管理。Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。

Spring

Spring

Spring框架(Spring Framework)是由Rod Johnson于2002年提出的开源Java企业级应用框架,旨在通过使用JavaBean替代传统EJB实现方式降低企业级编程开发的复杂性。该框架基于简单性、可测试性和松耦合性设计理念,提供核心容器、应用上下文、数据访问集成等模块,支持整合Hibernate、Struts等第三方框架,其适用范围不仅限于服务器端开发,绝大多数Java应用均可从中受益。

Sublime Text

Sublime Text

Sublime Text具有漂亮的用户界面和强大的功能,例如代码缩略图,Python的插件,代码段等。还可自定义键绑定,菜单和工具栏。Sublime Text 的主要功能包括:拼写检查,书签,完整的 Python API , Goto 功能,即时项目切换,多选择,多窗口等等。Sublime Text 是一个跨平台的编辑器,同时支持Windows、Linux、Mac OS X等操作系统。

用户登录
用户注册