基本概念
l 90%的web项目都会用到Spring;
l Spring除了可以和Hibernate整合也可以和JDBC整合;
l Spring的两个最基本的东西是:IOC(控制反转,也叫“依赖注入”)、AOP(面向切面);
IOC
【基本概念】:
<bean
id="userAction"
class="org.zttc.itat.spring.action.UserAction"
scope="prototype">//prototype:多例;singlton:单例(默认)
【提示】:
Action(Controller)中的状态没有修改就用单例,有修改的话就用多例。比如,添加User的Action,一般都会用多例,因为每次请求添加的内容都不一样。
【使用属性注入】:
<bean id="userService" class="org.zttc.itat.spring.service.UserService">
<!-- name中的值会在userService对象中调用setXX方法来注入,诸如:name="userDao"在具体注入时会调用setUserDao(IUserDao userDao)来完成注入ref="userDao"表示是配置文件中的bean中所创建的DAO的id -->
<property name="userDao" ref="userDao"></property>
</bean>
【使用构造函数注入(不常用)】:
public class UserAction {
private User user;
private IUserService userService;
private int id;
private List<String> names;
// 构造函数
public UserAction(IUserService userService) {
super();
this.userService = userService;
}
在配置文件中使用构造函数注入:
<!-- 以下是使用构造函数来注入,不常用,基本都是使用set方法注入 -->
<bean id="userAction"
class="org.zttc.itat.spring.action.UserAction"
scope="prototype">
<constructor-arg ref="userService"/>// 构造函数的参数,依顺序往下写
</bean>
【自动注入(不常用,也不建议使用)】:
// autowire会自动找到属性和对应的set方法进行注入(若值为default则不自动注入)
<bean id="userAction"
class="org.zttc.itat.spring.action.UserAction"
scope="prototype" autowire="byname ">
<!--<property ref="userService"/>-->
</bean>
【提示】:
Spring3.0后提供了基于Annotation注入。
使用Annotation
【基本使用方法】:
l beans.xml配置文件:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
……
>
<!-- 打开Spring的Annotation支持 -->
<context:annotation-config/>
<!-- 设定Spring 去哪些包中找Annotation -->
<context:component-scan base-package="org.zmj.test.spring"/>
<!-- 打开基于Annotation的AOP -->
<aop:aspectj-autoproxy/>
</beans>
l 将相关类注入
注入UserDao:
//等于完成了<bean id="userDao" class="org.zttc.itat.spring.UserDao"/>
//@Component("userDao")//公共的创建bean的Annotation,所有的类都能用
@Repository("userDao")//@Repository一般用于DAO的注入
public class UserDao implements IUserDao {
@Override
public void add(User user) {
System.out.println("添加了"+user);
}
……
}
注入UserService:
//@Component("userService")
@Service("userService")//业务层一般使用@Service
public class UserService implements IUserService {
private IUserDao userDao;
private IMessageDao messageDao;
@Resource(name="messageDao")
public void setMessageDao(IMessageDao messageDao) {
this.messageDao = messageDao;
}
//默认通过名称注入,在JSR330中提供了@Inject来注入
//@Autowired也可以,就是默认注入使用类型注入,不建议使用
@Resource(name="userDao")
public void setUserDao(IUserDao userDao) {
this.userDao = userDao;
}
}
注入UserAction:
//@Component("userAction")
@Controller("userAction")//MVC的控制层一般使用@Controller
@Scope("prototype")
public class UserAction {
private User user;
private IUserService userService;
private int id;
private List<String> names;
}
【提示】:
项目比较大的时候一般不适用Annotation。因为在大项目中的代码结构是按模块来划分的,类非常多,使用Annotation的话类的依赖关系不明显,但是在配置文件中看的话就会一目了然。
AOP(重点)
【实例1】:AOP-模拟(静态代理)
背景:假设我们的项目已经上线,已经在运行,突然客户提出要求,要在项目中加上日志的输出。
我们直观的想法是,创建一个类用来输出日志,然后在原来的代码里加入输出日志的代码。但是这个破坏了原来的代码。解决方法是使用代理。
比如,在我们的代码里,我们创建一个UserProxyDao类,这个类实现了IUserDao,然后实现接口中的方法,然后在这些方法前加入输出日志的代码,这样就不会破坏原来的代码了。
l 代码内容:
@Component("userProxyDao")
public class UserProxyDao implements IUserDao {
private IUserDao userDao;
@Resource
public void setUserDao(IUserDao userDao) {
this.userDao = userDao;
}
@Override
public void add(User user) {
Logger.info("添加了用户");
userDao.add(user);
}
@Override
public void delete(int id) {
Logger.info("删除了用户");
userDao.delete(id);
}
@Override
public User load(int id) {
return userDao.load(id);
}
}
UserSerivce中用到了这些方法,所以要在UserService中注入userProxyDao
【提示】:
以上是“静态代理”的实现。
这样虽然解决了问题且没有破坏原来的代码,但是这样还有一个问题,就是,如果还想给其他的Dao(如MessageDao)添加日志,我们还要再创建一个MessageProxyDao类用来输出日志,如果哪一天不想输出日志了,还要再创建新的代理,这样肯定很麻烦,不可行。
经过分析,我们发现,输出日志的代码都是独立的,和业务逻辑无关,所以,我们可以讲这些代码抽出来,然后注入到相关的代码中。
【实例2】:AOP-手动实现
动态代理是指通过一个代理对象来创建需要的业务对象,然后在这个代理对象中统一进行各种需求的处理。
【步骤】:
步骤1:写一个类实现InvocationHandler接口
步骤2:创建一个代理对象;
步骤3:创建一个方法来创建对象,方法的参数是要代理的对象
l 创建代理对象:
//1、写一个类实现InvocationHandler接口*/
public class LogProxy implements InvocationHandler {
private LogProxy(){}
//2、创建一个代理对象
private Object target;
//3、创建一个方法来生成对象,这个方法的参数是要代理的对象,
// getInstacne所返回的对象就是代理对象
public static Object getInstance(Object o) {
//3.1、创建LogProxy对象
LogProxy proxy = new LogProxy();
//3.2、设置这个代理对象
proxy.target = o;
//3.3、通过Proxy的方法创建代理对象,
// 第一个参数是要代理对象的classLoader
// 第二个参数是要代理对象实现的所有接口,
// 第三个参数是实现类InvocationHandler的对象
//此时的result就是一个代理对象,代理的是o
Object result = Proxy.newProxyInstance(
o.getClass().getClassLoader(),
o.getClass().getInterfaces(),
proxy);
return result;
}
/**当有了代理对象之后,不管这个代理对象执行什么方法,
* 都会调用以下的invoke方法*/
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
/**控制哪些方法做日志输出 - 判断方法名称*/
//if(method.getName().equals("add")
||method.getName().equals("delete")) {
// Logger.info("进行了相应的操作");
//}
/**控制哪些方法做日志输出 - 使用Annotation*/
//以下代码可以放在方法执行前、方法执行后也可放在异常中执行
if(method.isAnnotationPresent(LogInfo.class)) {
LogInfo li = method.getAnnotation(LogInfo.class);
Logger.info(li.value());
}
// 执行方法
Object obj = method.invoke(target, args);
return obj;
}
}
l 将代理对象注入到Spring中:(因为没有get、set所以不能使用注解注入)
// 以下代码的意思是,使用LoginProxy类使用getInstance创建userDao
// userDao会和注解中的“userDao”进行匹配,这个userDao会传入代理中的
// getInstance参数中
<bean id="userDynamicDao"
class="org.zttc.itat.spring.proxy.LogProxy"
factory-method="getInstance">
<constructor-arg ref="userDao"/>
</bean>
<bean id="messageDynamicDao"
class="org.zttc.itat.spring.proxy.LogProxy"
factory-method="getInstance">
<constructor-arg ref="messageDao"/>
</bean>
l 在UserService中使用userDynamicDao
//@Component("userService")
@Service("userService")//业务层一般使用@Service
public class UserService implements IUserService {
private IUserDao userDao;
private IMessageDao messageDao;
@Resource(name="messageDynamicDao")
public void setMessageDao(IMessageDao messageDao) {
this.messageDao = messageDao;
}
//默认通过名称注入,在JSR330中提供了@Inject来注入
@Resource(name="userDynamicDao")
public void setUserDao(IUserDao userDao) {
this.userDao = userDao;
}
}
l 使用Annotation来控制要输出的信息:
@Retention(RetentionPolicy.RUNTIME)
public @interface LogInfo {
public String value() default"";
}
l 在接口上(我们这里是基于接口的代理)添加该注解:
public interface IUserDao {
@LogInfo("添加用户信息")
public void add(User user);
@LogInfo("删除用户信息")
public void delete(int id);
public User load(int id);
}
【小结】:
所谓AOP(切面)就是将关注的模块抽出来做成一个独立的部分(切面)。
【实例3】:AOP- Annotation
步骤1:设置Schema、打开基于Annotation的AOP
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">
<!-- 打开Spring的Annotation支持 -->
<context:annotation-config/>
<!-- 设定Spring 去哪些包中找Annotation -->
<context:component-scan base-package="org.zttc.itat.spring"/>
<!-- 打开基于Annotation的AOP -->
<aop:aspectj-autoproxy/>
</beans>
步骤2:创建代理类(Spring使用的是第三方的切面技术,所以要另外导入包:
aopalliance.jar,aspectjrt.jar,aspectjweaver.jar)
@Component("logAspect")//让这个切面类被Spring所管理
@Aspect//申明这个类是一个切面类(需要导入第三方包)
public class LogAspect {
/**
* execution(* org.zttc.itat.spring.dao.*.add*(..))
* 第一个*表示任意返回值
* 第二个*表示 org.zttc.itat.spring.dao包中的所有类
* 第三个*表示以add开头的所有方法
* (..)表示任意参数
*/
@Before("execution(* org.zttc.itat.spring.dao.*.add*(..))||" +
"execution(* org.zttc.itat.spring.dao.*.delete*(..))||" +
"execution(* org.zttc.itat.spring.dao.*.update*(..))")
public void logStart(JoinPoint jp) {
//得到执行的对象
System.out.println(jp.getTarget());
//得到执行的方法
System.out.println(jp.getSignature().getName());
Logger.info("加入日志");
}
/**函数调用完成之后执行*/
@After("execution(* org.zttc.itat.spring.dao.*.add*(..))||" +
"execution(* org.zttc.itat.spring.dao.*.delete*(..))||" +
"execution(* org.zttc.itat.spring.dao.*.update*(..))")
public void logEnd(JoinPoint jp) {
Logger.info("方法调用结束加入日志");
}
/** 函数调用中执行 */
@Around("execution(* org.zttc.itat.spring.dao.*.add*(..))||" +
"execution(* org.zttc.itat.spring.dao.*.delete*(..))||" +
"execution(* org.zttc.itat.spring.dao.*.update*(..))")
public void logAround(ProceedingJoinPoint pjp) throws Throwable {
Logger.info("开始在Around中加入日志");
pjp.proceed();//执行程序
Logger.info("结束Around");
}
}
【实例3】:AOP-XML
首先,第三方包要导入、Schema头要设置。
l 创建切面(代理类):
@Component("logAspect")//让这个切面类被Spring所管理
public class LogAspect {
public void logStart(JoinPoint jp) {
//得到执行的对象
System.out.println(jp.getTarget());
//得到执行的方法
System.out.println(jp.getSignature().getName());
Logger.info("加入日志");
}
public void logEnd(JoinPoint jp) {
Logger.info("方法调用结束加入日志");
}
public void logAround(ProceedingJoinPoint pjp) throws Throwable {
Logger.info("开始在Around中加入日志");
pjp.proceed();//执行程序
Logger.info("结束Around");
}
}
l 配置文件中配置该切面:
<!-- 打开Spring的Annotation支持 -->
<context:annotation-config/>
<!-- 设定Spring 去哪些包中找Annotation -->
<context:component-scan base-package="org.zttc.itat.spring"/>
<aop:config>
<!-- 定义切面 -->
<aop:aspect id="myLogAspect" ref="logAspect">
<!-- 在哪些位置加入相应的Aspect -->
<aop:pointcut
id="logPointCut"
expression="
execution(* org.zttc.itat.spring.dao.*.add*(..))||
execution(* org.zttc.itat.spring.dao.*.delete*(..))||
execution(* org.zttc.itat.spring.dao.*.update*(..))"
/>
<aop:before method="logStart" pointcut-ref="logPointCut"/>
<aop:after method="logEnd" pointcut-ref="logPointCut"/>
<aop:around method="logAround" pointcut-ref="logPointCut"/>
</aop:aspect>
</aop:config>
Spring和JDBC整合
步骤1:导入Spring包和数据库驱动包;
步骤2:选择一个数据源(DBCP和C3P0);
步骤3:导入数据源的包;
步骤4:在beans.xml中创建dataSource数据源;
步骤5:创建数据库的属性文件,存放连接数据库的连接信息;
步骤6:在beans.xml中导入属性文件;
步骤7:创建UserDao并在UserDao中创建JDBCTemplate对象(通过JDBCTemplate可以方
便操作数据库);
步骤8:为Dao注入DataSource并创建JDBCTemplate;
步骤9:完成数据的增删改查
l beans.xml文件:
<bean id="dataSource"
class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close">
<property name="driverClassName" value="${jdbc.driverClassName}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
<!-- 导入Src目录下的jdbc.properties文件 -->
<context:property-placeholder location="jdbc.properties"/>
l 连接数据库的属性文件内容:
jdbc.driverClassName=com.mysql.jdbc.Driver
jdbc.url = jdbc:mysql://localhost:3306/spring_teach
jdbc.username = root
jdbc.password = 123456
l 创建User实体:
l 根据实体内容创建对应的数据库表:
l 创建UserDao和JDBCTemplate:
@Repository("userJdbcDao")
public class UserDao implements IUserDao {
private JdbcTemplate jdbcTemplate;
@Resource
public void setDataSource(DataSource dataSource) {
jdbcTemplate = new JdbcTemplate(dataSource);
}
@Override
public void add(User user,int gid) {
jdbcTemplate.update("insert into t_user(username,password,nickname,gid) value (?,?,?,?)",
user.getUsername(),user.getPassword(),user.getNickname(),gid);
}
}
l 使用Spring集成测试:
/**当使用了以下注解之后,就可以直接在Test中进行依赖注入*/
//让Junit运行在Spring的测试环境中
@RunWith(SpringJUnit4ClassRunner.class)
//加载beans.xml文件
@ContextConfiguration("/beans.xml")
public class TestJdbc {
@Resource(name="userJdbcDao")//注意:名字要和注解中配置的名字一致!
private IUserDao userJdbcDao;
@Resource(name="groupJdbcDao")
private IGroupDao groupJdbcDao;
@Test
public void testAdd() {
Group g = new Group();
g.setName("文章审核人员");
groupJdbcDao.add(g);
System.out.println(g.getId());
User u = new User("tangsheng","123","唐僧");
userJdbcDao.add(u, 1);
}
}
l 添加Group(用户属于组)
主要是想介绍如何在添加一个实体后返回主键的((非重点):
@Repository("groupJdbcDao")
public class GroupJdbcDao implements IGroupDao {
private JdbcTemplate jdbcTemplate;
@Resource
public void setDataSource(DataSource dataSource) {
jdbcTemplate = new JdbcTemplate(dataSource);
}
@Override
public void add(final Group group) {
/**
* 通过以下方法可以添加一个对象,并且获取这个对象自动递增的id
*/
KeyHolder keyHolder = new GeneratedKeyHolder();
jdbcTemplate.update(new PreparedStatementCreator() {
@Override
public PreparedStatement
createPreparedStatement(Connection con)
throws SQLException {
String sql = "insert into t_group (name) value(?)";
PreparedStatement ps =
con.prepareStatement(sql,new String[]{"id"});
ps.setString(1, group.getName());
return ps;
}
},keyHolder);
group.setId(keyHolder.getKey().intValue());
}
}
@Repository("userJdbcDao")
public class UserDao implements IUserDao {
private JdbcTemplate jdbcTemplate;
@Resource
public void setDataSource(DataSource dataSource) {
jdbcTemplate = new JdbcTemplate(dataSource);
}
@Override
public void add(User user,int gid) {
jdbcTemplate.update("
insert into t_user
(username,password,nickname,gid)
value (?,?,?,?)",
user.getUsername(),
user.getPassword(),
user.getNickname(),gid);
}
@Override
public void update(User user) {
jdbcTemplate.update("update t_user set username=?,password=?,nickname=? where id=?",
user.getUsername(),user.getPassword(),user.getNickname(),user.getId());
}
@Override
public void delete(int id) {
jdbcTemplate.update("delete from t_user where id=?",id);
}
@Override
public User load(int id) {
String sql = "select t1.id uid,t1.*,t2.* from t_user t1 left join t_group t2 on(t1.gid=t2.id) where t1.id=?";
/*
* 第一个参数是SQL语句
* 第二个参数是SQL语句中的参数值,需要传入一个对象数组
* 第三个参数是一个RowMapper,这个rowMapper可以完成一个对象和数据库字段的对应,实现这个RowMapper需要
* 实现mapRow方法,在mapRow方法中有rs这个参数,通过rs可以有效的获取数据库的字段
* 如果这个方法在该DAO中会被重复使用,建议通过内部类来解决,而不要使用匿名的内部类
*/
User u = (User)jdbcTemplate.queryForObject(sql, new Object[]{id},new UserMapper());
return u;
}
@Override
public List<User> list(String sql,Object[] args) {
String sqlCount = "select count(*) from t_user";
//获取整数值
int count = jdbcTemplate.queryForInt(sqlCount);
System.out.println(count);
String nCount = "select nickname from t_user";
//获取String类型的列表
List<String> ns = jdbcTemplate.queryForList(nCount,String.class);
for(String n:ns) {
System.out.println("--->"+n);
}
String tSql = "select username,nickname from t_user";
//无法取出user
/*List<User> us = jdbcTemplate.queryForList(tSql, User.class);
for(User u:us) {
System.out.println(u);
}*/
//对象数组也无法返回
/*List<Object[]> os = jdbcTemplate.queryForList(tSql, Object[].class);
for(Object[] oo:os) {
System.out.println(oo[0]+","+oo[1]);
}*/
List<User> us = jdbcTemplate.query(tSql,new RowMapper<User>(){
@Override
public User mapRow(ResultSet rs, int rowNum) throws SQLException {
User u = new User();
u.setNickname(rs.getString("nickname"));
u.setUsername(rs.getString("username"));
return u;
}
});
for(User u:us) {
System.out.println(u);
}
return jdbcTemplate.query(sql, args, new UserMapper());
}
private class UserMapper implements RowMapper<User> {
@Override
public User mapRow(ResultSet rs, int rowNum) throws SQLException {
Group g = new Group();
g.setName(rs.getString("name"));
g.setId(rs.getInt("gid"));
User u = new User();
u.setGroup(g);
u.setId(rs.getInt("uid"));
u.setNickname(rs.getString("nickname"));
u.setPassword(rs.getString("password"));
u.setUsername(rs.getString("username"));
return u;
}
}
}