天天看點

SSM架構-學習總結,SSM環境搭建總體梳理

SSM架構學習和總結(有執行個體和源碼)

寫在前面

打算了很久了,要從C切到Java。趁着今年的一些事情的計劃就決定過完年後(今天是三月二十八,好像過完年很久了)開始學習。找到了系列視訊,看了之後做一下學習總結。

SSM架構學習和總結

    • 寫在前面
  • 總體梳理
    • Mybatis架構與資料庫配置
      • Dao接口配置檔案
      • 使用注解sql語句
      • 資料連接配接池的認識
          • 1,DBCP架構
          • 2,C3P0架構
          • 3,MyBatis架構
          • 4,Druid架構
    • spring架構
      • maven坐标引入
      • factory 和 ioc
      • ioc 和 DI
        • 第一種構造器注入:
        • 第二種set方法注入:
        • 第三種就是用注解的自動注入方式
        • 一些注解
      • spring架構整合DBUtils架構和c3p0連接配接池
      • spring架構整合jdbcTemplate
      • 事務管理TransactionManager
        • 事務的概念
      • spring架構的AOP配置
        • 一些AOP術語
      • spring架構的聲明式事務管理
    • spring MVC架構
      • 架構模型
        • servlet,controller
        • jsp
        • JavaBean,Model
      • project建立
      • 依賴的maven坐标
      • 項目的結構
      • Controller的幾種響應。
        • 有傳回值
        • 無傳回值
        • 傳回ModelAndView
      • Controller層請求的參數綁定
        • 使用Model傳值
        • 使用ModelAndView傳值
      • 幾個注解
      • springMVC的自定義異常
      • springMVC的攔截器
    • SSM架構簡單整合
        • 一,版本問題
        • 二,測試的技巧
        • 三,源碼位址 ~~(硬湊的三)~~

總體梳理

SSM架構一般指spring+springMVC+Mybatis。

Spring架構是由于軟體開發的複雜性而建立的。Spring使用的是基本的JavaBean來完成以前隻可能由EJB完成的事情。然而,Spring的用途不僅僅限于伺服器端的開發。從簡單性、可測試性和松耦合性角度而言,絕大部分Java應用都可以從Spring中受益(百度)。

其中學到的三個技術IOC,DI,AOP我之前就聽說過,但一直不了解,這次看視訊,寫代碼之後就有了一些了解,它确實降低了代碼或者說工程中類的耦合,這是主要的,同時還簡化了開發,确實是個厲害的架構。這三個技術在後面會依次總結出來。

Spring MVC屬于SpringFrameWork的後續産品,已經融合在Spring Web Flow裡面。Spring 架構提供了建構 Web 應用程式的全功能 MVC 子產品。使用 Spring 可插入的 MVC 架構,進而在使用Spring進行WEB開發時,可以選擇使用Spring的Spring MVC架構或內建其他MVC開發架構,如Struts1(現在一般不用),Struts 2(一般老項目使用)等等(百度)。它暫時還沒開始學習,但之前我在用springboot時就聽過這個架構,等後面再補上。

MyBatis架構本是apache的一個開源項目iBatis, 2010年這個項目由apache software foundation 遷移到了google code,并且改名為MyBatis 。2013年11月遷移到Github。iBATIS一詞來源于“internet”和“abatis”的組合,是一個基于Java的持久層架構。iBATIS提供的持久層架構包括SQL Maps和Data Access Objects(DAOs)百度。作為持久層架構的實作(個人稱謂),它是第一個接觸的,記得之前使用是很難了解它在做什麼,這次看完後有點了解,尤其是Map映射功能,如果用好了确實牛。

這次視訊(加上之前看的Java EE)讓我對注解有了新的了解。一個小小的注解是那些大佬們最牛的展現。

OK,廢話不多說,開始說正事,看看能總結出些什麼來。

Mybatis架構與資料庫配置

使用它總重要的就是配置檔案,一般命名為SqlMapConfig.xml

<!--
引入限制:限制大于配置
-->
<?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>
    <!--核心配置檔案-->
    <settings>
        <setting name="mapUnderscoreToCamelCase" value="true"/>
    </settings>
<!--    環境配置-->
    <environments default="mysql">
        <environment id="mysql">
            <transactionManager type="JDBC"></transactionManager>
            <dataSource type="POOLED">
                <!--配置連接配接資料庫的資訊-->
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <!--
                url的格式:協定類型://伺服器位址:端口号/路徑/檔案名[參數=值]
                端口号後面的稱為uri
                mysql的端口号固定為3306,其他一些應用也有固定的端口号,如tomcat的是8080,ssh伺服器的是22,tpt伺服器的是21
                作用就是為了區分同一個IP位址的主機的不同應用
				-->
                <property name="url" value="jdbc:mysql://localhost:3306/xxx?useUnicode=true&amp;characterEncoding=utf8"/>
                <property name="username" value="xxx"/>
                <property name="password" value="xxx"/>
            </dataSource>
        </environment>
    </environments>
<!--    指定映射配置檔案的位置-->
<!-- 
<package name="com.example.dao.UserDao"/>
兩種方式都能指明映射配置檔案的位置
映射的了解:從資料庫表中拿出來的行列組成的key-value對,可以對應某個類的屬性名和它的值。
 -->
    <mappers>
        <mapper resource="com/example/dao/UserDao.xml"/>
    </mappers>
