郑州java培训教程:自定义spring
1 Java培训实战教程之自定义spring
1.1 描述
在企业级开发中,spring框架应用非常广。为了让已经学习过spring框架同学,可以更深入的理解和应用spring,本文将通过自定义spring,更佳系统的阐述spring核心:IoC、AOP。
IoC(Inversion of Control)控制反转:将对象的创建权交与spring框架,及将创建权反转给spring框架。IoC主要解决计算机程序的耦合问题。
AOP(Aspect Oriented Programming)面向切面编程:通过运行期动态代理实现程序功能的统一维护的一种技术。
1.2 分析
如果要实现自定义spring,可以将器拆分成多个功能实现。
阶段一:编写配置文件,服务器tomcat启动时,加载配置文件
阶段二:使用Jsoup解析xml,并封装到指定的JavaBean中
阶段三:编写工厂类用于创建指定bean,并完成property注入
阶段四:使用@Transactional进行事务管理
1.3 搭建环境
1.3.1 javabean
public class User {
private Integer uid;
private String username;
private String password;
1.3.2 dao
public interface UserDao {
/**
* 保存
* @param user
*/
public void save(User user);}
public class UserDaoImpl implements UserDao {
@Override
public void save(User user) {
//TODO 暂时只打印
System.out.println(user);
}
}
1.3.3 service
public interface UserService {
/**
* 注册
* @param user
*/
public void register(User user);
}
public class UserServiceImpl implements UserService {
private UserDao userDao;
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
@Override
public void register(User user) {
this.userDao.save(user);
}
}
1.4 阶段一:编写配置文件,服务器启动加载
1.4.1 xml配置文件
l 在src下添加“applicationContext.xml”,并将dao和service配置到xml文件中。
l 使用<bean>标签配置一个实现类
class: 配置实现类的全限定名称
id: 进行唯一命名,用于提供给程序获得
l 使用<property>配置javabean属性的注入
name: 配置的service的属性名
ref: 配置其他bean对象的引用
<?xml version=”1.0″ encoding=”UTF-8″?>
<beans>
<!– dao –>
<bean id=”userDaoId” class=”cn.itcast.demo.dao.impl.UserDaoImpl”></bean>
<!– service –>
<bean id=”userServiceId” class=”cn.itcast.demo.service.impl.UserServiceImpl”>
<property name=”userDao” ref=”userDaoId”></property>
</bean>
</beans>
1.4.2 加载配置文件
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配置
<!– 确定xml位置 –>
<context-param>
<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>
l 实现类
1.4.2 加载配置文件
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配置
<!– 确定xml位置 –>
<context-param>
<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>
l 实现类
public class ContextLoaderListener implements ServletContextListener {
@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=” ” class=” “>
一个<bean> 标签体中可以配置多个<property>需要一个容器存放,没有顺序要求,且不能重复,选择Set
3.提供BeanFactory类,并在类同提供容器存放多个Bean 类,为了方便获取使用Map。
1.5 阶段二:解析xml,并封装到指定javabean中
1.提供Property类,用于封装<property name=” ” ref=” “></property>
2.提供Bean类,用于封装<bean id=” ” class=” “>
一个<bean> 标签体中可以配置多个<property>需要一个容器存放,没有顺序要求,且不能重复,选择Set
3.提供BeanFactory类,并在类同提供容器存放多个Bean 类,为了方便获取使用Map。
1.5.1 property javabean
/**
* 用于封装 <property name=”userDao” ref=”userDaoId”></property>
*/
public class Property {//属性名称
private String name;
//另一个bean引用名
private String ref;
public Property(String name, String ref) {
super();
this.name = name;
this.ref = ref;
}
1.5.2 bean javabean
public class Bean {
//bean名称
private String beanId;
//bean的实现类
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;
}
1.5.3 BeanFactory 工厂模式类
public class BeanFactory {
//////////////////工厂模式////////////////////////
private static BeanFactory factory = new BeanFactory();
private BeanFactory(){
}
/**
* 获得工厂实例
* @author lt
* @return
*/
public static BeanFactory getInstance() {
return factory;
}
1.5.4 BeanFactory 提供Map 缓存
//////////////////缓存所有的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;
}
1.5.5 修改Listener解析xml
l 使用Jsoup解析,导入jar包
l 修改contextInitialized 方法
//TODO 3 解析配置
if (xmlIs != null) {
//3.1解析
Map<String, Bean> data = parserBeanXml(xmlIs);
//3.2 将解析结果放置到工厂中
BeanFactory.setBeanData(data);
}
l 解析方法parserBeanXml(InputStream)
/**
* 将数据解析成bean
* @param xmlIs
* @return
*/
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);
}
}
l 测试
//测试
UserService userService = (UserService) BeanFactory.getInstance().getBean(“userServiceId”);
userService.register(null);
1.7 阶段四:spring事务管理
1.7.1 修改自定义spring
l 提供JdbcUtils工具类,用于在当前线程中共享Connection
l 提供@Transaction 用于标记那些类需要进行事务管理
1.7.1.1 JdbcUtils工具类
l 使用ThreadLocal 保存Connection,在当前线程中共享Connection
l 并提供提交和回滚方法,自动关闭连接
public class JdbcUtils {
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.2 @Transactional
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Transactional {}
1.7.1.3 修改BeanFactory
l 通过getBean 获得对象实例时,如果对象有@Transactional注解,将返回代理对象,对目标对象上所有的方法都进行事务管理。
l 如果没有异常提交事务,并关闭连接
l 如果有异常回滚事务,并关闭连接
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);
}
//如果类上有注解返回代理对象
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);
}
}
1.7.2 初始化数据库
create table account(
id int primary key auto_increment,
username varchar(50),
money int
);
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完成。
public class AccountDaoImpl implements AccountDao {
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);
}
}
}
1.7.4 service层
l 在实现类上添加注解
@Transactional
public class AccountServiceImpl implements AccountService {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);
}
}
1.7.5 编写spring配置
<!– 创建queryRunner –>
<bean id=”runner” class=”org.apache.commons.dbutils.QueryRunner”></bean><bean id=”accountDao” class=”cn.itcast.dao.impl.AccountDaoImpl”>
<property name=”runner” ref=”runner”></property>
</bean>
<!– service –>
<bean id=”accountService” class=”cn.itcast.service.impl.AccountServiceImpl”>
<property name=”accountDao” ref=”accountDao”></property>
</bean>
1.7.6 编写servlet测试
l 通过请求servlet,进行转账,此处使用固定值进行测试
public class AccountServlet extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
AccountService accountService = (AccountService) BeanFactory.getInstance().getBean(“accountService”);
accountService.transfer(“jack”, “rose”, 100);
}