SSM框架

Spring

基本概念

  • 在没有Spring之前,程序是通过public UserService user = new UserService() 的形式,来给一个变量赋予引用值,user变量定义后就可以直接user.xxx()的来调用方法.
  • 在有Spring之后,可以直接使用public UserService user 的形式给来定义变量,而省略new UserService()这一部分,这部分由Spring来进行.
  • 由Spring创建对象,称其为”控制反转”,Spring创建对象的过程中,发现对象里面还有一个成员变量需要赋值,赋值的过程称为”依赖注入”.

控制反转

简称为IOC,英文全称:Inversion of Control,为什么原本自己new一个对象,现在要交由Spring交由Spring来管理,如需详细了解好处点击进入,我的理解就是解耦合,提升代码的灵活性- -

那么问题就来了,现在由Spring来new对象,总得告诉Spring大佬,应该new哪个对象出来,告诉Spring有两种方式如下:

  • 配置XML文件
  • 使用注解

告诉了Spring大佬new哪个对象后,如果对象里面有成员变量,还得告诉大佬,得给这个成员变量赋值,具体XML实例如下:

1
2
3
4
5
6
7
8
<bean id="mainUI" class="com.jungle.www.controller.MainUI">

<property name="jungle" ref="userService"/>

</bean>
<bean id="userService" class="com.jungle.www.service.serviceimp.UserServiceImpl">

</bean>

bean标签:告诉Spring我要开始创对象

  • id:指定对象名

  • class:类的全路径,写哪个类,就会创建哪个类的对象

  • property标签:需要Spring赋值的成员变量

    • name:需要赋值的成员变量名
    • ref:代表引用其他bean的id名所具有的值

这里面有两个bean,所以创建了两个对象,名字由id指定,分别名叫:mainUIuserService,其中mainUI实例里有一个叫jungle的成员变量,这个成员变量的值是通过ref来引用了userService对象的值,上述的配置,等同于以下:

1
2
3
public void MainUI{
private UserServiceImpl jungle = new UserServiceImpl();
}

上面的例子里的成员变量,是引用类型,也就是一个对象,那么基本数据类型又如何解决赋值问题?集合又怎样解决赋值问题?系统的归纳如下:

属性赋值

  • value注入基本类型

    • <bean id="mainUI" class="com.MainUI">
         <property name="str" value="李白"/>
      </bean>
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11

      > 上面的效果,创建了`com.MainUI`类(com是包名,MianUI是类名)的名叫`mainUI`的实例,并且给里面的str成员变量赋值为 李白,等同于如下:
      >
      > ```java
      > public void MainUI{
      > private String str;
      > /*Spring注入值时,是通过setter方法来的,因此需要注入的成员变量必须提供setter方法*/
      > public void setStr(String str){
      > this.str=str;
      > }
      > }
  • ref 注入已有的bean

    • 最开始的例子就是使用ref来给一个对象注入值的,在官方源码有一句话:The name of the property, following JavaBean naming conventions,对于对象的值的注入,一般就是通过引入其他的bean的id名来注入的.
  • List、Set、Map

    • <property name="list">
          <list>
              <value>J</value>
              <value>G</value>
          </list>
      </property>
      
      1
      2
      3
      4
      5
      6
      7
      8
      9

      - ```xml
      <property name="set">
      <set>
      <value>1</value>
      <value>2</value>
      <value>3</value>
      </set>
      </property>
    • <property name="map">
          <map>
                  <entry key="dock" value="yellow"/>
          </map>
      </property>
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32

      ## 依赖注入

      英文简称DI

      英文全称: Dependency Inject

      #### 注入方式

      - setter注入
      - 常用方式,必须提供setter方法,也就是上面给成员变量时赋值所用到的方式
      - 构造器注入
      - <constructor-arg name="参数名字" index="参数序号" value="参数值" ref="引用的bean" >
      - 注:name||index任选一项即可,value||ref任选一项即可

      ## 属性注入实例

      ### 1.项目整体结构及运行效果图

      ![](https://cdn.jsdelivr.net/gh/Griffonage/Figurebed/img/20200909141952.png)

      ### 2.导入依赖

      既然使用Spring框架,就应该导入Spring的东西,在eclipse是导入jar包,在idea是导入maven依赖方式进行的,需要导入的依赖如下:

      ```xml
      <!--SpringFrameWork-->
      <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>5.2.0.RELEASE</version>
      </dependency>

当导入此依赖时,maven会自动将相关依赖导入,不需要我们去自己导入,依赖关系如下图所示:

3.各模块主要代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
//User类
public class User {
private String account;
private String password;
//这里省略了getter和setter方法
}
//UserServiceImpl类,未赋值的成员变量必须拥有setter方法
public class UserServiceImpl implements UserService {

private Object obj;
private String account;
private List<String> list;
private Map<String,Integer> map;
private Set<Integer> set;

public void setObj(Object obj) {
this.obj = obj;
}

public void setAccount(String account) {
this.account = account;
}

public void setList(List<String> list) {
this.list = list;
}

public void setMap(Map<String, Integer> map) {
this.map = map;
}

public void setSet(Set<Integer> set) {
this.set = set;
}

@Override
public String toString() {
return "UserServiceImpl{" +
"obj=" + obj +
", account='" + account + '\'' +
", list=" + list +
", map=" + map +
", set=" + set +
'}';
}
}
//APP类,负责测试
public class App
{
public static void main( String[] args )
{
//读取配置文件
ApplicationContext context = new ClassPathXmlApplicationContext(
"applicationContext.xml");
//得到UserService对象
UserService userService = context.getBean("userService", UserServiceImpl.class);
//查看类中的成员变量赋值情况
System.out.println(userService.toString());
}
}

resources文件夹下的applicationContext.xml文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<!--UserServiceImpl类中的名叫obj的成员变量引用此值-->
<bean id="user" class="com.jungle.www.controller.entity.User"/>

<!--创建UserServiceImpl类的实例-->
<bean id="userService" class="com.jungle.www.controller.Service.ServiceImpl.UserServiceImpl">
<!--给UserServiceImpl类中的名叫obj的成员变量赋值-->
<!--UserServiceImpl类必须给变量obj设置setter方法,才能注入值-->
<property name="obj" ref="user"/>
<!--给String类型名叫account的成员变量赋值-->
<property name="account" value="李白"/>
<!--list集合-->
<property name="list">
<list>
<value>J</value>
<value>G</value>
</list>
</property>
<!--map集合-->
<property name="map">
<map>
<entry key="张三" value="15"></entry>
</map>
</property>
<!--set集合-->
<property name="set">
<set>
<value>1</value>
<value>2</value>
</set>
</property>
</bean>

完整项目链接

对象的实例化方式

也可以称作bean的实例化方式

上面通过bean来实例化对象时,默认采用的是无参构造来进行实例化,还有其他几种方法,也需要知道以下,下面总结一下:

  • 默认构造器实例化

    • <bean id="mainUI" class="com.jungle.com.MainUI">

      效果:创建了com.jungle.com.MainUI类的名叫mainUI的实例

      注意:需要com.jungle.com.MainUI类里具有无参构造!

  • 静态工厂实例化

    • <bean id="today" class="com.jungle.com.service" factory-method="getObject"/>

      效果:通过com.jungle.com.service类的名叫getObject的静态方法,创建一个名叫today的实例

  • 实例工厂实例化

    • //Factory类
      public class Factory {
      public Robot getRobot(){
                  return new Robot();
          }
      }
      
      1
      2
      3
      4
      5
      6
      7
      8

      - ```java
      //Robot类
      public class Robot {
      public void say(){
      System.out.println("i am a robot");
      }
      }
      1
      2
      3
      <!--XML-->
      <bean id="robot" factory-bean="factory" factory-method="getRobot"/>
      <bean id="factory" class="com.Factory"/>
      > 第一个bean > > - id:以后读取的时候,就是读取这个id来得到对象 > - factory-bean:指定生产者 > - factory-method:指定生产者所使用的方法 > > 第二个bean:创建一个com.Factory类的叫factory的对象
  • Spring的FactoryBean接口实例化

    • 类实现FactoryBean<T>接口,重写getObject()getObjectType()方法

    • 配置XML文件,bean的实例为getObject方法的返回值

    • 具体实例如下所示:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      //java代码
      public class Factory implements FactoryBean<User> {
      @Override
      public User getObject() throws Exception {
      return new User(1,"李白","123");
      }

      @Override
      public Class<?> getObjectType() {
      return User.class;
      }
      }
      //xml配置
      <bean id="user" class="com.jungle.www.controller.Factory"/>
      //注:在测试类中,使用getbean("user")即可得到User的实例

核心API

读取文件时,就是如下表格所示API来实现

BeanFactory IOC容器的基本接口
ApplicationContext 扩展的IOC容器接口
ClassPathXmlApplicationContext 基于xml配置文件的IOC容器

看一下下面的关系图,可能理解的更深

上面的AnonotationConfigApplicationContext在读取注解的配置时会用到,,图片来源于点击进入,作者很不错,有很多的源码的具体分析。

bean的作用域

概念

在上面的属性注入实例中,多次使用getbean获取到的实例,总会是同一个实例,这和bean的作用域有关,获取实例如下图所示:

总结归纳如下:

英文名 解释
prototype 原型,每次创建一个实例
singleton 单例[默认],一个bean的定义,只有一个实例,不是一个类只有一个实例
request 一个请求一个实例(仅适用于WebApplicationContext环境)
session 一个会话一个实例(仅适用于WebApplicationContext环境)
globalSession 用于Protlet应用环境(仅适用于WebApplicationContext环境)

修改作用域

XML实现

在bean的里面配置scope="prototype或者singleton"

注解实现

使用注解@Scope来指定,如下例所示

  • @Component
    @Scope(ConfigurableListableBeanFactory.SCOPE_PROTOTYPE)
    public class BeanExample {
    
        private String test;
    
        public String getTest() { return test; }
    
        public void setTest(String test) { this.test = test;}
    }
    
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39

    下图所示,为使用XML修改作用域后,测试的结果![](https://cdn.jsdelivr.net/gh/Griffonage/Figurebed/img/20200909150733.png)

    ## bean的生命周期

    如下图所示,[图片来源](https://www.cnblogs.com/zrtqsk/p/3735273.html)

    ![](https://cdn.jsdelivr.net/gh/Griffonage/Figurebed/img/20200909153438.png)

    ![](https://cdn.jsdelivr.net/gh/Griffonage/Figurebed/img/20200909153510.png)

    ### 重写Bean生命周期内的方法

    > ​ 注意:为了更加符合历史发展规律,许多例子都是使用XML配置来实现的,一般来说,都有对应的注解方式来解决

    - 在bean的XML配置上写`init-method`和`destroy-method`

    - 实现Java类实现`InitializingBean`和`DisposableBean`及其方法

    - 在容器关闭的时候销毁

    - ```java
    //例:
    public class Cycle2 implements InitializingBean, DisposableBean {
    @Override
    public void afterPropertiesSet() throws Exception {
    System.out.println("开始....");
    }
    @Override
    public void destroy() throws Exception {
    System.out.println("结束了....");
    }
    }
    //初始化很容易出结果,销毁测试需要手动关闭,才能看到效果,如下所示
    ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");

    ClassPathXmlApplicationContext applicationContext= (ClassPathXmlApplicationContext)context;

    applicationContext.close();

配置方式的演变

ps:下面的代码实现,是在IDEA中使用Maven来进行构建和管理,不会Maven的可以去网站学习

方式一[纯XML实现]

项目结构

APP类

1
2
3
4
5
6
7
8
9
10
11
public class App 
{
public static void main( String[] args )
{
ClassPathXmlApplicationContext cpac =
new ClassPathXmlApplicationContext(
"applicationContext.xml");
MainUI mainUI = cpac.getBean("mainUI", MainUI.class);
mainUI.mainUI();
}
}

MianUI类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class MainUI {
private UserService userService;
public void mainUI(){
Scanner temp = new Scanner(System.in);
while(true){
System.out.println("请输入登录账号:");
String account = temp.next();
System.out.println("请输入登录密码:");
String password = temp.next();
User user = userService.judgeUser(account, password);
if(null != user){
System.out.println("登录成功");
System.exit(0);
}else{
System.out.println("账号或密码错误,请重新登录");
}
}
}
public void setUserService(UserService userService) {
this.userService = userService;
}
}
  • 在此类中,声明了UserService类的userService变量,区别于传统的赋值方式,这里仅仅只是声明了该变量,根据java的编程规范,未赋值的引用变量,默认值为null.
    Spring要做的,就是将userService赋值,而采用xml配置方式,是需要setter方法的,所以在最后添加了一个setUserService方法

UserServiceImpl类

1
2
3
4
5
6
7
8
9
10
public class UserServiceImpl implements UserService {
private UserDAO userDAO;
@Override
public User judgeUser(String account, String password) {
return userDAO.select(account,password);
}
public void setUserDAO(UserDAO userDAO) {
this.userDAO = userDAO;
}
}
  • 代码分析:有一个变量userDAO需要赋值

UserDAOImpl类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class UserDAOImpl  implements UserDAO {
private QueryRunner qr;
@Override
public User select(String account, String password) {
//建立一个解决数据库下划线问题的对象
BasicRowProcessor brp = new BasicRowProcessor(new GenerousBeanProcessor());
//准备sql语句,实际应用时不要用 * 来查询,影响效率
String sql = "select * from user where user_account=? and user_password=?";
//准备填充数据
Object[] obj = {account,password};
//进行查询操作
User user = null;
try {
user = qr.query(sql, new BeanHandler<User>(User.class, brp),obj);

} catch (SQLException e) {
throw new RuntimeException("query异常",e);
}
return user;
}

public void setQr(QueryRunner qr) {
this.qr = qr;
}
}
  • 此代码中,有一个名叫qr的成员变量需要赋值,而这里还执行了数据库的查询操作,需要在pom.xml文件中引入jar包

User类

1
2
3
4
5
6
7
8
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User implements Serializable {
private int UserId;
private String UserAccount;
private String UserPassword;
}
  • 在这里,使用了lombok来给私有的成员变量建立getter、setter方法和空参构造及有参构造

applicationContext.xml文件配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
<!--在App类中开始启动,需要得到MainUI类的实例-->
<!--在MainUI类中,存在有叫userService的实例,
所以properties的name代表的就是实例名,ref指引用的地址-->
<bean id="mainUI" class="com.jungle.www.controller.MainUI">
<property name="userService" ref="userService"/>
</bean>

<!--得到UserServiceImpl的实例,UserServiceImpl的实例中存在有叫userDAO的实例-->
<bean id="userService" class="com.jungle.www.service.serviceimp.UserServiceImpl">
<property name="userDAO" ref="UserDAO"/>
</bean>

<!--UserDAOImpl的实例存在有qr的实例,所以得给qr赋值-->
<bean id="UserDAO" class="com.jungle.www.dao.daoimp.UserDAOImpl">
<property name="qr" ref="queryRunner"/>
</bean>

<!--通过构造器(
public QueryRunner(DataSource ds) {
super(ds);
})
得到QueryRunner的实例,需要传一个DataSouce的实例-->
<bean id="queryRunner" class="org.apache.commons.dbutils.QueryRunner">
<constructor-arg name="ds" ref="druidDataSource"/>
</bean>

<!--德鲁伊连接池-->
<bean id="druidDataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close">
<property name="driverClassName" value="${jdbc.classDriver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.user}"/>
<property name="password" value="${jdbc.password}"/>
</bean>

<!--配置文件-->
<bean id="propertySourcesPlaceholderConfigurer" class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer">
<property name="location" value="druid.properties"/>
</bean>

druid.properties文件

1
2
3
4
jdbc.classDriver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://127.0.0.1:3306/blog?userUnicode=true&characterEncoding=utf8
jdbc.user=root
jdbc.password=root123

pom.xml文件配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<!--数据库_驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.6</version>
</dependency>
<!--数据库_工具包-->
<dependency>
<groupId>commons-dbutils</groupId>
<artifactId>commons-dbutils</artifactId>
<version>1.6</version>
</dependency>
<!--数据库_德鲁伊连接池-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.10</version>
</dependency>
<!--Lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.8</version>
<scope>provided</scope>
</dependency>
<!--SpringFrameWork-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.0.RELEASE</version>
</dependency>

完整代码已上传:

链接:https://pan.baidu.com/s/1X64xcNhcqxOkr6sntraiBw
提取码:upuc

方式二[注解]

通过注解方式可以更加快速配置,只需要在类上面加上注解即可

  • 注册springbean
    • @Controller 加在Controller层的实现类上
    • @Service 加在Service层的实现类上
    • @Repository 加在Dao层的实现类上
    • @Component加类上,该类就会被Spring识别
  • 自动依赖注入
    • @Autowired默认按类型装配
    • @Resource默认按名称装配(具体细致区别参考此处
    • @Value 从配置文件中读取值
  • 生命周期 @PostConstruct@PreDestroy
  • 作用域范围 @Scope

方式三[java配置]

注解:

  • @Bean

    • 标记某个方法返回值为spring的bean,方法参数为依赖注入的请求
  • @Configuration

    • 标记一个类为配置类 applicationContext.xml
  • @Component

    • 将普通pojo实例化到spring容器中
  • @ComponentScan

    • 打开包扫描功能,并且指定扫描的包 等价于 <context:component-scan base-package=”xxx”>
  • @PropertySource

    • 指定配置文件所在位置

AOP

Aspect Oritented Programming AOP 面向切面编程

Object Oritened Proguramming OOP 面对对象编程

概念

  • 具有横切性质的系统功能,例如:日志记录、性能统计、事务管理、安全检查等等。散布在系统的各个类中。需要一种机制能将这类功能自动加入到需要的位置中去。这种机制就是AOP。

名词

中文名 英文名 解释
连接点 joinpoint 需要加入功能的位置(方法)
切入点 pointcut 执行加入功能的连接点,从连接点选出的需要加入功能的连接点
通知 advice 需要实现的功能
切面 aspect 切入点和通知的组合
目标对象 target 连接点(方法)所在的对象
织入 weave 将切面应用到目标对象的过程

步骤

1) 编写service类

2) 编写通知 , 实现MethodBeforeAdvice接口

1
2
3
4
5
//method 要执行的方法
// args 方法的参数
// target 方法所在的对象
public void before(Method method, Object[] args, Object target) throws Throwable {
}

3)配置xml

  • 配置service
  • 配置通知
  • 配置切入点,class= org.springframework.aop.support.JdkRegexpMethodPointcut ,配置属性pattern=> service的方法
  • 配置切面,class=org.springframework.aop.support.DefaultPointcutAdvisor 连接切入点和通知
  • 包装service类, class= org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator
  • 获取bean,调用方法,将会看到通知执行了
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
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">
<!--目标对象-->
<bean id="userService" class="com.woniuxy.spring.aop.service.impl.UserServiceImpl"/>
<!--通知: 实现了打日志的功能-->
<bean id="beforeExecution" class="com.woniuxy.spring.aop.component.BeforeExecution"/>

<!--切入点:选出了需要增加功能的方法-->
<bean id="pointCut" class="org.springframework.aop.support.JdkRegexpMethodPointcut">
<property name="pattern" value="com.woniuxy.spring.aop.service.impl.UserServiceImpl.addUser"/>
</bean>

<!--切面:连接切入点和通知,让打日志功能在切入点的位置执行-->
<bean id="aspect" class="org.springframework.aop.support.DefaultPointcutAdvisor">
<property name="pointcut" ref="pointCut"/>
<property name="advice" ref="beforeExecution"/>
</bean>

<!--包装userService-->
<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"/>

</beans>

通知类型

  • 前置通知 : 方法执行之前 MethodBeforeAdvice
  • 后置通知 : 方法执行之后 AfterReturningAdvice
  • 环绕通知 : 方法执行前后 MethodInterceptor
  • 异常通知 : 抛出异常时
  • 最终通知 : finally执行时

注:面向接口编程 1)解耦,修改实现类 2)默认使用接口的方式生成代理

依赖

  • aspectj

    • <dependency>  
          <groupId>org.aspectj</groupId>  
          <artifactId>aspectjweaver</artifactId>  
          <version>1.8.9</version>
      </dependency>
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      58
      59

      #### 配置方式

      ##### XML实现

      通知类实现通知接口,如:implements MethodBeforeAdvice、implements MethodInterceptor、 implements AfterReturningAdvice

      事务通知类需要有三个方法,分别对应切面的配置

      resources文件夹下:aop.xml(注释掉的为日志通知,没注释的切面是事务通知)

      ```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"
      xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
      <!--目标对象,下面的两个知识点都需要-->
      <bean id="userService" class="com.woniu.www.service.impl.UserServiceImpl"/>

      <!--通知: 实现了打日志的功能-->
      <bean id="beforeExecution" class="com.woniu.www.component.BeforeExecution"/>
      <bean id="afterExecution" class="com.woniu.www.component.AfterExecution"/>
      <bean id="interceptorExecution" class="com.woniu.www.component.InterceptorExecution"/>

      <!--切入点:选出了需要增加功能的方法-->
      <bean id="pointCut" class="org.springframework.aop.support.JdkRegexpMethodPointcut">
      <property name="pattern" value="com.woniu.www.service.impl.UserServiceImpl.addUser"/>
      </bean>
      <!--打印日志-->
      <!--切面:连接切入点和通知,让打日志功能在切入点的位置执行-->
      <!-- <bean id="aspect" class="org.springframework.aop.support.DefaultPointcutAdvisor">-->
      <!--需要增加功能的方法-->
      <!-- <property name="pointcut" ref="pointCut"/>-->
      <!--功能-->
      <!-- <property name="advice" ref="beforeExecution"/>-->
      <!-- <property name="advice" ref="afterExecution"/>-->
      <!-- <property name="advice" ref="interceptorExecution"/>-->
      <!-- </bean>-->

      <!--通知-->
      <bean id="txAdvice" class="com.woniu.www.component.TransactionAdvice"/>
      <!--切面配置-->
      <aop:config>
      <aop:aspect ref="txAdvice">
      <!--切入点-->
      <aop:pointcut id="servicePointcut" expression="execution(* com.woniu.www.service.impl.*.*(..) )" />
      <!--前置通知-->
      <aop:before method="begin" pointcut-ref="servicePointcut"/>
      <!--后置通知-->
      <aop:after-returning method="commit" pointcut-ref="servicePointcut"/>
      <!--异常通知-->
      <aop:after-throwing method="roolback" pointcut-ref="servicePointcut"/>
      </aop:aspect>
      </aop:config>


      <!--包装userService-->
      <bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"/>
      </beans>
Java注解实现

下面的例子分为纯java配置和带部分xml配置,具体体现在测试类的调用上

功能:实现了两个事务,一个正常,一个会进行回滚

aop.xml配置

1
2
3
4
5
6
7
8
9
10
11
<?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.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">

<context:component-scan base-package="com.woniu.www"/>

<aop:aspectj-autoproxy/>

</beans>

TxConfig类

1
2
3
4
5
@Configuration
@ComponentScan("com.woniu.www")
@EnableAspectJAutoProxy
public class TxConfig {
}

TxAspect类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Component
@Aspect
public class TxAspect {
@Pointcut("execution(* com.woniu.www.service.impl.*.*(..))")
public void servicePointcut(){};

@Around("servicePointcut()")
public Object invoke(ProceedingJoinPoint pjp) {
Object[] args = pjp.getArgs();
Object result = null;
try {
System.out.println("...开启事务...");
result = pjp.proceed(args);
System.out.println("...提交事务...");
} catch (Throwable throwable) {
System.out.println("...回滚事务...");
throwable.printStackTrace();
}
return result;
}
}

产生事务类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Service("userService")
public class UserServiceImpl implements UserService {
@Override
public void addUser(User user,int age) {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("执行了UserServiceImpl");
}
public void getStr(){
System.out.println("你好");
throw new RuntimeException("wokow");
}
}

测试类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class App 
{
public static void main( String[] args )
{
// xmlWithAnnotation();
//加注解的方式实现
ApplicationContext ctx = new AnnotationConfigApplicationContext(TxConfig.class);
UserService userService = ctx.getBean("userService", UserService.class);
userService.addUser(new User(),18);
userService.getStr();
}

//调用xml文件的方式
private static void xmlWithAnnotation() {
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("aop.xml");
UserService userService = ctx.getBean("userService", UserService.class);
userService.addUser(new User(),18);
userService.getStr();
}
}
切入点表达式语法

点击进入来源

execution (* com.sample.service.impl..*.*(..))

execution()是最常用的切点函数,其语法如下所示:

整个表达式可以分为五个部分:

  • execution(): 表达式主体。

  • 第一个号:表示返回类型,号表示所有的类型。

  • 包名:表示需要拦截的包名,后面的两个句点表示当前包和当前包的所有子包,com.sample.service.impl包、子孙包下所有类的方法。

  • 第二个号:表示类名,号表示所有的类。

  • *(..):最后这个星号表示方法名,*号表示所有的方法,后面括弧里面表示方法的参数,两个句点表示任何参数。

AOP与代理模式的关系

  • AOP使用代理模式实现
  • 我们编写的业务对象是被代理的对象;代理对象是由Spring容器动态生成,实现了业务对象的接口
  • 当使用AOP时,
    1. 由Spring容器为被拦截的目标对象生成代理类,内部持有目标对象的引用,并加入通知功能
    2. 将生成的bean放入到spring容器中
    3. 当获取bean时,得到是spring生成的代理类实例
    4. 调用时,首先执行代理类的代码,再有代理类调用被代理类

事务管理

AOP的典型应用就是Spring声明式事务管理

  • 编程式事务管理 使用spring提供的TransactionTemplate来实现事务管理
  • 声明式事务管理 使用注解@Transactional,利用AOP来实现事务管理

步骤

  1. 加依赖 spring-tx 、 spring-jdbc

  2. 配置类

    • 数据源
    • 数据源代理
    • queryRunner
    • 事务管理器 (引用数据源代理)
    • 事务模板(引用事务管理器)
  3. 编码时

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    transactionTemplate.execute(new TransactionCallBakWithoutResult(){

    ....doInTransactionWithoutResult(TransactionStatus status){
    try{
    //需要被事务管理的代码
    }catch(Exception e){
    //标记回滚
    status.setRollbackOnly()
    }
    }
    })

声明式

  1. 加依赖 spring-tx 、 spring-jdbc

  2. 配置类

    • 数据源
    • 数据源代理
    • queryRunner
    • @EnableTransactionManagement
  3. 编码时

    1
    2
    3
    4
    5
    @Transactional
    public void test2(){
    //需要事务管理的代码
    //抛出异常时自动回滚
    }

    实例

    配置类(两个都可以)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    @Configuration
    @PropertySource("druid.properties")
    @ComponentScan("com.jungle.www")
    public class AppConfig {
    @Value("${jdbc.classDriver}")
    private String classDriver;
    @Value("${jdbc.url}")
    private String url;
    @Value("${jdbc.user}")
    private String username;
    @Value("${jdbc.password}")
    private String password;

    @Bean
    public QueryRunner queryRunner(){
    return new QueryRunner(dataSourceProxy());
    }

    @Bean(destroyMethod = "close")
    public DruidDataSource druidDataSource(){
    DruidDataSource druidDataSource = new DruidDataSource();
    druidDataSource.setDriverClassName(classDriver);
    druidDataSource.setUrl(url);
    druidDataSource.setUsername(username);
    druidDataSource.setPassword(password);
    return druidDataSource;
    }
    //数据源的代理,Spring能够去控制事务的打开和提交等待
    @Bean
    public TransactionAwareDataSourceProxy dataSourceProxy(){
    return new TransactionAwareDataSourceProxy(druidDataSource());
    }
    //事务管理器
    @Bean
    public PlatformTransactionManager transactionManager(){
    return new DataSourceTransactionManager(dataSourceProxy());
    }
    //事务模板
    @Bean
    public TransactionTemplate transactionTemplate(){
    return new TransactionTemplate(transactionManager());
    }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    @Configuration
    @PropertySource("druid.properties")
    @ComponentScan("com.jungle.www")
    @EnableTransactionManagement
    public class AppConfigTwo {
    @Value("${jdbc.classDriver}")
    private String classDriver;
    @Value("${jdbc.url}")
    private String url;
    @Value("${jdbc.user}")
    private String username;
    @Value("${jdbc.password}")
    private String password;

    @Bean
    public QueryRunner queryRunner(){
    return new QueryRunner(dataSourceProxy());
    }

    @Bean(destroyMethod = "close")
    public DruidDataSource druidDataSource(){
    DruidDataSource druidDataSource = new DruidDataSource();
    druidDataSource.setDriverClassName(classDriver);
    druidDataSource.setUrl(url);
    druidDataSource.setUsername(username);
    druidDataSource.setPassword(password);
    return druidDataSource;
    }

    //数据源的代理,Spring能够去控制事务的打开和提交等待
    @Bean
    public TransactionAwareDataSourceProxy dataSourceProxy(){
    return new TransactionAwareDataSourceProxy(druidDataSource());
    }

    //事务管理器
    @Bean
    public PlatformTransactionManager transactionManager(){
    return new DataSourceTransactionManager(dataSourceProxy());
    }
    }

    事务类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    @Service
    public class UserServiceImpl implements UserService {

    @Autowired
    private QueryRunner queryRunner;
    @Autowired
    private TransactionTemplate transactionTemplate;

    @Override
    @Transactional
    public void test() {
    transactionTemplate.execute(new TransactionCallbackWithoutResult() {
    @Override
    protected void doInTransactionWithoutResult(TransactionStatus status) {
    try {
    queryRunner.update("insert into user values(default,?,?)","abc","qwe");

    if( null != status){
    throw new RuntimeException("error");
    }
    queryRunner.update("update user set user_password=?","123456");

    } catch (SQLException e) {
    e.printStackTrace();
    status.setRollbackOnly();
    }
    }
    });
    }
    @Transactional
    @Override
    public void testTwo() throws SQLException{
    queryRunner.update("insert into user values(default,?,?)","abc","qwe");
    if(null != queryRunner){
    throw new RuntimeException("test2");
    }
    queryRunner.update("update user set user_password=?","123456");
    }
    }

测试类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class App 
{
public static void main( String[] args )
{
// testOne();
testTwo();
}

private static void testOne() {
ApplicationContext context= new AnnotationConfigApplicationContext(AppConfig.class);
UserService userService = context.getBean(UserService.class);
userService.test();
}
private static void testTwo() {
ApplicationContext context= new AnnotationConfigApplicationContext(AppConfigTwo.class);
UserService userService = context.getBean(UserService.class);
try {
userService.testTwo();
} catch (SQLException e) {
e.printStackTrace();
}
}
}

SpringMVC

基本概念

  • Model:模型
    • 数据+功能 模型类+Service+DAO
  • View:视图
    • 展示页面 JSP / HTML+AJAX
  • Controller:控制器
    • 控制流程 @Controller控制器

配置方式一:XML

加依赖

  • Spring Web MVC依赖

    • org.springframework spring-webmvc

    • <dependency>
          <groupId>org.springframework</groupId>
          <artifactId>spring-webmvc</artifactId>
          <version>4.3.4.RELEASE</version>
      </dependency>
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      - servlet API支持

      - `javax.servlet javax.servlet-api`

      - ```XML
      <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>javax.servlet-api</artifactId>
      <version>3.1.0</version>
      <scope>provided</scope>
      </dependency>
  • JSTL支持

    • javax.servlet jstl

    • taglibs standard

    • <dependency>
          <groupId>javax.servlet</groupId>
          <artifactId>jstl</artifactId>
          <version>1.1.2</version>
          <type>jar</type>
      </dependency>
      <dependency>
          <groupId>taglibs</groupId>
          <artifactId>standard</artifactId>
          <version>1.1.2</version>
          <type>jar</type>
      </dependency>
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
        
      - 参数验证

      - `org.hibernate.validator hibernate-validator`

      - ```XML
      <!--参数验证-->
      <dependency>
      <groupId>org.hibernate.validator</groupId>
      <artifactId>hibernate-validator</artifactId>
      <version>6.0.17.Final</version>
      </dependency>
  • JSON类型转换

    • com.fasterxml.jackson.core jackson-core

    • com.fasterxml.jackson.core jackson-databind

    • com.fasterxml.jackson.core jackson-annotations

    • <!--下面的jackson版本放在properties标签之间,对于同种类型的多个依赖,建议使用此方式,便于后期维护-->
      <jackson.version>2.10.0</jackson.version>
      ...
      <!--JSON类型转换-->
      <dependency>
      <groupId>com.fasterxml.jackson.core</groupId>
          <artifactId>jackson-core</artifactId>
          <version>${jackson.version}</version>
      </dependency>
      
      <dependency>
      <groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-databind</artifactId>
          <version>${jackson.version}</version>
      </dependency>
      
      <dependency>
      <groupId>com.fasterxml.jackson.core</groupId>
       <artifactId>jackson-annotations</artifactId>
          <version>${jackson.version}</version>
      </dependency>
      
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
        

      ### 建包

      ### 配置web.xml

      #### 配置web.xml版本

      检查一下web.xml最上方的头文件,版本是否过低,可以将上方内容改为如下:

      ```xml
      <?xml version="1.0" encoding="UTF-8"?>
      <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xmlns="http://xmlns.jcp.org/xml/ns/javaee"
      xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
      version="3.1">
      <!--内容区-->
      </web-app>

配置监听器

  • 作用:在tomcat启动时加载Spring容器

  • 方式

    1
    2
    3
    <listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

配置servlet

  • 作用:接受并分发请求

  • 方式

    1
    2
    3
    4
    5
    6
    7
    8
    9
    <servlet>
    <servlet-name>mvcmylove</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
    <servlet-name>mvcmylove</servlet-name>
    <url-pattern>/</url-pattern>
    </servlet-mapping>

配置filter

  • 作用:过滤请求字符编码集

  • 方式

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    <!--filter-->
    <filter>
    <filter-name>encodingFilter</filter-name>
    <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
    <init-param>
    <param-name>encoding</param-name>
    <param-value>UTF-8</param-value>
    </init-param>
    </filter>
    <filter-mapping>
    <filter-name>encodingFilter</filter-name>
    <url-pattern>*</url-pattern>
    </filter-mapping>

配置成功代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1">
<!--监听器,在tomcat启动时加载Spring容器-->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!--Servlet接受并分发请求-->
<servlet>
<servlet-name>mvcmylove</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>mvcmylove</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<!--filter-->
<filter>
<filter-name>encodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>encodingFilter</filter-name>
<url-pattern>*</url-pattern>
</filter-mapping>

<display-name>Archetype Created Web Application</display-name>
</web-app>

配置applicationContext

  • 作用:根容器的配置文件, 包括非Web的SpringBean

新建文件,选择XML Configuration File –>Spring Config,命名为applicationContext.xml即可,创建完成放着就行,暂时不配置

配置mvcmylove-servlet.xml

  • 命名规范:{servlet-name}-servlet.xml
  • 作用:子容器的配置文件,包括Controller、ViewResolver的Web相关的bean
  • 注意:子容器可以访问根容器,根容器不能访问子容器

新建文件,选择XML Configuration File –>Spring Config,命名为mvcmylove-servlet.xml即可,创建完成放着就行,暂时不配置

配置包扫描路径

  • <context:component-scan base-package="com.jungle.www.controller"/>
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
      
    > 出现:Namespace 'context' is not bound后,可以手动在上面加:
    >
    > xmlns:context="http://www.springframework.org/schema/context"
    >
    > 或者让idea自动添加

    #### 配置内容协商视图解析器

    - 作用:根据请求自动返回jsp或者json

    ```xml
    <bean class="org.springframework.web.servlet.view.ContentNegotiatingViewResolver"><!--没提示就自己复制上去,复制上去后不报红即可-->
    <property name="viewResolvers">
    <list>
    <!-- home => /WEB-INF/page/home.jsp -->
    <!--内部资源视图解析器,JSP-->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <property name="prefix" value="/WEB-INF/page/"/>
    <property name="suffix" value=".jsp"/>
    </bean>
    </list>
    </property>

配置启动mvc注解

1
<mvc:annotation-driven/>

配置静态资源映射

  • 作用:解决WEB-INF文件下的资源无法访问问题
1
2
3
4
5
<!--mapping : 访问路径      location : 本地路径-->
<mvc:resources mapping="/style/**" location="/WEB-INF/css/"/>
<mvc:resources mapping="/js/**" location="/WEB-INF/js/"/>
<mvc:resources mapping="/images/**" location="/WEB-INF/images/"/>
<mvc:resources mapping="/fonts/**" location="/WEB-INF/fonts/"/>

配置成功的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
<?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:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
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 http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd">

<!--包扫描,路径为controller-->
<context:component-scan base-package="com.jungle.www.controller"/>

<!--viewResolver: 内容协商视图解析器,根据请求自动返回jsp或者json-->
<bean class="org.springframework.web.servlet.view.ContentNegotiatingViewResolver">
<property name="viewResolvers">
<list>
<!-- home => /WEB-INF/page/home.jsp -->
<!--内部资源视图解析器,JSP-->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/page/"/>
<property name="suffix" value=".jsp"/>
</bean>
<!-- <bean class="org.springframework.web.servlet.view.BeanNameViewResolver" />-->
</list>
</property>
<!--<property name="defaultViews">
<list>
&lt;!&ndash;解析json&ndash;&gt;
<bean class="org.springframework.web.servlet.view.json.MappingJackson2JsonView"/>
</list>
</property>-->
</bean>

<!--启动mvc注解-->
<mvc:annotation-driven/>
<!--mapping : 访问路径 location : 本地路径-->
<mvc:resources mapping="/style/**" location="/WEB-INF/css/"/>
<mvc:resources mapping="/js/**" location="/WEB-INF/js/"/>
<mvc:resources mapping="/images/**" location="/WEB-INF/images/"/>
<mvc:resources mapping="/fonts/**" location="/WEB-INF/fonts/"/>

</beans>

测试环境搭建

出现:No bean named ‘cacheManager’ available的报错

原因:xml文件头声明有问题

解决方案:使用下面的直接将原来的替换掉即可

1
2
3
4
5
6
<?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:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
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 http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd">
1
2
3
4
5
6
7
@Controller
public class UsercController {
@RequestMapping("getUser")
public String getUser(){
return "index";
}
}

使用ip+端口号+项目名+请求名,访问成功跳转,即说明配置成功

http://localhost:8080/test/getUser

补充概念

ViewResolver 视图解析器

作用

  • Controller将请求处理结果放入到ModelAndView后,DispatcherServlet会根据ModelAndView选择合适的视图进行渲染。ViewResolver就是负责挑选出合适的视图,ViewResolver接口定义了resolverViewName方法,根据viewName创建合适类型的View实现。

  • 在前面的配置mvcmylove-servlet.xml中,就有对viewResolvers的配置

使用场景

  • jsp解析器
  • thymeleaf解析器
  • json解析器

配置方式二:Java

  • AppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer
    • 提供根容器配置文件
    • 提供子容器配置文件
    • 配置DispatcherServlet的url-pattern
    • 提供Filter
  • WebCtxConfig extends WebMvcConfigurerAdapter
    • @Configuration @ComponentScan @EnableWebMVC
    • 视图解析器
    • 静态资源处理
    • 文件上传

配置两个java文件即可

WebCtxConfig.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@Configuration
@ComponentScan("com.jungle.www.controller")
@EnableWebMvc
public class WebCtxConfig extends WebMvcConfigurerAdapter {
//视图解析器
@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
registry.jsp("/WEB-INF/page",".jsp");
registry.enableContentNegotiation();
}
//静态资源处理
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/style/**").addResourceLocations("/WEB-INF/css/");
}
//文件上传的部分
@Bean
public MultipartResolver multipartResolver(){
CommonsMultipartResolver cmr = new CommonsMultipartResolver();
cmr.setMaxUploadSize(10485760);
cmr.setMaxUploadSizePerFile(2097152);
return cmr;
}
}

AppInitializer.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class AppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {

@Override
protected Class<?>[] getRootConfigClasses() {
return new Class[]{RootCtxConfig.class};
}

@Override
protected Class<?>[] getServletConfigClasses() {
return new Class[]{WebCtxConfig.class};
}

@Override
protected String[] getServletMappings() {
return new String[]{"/"};
}

@Override
protected Filter[] getServletFilters() {
CharacterEncodingFilter encodingFilter = new CharacterEncodingFilter("utf-8");
return new Filter[]{encodingFilter};
}
}

功能使用

自动注入对象

  • 对于需要注入的对象,加@Autowired注解即可

    • 例:

      1
      2
      @Autowired
      public Service service;

form传递

假如数据库中有字段user_id,user_name,userpassword,当用户登录的时候,会将名字和密码发送过来,这时我们只要新建一个实体类(最好新建目录),请求参数为实体类,即可自动绑定值,例如下所示:

1
2
3
4
5
6
7
8
@Controller
public class UsercController {
/*下面的Login是自己建的java实体类,建议准守javaBean的规范*/
@RequestMapping("getUser")
public String getUser(Login userLogin){
return "index";
}
}

前面在xxx-servlet配置里,配置的跳转路径是WEB-INF,所以这个请求会去WEB-INF/page下面找,且加上后缀名(也是配置的),也就是WEB-IN/page/index.jsp

跳转除了这种默认的方式,还可以使用

  • forward:/xxx
  • redirect:xxx
  • 区别在于,不写前面的方式,默认会去配置的路径下找,写了前面的方式,除了可以跳转配置的路径,还可以进行类自身的跳转

页面的取值方式

  • 像上面的实体类来接收值,在前端jsp页面,可以直接使用${类名(首字母小写)}取得值,例:${login}

controller层的设置值的方式

  • HttpServletResponse response照样可以设置值给页面

  • ModelMap的方式,例:

    1
    2
    ModelMap modelMap = new ModelMap();
    modelMap.put("account",userLogin.getAccount());

参数校验

  • 需要依赖

    • <!--参数验证-->
      <dependency>
         <groupId>org.hibernate.validator</groupId>
          <artifactId>hibernate-validator</artifactId>
          <version>6.0.17.Final</version>
      </dependency>
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22



      - 对实体类中的成员变量加注解 :

      - @NotNull

      - @NotBlank

      - @Email

      - @Length

      - @Future

      - @Past

      - @Pattern ...

      - ```java
      @NotBlank
      private String account;
  • 在需要校验的参数对象前@Valid

    • public String getUser(@Valid Login userLogin){}
  • 方法签名,BindingResult参数,获取校验结果

具体实例如下:

实体类:

1
2
3
4
5
6
7
8
@Data
@Controller
@NoArgsConstructor
public class Login {
@NotBlank(message = "账户千万不能为空哟")
private String account;
private String password;
}

Controller层的类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@Controller
public class UsercController {
/*下面的Login是自己建的java实体类,建议准守javaBean的规范*/
@RequestMapping("getUser")
public String getUser(@Valid Login userLogin, BindingResult bindingResult, HttpServletRequest request){

System.out.println("校验结果:"+bindingResult);

//转换校验结果,返回给页面
StringBuilder sb = new StringBuilder();

if(bindingResult.hasErrors()){
List<FieldError> fieldErrors = bindingResult.getFieldErrors();
for(FieldError fieldError:fieldErrors){
sb.append(fieldError.getField()+":");
sb.append(fieldError.getDefaultMessage()+";");
}
}

request.setAttribute("mes",sb.toString());

return "index";
}
}

JSP直接:${mes}取值即可取到实体类设置的值

测试的网址:http://localhost:8080/getUser?password=5555

效果:account:账户千万不能为空哟;在controller层的类中,可以将前面的account去掉的

参数类型转换

当界面的数据传到controller层的时候,是字符串类型,无法直接将字符串类型的日期转换成日期类型,通过下面的方式,会自动将请求的字符串类型转换成日期类

  • @InitBinder 参数 WebDataBinder
1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Controller
public class TypeTransfer {
@InitBinder
public void start(WebDataBinder binder){
binder.addCustomFormatter(
new DateFormatter("yyyy-MM-dd HH:mm:ss")
);
}
@ResponseBody
@RequestMapping("getDate")
public void getDate(Date date){
System.out.println(date);
}
}

参数校验和参数类型转换可以变得更加精简,具体做法是建立一个抽象类,将主要代码抽取到抽象类中,需要的类再继承即可

json自动转换

  • @ResponseBody
  • @RestController

静态资源处理

1
2
3
4
5
<!--解决静态资源访问限制问题-->
<mvc:resources mapping="/css/**" location="/WEB-INF/css/"/>
<mvc:resources mapping="/js/**" location="/WEB-INF/js/"/>
<mvc:resources mapping="/images/**" location="/WEB-INF/images/"/>
<mvc:resources mapping="/fonts/**" location="/WEB-INF/fonts/"/>

全局异常处理

  • @ControllerAdvice (针对于所有的Controller的通知)
  • @ExceptionHandler(异常处理器)

实例:

1
2
3
4
5
6
7
8
9
10
11
@ControllerAdvice
public class ExceptionManager {
//异常处理器
@ExceptionHandler
public String handleException(Exception e, Model model){
e.printStackTrace();
System.out.println("出错了"+e.getMessage());
Model model1 = model.addAttribute("mes", "程序员哥哥正在加班");
return "404";
}
}

​ 只要出现异常,就会进入这里,Model仅在此使用ModelMap在其他场景下都可以使用,这里直接上HttpServletRequest也不错,SpringMVC里都可以使用HttpServletRequest的

参数封装

文件上传

  • 加依赖commons-fileupload commons-fileupload

    1
    2
    3
    4
    5
    <dependency>
    <groupId>commons-fileupload</groupId>
    <artifactId>commons-fileupload</artifactId>
    <version>1.3.2</version>
    </dependency>
  • 配置xxx-servlet.xml

    1
    2
    3
    4
    5
    6
    7
    8
    <!--文件上传的配置-->
    <!--下面的id名字不要改变-->
    <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
    <!--最大的上传文件总大小-->
    <property name="maxUploadSize" value="10485760"/>
    <!--最大的上传文件单个大小-->
    <property name="maxUploadSizePerFile" value="2097152"/>
    </bean>
  • 编写jsp页面

    1
    2
    3
    4
    5
    6
    7
    <%--method=post--%>
    <%--enctype=multipart/form-data--%>
    <form action="upload" method="post" enctype="multipart/form-data">
    <input type="file" name="avatar">
    <input type="text" name="username">
    <input type="submit" value="上传一下">
    </form>
  • 编写Controller, 参数为MultipartFile

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//JAVA代码
@Controller
public class AvatarController {
@RequestMapping("upload")
public String upload(MultipartFile avatar,String username){
System.out.println("上传文件处理:"+avatar.getOriginalFilename()+"\n用户名:"+username);
//得到原始的文件名
String originalFilename = avatar.getOriginalFilename();
try {
avatar.transferTo(new File("D:/temp/"+originalFilename));
} catch (IOException e) {
throw new RuntimeException("文件上传失败");
}
return "mainUI";
}
}

文件下载

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//设置响应头,控制浏览器下载该文件
response.setHeader("content-disposition", "attachment;filename=" + URLEncoder.encode(realname, "UTF-8"));
//读取要下载的文件,保存到文件输入流
FileInputStream in = new FileInputStream(path + "\\" + fileName);
//创建输出流
OutputStream out = response.getOutputStream();
//创建缓冲区
byte buffer[] = new byte[1024];
int len = 0;
//循环将输入流中的内容读取到缓冲区当中
while((len=in.read(buffer))>0){
//输出缓冲区的内容到浏览器,实现文件下载
out.write(buffer, 0, len);
}
//关闭文件输入流
in.close();
//关闭输出流
out.close();

拦截器

自带的,所以不用加依赖

类implements HandlerInterceptor

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object o) throws Exception {
Object username = request.getSession().getAttribute("account");
System.out.println("username:"+username);
if(null == username){
System.out.println("没有登录");
request.setAttribute("msg","您尚未登录,请登录");
request.getRequestDispatcher("/index.jsp").forward(request,response);
return false;
}
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object o, ModelAndView modelAndView) throws Exception {

}
@Override
public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {
}
}

配置xxx-servlet.xml

1
2
3
4
5
6
7
8
9
10
<mvc:interceptors>
<mvc:interceptor>
<!--拦截的路径-->
<mvc:mapping path="/**"/>
<!--放过的请求-->
<mvc:exclude-mapping path="/login"/>
<!--负责拦截的类路径-->
<bean class="com.jungle.www.controller.LoginInterceptor"></bean>
</mvc:interceptor>
</mvc:interceptors>

Mybatis

ORM

  • Object-Relation-Mapping 对象-关系-映射

概念

  • mybatis是一个优秀的持久化框架,支持定制化的SQL语句、储存过程和高级映射。避免了所有JDBC代码和手动设置参数。

  • mybatis vs hibernate

    • mybatis 支持 定制化SQL,hibernate自动生成所有的SQL
    • mybatis比较灵活,支持SQL调优;hibernate可以跨数据库,理论上可以无缝数据库迁移
    • mybatis适用于业务更改频繁,需要SQL调优的系统;hibernate适用于单表查询较多的系统,比如后台

作用

  • 可以直接写SQL,封装了除SQL以外的其他的ORM功能

缓存

查询数据时将查询结果存放到内存(缓存区)中。

每次查询数据时,先判断缓存区中是否存在数据,

  • 如果存在,就从缓存区中获取数据
  • 如果不存在,就从数据库中获取数据,将数据存放到缓存区中,给下次访问使用

好处

  • 避免频繁与数据库交互,提高数据访问效率。提升系统性能

一级缓存

  • 一级缓存是SqlSession自带的。SqlSession对象被创建,一级缓存就存在了。

  • 如果SqlSession对象关闭或调用清理方法,会导致缓存失效。

  • 缓存底层实现就是通过HashMap实现的。

  • 一级缓存的介质:内存

一级缓存介绍

在应用运行过程中,我们有可能在一次数据库会话中,执行多次查询条件完全相同的SQL,MyBatis提供了一级缓存的方案优化这部分场景,如果是相同的SQL语句,会优先命中一级缓存,避免直接对数据库进行查询,提高性能。

每个SqlSession中持有了Executor,每个Executor中有一个LocalCache。当用户发起查询时,MyBatis根据当前执行的语句生成MappedStatement,在Local Cache进行查询,如果缓存命中的话,直接返回结果给用户,如果缓存没有命中的话,查询数据库,结果写入Local Cache,最后返回结果给用户

  1. MyBatis一级缓存的生命周期和SqlSession一致。
  2. MyBatis一级缓存内部设计简单,只是一个没有容量限定的HashMap,在缓存的功能性上有所欠缺。
  3. MyBatis的一级缓存最大范围是SqlSession内部,有多个SqlSession或者分布式的环境下,数据库写操作会引起脏数据,建议设定缓存级别为Statement。
1
<setting name="localCacheScope" value="SESSION"/>

二级缓存

二级缓存介质——内存,硬盘

二级缓存SqlSessionFactory进行管理的

配置

二级缓存介绍

在上文中提到的一级缓存中,其最大的共享范围就是一个SqlSession内部,如果多个SqlSession之间需要共享缓存,则需要使用到二级缓存。开启二级缓存后,会使用CachingExecutor装饰Executor,进入一级缓存的查询流程前,先在CachingExecutor进行二级缓存的查询,具体的工作流程如下所示。

二级缓存 -> 一级缓存 -> 数据库

项目基本结构

使用方式

  1. 建项目+加依赖(mybatis、mysql-connector)

  2. 创建配置文件mybatis-config.xml(数据源、引入单表的配置)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE configuration
    PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
    "http://mybatis.org/dtd/mybatis-3-config.dtd">
    <configuration>

    <environments default="dev">
    <environment id="dev">
    <transactionManager type="JDBC"></transactionManager>
    <dataSource type="POOLED">
    <property name="driver" value="com.mysql.jdbc.Driver"/>
    <property name="url" value="jdbc:mysql://localhost:3306/class26"/>
    <property name="username" value="root"/>
    <property name="password" value="root123"/>
    </dataSource>

    </environment>
    </environments>

    <mappers>
    <mapper resource="mapper/user.mapper.xml"/>
    </mappers>


    </configuration>
  3. 单表配置

    1
    2
    3
    4
    5
    6
    7
    8
    9
    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE mapper
    PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
    "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <mapper namespace="maiz">
    <delete id="delete" >
    delete from user where user_id=#{userId}
    </delete>
    </mapper>
    • namespace ,可以任意填
  4. 启动框架

    1. 读取配置
    2. 构建SQLSessionFactory
    3. 开启SQLSession
    4. 执行数据库操作
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    public class App 
    {
    public static void main( String[] args ) throws IOException {
    //读配置文件
    InputStream resourceAsStream = Resources.getResourceAsStream("mybatis-config.xml");
    //构建SQLSessionFactory
    SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
    //创建SqlSession
    SqlSession sqlSession = sessionFactory.openSession();

    sqlSession.delete("maiz.delete",16);

    sqlSession.commit();
    }
    }

==>传参

  • 一个参数,自动绑定

  • 多个参数,需要传入对象(或map)将多个参数封装

  • xml中,参数需要用#{属性名}获取

    1
    select * from user where username=#{username} and password=#{password}

<==结果映射

  • resultType : 将结果映射到一个对象上,默认 数据库字段与实体类的属性名完全匹配才能映射
  • resultMap : 将结果映射到一个配置好的resultMap
    • <resultMap id=”” type=”{基于某个类型进行映射}”>

Q : 下划线转为驼峰式的问题

1
2
3
<settings>
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>