</configuration>
           

mybatis架構的映射檔案:

<?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="com.example.dao.UserDao">
<!--配置查詢所有-->
    <select id="findAll" resultType="com.example.bean.User">
        select * from usera
    </select>
</mapper>
           

Dao接口配置檔案

namespace就是指明接口的路徑,這樣就不用寫接口的實作類。然後根據id比對方法,如

findALL()

,執行了sql語句後就根據resultType打包成一個類的對象傳回。這裡的打包就是用的映射。這是通過xml檔案配置的方式,上面這個對應的UserDao.xml的檔案位置在rescouces下,如圖所示

SSM架構-學習總結,SSM環境搭建總體梳理

UserDao.xml的目錄等級要和Java目錄下的UserDao.java的目錄等級一樣,如namespace的路徑。如果有這個UserDao.xml,就是用xml方式配置和使用Mybatis架構的方式。

使用注解sql語句

用注解使用Mybatis時,不用這個UserDao.xml檔案,直接在UserDao.java的方法上用注解寫明sql語句。

public interface UserDao {
    @Select("select * from usera")
    List<User> findAll();
    @Update("update usera set username=#{userName},age=#{age} where id=#{id}")
    void updateUser(User user);
    @Delete("delete from usera where id=#{id}")
    void deleteUser(Integer id);
    @Insert("insert into usera(username,age) values(#{userName},#{age})")
    void insertUser(User user);
}
           

這種注解的方式一般在練習時寫一些普遍用的簡單的sql語句。

資料連接配接池的認識

1,DBCP架構

dbcp架構中的DataSource類是:org.apache.commons.dbcp.BasicDataSource,這是commons-dbcp.jar包中的類。

2,C3P0架構

c3p0架構的DataSource類是:com.mchange.v2.c3p0.ComboPooledDataSource,這是c3p0.jar包中的類。

這個練習經常使用。

3,MyBatis架構

MyBatis架構的DataSource類是:org.apache.ibatis.datasource.pooled.PooledDataSource,這是mybatis.jar包中的類。

4,Druid架構

Druid架構的DataSource類是:com.alibaba.druid.pool.DruidDataSource

這是druid.jar包中的類。

阿裡巴巴的這個資料連接配接池是比較好用的,高并發性能好,而且能配合做到背景監控。

引用自:DataSource的類型

spring架構

看的視訊裡,老師手撕源碼,真的是無意間就看到了架構底層代碼和一些思想。

maven坐标引入

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.0.2.RELEASE</version>
</dependency>
           

factory 和 ioc

控制反轉(Inversion of Control,縮寫為IoC),是面向對象程式設計中的一種設計原則,可以用來減低計算機代碼之間的耦合度。其中最常見的方式叫做依賴注入(Dependency Injection,簡稱DI),還有一種方式叫“依賴查找”(Dependency Lookup)。通過控制反轉,對象在被建立的時候,由一個調控系統内所有對象的外界實體将其所依賴的對象的引用傳遞給它。也可以說,依賴被注入到對象中(百度)。

//    根據bean的全限定路徑通過反射擷取對象
public static Object getBean(String beanName){
        Object bean = null;
        String beanPath = properties.getProperty(beanName);
        try {
            bean = Class.forName(beanPath).getDeclaredConstructor().newInstance();
        } catch(Exception e) {
            e.printStackTrace();
        }
        return bean;
    }
           

如注釋所說,這就是一個對象工廠,調用

getBean()

就傳回一個對應類的對象,這其實就是容器的原理,但用了spring架構之後,就由spring來管理對象的建立,把對象的管理交給spring這就是ioc(個人了解),使用者隻需要告訴spring哪些類需要它管理,就是通過bean.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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd">
<!--    把對象的建立交給spring來管理-->
    <bean id="accountService" class="com.example.service.impl.AccountServiceImpl"></bean>
    <bean id="accountMapper" class="com.example.mapper.impl.AccountMapperImpl"></bean>
</beans>
           

這裡隻是簡單的兩個實作類交給了spring管理。

ioc 和 DI

Dependency Injection就是DI,依賴注入。

第一種構造器注入:

<bean id="accountService" class="com.example.service.impl.AccountServiceImpl">
        <constructor-arg name="name" value="test"></constructor-arg>
        <constructor-arg name="age" value="22"></constructor-arg>
        <constructor-arg name="birth" ref="now"></constructor-arg>
        <property name="accountMapper" ref="accountMapper"></property>
    </bean>
           

第二種set方法注入:

<bean id="accountService2" class="com.example.service.impl.AccountServiceImpl2">
        <property name="name" value="ttr"></property>
        <property name="age" value="344"></property>
        <property name="birth" ref="now"></property>
    </bean>
           

注意第一種方式和第二種方式的内部标簽名不一樣。

<bean id="userService" class="com.example.service.impl.UserServiceImpl">
        <property name="userDao" ref="userDao"></property>
    </bean>
    <bean id="userDao" class="com.example.dao.impl.UserDaoImpl">
        <property name="jdbcTemplate" ref="jdbcTemplate"></property>
    </bean>
           

比較複雜一點的注入。其中UserDaoImpl這個類依賴了UserDaoImpl這個類。

使用xml配置方式時,在擷取bean對象時先要擷取核心容器,

根據容器對象

ac

getbean()

,這個

ac

