概述:
- Spring的概述、SpringIOC入门(XML)、Spring的Bean管理、Spring属性注入
- Spring的IOC的注解方式、Spring的AOP开发(XML)
- Spring的AOP的注解开发、Spring的声明式事务、JdbcTemplate。
- SSH的整合、HibernateTemplate的使用、OpenSessionInViewFilter的使用。
1. 快速引入遗留问题
在案例中,遗留的一个程序问题是:每次请求都会创建一个Spring的工厂,这样浪费服务器资源,应该一个项目只有一个Spring的工厂。在实际开发中,应该遵循:
-
在服务器启动的时候,创建一个Spring的工厂。
-
创建完工厂,将这个工厂类保存到ServletContext中。
-
每次使用的时候都从ServletContext中获取。
在整合Web项目时的解决方法:引入Spring_web.jar,使用ServletContextListener来监听ServletContext对象的创建和销毁,在使用时通过工具类加载工厂applicationContext。也就是通常说的“Spring核心监听器”。注意:Spring_wab.jar中默认使用的是/WEB-INF/applicationcontext.xml,要手动修改。
2. IOC的注解方式
2.1 使用案例
-
IOC注解方式的JAR包:4核心+2日志,Spring4.x后额外需要一个aop的依赖。
-
xml中引入Context的相关约束:解压目录下
/docs/spring-framework-reference/html/xsd-configuration.html
-
示例:
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <!-- 使用context来告知Spring的注解形式 --> <context:component-scan base-package="com.leehao.springlearning.Anno1" /> </beans> -------------------------------------Java--------------------------------------- @Component(value="userDao") public class UserDaoImpl implements UserDao { @Value("HHHH") private String name; // public void setName(String name) { // this.name = name; // } @Override public void save() { System.out.println("Anno1....save:" + name); } }
2.2 Component类注解
- 注解Bean时,使用时必须在xml中配置
<context:component-scan>
,按名称注入- @符号标识,是一个类;
- 可以使用注解代替xml配置
- @component 取代<bean calss=>
- @component(value=“id”) 取代<bean id=,calss=>,value可以省略
- Component注解的三个衍生注解,便于开发,后续Spring将逐渐分开支持
- controller:Web层
- service:Service层
- Repository:Dao层
- 使用与直接使用Component等效
2.3 注入属性的注解
属性的注入可以直接在属性的上方,也可以另外生产set方法,对方法进行修饰。
-
普通值:@Value
-
对象引用:@Autowired,按属性类型自动注入,单独可以使用。一般地,和@Qualifier(value=“”)一同使用
- 对象引用2:@Resource(name=“”),按照名称进行注入(Spring引用的其他接口规范的注解),
name
不可省略。
@Service("userService") public class UserServiceImpl implements UserService { // @Autowired // @Qualifier(value="userDao") //两个注解同时使用,按名称注入,value可以省略 @Resource(name="userdao") private UserDao userDao; @Override public void save() { userDao.save(); System.out.println("UserService save..."); } }
- 对象引用2:@Resource(name=“”),按照名称进行注入(Spring引用的其他接口规范的注解),
2.4 Bean的其他注解
-
生命周期的注解
- @PostConstruct:初始化注解,相当于<bean>标签中的
init-method
- @PreDestroy:销毁注解,相当于<bean>标签中的
destroy-method
- @PostConstruct:初始化注解,相当于<bean>标签中的
-
类/bean作用范围注解,后三种不常用
-
@Scope(“prototype”):默认为singleton,单例;
-
同样,在多例中Spring无法进行销毁
-
request
-
session
-
globalsession
-
2.5 XML和注解方式比较
-
比较
- XML结构清晰,任何情况下都适用
- 注解方便,但是只能对自定义的类进行注解,如一些默认的源码或工具类无法配置
-
混合开发的方式
- 如XML管理Bean,属性注入使用注解
- 注意:使用<context:component-scan>开启Bean的注解,而若只用属性注入等注解时,只开启
<context:annotation-config />
也可。
<!-- 使用context来告知Spring的注解形式 --> <!-- <context:component-scan base-package="com.leehao.springlearning" /> --> <!-- 可以不开启扫描,只开启注释配置 --> <context:annotation-config /> <bean id="productDao" class="com.leehao.springlearning.Anno2.ProductDao" /> <bean id="userService" class="com.leehao.springlearning.Anno2.UserServiceImpl"/>
-
3 AOP
面向切面编程,底层以代理方式实现;用于事务管理、性能监控、缓存、日志等。
3.1 概念
AOP是OOP的一种延伸,一种新的设计理念。是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
- AOP采取横向抽取机制,取代了传统纵向继承体系重复性代码
- 经典应用:事务管理、性能监视、安全检查、缓存、日志等
- Spring AOP使用纯Java实现,不需要专门的编译过程和类加载器,运行期通过代理方式向目标类织入增强代码
- AspectJ是一个基于Java语言的AOP框架,Spring2.0开始,Spring AOP引入对Aspect的支持,AspectJ扩展了Java语言,提供了一个专门的编译器,在编译时提供横向代码的织入
3.2 Spring的代理机制
Spring底层使用动态代理的机制,来完成功能的增强;在不改变源代码基础上进行拓展增强。
-
JDK 的动态代理:针对实现了接口的类(接口+实现类)产生代理,Spring默认使用的代理机制
-
Cglib 的动态代理 :针对没有实现接口的类产生代理。应用底层的字节码增强的技术,生成当前类的子类对象。类似于Javassist,Hibernate里使用的——早期版本用的也是cglib。
-
JDK的底层实现——手动示例:
* 实现InvocationHandler完成自身的代理执行,使用匿名内部类或其他类亦可 public class JDKProxy implements InvocationHandler { private UserDao userDao; //通过目标对象的引用,对其进行功能的增强 public JDKProxy(UserDao userDao) { this.userDao = userDao; } //生成代理对象 public UserDao createProxy() { UserDao userDaoProxy = (UserDao) Proxy.newProxyInstance( userDao.getClass().getClassLoader(), userDao.getClass().getInterfaces(), this); return userDaoProxy; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //实现增强,proxy创建实例后的执行方法 System.out.println("增强之前...."); return method.invoke(userDao, args); //执行原来的方法 } }
-
Cglib的底层实现——手动示例:
spring和核心包已经包含了cglib的包库。Cglib使用一个
Enhancer
的核心对象来创建代理对象,但需要说明的是,它采用的是继承目标对象的方式,一旦目标对象是Final
时则无法实现。public class CglibPorxy implements MethodInterceptor{ private CustomerDao customerDao; public CglibPorxy(CustomerDao customerDao) { this.customerDao = customerDao; } //创建代理 public CustomerDao createCustomer() { //1.enhancer核心 Enhancer enhancer = new Enhancer(); //2.继承目标类 enhancer.setSuperclass(CustomerDao.class); //3.通过回调生成代理 enhancer.setCallback(this); return (CustomerDao) enhancer.create(); } @Override public Object intercept(Object object, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { //执行增强 System.out.println("增强..."); return method.invoke(customerDao, args); //methoProxy.invokeSuper(customerDao, args); } }
3.3 Spring的AOP——基于AspectJ的XML
AOP是一个概念,存在AOP联盟,而Spring只是较好地运用起来。Spring原始的AOP技术较为复杂,引入了一个AspectJ的框架,来支持Spring的AOP编程。
-
AOP相关术语
Joinpoint(连接点) :所谓连接点是指那些被拦截到的点。在spring中,这些点指的是方法,因为spring只支持方法类型的连接点. Pointcut(切入点) :所谓切入点是指我们要对哪些 Joinpoint 进行拦截的定义 Advice(通知/增强) :所谓通知是指拦截到 Joinpoint 之后所要做的事情就是通知。通知分为前置通知、后置通知、异常通知、最终通知、环绕通知(切面要完成的功能) Introduction(引介) :引介是一种特殊的通知在不修改类代码的前提下,Introduction可以在运行期为类动态地添加一些方法或 Field Target(目标对象) :代理的目标对象 Weaving(织入) :是指把增强应用到目标对象来创建新的代理对象的过程。spring 采用动态代理织入,而AspectJ 采用编译期织入和类装载期织入 Proxy(代理):一个类被 AOP 织入增强后,就产生一个结果代理类 Aspect(切面): 是切入点和通知(引介)的结合
-
-
JAR包:
- 基础Spring的4+2
- AOP:spring-aop、aspectj、aop联盟、spring-aspects整合包
-
spring的aop的XML约束:xmlns命名空间
3.4 案例Demo
-
整合Spring的Junit,使用注解的方式完成工厂的加载,对applicationConetext.xml文件进行解析,在使用时直接注入需要获取的bean,方便测试。
-
Jar包:spring-test.xxx.jar
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration("classpath:applicationContext.xml") public class AspectJDemo1 { @Resource(name="productDao") private ProductDao productDao; @Test public void demo1(){ productDao.save(); productDao.delete(); productDao.update(); } }
-
-
将需要增强的功能通过切面类来组织,并使用Spring中的xml进行配置。
<!-- aspectj --> <bean id="productDao" class="com.leehao.springlearning.aspectj1.ProductDaoImpl"/> <!-- aop的配置 1.切面类(增强的封装):执行的什么增强 2.切面:将哪个对象用到了什么增强,联系在一起;执行什么样的增强(前,后,异常等) 3.切入点表达式:在对象的什么位置执行 --> <bean id="myAspect" class="com.leehao.springlearning.aspectj1.MyAspect"/> <aop:config > <!-- 切入点表达式 --> <aop:pointcut expression="execution(* com.leehao.springlearning.aspectj1.ProductDaoImpl.save(..))" id="pointcut1"/> <!-- 切面 --> <aop:aspect ref="myAspect"> <aop:before method="checkPri" pointcut-ref="pointcut1"/> </aop:aspect> </aop:config>
-
Apsect的通知类型:
-
前置通知:可以使用
Jointpoint
获得切入点的相关属性public void checkPri(JoinPoint joinPoint){ System.out.println("增强..." + joinPoint); } ------------------------------------------------------------------- <aop:aspect ref="myAspect"> <aop:before method="checkPri" pointcut-ref="pointcut1"/> </aop:aspect>
-
后置通知:可以获得方法的返回值,但xml的配置和方法的通知方法的形参名必须一致
public void writeLog(Object res) { System.out.println("后置....日志:" + res); } ------------------------------------------------------------------- <aop:after-returning method="writeLog" pointcut-ref="pointcut2" returning="res"/>
-
环绕通知:前后执行信息,使用ProceedingJoinPoint类型参数控制切入点的执行(可以阻止)。
public Object around(ProceedingJoinPoint point) throws Throwable { System.out.println("环绕前.."); Object object = point.proceed(); System.out.println("环绕后.."); return object; } ------------------------------------------------------------------- <aop:around method="around" pointcut-ref="pointcut3"/>
-
异常抛出
public void afterThrow(Throwable tw) { System.out.println(tw.getMessage()); } ------------------------------------------------------------------- <aop:after-throwing method="afterThrow" pointcut-ref="pointcut1" throwing="tw"/>
-
最终通知:相当于finally块
public void finalReturn() { System.out.println("finallyReturn.."); } ------------------------------------------------------------------- <aop:after method="finalReturn" pointcut-ref="pointcut1"/>
-
3.5 Aop 的切入点表达式
切入点表达式基于execution函数完成。
表达式:[方法访问修饰符] 方法返回值 包名.类名.方法名(方法的参数)
- public * cn.itcast.spring.dao.*.*(…)
- * cn.itcast.spring.dao.*.*(…) dao下的所有类的所有方法
- * cn.itcast.spring.dao.UserDao+.*(…)* ——+号表示含其子类
- * cn.itcast.spring.dao…*.*(…) dao下所有子包的所有类的所有方法
4 AOP的注解方式
基于AspectJ的注解方式。除了Bean之外的文件需要在xml文件中配置外,其他如切面类、切入点(含表达式)、通知类型等都直接在需要增强的地方使用注解来完成。
-
切面类:@Aspect
-
通知类型:@Before,@After,@AfterReturning,@Around,@AfterThrowing
-
@Aspect public class MyAspectAnno { @Before(value="execution(* com.leehao.springlearning.aop_anno.CustomerDaoImpl.save(..))") public void before(JoinPoint joinPoint) { System.out.println("before..." + joinPoint); } @AfterReturning(value="execution(* com.leehao.springlearning.aop_anno.CustomerDaoImpl.delete(..))",returning="res") public void afterReturn(Object res) { System.out.println("afterReturn..." + res); } @Around("execution(* com.leehao.springlearning.aop_anno.CustomerDaoImpl.update(..))") public Object around(ProceedingJoinPoint point) throws Throwable { System.out.println("环绕前.."); Object object = point.proceed(); System.out.println("around...后"); return object; } @AfterThrowing(value="execution(* com.leehao.springlearning.aop_anno.CustomerDaoImpl.query(..))",throwing="e") public void afterThrow(Throwable e) { System.out.println("afterThrow..."+e.getMessage()); } @After("execution(* com.leehao.springlearning.aop_anno.CustomerDaoImpl.query(..))") public void finalThrow() { System.out.println("finalThrow...."); } }
-
注解切入点:可以在使用注解时,单独定义切入点,在需要使用时进行引用即可。可以是作为将执行的切入点表达式抽离出来了。
//声明切入点注解 @Pointcut(value="execution(* com.leehao.springlearning.aop_anno.CustomerDaoImpl.save(..))") public void pointCut1(){} @Pointcut(value="execution(* com.leehao.springlearning.aop_anno.CustomerDaoImpl.delete(..))") public void pointCut2(){} @Pointcut(value="execution(* com.leehao.springlearning.aop_anno.CustomerDaoImpl.update(..))") public void pointCut3(){} //value可省略 @Pointcut("execution(* com.leehao.springlearning.aop_anno.CustomerDaoImpl.query(..))") public void pointCut4(){} ----------------------------使用--------------------- @Before(value="MyAspectAnno.pointCut1()") //value可省略 public void before(JoinPoint joinPoint) { System.out.println("before..." + joinPoint); }
5 Spring的JDBC
Spring为一站式开发框架,其持久层的支持则是ORM框架,另外一种重要的就是JDBC。Spring提供了许多的模板,简化了对各个持久层框架的支持,JDBC模板就是其中一种,另外还有如支持Hibernate的、Mybatis的模板。
-
一个示例:
* 使用JDBC模板进行创建连接,对数据库进行操作 * JDBC模板的使用类似于JDBCUtils public void demo1(){ //1.创建连接池 DriverManagerDataSource dataSource = new DriverManagerDataSource(); dataSource.setDriverClassName("com.mysql.jdbc.Driver"); dataSource.setUrl("jdbc:mysql://localhost:3306/springlearning"); dataSource.setUsername("root"); dataSource.setPassword("123456"); //2.创建JDBC模板,需要连接池 JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource); jdbcTemplate.update("insert into user values(null, ?, ?)", "王五", 2000d); }
5.1 JDBC的模板使用
使用Spring的注解方式,将JDBC模板及其依赖的数据源,都交给Spring去完成,在配置文件中配置相应的Bean。注意:Spring4之后,使用时需要引入AOP的jar包。
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class JdbcDemo2 {
@Resource(name="jdbcTemplate")
private JdbcTemplate jdbcTemplate;
@Test
public void demo1() {
jdbcTemplate.update("insert into user values(null, ?, ?)", "zhaoliu", 2000d);
}
}
-----------------------------------------------------------
<!-- 配置数据驱动源 -->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<!-- 设置属性 -->
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/springlearning"/>
<property name="username" value="root" />
<property name="password" value="123456"/>
</bean>
<!-- JDBC模板 -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"/>
</bean>
5.2 其他的开源数据库连接池
-
DBCP:DataBase Connection Pool。使用第三方的开源数据库连接池,性能更加稳定,适用于生成环境。需要引入commons的两个包:
- com.springsource.org.apache.commons.pool-1.5.3.jar
- com.springsource.org.apache.commons.dbcp-1.2.2.osgi.jar
<!-- DBCP数据源 --> <bean id="dataSource2" class="org.apache.commons.dbcp.BasicDataSource"> <property name="driverClassName" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/springlearning"/> <property name="username" value="root" /> <property name="password" value="123456"/> </bean>
-
C3P0:com.springsource.com.mchange.v2.c3p0-0.9.1.2.jar
<!-- C3P0 --> <bean id="dataSource3" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <property name="driverClass" value="com.mysql.jdbc.Driver" /> <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/springlearning"/> <property name="user" value="root" /> <property name="password" value="123456"/> </bean>
5.3 使用properties配置数据源
将数据源设置的相应的值,以properties的格式保存到属性文件中;在XML中配置对properties文件的引用,然后使用${key}的形式进行引用。
jdbc.driverClass=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/springlearning
jdbc.username=root
jdbc.password=123456
--------------------------------------------------------------
<!-- 引入properties -->
<!-- 方式1 -->
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="location" value="classpath:jdbc.properties" />
</bean>
<!-- 方式二 更常用-->
<context:property-placeholder location="classpath:jdbc.properties"/>
<bean id="dataSource4" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="${jdbc.driverClass}" />
<property name="jdbcUrl" value="${jdbc.url}"/>
<property name="user" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}"/>
</bean>
5.4 JDBC的CRUD操作
//JDBC的CRUD操作
@Test
public void demo2(){
//修改
jdbcTemplate.update("update user set name=? where id=?", "666", 4);
//删除
jdbcTemplate.update("delete from user where id=?", 4);
}
@Test
public void demo3(){
Integer integer = jdbcTemplate.queryForObject("select count(*) from user", Integer.class);
System.out.println(integer);
}
.....其他的query/queryForObject/update等操作方法