您的位置:首页 > 编程语言 > Java开发

JavaWeb相关框架及必备套路

2016-01-11 09:00 381 查看

一.SpringMVC

1.1:SpringMVC的扩展接口:《SpringMVC源码剖析(一)- 从抽象和接口说起》《盘点springmvc的常用接口》《spring mvc 提供的几个常用的扩展点
1.2:一些Spring MVC的使用技巧
1.3:SpringMVC原理浅析
1.4:一些Spring MVC的使用技巧 (本文作者的其他文章值得一读,如:Spring Boot使用心得自定义HttpMessageConverter)

二.Spring 

2.1:获取spring 的bean 方法总 2.2:点睛Spring4.1系列 (本文作者总结的其他系列文章值得一读,如:SpringMVCSpringBoot等)
2.3:spring的一些常用扩展点(第八篇)参考一 2.4:Spring钩子方法和钩子接口的使用详解

--------------------- 套路分割线 ------------------------

三.Web开发套路和方法论

1.JSR-303参数校验
1.1Hibernate Validation使用示例及讲解
1.2 JAVA中通过Hibernate-Validation进行参数验证
2.HttpServletRequest中参数处理
2.1.request请求中的参数处理
Filter能在request到达servlet的服务方法之前拦截HttpServletRequest对象,而在服务方法完成处理转移控制后又能被filter拦截到HttpServletResponse对象。你可以使用filter来实现特定的任务,比如验证用户输入、过滤请求参数中的特殊字符以及压缩web内容。但是根据Java规范:request.getParameterMap()返回的是一个Map类型的值,该返回值记录着前端(如jsp页面)所提交请求中的请求参数和请求参数值的映射关系。这个返回值有个特别之处——只能读。不像普通的Map类型数据一样可以修改(类似String是一个不能修改的对象,线程安全、提高效率)。这是因为服务器为了实现一定的安全规范,所作的限制。比如WebLogic,Tomcat,Resin,JBoss等服务器均实现了此规范。
如果实在有必要在取得此值以后做修改的话,要新建一个map对象,将返回值复制到此新map对象中进行修改,用新的map对象代替使用之前的返回值。Map readOnlyMap = request.getParameterMap();
Map writeAbleMap = new HashMap();
writeAbleMap.putAll(readOnlyMap);
writeAbleMap.remove()或者put()...
在后续的程序代码中使用writeAbleMap即可
我们可以通过使用装饰模式(继承**Wrapper类并重写getParameterMap等几个函数)来改变其值。
2.1.1. 继承HttpServletRequestWrapper以实现在Filter中修改HttpServletRequest的参数
2.1.2. HttpServletRequestWrapper 用法
2.1.3. 如何修改request的parameter的几种方式
2.1.4. Filter和HttpServletRequest代理类实现Request参数值更改
2.1.5. 如何往HttpServletRequest中塞请求参数
备注:装饰模式HttpServletRequestWrapper
2.2.request请求中的输入流的限制
我们知道,前端post提交的请求数据存储于request的输入流inputstream中,有时候我们需要获取输入流,从输入流中解析出post提交的消息体,但是ServletRequest的getReader()和getInputStream()这两个方法只能被调用一次,而且不能两个都调用。
2.2.1.ServletRequest中getReader()和getInputStream()只能调用一次的解决办法
2.2.2 多次获取request里面的请求参数
总之,通过HttpServletRequest 这个类获得的变量或函数在使用上有许多的限制,其实j2ee规范中也不建议直接去操作HttpServletRequest这个类,而是通过继承已封装过的类HttpServletRequestWrapper来实现自己的业务。

3.利用ThreadLocal变量实现线程内各class的共享
在SpringMVC中如下方法获取request时容易导致request串(线程不安全),因此不建议采用该方法
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
推荐方法:定义一个Filter,在Filter中定义一个全局的线程安全变量ThreadLocal来保存request,之后在用到request的地方
获取到该线程变量ThreadLocal,从ThreadLocal中取出request即可;
总结:为了保证线程安全,在一个请求过程中可以用ThreadLocal设置全局变量,将需要传递的参数塞到该ThreadLocal全局变量中,可参考这篇文章