就是bean.xml中配置的JavaBean的執行個體化對象。

第三種就是用注解的自動注入方式

@Component,元件,和xml中bean标簽的作用一樣,表示把該類交給spring管理。

@Autowired,表示自動寫入,作用就是讓spring自動注入某個類所依賴的類的對象。

@Component
public class AccountServiceImpl implements AccountService {
    @Autowired
    private AccountMapper accountMapper;
    @Override
    public void saveAccount() {
        accountMapper.saveAccount();
    }
}
           

accountMapper就是使用了注解的方式注入。

一些注解

@RunWith(SpringJUnit4ClassRunner.class)//表示讓測試運作于spring環境
@ContextConfiguration(locations = "classpath:bean.xml")//注解方式擷取spring容器
           
@Configuration//配置類
@ComponentScan("com.example")//元件掃描包路徑
@Import(JdbcConfig.class)//導入配置類
@PropertySource("jdbcConfig.properties")//導入jdbc配置檔案
           

spring架構整合DBUtils架構和c3p0連接配接池

除了spring架構的坐标外,還有多增加以下坐标:

<dependency>
    <groupId>commons-dbutils</groupId>
    <artifactId>commons-dbutils</artifactId>
    <version>1.4</version>
</dependency>
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.6</version>
</dependency>
<dependency>
    <groupId>c3p0</groupId>
    <artifactId>c3p0</artifactId>
    <version>0.9.1.2</version>
</dependency>
           

以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: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/context
        http://www.springframework.org/schema/context/spring-context.xsd">

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

    <bean id="runner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype">
        <constructor-arg name="ds" ref="dataSource"></constructor-arg>
    </bean>

    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <property name="driverClass" value="com.mysql.cj.jdbc.Driver"></property>
        <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/test?useUnicode=true&amp;characterEncoding=utf8&amp;useSSL=false&amp;serverTimezone=UTC"></property>
        <property name="user" value="xxx"></property>
        <property name="password" value="xxx"></property>
    </bean>

</beans>
           

個人感覺資料庫的相關配置還是使用xml配置的方法比較簡便,而使用類的依賴用自動注入的方式就比較友善。

資料庫的sql語句:

@Repository
public class UserMapperImpl implements UserMapper {
    @Autowired
    private QueryRunner runner;
    @Override
    public List<User> findAll() {
        try {
            return (List<User>) runner.query("select * from usera",new BeanListHandler(User.class));
        } catch(Exception e) {
            throw new RuntimeException(e);
        }
    }
    @Override
    public User findById(Integer id) {
        try {
            return runner.query("select * from usera where id=?",new BeanHandler<User>(User.class),id);
        } catch(Exception e) {
            throw new RuntimeException(e);
        }
    }
}
           

第一次接觸QueryRunner這個類,沒想到使用還是挺友善的。

簡單的測試就隻使用這兩個方法。

spring架構整合jdbcTemplate

它的出現,解決了我疑惑,Java果然有自己親生的資料庫通路架構,而且為了使用友善都整合到這個類中,真的佩服寫jdk的大佬。

增加的坐标:

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-jdbc</artifactId>
    <version>5.0.2.RELEASE</version>
</dependency>
           

xml方式的ioc配置:

<?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.example.service.impl.UserServiceImpl">
        <property name="userDao" ref="userDao"></property>
    </bean>
    <!--set方式注入jdbcTemplate的方式-->
    <bean id="userDao" class="com.example.dao.impl.UserDaoImpl">
        <property name="jdbcTemplate" ref="jdbcTemplate"></property>
    </bean>
    <!--繼承JdbcDaoSupport方式-->
    <bean id="userDao2" class="com.example.dao.impl.UserDaoImpl2">
        <property name="dataSource" ref="dataSource"></property>
    </bean>
<!--    配置JdbcTemplate-->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"></property>
    </bean>
<!--    配置資料源-->
    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"></property>
        <property name="url" value="jdbc:mysql://localhost:3306/test?useUnicode=true&amp;characterEncoding=utf8&amp;useSSL=false&amp;serverTimezone=UTC"></property>
        <property name="username" value="root"></property>
        <property name="password" value="root"></property>
    </bean>
</beans>
           

這裡兩種方式都是用注解的方法用sql語句。

UserDaoImpl.java:

public class UserDaoImpl implements UserDao {
    JdbcTemplate jdbcTemplate;
    public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }
    @Override
    public List<User> findAllUser() {
        return jdbcTemplate.query("select * from usera", new BeanPropertyRowMapper<>(User.class));
    }
}
           

UserDaoImpl2.java:

public class UserDaoImpl2 extends JdbcDaoSupport implements UserDao {
    @Override
    public void saveUser(User user) {
        getJdbcTemplate().update("insert into usera (username,age) values (?,?)",user.getUsername(),user.getAge());
    }
    @Override
    public void updateUser(User user) {
        getJdbcTemplate().update("update usera set age=? where id=?",user.getAge(),user.getId());
    }
}
           

測試兩種方式:

