Java培训实战教程之自定义spring1.1 描述 在企业级开发中,spring框架应用非常广。为了让已经学习过spring框架同学,可以更深入的理解和应用spring,本文将通过自定义spring,更佳系统的阐述spring核心:IoC、AOP。 IoC(Inversion of Control)控制反转:将对象的创建权交与spring框架,及将创建权反转给spring框架。IoC主要解决计算机程序的耦合问题。 AOP(Aspect Oriented Programming)面向切面编程:通过运行期动态代理实现程序功能的统一维护的一种技术。 1.2 分析l 如果要实现自定义spring,可以将器拆分成多个功能实现。 l 阶段一:编写配置文件,服务器tomcat启动时,加载配置文件 l 阶段二:使用Jsoup解析xml,并封装到指定的JavaBean中 l 阶段三:编写工厂类用于创建指定bean,并完成property注入 l 阶段四:使用@Transactional进行事务管理 1.3 搭建环境1.3.1 javabean private UserDao userDao; public void setUserDao(UserDao userDao) { this.userDao = userDao; } @Override public void register(User user) { this.userDao.save(user); } 1.4.1 xml配置文件l 在src下添加 applicationContext.xml ,并将dao和service配置到xml文件中。 l 使用 bean 标签配置一个实现类 class: 配置实现类的全限定名称 id: 进行唯一命名,用于提供给程序获得 l 使用 property 配置javabean属性的注入 name: 配置的service的属性名 ref: 配置其他bean对象的引用 bean id= userServiceId > property name= userDao ref= userDaoId /property /bean /beans l tomcat启动时,加载配置文件方式总结: 1.编写Servlet,配置servlet,并添加 load-on-startup ,在init(ServletConfig)初始化方式中加载。 2.编写Filter,配置filter,在init(FilterConfig)初始化方法中加载 3.编写Listener,实现接口ServletContext,配置listener,在contextInitialized(ServletContextEvent sce)方法中加载。 l spring采用listener方案 1.提供实现类ContextLoaderListener 2.编写全局初始化参数contextConfigLocation,用于确定xml位置 param-value classpath:applicationContext.xml /param-value 加载类路径下的xml文件 param-value applicationContext.xml /param-value 加载WEB-INF目录的配置文件
l xml配置 param-name contextConfigLocation /param-name param-value classpath:applicationContext.xml /param-value /context-param !-- 配置监听器 -- listener listener-class cn.itcast.myspring.listener.ContextLoaderListener /listener-class /listener @Override public void contextInitialized(ServletContextEvent sce) { // 0 获得ServletContext对象应用 ServletContext context = sce.getServletContext(); // 1 加载配置 String config = context.getInitParameter( contextConfigLocation if(config == null){ //默认配置文件位置 config= applicationContext.xml } InputStream xmlIs = null; // 2 处理路径不同情况 // * classpath:applicationContext.xml -- 表示 src/applicationContext.xml // * applicationContext.xml -- 表示 /WEB-INF/applicationContext.xml if (config.startsWith( classpath: )) { // 2.1 加载 类路径 (classpath、src)下的xml xmlIs = ContextLoaderListener.class.getClassLoader().getResourceAsStream(config.substring( classpath: .length())); } else { //2.2 加载/WEB-INF/目录下的资源 xmlIs = context.getResourceAsStream( /WEB-INF/ + config); } //2.3 配置文件必须存在,否则抛异常 if(xmlIs == null){ throw new RuntimeException( 资源文件[ +config+ ]没有找到 } //TODO 3 解析配置 if (xmlIs != null) { System.out.println(xmlIs); } } @Override public void contextDestroyed(ServletContextEvent sce) { } 1.5 阶段二:解析xml,并封装到指定javabean中1.提供Property类,用于封装 property name= ref= /property 2.提供Bean类,用于封装 bean id= > 一个 bean 标签体中可以配置多个 property 需要一个容器存放,没有顺序要求,且不能重复,选择Set 3.提供BeanFactory类,并在类同提供容器存放多个Bean 类,为了方便获取使用Map。1.5.1 property javabean private String beanClass; //取值:singleton 单例,prototype 原型(多例)【扩展】 private String beanType; //所有的property private Set Property propSet = new HashSet Property public Bean(String beanId, String beanClass) { super(); this.beanId = beanId; this.beanClass = beanClass; } //工厂模式 private static BeanFactory factory = new BeanFactory(); private BeanFactory(){ } /** * 获得工厂实例 * @author lt * @return */ public static BeanFactory getInstance() { return factory; } //缓存所有的Bean/ //bean数据缓存集合 ,key:bean名称 ,value:bean封装对象 private static Map String, Bean beanData;// = new HashMap String, String static{ // 从配置文件中获得相应的数据 1.properties 2.xml //beanData.put( userDao , com.itheima.ebs.service.impl.BusinessServiceImpl } public static void setBeanData(Map String, Bean beanData) { BeanFactory.beanData = beanData; } l 使用Jsoup解析,导入jar包 l 修改contextInitialized 方法 Map String, Bean data = parserBeanXml(xmlIs); //3.2 将解析结果放置到工厂中 BeanFactory.setBeanData(data); } */ public static Map String, Bean parserBeanXml(InputStream xmlIs) { try { //0提供缓冲区域 Map String, Bean data = new HashMap String, Bean //1解析文件,并获得Document Document document = Jsoup.parse(xmlIs, UTF-8 , //2 获得所有的bean元素 Elements allBeanElement = document.getElementsByTag( bean //3遍历 for (Element beanElement : allBeanElement) { //5 将解析的结果封装到bean中 // 5.1 bean名称 String beanId = beanElement.attr( id // 5.2 bean实现类 String beanClass = beanElement.attr( class // 5.3 封装到Bean对象 Bean bean = new Bean(beanId,beanClass); // 6 获得所有的子元素 property Elements allPropertyElement = beanElement.children(); for (Element propertyElement : allPropertyElement) { String propName = propertyElement.attr( name String propRef = propertyElement.attr( ref Property property = new Property(propName, propRef);
// 6.1 将属性追加到bean中 bean.getPropSet().add(property); } data.put(beanId, bean); } return data; } catch (Exception e) { throw new RuntimeException(e); } } 1.6 阶段三:完善BeanFactory获得实例l 使用 BeanUtils.setProperty 设置数据,需要导入jar包 l BeanFactory 提供 getBean方法 获得Bean实例/// public Object getBean(String beanId) { try { // 通过bean 的名称获得具体实现类 Bean bean = beanData.get(beanId); if(bean ==null) return null; String beanClass = bean.getBeanClass(); Class clazz = Class.forName(beanClass); Object beanObj = clazz.newInstance(); //DI 依赖注入,将在配置文件中设置的内容,通过bean的set方法设置到bean实例中 Set Property props = bean.getPropSet(); for (Property property : props) { String propName = property.getName(); String propRef = property.getRef(); //另一个javabean,需要重容器中获得 Object propRefObj = getBean(propRef); //PropertyDescriptor pd = new PropertyDescriptor(propName, clazz); //pd.getWriteMethod().invoke(bean, propRefObj); BeanUtils.setProperty(beanObj, propName, propRefObj); } return beanObj; } catch (Exception e) { throw new RuntimeException(e); } } //测试 UserService userService = (UserService) BeanFactory.getInstance().getBean( userServiceId userService.register(null); 1.7.1 修改自定义springl 提供JdbcUtils工具类,用于在当前线程中共享Connection l 提供@Transaction 用于标记那些类需要进行事务管理 1.7.1.1 JdbcUtils工具类l 使用ThreadLocal 保存Connection,在当前线程中共享Connection l 并提供提交和回滚方法,自动关闭连接 private static ThreadLocal Connection local = new ThreadLocal Connection static{ try { //注册驱动 Class.forName( com.mysql.jdbc.Driver } catch (Exception e) { throw new RuntimeException(e); } } /** * 获得连接 * @return */ public static Connection getConnection(){ try { Connection conn = local.get(); if (conn == null) { conn = DriverManager.getConnection( jdbc:mysql://localhost:3306/test2 , root , 1234 local.set(conn); } return conn; } catch (Exception e) { throw new RuntimeException(e); } } /** * 提交事务 */ public static void commit() { Connection conn = getConnection(); DbUtils.commitAndCloseQuietly(conn); }
/** * 回滚事务 */ public static void rollback() { Connection conn = getConnection(); DbUtils.rollbackAndCloseQuietly(conn); } 1.7.1.3 修改BeanFactoryl 通过getBean 获得对象实例时,如果对象有@Transactional注解,将返回代理对象,对目标对象上所有的方法都进行事务管理。 l 如果没有异常提交事务,并关闭连接 l 如果有异常回滚事务,并关闭连接 // 通过bean 的名称获得具体实现类 Bean bean = beanData.get(beanId); if(bean ==null) return null; String beanClass = bean.getBeanClass(); Class ? clazz = Class.forName(beanClass); Object beanObj = clazz.newInstance(); //DI 依赖注入,将在配置文件中设置的内容,通过bean的set方法设置到bean实例中 Set Property props = bean.getPropSet(); for (Property property : props) { String propName = property.getName(); String propRef = property.getRef(); //另一个javabean,需要重容器中获得 Object propRefObj = getBean(propRef); //PropertyDescriptor pd = new PropertyDescriptor(propName, clazz); //pd.getWriteMethod().invoke(bean, propRefObj); BeanUtils.setProperty(beanObj, propName, propRefObj); } //如果类上有注解返回代理对象 if(clazz.isAnnotationPresent(Transactional.class)){ final Object _beanObj = beanObj; return Proxy.newProxyInstance( clazz.getClassLoader(), clazz.getInterfaces(), new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { try { //开启事务 JdbcUtils.getConnection().setAutoCommit(false); //执行目标方法 Object obj = method.invoke(_beanObj, args); //提交事务 JdbcUtils.commit(); return obj; } catch (Exception e) { //回顾事务 JdbcUtils.rollback(); throw new RuntimeException(e); } } }); } return beanObj; } catch (Exception e) { throw new RuntimeException(e); } } insert into account(username,money) values( jack , 10000 insert into account(username,money) values( rose , 10000 1.7.3 dao层l 必须使用自定义spring提供的JdbcUtils获得连接,从而保证当前线程使用的是同一个线程。 l 通过xml配置文件创建QueryRunner实例,并注入给dao l 扩展:如果将QueryRunner和JdbcUtils都省略,需要自己实现JdbcTemplate完成。 private QueryRunner runner; public void setRunner(QueryRunner runner) { this.runner = runner; } @Override public void out(String outer, Integer money) { try { Connection conn = JdbcUtils.getConnection(); runner.update(conn, update account set money = money - ? where username = ? , money, outer); } catch (Exception e) { throw new RuntimeException(e); } } @Override public void in(String inner, Integer money) { try { Connection conn = JdbcUtils.getConnection(); runner.update(conn, update account set money = money + ? where username = ? , money,inner); } catch (Exception e) { throw new RuntimeException(e); } } private AccountDao accountDao; public void setAccountDao(AccountDao accountDao) { this.accountDao = accountDao; } @Override public void transfer(String outer, String inner, Integer money) { accountDao.out(outer, money); //断电 // int i = 1/0; accountDao.in(inner, money); } bean id= accountService > property name= accountDao ref= accountDao /property /bean 1.7.6 编写servlet测试l 通过请求servlet,进行转账,此处使用固定值进行测试 public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { AccountService accountService = (AccountService) BeanFactory.getInstance().getBean( accountService accountService.transfer( jack , rose , 100); }