4.在Filter中针对每一次请求产生一个requestId,以此来跟踪和定位每次请求信息。
long requestId = System.currentTimeMillis();
int randomNumber = (int) (Math.random() * (999999 - 100000) + 100000);
String requestIdString = String.valueOf(requestId) + String.valueOf(randomNumber);
MDC.put(REQUESTID, requestIdString);
httpServletRequest.setAttribute(REQUESTID, requestIdString)5.线程安全
类成员变量不能被在类中的成员方法进行赋值,否则在单例情况下,容易造成多线程不安全;
小结:类成员变量中仅仅放那些只读的或被@AutoWire注解的服务类,如果有些变量必须要用,则改为ThreadLocal或先塞到request中,之后在从request中取出。

6.异常发生时finally释放资源
对于一些资源释放的操作,一般都放在函数的最后处理,此时尤其需要注意的是,在资源释放之前是否会有异常发生从而导致释放代码无法执行。
小结:将资源释放代码放到finally块中,以确保一定执行资源释放。

7.某些场景下,如定时任务中
在一个循环中可以利用try catch来避免因个别错误导致的整个循环卡死
for (UserDto userDto : userDtoList) {
//采用try catch 防止一个用户的失败 导致所有用户的失败
try {
Map<Long, List<RoleDto>> roleDtoMap = getAllRoleDto(userDto);
//处理feature缓存
processUserFeature(userDto, roleDtoMap);
//处理角色缓存
processUserRole(userDto, roleDtoMap);
//顺便更新一下用户的缓存
processUserCache(userDto);
} catch (Exception e) {
logger.error("sync feature error", e);

//继续下一次的循环
continue;
}
}8.分布式锁和Redis击穿
8.1.在分布式服务中,如微服务开发,往往会涉及到多个服务对相同的数据进行写或修改(如同时操作redis内存)操作,此时需要对共享数据进行分布式锁控制,
以防止各服务并发操作而出现数据不一致现象。
分布式锁Redis的一种实现思路,多台服务器共享同一个redis的key,每个服务产生一个唯一的value,当各服务操作共享数据(如redis内存中的数据)时
首先获取key的value,如果是自己的value,则可执行,否则等待。关于Redis击穿,即命中率低,详情可百度查阅。
8.2 共性数据直接缓存到内存
例如,像机构数据,对于公司中的每一个人看到的机构数据在一定时间内都是固定的,类似这样的固定、共性数据可以直接缓存到内存中,提高访问效率。开源操作本地缓存库:Google的guava库cache接口getIfPresent
9.spring中的事务管理以及注意事项
9.1.同一个类中方法嵌套调用时,被调用内部子函数上的事务不会起作用
如下所示,methodA调用同一个类中的methodB时,methodB上的@Transaction事务注解不会起作用。public class tranctionTest {

//@Tranction
public void methondA() {
methondB();
}

@Tranction
public void methondB() {
//业务逻辑实现,对数据库的插入或修改操作;
}
}原因可参考:
spring事务@Transactional在同一个类中的方法调用不生效

关于拦截方法调用其他内部方法无法被拦截问题的解决
在同一个类中,一个方法调用另外一个有注解(比如@Async,@Transational)的方法,注解失效的原因和解决方法