public class JdbcTemplateTest1 {
    public static void main(String[] args) {
        DriverManagerDataSource dmd = new DriverManagerDataSource();
        dmd.setDriverClassName("com.mysql.cj.jdbc.Driver");
        dmd.setUrl("jdbc:mysql://localhost:3306/test?serverTimezone=UTC");
        dmd.setUsername("root");
        dmd.setPassword("root");
//        建立jdbcTemplate對象
        JdbcTemplate jt = new JdbcTemplate();
//        配置資料源
        jt.setDataSource(dmd);
        List<User> users = jt.query("select * from usera", new BeanPropertyRowMapper<>(User.class));
        for(User user:users)
            System.out.println(user);
    }
}
           

其中的DriverManagerDataSource類就整合了資料源的所有功能,用這種方式就不用bean.xml來配置ioc。

public class JdbcTemplateTest2 {
    public static void main(String[] args) {
//        擷取容器
        ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
//        擷取jdbcTemplate對象
        JdbcTemplate jt = (JdbcTemplate) ac.getBean("jdbcTemplate",JdbcTemplate.class);
        List<User> users = jt.query("select * from usera", new BeanPropertyRowMapper<>(User.class));
        for(User user:users)
            System.out.println(user);
    }
}
           

這是一般的方式,讀取bean.xml,擷取ioc容器然後通路資料庫,其中JdbcTemplate類就是Java自己對資料庫通路方式的封裝。

個人了解:使用mybatis架構最重要的特征就是有沒有映射檔案,比如UserDao.xml,如果沒有就可以認為沒有用到,因為它提供的就是SQL Maps和DAOs,如果沒用到那就不算整合了Mybatis架構。

事務管理TransactionManager

事務的概念

事務的概念:事務時通路并可能改變資料項的一個程式執行單元,說白了就是執行一次sql語句。它有四個性質:

1.原子性:事務中的全部操作要麼全部做完要麼全部不做。

2.隔離性:多個執行并發執行時,系統保證對任意事務都是單獨執行的。

3.一緻性:在沒有其他事務并發執行的情況下,保持資料庫的一緻性。

4.持久性:一個事務結束後,它對資料的改變時永久的,即使系統崩潰。

更詳細的介紹

這次學習的具體的事務就是,擷取連接配接後設定不自動送出,送出事務,復原事務,釋放連接配接,具體如下:

package com.example.utils;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.sql.SQLException;

/**
 * @className TransactionManager
 * @date 2021/3/27 14:53
 * @description 事務管理相關的工具類,包含,開啟事務,送出事務,復原事務,釋放連接配接
 **/
@Component
public class TransactionManager {
    @Autowired
    private ConnectionManager cm;
    public void setCm(ConnectionManager cm) {
        this.cm = cm;
    }
    public void startTransaction(){
        try {
            cm.getThreadConnection().setAutoCommit(false);
        } catch(SQLException e) {
            e.printStackTrace();
        }
    }
    public void commit(){
        try {
            cm.getThreadConnection().commit();
        } catch(SQLException e) {
            e.printStackTrace();
        }
    }
    public void rollBack(){
        try {
            cm.getThreadConnection().rollback();
        } catch(SQLException e) {
            e.printStackTrace();
        }
    }
    public void release(){
        try {
            cm.getThreadConnection().close();//傳回連接配接池中
            cm.removeConnection();//斷開連接配接
        } catch(SQLException e) {
            e.printStackTrace();
        }
    }
}
           

最開始的做法是每個service都要寫這幾個步驟,這樣一來寫代碼就變得繁瑣,而且程式直間的依賴或者說是耦合比較強,後來就使用靜态代理簡化代碼編寫,同時降低耦合。但靜态代理也有缺點就是如果有多個被代理對象那就必須有多個代理對象,這種做法也不好。後來就使用動态代理,它的主體思想就是随用随建立。對于動态代理,Java提供了一個Proxy對象,具體做法如下所示:

package com.example.factory;
import com.example.service.UserService;
import com.example.utils.TransactionManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
 * @className UserServiceProxyFactory
 * @date 2021/3/27 14:48
 * @description
 **/
public class UserServiceProxyFactory {
    @Autowired
    private UserService userService;
    @Autowired
    private TransactionManager txManager;
    /**
         * 動态代理:
         *  特點:位元組碼随用随建立,随用随加載
         *  作用:不修改源碼的基礎上對方法增強
         *  分類:
         *      基于接口的動态代理
         *      基于子類的動态代理
         *  基于接口的動态代理:
         *      涉及的類:Proxy
         *      提供者:JDK官方
         *  如何建立代理對象:
         *      使用Proxy類中的newProxyInstance方法
         *  建立代理對象的要求:
         *      被代理類最少實作一個接口,如果沒有則不能使用
         *  newProxyInstance方法的參數:
         *      ClassLoader:類加載器
         *          它是用于加載代理對象位元組碼的。和被代理對象使用相同的類加載器。固定寫法。
         *      Class[]:位元組碼數組
         *          它是用于讓代理對象和被代理對象有相同方法。固定寫法。
         *      InvocationHandler:用于提供增強的代碼
         *          它是讓我們寫如何代理。我們一般都是些一個該接口的實作類,通常情況下都是匿名内部類,但不是必須的。
         */
    @Bean
    public UserService getUserServiceProxy(){
        return (UserService) Proxy.newProxyInstance(userService.getClass().getClassLoader(),
                userService.getClass().getInterfaces(), new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                /**
                 * @Date 14:51 2021/3/27
                 * @Description //添加資料庫的事務支援
                 **/
                 /**
                     * 作用:執行被代理對象的任何接口方法都會經過該方法
                     * 方法參數的含義
                     * @param proxy   代理對象的引用
                     * @param method  目前執行的方法
                     * @param args    目前執行方法所需的參數
                     * @return        和被代理對象方法有相同的傳回值
                     * @throws Throwable
                     */
                if("test".equals(method.getName()))
                    return method.invoke(userService,args);
                Object rtValue=null;
                try {
//                  開啟事務
                    txManager.startTransaction();
//                  執行事務
                    rtValue = method.invoke(userService,args);
//                  送出事務
                    txManager.commit();
//                  傳回結果
                    return rtValue;
                }catch(Exception e){
//                    事務復原
                    txManager.rollBack();
                    throw new RuntimeException(e);
                }finally {
//                    斷開連接配接
                    txManager.release();
                }
            }
        });
    }
}

           

