Servlet优化3 增加监听器和过滤器的优化
一、过滤器Filter
-
Fileter也属于Servlet规范
-
Filter开发步骤:新建一个Filter接口的实现类,在类中重写三个方法:init、doFilter、destroy
-
配置Filter:
3.1 注解@WebFilter(" ")
3.2 xml文件:
-
过滤器链
采取的是注解的方式进行配置,那么过滤器链的拦截顺序是按照全类名的字母顺序进行排序的
采取的是xml的方式进行配置,那么按照配置的先后顺序进行排序
-
作用
保证事务的原子性,确保线程安全
-
增加过滤器的逻辑图
二、ThreadLocal
-
本地线程,可以避免多线程中的共享变量出现赋值混乱问题,且一个本地线程只能保存一个共享变量
-
set方法:在当前线程上存储数据
// set方法源码 public class ThreadLocal<T> { public void set(T value) { // 1.获取当前线程 Thread t = Thread.currentThread(); // 2.每个线程都维护着一个自己独有的容器(ThreadLocalMap) ThreadLocalMap map = getMap(t); // 3.如果容器不为空,则将当前的值传入,this代表的此ThreadLocal对象 if (map != null) map.set(this, value); // 4.如果容器为空,则创建一个容器 else createMap(t, value); } }
-
get方法:获取存储在该线程上的数据
// get方法源码 public class ThreadLocal<T> { public T get() { // 1.获取当前线程 Thread t = Thread.currentThread(); // 2.获取当前线程对应的容器 ThreadLocalMap map = getMap(t); // 3.如果容器不为空,则获取该ThteadLocal对象对应的共享变量 if (map != null) { ThreadLocalMap.Entry e = map.getEntry(this); // 4.如果该共享变量不为空,则取出该共享变量的值,并返回 if (e != null) { @SuppressWarnings("unchecked") T result = (T)e.value; return result; } } return setInitialValue(); } }
三、监听器
-
监听器分类
ServletContextListener:监听ServletContext对象的创建和销毁过程
HttpSessionListener:监听HttpSession对象的创建和销毁过程
ServletRequestListener:监听ServletRequest对象的创建和销毁过程
ServletContextAttributeListener:监听ServletContext的保存作用域的改动(add、remove、replace)
HttpSessionAttributeListener:监听HttpSession的保存作用域的改动(add、remove、replace)
ServletRequestAttributeListener:监听ServletRequest的保存作用域的改动(add、remove、replace)
HttpSessionBindingListener:监听某个对象在Session域中的创建与移除
HttpSessionActivationListener:监听某个对象在Session域中的序列化与反序列化
四、web全面优化
五、优化后的所有程序
-
监听器Listener
@WebListener public class ContextLoaderListener implements ServletContextListener { @Override public void contextInitialized(ServletContextEvent servletContextEvent) { // 获取Servlet上下文(application)作用域 ServletContext application = servletContextEvent.getServletContext(); // 获取web.xml文件内的context参数 String path = application.getInitParameter("contextConfigLocation"); // 将获取的参数传入IOC内 BeanFactory beanFactory = new ClassPathXmlApplication(path); // 将beanFactory保存到application作用域中 application.setAttribute("beanFactory",beanFactory); } @Override public void contextDestroyed(ServletContextEvent servletContextEvent) { // 监听销毁过程 } }
-
创建Filter过滤层,完善事务执行逻辑
@WebFilter("*.do") public class OpenFilter implements Filter { @Override public void init(FilterConfig filterConfig) throws ServletException { } @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { try { ((HttpServletRequest)servletRequest).setCharacterEncoding("UTF-8"); TransactionManager.beginTransaction(); System.out.println("开启事务"); filterChain.doFilter(servletRequest,servletResponse); TransactionManager.commitTransaction(); System.out.println("提交事务"); } catch (SQLException throwables) { throwables.printStackTrace(); try { TransactionManager.rollbackTransaction(); System.out.println("回滚事务"); } catch (SQLException e) { e.printStackTrace(); } } } @Override public void destroy() { } }
-
创建事务管理类
public class TransactionManager { public static void beginTransaction() throws SQLException { JdbcUtils.getConnection().setAutoCommit(false); } public static void commitTransaction() throws SQLException { Connection conn = JdbcUtils.getConnection(); conn.commit(); JdbcUtils.closeConn(); } public static void rollbackTransaction() throws SQLException { Connection conn = JdbcUtils.getConnection(); conn.rollback(); JdbcUtils.closeConn(); } }
-
改造JdbcUtil工具类,增加ThreadLocal对象,确保事务的原子性
public class JdbcUtils { // 1.新建ThreadLocal属性 private static ThreadLocal<Connection> threadLocal = new ThreadLocal<>(); // 2.创建Connection对象的方法 public static Connection createConnection() { try { InputStream is = JdbcUtils.class.getClassLoader().getResourceAsStream("jdbc.properties"); Properties ps = new Properties(); ps.load(is); String url = ps.getProperty("url"); String user = ps.getProperty("user"); String password = ps.getProperty("password"); String driver = ps.getProperty("driver"); // 2.注册驱动 Class.forName(driver); // 3.创建连接 Connection conn = DriverManager.getConnection(url, user, password); return conn; } catch (IOException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (SQLException throwables) { throwables.printStackTrace(); } return null; } // 3.在threadLocal对象的基础上获取Connection对象,确保其在不同的线程中保持独立性 public static Connection getConnection() { Connection conn = threadLocal.get(); if (conn == null) { conn = createConnection(); threadLocal.set(conn); } return threadLocal.get(); } // 4.通用关闭程序1 public static void closeResource(Connection conn, Statement ps) { try { if (ps != null) { ps.close(); } } catch (SQLException throwables) { throwables.printStackTrace(); } try { if (conn != null) { conn.close(); } } catch (SQLException throwables) { throwables.printStackTrace(); } } // 5.通用关闭程序2 public static void closeResource(Connection conn, Statement ps, ResultSet rs) { try { if (ps != null) { ps.close(); } } catch (SQLException throwables) { throwables.printStackTrace(); } try { if (conn != null) { conn.close(); } } catch (SQLException throwables) { throwables.printStackTrace(); } try { if (rs != null) { rs.close(); } } catch (SQLException throwables) { throwables.printStackTrace(); } } // 6.使用threadLocal对象的关闭程序 public static void closeConn() throws SQLException { Connection conn = threadLocal.get(); if (conn == null) { return ; } if (!conn.isClosed()) { conn.close(); threadLocal.set(null); } } }
-
业务层,解析xml配置文件的各标签信息,并组装各依赖关系
public class ClassPathXmlApplication implements BeanFactory{ private Map<String,Object> beanMap = new HashMap<>(); public ClassPathXmlApplication() { this("applicationContext.xml"); } public ClassPathXmlApplication(String path) { try { InputStream is = getClass().getClassLoader().getResourceAsStream(path); DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); DocumentBuilder builder = factory.newDocumentBuilder(); Document document = builder.parse(is); // 获取bean节点 NodeList beanNodeList = document.getElementsByTagName("bean"); for (int i = 0; i < beanNodeList.getLength() ; i++) { Node beanNode = beanNodeList.item(i); if (beanNode.getNodeType() == Node.ELEMENT_NODE) { Element beanElement = (Element)beanNode; String beanId = beanElement.getAttribute("id"); String className = beanElement.getAttribute("class"); // 获取class对应的类的实例对象 Class beanClass = Class.forName(className); Object beanObj = beanClass.newInstance(); // beanMap中存储的是id的变量名和class对应的类的实例对象 beanMap.put(beanId,beanObj); // 上述只是将bean标签全部保存到了beanMap中,接下来进行bean之间依赖关系的配置 } } // 组装bean之间的依赖关系 for (int i = 0; i < beanNodeList.getLength(); i++) { Node beanNode = beanNodeList.item(i); // 如果该节点的类型是否为元素节点 if (beanNode.getNodeType() == Node.ELEMENT_NODE) { // 如果是元素节点,就将其从Node节点类型强转为Element元素节点类型 Element beanElement = (Element) beanNode; String beanId = beanElement.getAttribute("id"); NodeList childNodesList = beanElement.getChildNodes(); for (int j = 0; j < childNodesList.getLength(); j++) { // 取其中一个 Node beanChildNode = childNodesList.item(j); if (beanChildNode.getNodeType() == Node.ELEMENT_NODE && "property".equals(beanChildNode.getNodeName()) ){ Element beanChildElement = (Element)beanChildNode; String propertyName = beanChildElement.getAttribute("name"); String propertyRef = beanChildElement.getAttribute("ref"); // 得到propertyRef对应的实例,ref对应其它bean的id Object beanRef = beanMap.get(propertyRef); // 得到当前beanId对应得对象 Object beanObj = beanMap.get(beanId); // 通过反射,获取当前bean中class的对象中与propertyName同名的属性 Class beanObjClazz = beanObj.getClass(); Field propertyField = beanObjClazz.getDeclaredField(propertyName); propertyField.setAccessible(true); // 将与ref同名的id对应的bean标签中的class对象赋值给该属性 propertyField.set(beanObj,beanRef); } } } } } catch (Exception e) { e.printStackTrace(); } } @Override public Object getBean(String id) { return beanMap.get(id); } }
-
调度型的Servlet组件,将拦截到的各请求分配至对应的实现功能体
@WebServlet("*.do") public class DispatcherServlet extends ViewBaseServlet{ private BeanFactory beanFactory; // 一、在构造器中解析相关联的xml配置文件,获取请求标识与servlet组件的全类名的实例对象 @Override public void init() throws ServletException { super.init(); beanFactory = new ClassPathXmlApplication(); } @Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws IOException { // 二、解析出请求路径中的请求标识 /* 1.获取servlet请求路径,来自tomcat的url配置或html/js文件内的url配置 假设:url = "http://localhost.8080/optimization_2/customers.do" 那么:ServletPath = "/customers.do" */ System.out.println("调度层Servlet执行service"); String servletPath = req.getServletPath(); // 2.获取最后出现该后缀名字符串的索引值 int lastDotIndex = servletPath.lastIndexOf(".do"); /* 3.使用String substring(int beginIndex, int endIndex)方法,将servletPath头部的斜杠和后缀的.do去除 servletPath.substring(1):表示从索引1处开始,故索引0处的值就被截取掉了 */ servletPath = servletPath.substring(1,lastDotIndex); // 三、通过从在请求路径中解析出的请求标识,传入beanMap中,即可获取对应的controller(组件) Object controllersObj = beanFactory.getBean(servletPath); // 1.通过req获取html/js文件内的operate的值 String operate = req.getParameter("operate"); // 2.如果operate的值为空,就赋初始值index if (StringUtil.isEmpty(operate)) { operate = "index"; } // 3.使用反射,从beanMap中得到的指定Controller类内与operate匹配的方法 try { Method[] methods = controllersObj.getClass().getDeclaredMethods(); for (Method method : methods) { if (operate.equals(method.getName())) { Parameter[] parameters = method.getParameters(); // 创建一个Object数组,用于存放通过反射得到的各参数的对应值 Object[] parameterValues = new Object[parameters.length]; for (int i = 0; i < parameters.length; i++) { Parameter p = parameters[i]; // 使用Parameter类的getName方法,获取该索引值对应参数的名称 String parameterName = p.getName(); // 将这些参数名赋给parameterValues数组 if ("req".equals(parameterName)) { parameterValues[i] = req; } else if ("resp".equals(parameterName)) { parameterValues[i] = resp; } else if ("session".equals(parameterName)) { parameterValues[i] = req.getSession(); } else { String parameterValue = req.getParameter(parameterName); parameterValues[i] = parameterValue; } } method.setAccessible(true); Object returnObj = method.invoke(controllersObj, parameterValues); // 5.视图处理 // 5.1 获取该方法返回的重定向字符串 String retrunStr = (String) returnObj; // 5.2 截取重定向内字符串内包含的请求路径(例如:index.do) if (retrunStr.startsWith("redirect:")) { String redirectStr = retrunStr.substring("redirect:".length()); resp.sendRedirect(redirectStr); System.out.println("调度层执行redirect"); } else { // 5.3 将controller内返回的模板传入父类的模板处理方法 System.out.println("调度层执行 - processTemplate"); super.processTemplate(retrunStr,req,resp); } } } } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } } }
-
去掉close功能的BaseDAO
/** * @author e_n * @version 1.0.0 * @ClassName BaseDAO.java * @Description 优化后的BaseDAO,可以利用泛型进行通用化,简化内方法的调用 * @CreateTime 2022/01/30 16:13 */ public abstract class BaseDAO<T> { private Class<T> clazz = null; // 在代码块中对clazz进行赋值,当前对象的父类的泛型 { // this是该类的实例对象 Type genericSuperclass = this.getClass().getGenericSuperclass(); ParameterizedType paramType = (ParameterizedType) genericSuperclass; Type[] typeArguments = paramType.getActualTypeArguments(); clazz = (Class<T>) typeArguments[0]; } // 通用的增删改查方法 public int update(Connection conn, String sql, Object...args) { System.out.println("Dao - update"); PreparedStatement ps = null; try { // 1.sql预编译 ps = conn.prepareStatement(sql); // 2.填充占位符 for (int i = 0; i < args.length; i++) { ps.setObject(i+1,args[i]); } // 3.执行sql return ps.executeUpdate(); } catch (SQLException throwables) { throwables.printStackTrace(); } return 0; } // 通用的多结果查询方法 public List<T> CommonRetrieve(Connection conn, String sql, Object...args) { System.out.println("Dao - 通用查询"); PreparedStatement ps = null; ResultSet rs = null; try { // 1.预编译sql,生成PreparedStatement对象 ps = conn.prepareStatement(sql); // 2.填充占位符 for (int i = 0; i < args.length; i++) { ps.setObject(i+1,args[i]); } // 3.执行并返回结果集 rs = ps.executeQuery(); // 4.处理结果集 // 4.1 创建该结果集的元数据,通过元数据获取结果集中数据的信息 ResultSetMetaData rsmd = rs.getMetaData(); // 4.2 获取结果集中列的个数 int columnCount = rsmd.getColumnCount(); // 4.3 创建用于存储结果集中多条记录的集合 ArrayList<T> tSet = new ArrayList<>(); while (rs.next()) { // 4.4 处理该行记录的每一个字段 // 4.4.1 创建该行记录在JavaBean中类的对象,此处用泛型实现 T t = clazz.newInstance(); // 4.4.2 利用通过元素据获得的结果集中列的个数,结合for循环对结果集中的列进行遍历 for (int i = 0; i < columnCount; i++) { // 4.4.3 通过结果集对象获取该字段的值 Object columnValue = rs.getObject(i + 1); // 4.4.4 通过元素据获取该字段的别(列)名,该列名与该行记录对应的对象中的属性名相同 String columnLabel = rsmd.getColumnLabel(i+1); // 4.4.5 通过反射,给Customers对象中对应的属性赋值,实现将结果集中的数据存储到对象中 Field field = clazz.getDeclaredField(columnLabel); // 确保私有属性可以访问 field.setAccessible(true); // 给该字段对应的属性赋值 field.set(t,columnValue); } // 4.4.6 将此次循环产生的对象加入新建的集合中 tSet.add(t); } return tSet; } catch (Exception e) { e.printStackTrace(); } return null; } // 通用的单结果查询方法 public T singleCommonRetrieve(Connection conn, String sql, Object...args) { PreparedStatement ps = null; ResultSet rs = null; try { // 1.预编译sql,生成PreparedStatement对象 ps = conn.prepareStatement(sql); // 2.填充占位符 for (int i = 0; i < args.length; i++) { ps.setObject(i+1,args[i]); } // 3.执行并返回结果集 rs = ps.executeQuery(); // 4.处理结果集 // 4.1 创建该结果集的元数据,通过元数据获取结果集中数据的信息 ResultSetMetaData rsmd = rs.getMetaData(); // 4.2 获取结果集中列的个数 int columnCount = rsmd.getColumnCount(); if (rs.next()) { // 4.3 处理该行记录的每一个字段 // 4.3.1 创建该行记录在JavaBean中类的对象,此处用泛型实现 T t = clazz.newInstance(); // 4.3.2 利用通过元素据获得的结果集中列的个数,结合for循环对结果集中的列进行遍历 for (int i = 0; i < columnCount; i++) { // 4.3.3 通过结果集对象获取该字段的值 Object columnValue = rs.getObject(i + 1); // 4.3.4 通过元素据获取该字段的别(列)名,该列名与该行记录对应的对象中的属性名相同 String columnLabel = rsmd.getColumnLabel(i+1); // 4.3.5 通过反射,给Customers对象中对应的属性赋值,实现将结果集中的数据存储到对象中 Field field = clazz.getDeclaredField(columnLabel); // 确保私有属性可以访问 field.setAccessible(true); // 给该字段对应的属性赋值 field.set(t,columnValue); } return t; } } catch (Exception e) { e.printStackTrace(); } return null; } // 用于查询特殊值的通用方法(聚合函数) public <E> E getValue(Connection conn, String sql, Object...args) { PreparedStatement ps = null; ResultSet rs = null; try { // 1.sql预编译 ps = conn.prepareStatement(sql); // 2.填充占位符 for (int i = 0; i < args.length; i++) { ps.setObject(i+1,args[i]); } // 3.执行sql rs = ps.executeQuery(); // 4.返回值 if (rs.next()) { return (E) rs.getObject(1); } } catch (SQLException throwables) { throwables.printStackTrace(); } return null; } }
-
controller:实现各请求功能的方法集
public class CustomersController { private CustomersServiceImpl cService = null; protected String index(String oper,String keyword,String page,HttpServletRequest req) { System.out.println("Controller层 - index"); Connection conn = null; try { conn = JdbcUtils.getConnection(); HttpSession session = req.getSession(); int pageNo = 1; if (StringUtil.isNotEmpty(oper) && "search".equals(oper)) { pageNo = 1; if (StringUtil.isEmpty(keyword)) { keyword = ""; } session.setAttribute("k",keyword); }else { Object keyObj = session.getAttribute("k"); if (keyObj == null) { keyword = ""; }else { keyword = (String) keyObj; } } if (page != null) { pageNo = Integer.parseInt(page); } session.setAttribute("pageOn",pageNo); List<Customers> custList = cService.getList(conn,"%"+ keyword +"%",pageNo); session.setAttribute("cl",custList); long count = 0L; count = cService.getCount(conn,"%"+keyword+"%"); int max = (int) (count+2-1)/2; session.setAttribute("max",max); return "index"; } catch (Exception e) { e.printStackTrace(); } return null; } private String add(String name,String email,String birth) { System.out.println("Controller层 - add"); Connection conn = null; try { SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd"); Date date = format.parse(birth); conn = JdbcUtils.getConnection(); cService.addCustomer(conn,name,email,date); System.out.println("add执行"); return "redirect:customers.do"; } catch (Exception e) { e.printStackTrace(); } return null; } private String delete(String sid) { if (StringUtil.isNotEmpty(sid)) { Connection conn = null; try { int id = Integer.parseInt(sid); conn = JdbcUtils.getConnection(); cService.delById(conn,id); return "redirect:customers.do"; } catch (Exception e) { e.printStackTrace(); } } return null; } private String edit(String cid,HttpServletRequest req) { Connection conn = null; try { conn = JdbcUtils.getConnection(); if (StringUtil.isNotEmpty(cid)) { int id = Integer.parseInt(cid); Customers customer = cService.getCustomerById(conn, id); req.setAttribute("cu" ,customer); return "edit"; } } catch (Exception e) { e.printStackTrace(); } return null; } private String update(String id,String name,String email,String birth) { Connection conn = null; try { int idInt = Integer.parseInt(id); SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd"); Date date = format.parse(birth); conn = JdbcUtils.getConnection(); cService.updateById(conn,name,email,date,idInt); return "redirect:customers.do"; } catch (Exception e) { e.printStackTrace(); } return null; } }
-
service:各业务的实现类
public class CustomersServiceImpl implements CustomersService { private ListIpm baseDAO = null; @Override public List<Customers> getList(Connection conn, String str, int pageOn) { return baseDAO.getList(conn,str,pageOn); } @Override public void addCustomer(Connection conn, String name, String email, Date date) { baseDAO.addCustomer(conn,name,email,date); } @Override public Customers getCustomerById(Connection conn, int id) { return baseDAO.getCustomerById(conn,id); } @Override public void delById(Connection conn, int id) { baseDAO.delById(conn,id); } @Override public Long getCount(Connection conn, String str) { return baseDAO.getCount(conn,str); } @Override public void updateById(Connection conn, String name, String email, Date date, int id) { baseDAO.updateById(conn, name, email, date, id); } }
-
具体数据表的DAO实现类
public class ListIpm extends BaseDAO<Customers> implements ListDAO{ @Override public List<Customers> getList(Connection conn ,String str,int pageOn) { String sql = "select cust_name name,cust_email email,cust_birth birth,cust_id id from customers where " + "cust_email like ? limit ? , 2"; return super.CommonRetrieve(conn, sql,str,(pageOn-1)*2); } @Override public Customers getCustomerById(Connection conn,int id) { String sql = "select cust_name name,cust_email email,cust_birth birth,cust_id id from customers where cust_id" + " = ?"; return super.singleCommonRetrieve(conn,sql,id); } @Override public void updateById(Connection conn, String name, String email, Date date, int id) { String sql = "update customers set cust_name = ? , cust_email= ? , cust_birth = ? where cust_id = ?"; super.update(conn,sql,name,email,date,id); } @Override public void delById(Connection conn, int id) { String sql = "delete from customers where cust_id = ?"; super.update(conn,sql,id); } @Override public void addCustomer(Connection conn, String name, String email, Date date) { String sql = "insert into customers values(0,? , ? , ?)"; super.update(conn,sql,name,email,date); } @Override public Long getCount(Connection conn,String str) { String sql = "select count(*) from customers where cust_email like ?"; return super.getValue(conn, sql,str); } }
-
pojo:单行数据的模型类
public class Customers { private int id; private String name; private String email; private java.util.Date birth; public Customers() { } public Customers(int id, String name, String email, java.util.Date birth) { this.id = id; this.name = name; this.email = email; this.birth = birth; } public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } public java.util.Date getBirth() { return birth; } public void setBirth(java.util.Date birth) { this.birth = birth; } }
-
配置文件
applicationContext.xml
<?xml version="1.0" encoding="utf-8"?> <!-- 新建一个beans标签,在该标签内,对每个bean进行具体配置 --> <beans> <bean id="customersDAO" class="com.atguigu.consrumers.dao.ListIpm"/> <bean id="customersService" class="com.atguigu.consrumers.service.impl.CustomersServiceImpl" > <!-- property标签用来表示属性:name表示该类内的属性名;ref表示引用其它bean的id值 建立了service层和DAO层之间的关联 --> <property name="baseDAO" ref="customersDAO" /> </bean> <bean id="customers" class="com.atguigu.consrumers.controllers.CustomersController" > <!-- 建立controller层和service层之间的关联 --> <property name="cService" ref="customersService"/> </bean> </beans>
jdbc.properties
user=root password=******** url=jdbc:mysql://localhost:3306/test02_market?rewriteBatchedStatements=true&serverTimezone=UTC&useSSL=false driver=com.mysql.cj.jdbc.Driver
web.xml
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd" version="4.0"> <context-param> <param-name>view-prefix</param-name> <param-value>/</param-value> </context-param> <context-param> <param-name>view-suffix</param-name> <param-value>.html</param-value> </context-param> <context-param> <param-name>contextConfigLocation</param-name> <param-value>applicationContext.xml</param-value> </context-param> <!-- <listener> <listener-class>com.atguigu.myssm.listener.ContextLoaderListener</listener-class> </listener> --> </web-app>
-
html / css / js 文件
参考:JavaWEB十:Servlet优化1 实现将各种请求由一个组件进行统一调度后响应_e_nanxu的博客-CSDN博客
五、出现过的bug
-
html关联css时,其标签中的路径一定要同tomcat中的Application Context一样,不能写成模块名
-
加入过滤层,进行事务管理后,除了事务管理中进行数据库的conn关闭,要清除掉其它类中的方法中所有对conn关闭的程序,从而保证事务的原子性,否则会报错
java.sql.SQLNonTransientConnectionException: No operations allowed after connection closed.