SSM框架学习笔记
1、传统javaweb项目开发
在不使用框架的传统javaweb开发中,我们常见的四个层级结构是
- domain层:用于存放javabean
- dao层:操作数据库的简单步骤,注意这里不涉及业务逻辑,仅仅是操作数据库的单个操作。例如,
public class UserDaoImpl{
public void addUser(User user){ // 增
// sql语句的增操作
}
public void deleteUser(User user){ // 删
// sql语句的删操作
}
public void updateUser(User user){ // 改
// sql语句的改操作
}
public User findUserByUsername(String username){ //查单个
// sql语句的查询操作
}
public List<User> findAll(){ // 查多个
// sql语句的查询操作
}
}
-
service层:通过调用dao层的方法来组装成一个业务逻辑。
例如,一个简单的用户注册业务逻辑是:
- 用户名是否已经存在?
- 若存在,则抛出一个错误给servlet处理。
- 若不存在,则进行把用户添加到数据库操作。
// 简单的用户注册业务
public class UserService{
private UserDao userDao = new UserDaoImpl();
public void regist(User form) throw UserException{
String username = form.getUsername();
User user = userDao.findUserByUsername(username);
// 业务逻辑:若user不为null,说明数据库中该用户名已存在,则注册失败,抛出异常给servlet处理
if (user != null){
throw new UserException("用户名已存在!");
}
// 来到这里说明可以进行注册
userDao.addUser(form);
}
}
- web层servlet:调用service对象执行相应的页面需求业务
public class UserServlet extends BaseServlet {
private UserService userService = new UserService();
public String regist(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException{
User form = new User();
// apache的工具类BeanUtils,把一个map对象封装成javabean,前提是表单字段名和javabean属性名相同
BeanUtils.populate(form,request.getParameterMap());
try {
userService.regist(form);
} catch (UserException e) {
request.setAttribute("msg",e.getMessage());
// 打回注册页面提示错误
}
request.setAttribute("msg","注册成功!");
// 跳转到注册成功的信息页面
}
}
1.1 传统javaweb项目开发存在的一些缺点
- Dao层频繁地创建和释放连接会造成系统资源浪费,影响性能。(可通过c3p0等连接池解决问题)
- sql语句写死在java代码的Dao层(硬编码),修改sql语句需要改变java代码。(mybatis框架:sql语句写在配置文件,维护方便)
- 对Dao层和Service层的对象增强,需要去修改源码,不方便。(可通过jdk动态代理/cglib动态代理解决,使用spring框架管理更方便)
- (springMVC层优点,待更新)
2、Spring
2.1 IOC:控制反转
在传统的java代码中,创建一个类对象需要通过new语句进行,(当然还可以通过反射)。
控制反转是指,对象的创建不再是程序员主动new出来,而是spring通过解析读取配置文件自动创建类对象并存放到自身容器中。程序员需要时,从spring容器中获取相应对象。(对象的创建不再是程序员来控制而是spring控制)
2.1.1 IOC-xml配置文件方式
<!-- spring配置文件 src下文件名:applicationContext.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<beans > <!-- 这里出于篇幅考虑省略spring约束配置,约束配置最直观的体现就是idea中代码提示的可选项 -->
<bean id="order" class="cn.myh.domain.Order" scope="singleton" >
<property name="oid" value="order_001" />
<property name="product" value="switch" />
</bean>
<bean id="user" class="cn.myh.domain.User" scope="singleton">
<property name="username" value="马里奥" />
<property name="password" value="123456" />
<property name="order" ref="order" />
</bean>
</beans>
public class testSpring{
@Test
public void testIOC(){
// 读取spring配置文件applicationContext.xml
ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
// spring考虑通用性,getBean方法返回类的最高形态Object,需要进行强转
User user = (User)ac.getBean("user");
System.out.println(user);
// 可以看到输出结果不仅成功获得了User对象,还成功为属性username和password赋值为"马里奥"和"123456"
// User里的订单对象也被赋值为实例对象
}
}
- spring配置文件详解:
- 每一个bean标签代表一个类对象
- id为该javebean的唯一身份码(身份证)
- class为它的全限定类名
- scope表示javabean的作用范围,可选为:
- singleton:默认值,单例设计模式
- prototype:多例设计模式
- request:在WEB项目中,spring创建一个Bean的对象,并将该对象存入到request域
- session:WEB项目,spring创建一个Bean并存在session域
- property标签:依赖注入DI,表示为该javabean的属性值,注入属性。需要强调的是,必须提供相应属性的set方法
- 基本类型用value属性:name为属性字段名,value为相应属性值
- 对象类型用ref:name为属性字段名,ref为相应javabean的id值
除了为基本类型和对象类型注入属性外,spring还提供了复杂属性(array/list/map)的依赖注入方法,如下。
<bean id="user" class="cn.itcast.ioc.User">
<!-- 为Array类型的属性进行属性注入 -->
<!-- 对应数组属性名arrs;数组只有一个值时可用value="xxx"即可 -->
<property name="arrs" >
<array>
<value>马里奥</value>
<value>笨币小蓝</value>
<value>闸总陆毅</value>
</array>
</property>
<!-- 为List属性进行属性注入 -->
<property name="list" >
<list>
<value>马里奥</value>
<value>笨币小蓝</value>
<value>闸总陆毅</value>
</list>
</property>
<!-- 为List属性进行属性注入 -->
<property name="map" >
<map>
<entry key="mario" value="马里奥" />
<entry key="luigi" value="路易吉" />
<entry key="qinuobiao" value="奇诺比奥" />
</map>
</property>
</bean>
2.1.2 IOC-注解方式
从spring2.0版本后,spring引入了基于注解的配置方式。新引入的方式自然需要引入新的约束:context约束。引入约束之后,开启上下文扫描功能,spring将会扫描base-package包下,在类/方法/属性上的注解进行依赖注入。
<?xml version="1.0" encoding="UTF-8" ?>
<beans > <!-- 出于篇幅,约束略 -->
<!-- 开启后spring会扫描cn.itcast包下的类、方法、属性上的注解 -->
<context:component-scan base-package="cn.itcast"></context:component-scan>
</beans>
@Service(value="userService")
public class UserService{
@Autowired //在属性上使用Autowired注解,将自动根据属性类型完成依赖注入
private UserDao userDao;
@Resource(name="user") // 除了上面这种方法注入属性,还可以用这种方法,名字和组件的value必须对应
private User user;
}
@Component(value="user")
public class User{
//...
}
- 作用在类上的注解:目前功能一样,是spring为了让标注类本身的用途更清晰明确,而且会在后续进行增强。
- @Component 作用在类上,等价于 bean id="user" class="" 。其中value值相当于id值
- @Controller:用在web层
- @Service:用在service层
- @Repository:持久层
- 作用在属性上的注解:
- @Autowired:自动根据属性类型去找相应类型的对象进行注入
- @Resource:需要显式声明value值,根据value去寻找相应对象完成注入
2.2 AOP:面向切面编程思想
面向切面编程思想,是指对原有代码进行功能拓展时,通过不修改源码的方式来实现增强的一种思想。
- AOP操作相关术语
- Joinpoint(连接点):任何可能被增强的方法,都可以称之为连接点。
- Pointcut(切入点):被增强了的方法,称之为切入点。
- Advice (通知/增强):要添加的增强操作,例如需要在某一方法执行前后,打印时间。打印时间这一操作就是增强。
- Advice可分为:前置通知,后置通知,环绕通知,异常抛出通知,最终通知,
引介通知。(这里通知/增强的说法没有固定) - 前置通知、后置通知、环绕通知:如字面意思,分别在方法的前、后、以及前和后执行的通知。
- 异常抛出通知:当被增强的方法抛出异常时,才会执行该通知。类似于try..catch的catch部分。
- 最终通知:不管是否抛出异常都会执行的通知。类似于try..catch..finally的finally部分。
- Advice可分为:前置通知,后置通知,环绕通知,异常抛出通知,最终通知,
- Aspect (切面): 切入点和通知的结合体。即,把增强的内容应用到具体方法上的全过程。
- 通过AspectJ来实现AOP操作
- 相关jar包和aop约束:aopalllliance.jar,aspectj.weaver.jar,spring-aop.jar,spring-aspects.jar
- 定义切入点语法:两个execution语句可以用||来连接,表示任意匹配一个都可以
execution( <访问修饰符>?<返回类型><方法名> ( <参数> ) <异常> ) // 表示对Book类下的add方法增强,..表示任意个数的参数 1. execution(* cn.itcast.aop.Book.add(..)); // 对Book类下所有方法增强 2. execution(* cn.itcast.aop.Book.*(..)); // 对所有类下的所有方法增强 3. execution(* *.*(..)); // 匹配所有以save开头的方法 4. execution(* save*(..));
- 基于aspectj的xml配置实现
<beans >
<bean id="book" class="cn.myh.aop.Book"></bean>
<bean id="myBook" class="cn.myh.aop.MyBook"></bean>
<!-- 配置aop -->
<aop:config>
<!-- 配置切面,Book是被增强的,而MyBook是增强的内容 -->
<aop:aspect ref="myBook">
<!-- 配置切入点,本例切入点为Book的add()方法,即要增强该方法 -->
<aop:pointcut id="pointcut1"
expression="execution(* cn.myh.aop.Book.add(..))"/>
<!-- aop:before表示前置增强,使用myBook的beforeAdd()方法,对切入点进行增强 -->
<aop:before method="beforeAdd" pointcut-ref="pointcut1"></aop:before>
<aop:after-returning method="AfterAdd" pointcut-ref="pointcut1"
returning="ret"></aop:before>
<aop:arround method="arroundAdd" pointcut-ref="pointcut1" />
<aop:after-throwing method="afterThrowing"
pointcut-ref="pointcut1" throwing="e" />
<aop:after method="finally" pointcut-ref="pointcut1" />
</aop:aspect>
</aop:config>
</beans>
@Component
@Aspect
public class MyBook{
// 增强方法可以带参数(可选),参数必须是org.aspectJ.JoinPoint类型,而不是Joinpoint!
// org.aspectJ.JoinPoint用于描述目标方法,可以获得目标方法签名(修饰符,返回值,参数)
public void beforeAdd(JoinPoint joinpoint){
joinpoint.getSignature(); // 方法签名
joinpoint.getSignature().getName() // 方法名
}
// 后置通知,第一个参数可选,可以获得目标方法的返回值,即第二个参数。注意,类型为Object,名称ret必须与配置文件的相同
public void AfterAdd(JoinPoint joinpoint,Object ret){
joinpoint.getSignature(); // 方法签名
joinpoint.getSignature().getName() // 方法名
}
// 环绕通知 - 返回值必须是Object,需要手动执行目标方法,返回目标方法的结果
// 参数必须是ProceedingJoinPoint类型
public Object aroundAdd(ProceedingJoinPoint joinpoint) throws Throwable{
System.out.println("前置")
// 手动执行目标方法
Object obj = joinpoint.proceed();
System.out.println("后置")
return obj;
}
// 异常通知 异常名称e和xml配置文件必须要对应
public void afterThrowing(JoinPoint joinpoint , Throwable e){
System.out.println("异常抛出:"+e.getMessage());
}
// 最终通知
public void finally(JoinPoint joinpoint){
}
}
- 基于aspectj的注解方式
- 配置文件中开启aop的自动代理扫描
<beans >
<!-- 开启bean的注解扫描 -->
<context:component-scan base-package="cn.itcast"></context:component-scan>
<!-- 开启AOP的自动代理注解 -->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>
// 在实现增强的类上使用注解
@Component("myBook")
@Aspect // 切面注解声明
public class MyBook {
// 前置增强,这里即是切入点,无需显式配置,但切入点仅该方法可见
@Before(value="execution(* cn.itcast.aop.Book.*(..))")
public void beforeAdd(){
System.out.println("前置增强.....");
}
// 也可以配置公共的切入点表达式
@Pointcut("execution(* cn.itcast.aop.Book.*(..))")
private void myPointcut(){}
// 后置增强,value值不再写切入点表达式,而是使用公共表达式
@AfterReturning(value="myPointcut()",returning="ret")
public void afterReturning(JoinPoint joinpoint,Object ret){
System.out.println("后置增强.....");
}
}
- 常见的AspectJ注解类型
- @Before 前置通知
- @AfterReturning 后置通知
- @Around 环绕通知
- @AfterThrowing 异常抛出通知
- @After 最终通知
- @Pointcut 用于声明切入点