這裡動态代理對象對通路資料庫的操作做了增強,也就是說每次調用執行userService中的方法都要執行增加的函數,這些方法就是對資料庫操作的事務管理。

關于動态代理,其原理就是利用反射生成與被代理類擁有相同屬性和方法的代理類,然後用代理類的對象調用方法和屬性,由于面向對象的多态性,最終還是會執行被代理對象的方法。

spring架構的AOP配置

在軟體業,AOP為Aspect Oriented Programming的縮寫,意為:面向切面程式設計,通過預編譯方式和運作期動态代理實作程式功能的統一維護的一種技術。AOP是OOP的延續,是軟體開發中的一個熱點,也是Spring架構中的一個重要内容,是函數式程式設計的一種衍生範型。利用AOP可以對業務邏輯的各個部分進行隔離,進而使得業務邏輯各部分之間的耦合度降低,提高程式的可重用性,同時提高了開發的效率。

使用架構最大的改變就是配置大于程式設計,是以這次學習的重點還是AOP配置。

<!--配置連接配接工具類-->
    <bean id="connectionUtils" class="com.example.utils.ConnectionManager">
        <property name="dataSource" ref="dataSource"></property>
    </bean>
<!--配置事務管理器(相當于通知/增強)-->
    <bean id="txManager" class="com.example.utils.TransactionManager">
        <property name="cm" ref="connectionUtils"></property>
    </bean>
    <!--配置aop-->
    <aop:config>
        <!--配置通用切入點表達式-->
        <aop:pointcut id="pt1" expression="execution(* com.example.service.impl.*.*(..))"></aop:pointcut>
        <aop:aspect id="txAdvice" ref="txManager">
            <!--配置前置通知:開啟事務-->
            <aop:before method="startTransaction" pointcut-ref="pt1"></aop:before>
            <!--配置後置通知:送出事務-->
            <aop:after-returning method="commit" pointcut-ref="pt1"></aop:after-returning>
            <!--配置異常通知:復原事務-->
            <aop:after-throwing method="rollBack" pointcut-ref="pt1"></aop:after-throwing>
            <!--配置最終通知:釋放連接配接-->
            <aop:after method="release" pointcut-ref="pt1"></aop:after>
            <!--環繞通知配置-->
<!--            <aop:around method="aroundTransaction" pointcut-ref="pt1"></aop:around>-->
        </aop:aspect>
    </aop:config>
           

ConnectionManager這個類是擷取目前線程上對資料庫的連接配接,TransactionManager類就是增強類,所謂的事務管理方法類。

AOP面向切面程式設計,由數學知識點形成線,線組成面來看,這裡的切面就是由各個切入點和通知組合,稱之為切面。上面配置AOP所做的事情就是把增強方法和切入點關聯起來,由spring架構自動生成切入點所在類的代理類,進而自動實作事務管理。

還有一個環繞通知,也算是一種固定寫法:

public Object aroundTransaction(ProceedingJoinPoint pjp){
        Object rtValue = null;
        try {
            Object[] args = pjp.getArgs();
            System.out.println("。。。前置");
            cm.getThreadConnection().setAutoCommit(false);
            rtValue = pjp.proceed(args);
            System.out.println("。。。後置");
            cm.getThreadConnection().commit();
            return rtValue;
        } catch(Throwable throwable) {
            try {
                System.out.println("。。。異常");
                cm.getThreadConnection().rollback();
            } catch(SQLException e) {
                e.printStackTrace();
            }
            throw new RuntimeException(throwable);
        } finally {
            try {
                System.out.println("。。。最終");
                cm.getThreadConnection().close();//傳回連接配接池中
                cm.removeConnection();//斷開連接配接
            } catch(SQLException e) {
                e.printStackTrace();
            }
        }
    }
           

這種方式感覺沒有單獨配置靈活。

還有另外一種用注解配置AOP的方法就不寫了。

一些AOP術語

連接配接點(JoinPoint) 通俗來說就是配置切入點表達式中的類的所有方法。

切入點(Pointcut) 在這裡來說就是通路資料庫的幾個方法,比如前面提到的findAll,findById等。

切面(Aspect) 切面是通知和切入點的結合。

引入(introduction) 允許我們向現有的類添加新方法屬性。

目标(target) 引入中所提到的目标類,也就是要被通知的對象,也就是真正的業務邏輯,他可以在毫不知情的情況下,被織入切面。

織入(weaving) 把切面應用到目标對象來建立新的代理對象的過程。

spring架構的聲明式事務管理