注意:在一个事务(@Tranction)函数内部调用一个异步(@Async)函数且需要往异步函数传参时,需要注意异步函数内部接收到的参数是否完整。
9.2.当有try catch后捕获了异常,异常会被catch吃掉导致spring拿不到异常信息,从而导致事务不会回滚。如果不得不在service层写try catch ,务必需要catch后重新抛出一个运行时异常(unchecked) throw new RuntimeException 以便让事务回滚或者加上rollbackFor参数,形如:@Transactional(readOnly = true, rollbackFor = Exception.class)即:有try块放到了事务代码中,catch异常后,如果需要回滚事务,一定要注意手动回滚事务或加上rollbackFor参数。
备注:Spring事务不生效问题汇总
9.3.在Spring的事务管理中,特别需要注意事务的七个传播行为和五个事务隔离级别
事务的传播行为描述了各类间的事务方法嵌套调用时,嵌套事务该如何传播起作用。
深入理解事务--Spring事务的传播机制》 《Spring事务传播机制
事务的隔离级别描述了多个事务并发执行时,如何保证数据的一致性,相当于用不同粒度的事务锁进行有序控制。《浅谈SQL SERVER中事务的ACID
10.定时任务线程池与异步任务线程池
10.1定时任务和异步任务注解模式
在项目中会用注解@Scheduled开启一个定时任务,如果在一个工程中有多个@Scheduled注解的函数时,这些定时任务函数默认是单线程的,所有的定时任务都放到一个队列中由单一线程去执行,此时如果其中某一个定时任务执行过长会导致其他定时任务在队列中得不到执行。所以,对于多个定时任务时,务必开启多线程模式:@Configuration
@EnableScheduling
public class ScheduleConfig implements SchedulingConfigurer {
@Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
taskRegistrar.setScheduler(taskExecutor());
}
@Bean(destroyMethod="shutdown")
public ScheduledExecutorService taskExecutor() {
return Executors.newScheduledThreadPool(100);
}
}同理,对于使用注解@Async的异步函数,如果有多个时也需要开启多线程模式:
@Bean
public ThreadPoolTaskExecutor asyncExecutor() {
ThreadPoolTaskExecutor threadPool = new ThreadPoolTaskExecutor();
threadPool.setCorePoolSize(3);
threadPool.setMaxPoolSize(3);
threadPool.setWaitForTasksToCompleteOnShutdown(true);
threadPool.setAwaitTerminationSeconds(60 * 15);
return threadPool;
}
10.2.线程池使用注意
a.在一个工程中把线程池封装成一个单例模式,防止在工程的多个地方各自产生一个线程池,通过封装成单例模式,可以确保在一个工程中只有一个线程池。
b.任务异常未捕获
对于工作线程数目固定的池,如果工作线程在执行任务时抛出 RuntimeException或Error, 并且这些异常或错误没有被捕获,那么这个工作线程就会异常终止,使线程池永久丢失了一个线程。
说明:其实不光线程池中的线程,任何一个正在运行的线程,如果发生了运行时异常或error,在没有捕获处理时,线程都会自动退出。
11.MultiValueMap与Map的区别
发送http请求或http请求返回时,参数组装上的区别:
1.MultiValueMap:key=value的键值对,多个键值对以&分割;
2.Map:Json形式的key=value键值对,即key=value的键值对在一个大括号中。

12.Filter抛出的异常在统一异常@ControllerAdvise处理中捕获不到
统一异常处理能捕获interceptor/controller/service/dao层的异常,对于Filter里面抛出的异常不能捕获和处理
13.Spring和SpringMVC中常用的扩展接口
1.RequestBodyAdvice和ResponseBodyAdvice

四、数据结构与常用方案

4.1 树形结构
4.1.1 预排序遍历树算法: 参考一参考二
4.1.2 跳跃表
4.1.3 设计及封装无限层级的树状结构
4.1.4 树形结构 数据库表设计
4.1.5 从Trie树(字典树)谈到后缀树   海量数据处理之Tire树(字典树)
4.1.6【数据结构和算法05】 红-黑树(看完包懂~) 红黑树 
4.1.7 一致性哈希算法

附录
1.各大公司Java后端开发面试题总结  2.多maven模块工程从头搭 
3.程序设计模式:

一、Dao层设计:mapper接口和xml配置文件sql语句
1.mapper接口:输入参数若有多个需要用注解@Param进行标识,如果输入参数过多可用map或一个dto对象封装;
  mapper返回值:不建议采用entity对象作为返回值,而是定义一套返回值dto对象,虽然返回值dto对象里的字段与entity对象里的字段大部分都一样,之所以单独定义一套返回dto对象,是方便在返回dto对象中标识出各表一对一/一对多/多对多关系;
 备注:在xml中自定义SQL的resultMap中,对于一个属性的类型是一个集合,就使用collection ;er对于一个属性的类型是一个类,就使用association。
2.数据库设计:如果涉及到一对多可以直接采用两个表,如果是多对多则需要额外新建一个关联表;

二、Service层设计:
1.在Service层封装了dao层对数据库的操作(增删改查),对应增删改操作,需要加上声明式事务,需要注意事务不能在同一个方法内部调用,否则事务失效;
2.对于查询操作,如果是热点数据需要加上缓存或数据表的冗余字段设计,以提升查询效率,经验值查询与增删改的调用频率=10:1

三、Controller层设计:
1.统一异常处理
2.参数对象采用hibernate-validation进行校验
3.单独新建一套Vo对象,用于前后端参数传递


内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  spring java web 框架