這麼牛的架構,事務管理難道還有我們另外寫嗎?不,完全不用,spring架構自帶一套事務管理就是聲明式事務管理。

Spring的聲明式事務顧名思義就是采用聲明的方式來處理事務。這裡所說的聲明,就是指在配置檔案中聲明。用在Spring配置檔案中聲明式的處理事務來代替代碼式的處理事務。這樣的好處是,事務管理不侵入開發的元件,具體來說,業務邏輯對象就不會意識到正在事務管理之中,事實上也應該如此,因為事務管理是屬于系統層面的服務,而不是業務邏輯的一部分,如果想要改變事務管理策劃的話,也隻需要在定義檔案中重新配置即可;在不需要事務管理的時候,隻要在設定檔案上修改一下,即可移去事務管理服務,無需改變代碼重新編譯,這樣維護起來極其友善百度百科。

所有重點還是對它的配置:

<!--    spring中基于xml的聲明式事務控制配置-->
<!--    配置事務管理器-->
    <bean id="tx" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"></property>
    </bean>
<!--    配置事務的通知/增強-->
    <tx:advice id="txAdvice" transaction-manager="tx">
<!--        配置事務的屬性-->
        <tx:attributes>
            <tx:method name="*" propagation="REQUIRED" read-only="false"/>
            <tx:method name="find*" propagation="SUPPORTS" read-only="true"></tx:method>
        </tx:attributes>
    </tx:advice>
<!--    配置AOP-->
    <aop:config>
<!--        配置切入點表達式-->
        <aop:pointcut id="pt1" expression="execution(* com.example.service.impl.*.*(..))"/>
<!--        建立切入點和事務通知之間的對應關系-->
        <aop:advisor advice-ref="txAdvice" pointcut-ref="pt1"></aop:advisor>
    </aop:config>
           

我在練習時,自定義的事務管理不成功,一度認為是資料庫有問題,但用spring的聲明式事務管理就能成功,可見架構的力量多麼強大。到現在我都沒找到自定義事務不成功的原因。

自此spring架構的學習總結就結束了,确實從一個小白到現在知道點東西不容易。至于spring的源碼分析,以後再說。

spring MVC架構

說起來,springmvc是我第一次接觸,在加上之前學前端已經過去很久了,是以真沒感覺。

架構模型

SSM架構-學習總結,SSM環境搭建總體梳理

模型圖

SSM架構-學習總結,SSM環境搭建總體梳理

流程圖

servlet,controller

Java Servlet 是運作在 Web 伺服器或應用伺服器上的程式,它是作為來自 Web 浏覽器或其他 HTTP 用戶端的請求和 HTTP 伺服器上的資料庫或應用程式之間的中間層。

servlet我之前寫過,就是運作在web伺服器中的一段Java代碼,現在已經忘了,不過有了架構,好像也不用自己寫servlet了。

Controller說白了就是控制層,處理前端的具體請求,SSM這個架構中主要就是根據前端不同的請求調用service層的方法然後操作資料庫,傳回前端所需的資料。

jsp

JSP(全稱JavaServer Pages)是由Sun Microsystems公司主導建立的一種動态網頁技術标準。JSP部署于網絡伺服器上,可以響應用戶端發送的請求,并根據請求内容動态地生成HTML、XML或其他格式文檔的Web網頁,然後傳回給請求者。JSP技術以Java語言作為腳本語言,為使用者的HTTP請求提供服務,并能與伺服器上的其它Java程式共同處理複雜的業務需求。

JSP将Java代碼和特定變動内容嵌入到靜态的頁面中,實作以靜态頁面為模闆,動态生成其中的部分内容。JSP引入了被稱為“JSP動作”的XML标簽,用來調用内建功能。另外,可以建立JSP标簽庫,然後像使用标準HTML或XML标簽一樣使用它們。标簽庫能增強功能和伺服器性能,而且不受跨平台問題的限制。JSP檔案在運作時會被其編譯器轉換成更原始的Servlet代碼。JSP編譯器可以把JSP檔案編譯成用Java代碼寫的Servlet,然後再由Java編譯器來編譯成能快速執行的二進制機器碼,也可以直接編譯成二進制碼。百度

JavaBean,Model

JavaBean 是一種JAVA語言寫成的可重用元件。為寫成JavaBean,類必須是具體的和公共的,并且具有無參數的構造器。JavaBean 通過提供符合一緻性設計模式的公共方法将内部域暴露成員屬性,set和get方法擷取。衆所周知,屬性名稱符合這種模式,其他Java 類可以通過自省機制(反射機制)發現和操作這些JavaBean 的屬性。

Model是每次請求中都存在的預設參數,利用其addAttribute()方法即可将伺服器的值傳遞到jsp頁面中。

ModelAndView包含model和view兩部分,使用時需要自己執行個體化,利用ModelMap用來傳值,也可以設定view的名稱。

project建立

建立project或module選擇maven-archetype-webapp

依賴的maven坐标

<dependencies>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>5.1.20.RELEASE</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-web</artifactId>
      <version>5.1.20.RELEASE</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-webmvc</artifactId>
      <version>5.1.20.RELEASE</version>
    </dependency>
    <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>servlet-api</artifactId>
      <version>2.5</version>
    </dependency>
    <dependency>
      <groupId>javax.servlet.jsp</groupId>
      <artifactId>jsp-api</artifactId>
      <version>2.2</version>
    </dependency>
  </dependencies>
           

項目的結構

SSM架構-學習總結,SSM環境搭建總體梳理

其中的Java和resources是自己增加的,原本沒有。index.jsp是預設的啟動服務的加載頁,web.xml主要用來配置servlet,WEB-INF主要存放靜态資源。resources下還要配置視圖解析器等一些JavaBean。

springMVCconfig.xml:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       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/mvc
        http://www.springframework.org/schema/mvc/spring-mvc.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd">
	<!--注解掃描的包路徑-->
    <context:component-scan base-package="com.example"></context:component-scan>
    <!--視圖解析器-->
    <bean id="internalResourceViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/pages/"></property>
        <property name="suffix" value=".jsp"></property>
    </bean>
<!--前端控制器那些靜态資源請求不攔截-->
    <mvc:resources location="/js/" mapping="/js/**"></mvc:resources>
    <mvc:resources location="/css/" mapping="/css/**"/> <!-- 樣式 -->
    <mvc:resources location="/images/" mapping="/images/**"/> <!-- 圖檔 -->
    <!--開啟spring MVC架構注解支援-->
    <mvc:annotation-driven></mvc:annotation-driven>
</beans>
           

web.xml

<!DOCTYPE web-app PUBLIC
 "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
 "http://java.sun.com/dtd/web-app_2_3.dtd" >

<web-app>
  <display-name>Archetype Created Web Application</display-name>
  <servlet>
<!--    配置前端控制器-->
    <servlet-name>dispatcherServlet</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
<!--      擷取spring容器-->
      <param-name>contextConfigLocation</param-name>
      <param-value>classpath:springMVC.xml</param-value>
    </init-param>
<!--    啟動時加載-->
    <load-on-startup>1</load-on-startup>
  </servlet>
  <servlet-mapping>
    <servlet-name>dispatcherServlet</servlet-name>
<!--      所有請求都攔截-->
    <url-pattern>/</url-pattern>
  </servlet-mapping>
</web-app>
           

還記得要在controller層的類中加上

@Controller

注解,表示由spring架構管理。

Controller的幾種響應。

有傳回值

一般都是傳回字元串,如下所示:

@RequestMapping("/test")
    public String test(){
        System.out.println("TestController.test");
        //傳回到success.jsp頁面
        return "success";
    }
	/**
     * @Date 16:11 2021/3/29
     * @Description //關鍵字重新轉發
     **/
    @RequestMapping("/forward")
    public String testForward(){
        System.out.println("TestController.testForward");
        //直接轉到index.jsp頁面
        return "forward:/index.jsp";
    }
    /**
     * @Date 16:11 2021/3/29
     * @Description //關鍵字重定向
     **/
    @RequestMapping("/redirect")
    public String testRedirect(){
        System.out.println("TestController.testRedirect");
        //直接重定向到jsp頁面
        return "redirect:/index.jsp";
    }
           

無傳回值

/**
     * @Date 15:57 2021/3/29
     * @Description //傳回值是void
     **/
    @RequestMapping("/testVoid")
    public void testVoid(HttpServletRequest request, HttpServletResponse response) throws Exception{
//        請求轉發
//        request.getRequestDispatcher("/WEB-INF/pages/success.jsp").forward(request,response);
//        響應重定向
//        response.sendRedirect(request.getContextPath()+"/index.jsp");
//        直接響應
        response.setCharacterEncoding("utf-8");
        response.getWriter().print("hello你好");
        return;
    }
           
@RequestMapping("/test")
    public void test(HttpServletResponse response){
        response.setCharacterEncoding("utf-8");
        //直接在頁面上列印hello test
        response.getWriter().println("hello test");
    }
           

傳回ModelAndView

/**
     * @Date 16:46 2021/3/29
     * @Description //傳回ModelAndView
     **/
    @RequestMapping("/testModelAndView")
    public ModelAndView testModelAndView(){
        ModelAndView mv = new ModelAndView();
        User user = new User();
        mv.addObject("user1",user);
        mv.setViewName("success");
        return mv;
    }
           

Controller層請求的參數綁定

基本資料類型和String的綁定,jsp頁面的input标簽的name的值要與形參名保持一樣。

引用資料類型用form表單送出,同樣,input标簽的name的值要和類的屬性名保持一樣。

/**
     * 請求參數綁定入門
     * @return
     */
    @RequestMapping("/testParam")
    public String testParam(String username,String password){
        System.out.println("執行了...");
        System.out.println("使用者名:"+username);
        System.out.println("密碼:"+password);
        return "success";
    }
    /**
     * 請求參數綁定把資料封裝到JavaBean的類中
     * @return
     */
    @RequestMapping("/saveAccount")
    public String saveAccount(Account account){
        System.out.println("執行了...");
        System.out.println(account);
        return "success";
    }
           
<%--請求參數綁定--%>
    <a href="param/testParam?username=hehe&password=123">請求參數綁定</a>
    把資料封裝Account類中
    <form action="param/saveAccount" method="post">
        姓名:<input type="text" name="username" /><br/>
        密碼:<input type="text" name="password" /><br/>
        金額:<input type="text" name="money" /><br/>
        使用者姓名:<input type="text" name="user.uname" /><br/>
        使用者年齡:<input type="text" name="user.age" /><br/>
        <input type="submit" value="送出" />
    </form>
           

使用Model傳值

@RequestMapping("/findAll")
    public String findAll(Model model){
        System.out.println("AccountController.findAll");
        List<Account> accountList = accountService.findAll();
        model.addAttribute("list",accountList);
        return "success";
    }
           

把accountList與list關聯起來,可以在頁面上用EL表達式

${list}

擷取accountList的值。

使用ModelAndView傳值

@RequestMapping("/testModelAndView")
    public ModelAndView testModelAndView(){
        ModelAndView mv = new ModelAndView();
        User user = new User();
        mv.addObject("user1",user);
        mv.setViewName("success");
        return mv;
    }
           

同樣這個user1在success.jsp頁面上用

${user1.username} ${user1.password}

取出。

幾個注解

@RequestParam

@RequestMapping("/testRequestParam")
    public String testRequestParam(@RequestParam(name="name") String username){
        System.out.println("執行了...");
        System.out.println(username);
        return "success";
    }
           

這裡會列印出"哈哈"。

@RequestBody

/**
     * 擷取到請求體的内容
     * @return
     */
    @RequestMapping("/testRequestBody")
    public String testRequestBody(@RequestBody String body){
        System.out.println("執行了...");
        System.out.println(body);
        return "success";
    }
           

@PathVariable

/**
     * PathVariable注解
     * @return
     */
    @RequestMapping(value="/testPathVariable/{sid}")
    public String testPathVariable(@PathVariable(name="sid") String id){
        System.out.println("執行了...");
        System.out.println(id);
        return "success";
    }
           

@ModelAttribute

springMVC的自定義異常

自定義異常類要繼承異常類,同時還要配置異常處理類

package com.example.exception;
/**
 * @className SysException
 * @date 2021/3/30 11:59
 * @description 自定義異常類
 **/
public class SysException extends Exception{
    private String msg;
    public String getMsg() {
        return msg;
    }
    public SysException(String msg) {
        this.msg = msg;
    }
    public void setMsg(String msg) {
        this.msg = msg;
    }
    @Override
    public String toString() {
        return "SysException{" +
                "msg='" + msg + '\'' +
                '}';
    }
}
           
package com.example.exception;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
 * @className SysExceptionResolver
 * @date 2021/3/30 12:02
 * @description 異常處理類
 **/
@Component
public class SysExceptionResolver implements HandlerExceptionResolver {
    @Override
    public ModelAndView resolveException(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) {
//        擷取異常
        SysException exception = null;
        if(e instanceof SysException)
            exception = (SysException) e;
        else
            exception = new SysException("system fixing");
        ModelAndView mv = new ModelAndView();
        mv.addObject("errorMsg",exception.getMsg());
        mv.setViewName("error");
        return mv;
    }
}
           

重寫resolveException方法,實作自己的異常處理,這裡就向頁面傳回出錯頁面,同時輸出錯誤資訊。

<!--注入sysExceptionResolver-->
<bean id="sysExceptionResolver" class="com.example.exception.SysExceptionResolver"></bean>
           
@RequestMapping("/testException")
    public String testException() throws SysException{
        System.out.println("TestController.testException");
        try {
        //模拟異常
            int i=10/0;
        } catch(Exception e) {
            e.printStackTrace();
            throw  new SysException("sysException error");
        }
        return "success";
    }
           

springMVC的攔截器

自定義類要實作攔截器接口HandlerInterceptor

/**
 * @className MyInterceptor
 * @date 2021/3/30 13:12
 * @description 自定義攔截器
 **/
public class MyInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("MyInterceptor.preHandle");
        return true;
    }
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("MyInterceptor.postHandle");
    }
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("MyInterceptor.afterCompletion");
    }
           

配置自定義攔截器

<!--配置攔截器-->
    <mvc:interceptors>
        <mvc:interceptor>
            <mvc:mapping path="/test/*"/>
            <bean class="com.example.interceptor.MyInterceptor"></bean>
        </mvc:interceptor>
    </mvc:interceptors>
           

到這裡springMVC的基礎内容就結束了。

平時測試如果不熟悉寫頁面就可以用postman來發送請求,可以滿足基本的需要。

SSM架構簡單整合

項目就隻有一個資料類Account,使用Mysql資料庫,簡單的使用注解sql的方式,加入了spring架構的聲明式事務管理,簡單的實作了Account在資料庫中的CRUD。

單獨說明一點,使用架構的最大特點就是配置大于程式設計。

因為要提供源碼,是以這裡不再大段的粘貼了,着重說一下注意的問題。

一,版本問題

這裡spring相關的架構都用這個版本

<spring.version>5.0.2.RELEASE</spring.version>

,然後MySQL的驅動版本是

<version>8.0.11</version>

,配置資料源時要注意drive和url。還要注意c3p0這個連接配接池的版本,要用高版本的

<version>0.9.5.1</version>

,否則會出現參數無法解析的問題(好像是)。

二,測試的技巧

在整合時,可以先測試spring整合mybatis,直接調用dao的接口讀取資料庫的資料,看能否成功。然後再整合springMVC,也可以先用簡單的測試請求測試,所有的都ok後再整合。

三,源碼位址 (硬湊的三)

稍等,我看看GitHub能響應不,順帶回想一下怎麼用!!!

Github無法登入或響應時間長解決方法親測有用。

終于出現了。。。

[email protected]:azermu/SSM-heima-cast-ssm.git