【Spring源码分析】2、Spring 传统开发转为注解驱动开发

1、传统开发转为注解驱动开发

传统开发转为注解驱动开发在实际项目开发过程中,最明显的就是体现在ssm项目转变为springboot项目,原本各式各样的配置文件等,全部被取消了,从而采用零配置文件的方式进行开发。也就是使用java代码的形式进行配置,以下全文分析怎么从传统开发转为注解驱动开发,也就是说,怎么将配置文件一步步转变为注解的形式进行开发
在这里插入图片描述

(1)spring传统开发

spring中,最常见的传统开发为ssm(spring + springmvc + mybatis),通常情况下,我们需要在项目目录的的src\main\resources目录中创建applicationContext.xml配置文件,然后在src\main\webapp\WEB-INF\web.xml配置扫描我们的自定义文件,告诉DispatcherServlet加载的文件位置

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
         http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
         version="3.1">
         
    <!-- Spring mvc -->
    <servlet>
        <servlet-name>springmvc</servlet-name>
        <servlet-class>
            org.springframework.web.servlet.DispatcherServlet
        </servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:application-context.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
</web-app>

applicationContext.xml配置文件是ssm中最核心的配置文件,这里我举例说明通常情况下配置了些什么,后面再分析怎么将传统的配置文件转为注解驱动开发

<?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:p="http://www.springframework.org/schema/p"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:task="http://www.springframework.org/schema/task"
       xmlns:websocket="http://www.springframework.org/schema/websocket"
       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
           http://www.springframework.org/schema/aop
           http://www.springframework.org/schema/aop/spring-aop.xsd
           http://www.springframework.org/schema/tx
           http://www.springframework.org/schema/tx/spring-tx.xsd
           http://www.springframework.org/schema/task
           http://www.springframework.org/schema/task/spring-task.xsd
           http://www.springframework.org/schema/websocket
           http://www.springframework.org/schema/websocket/spring-websocket.xsd">

    <!-- 自动扫描bean,把所有注解类转换成bean -->
    <context:component-scan base-package="cn.tellsea"/>

    <!-- 加载驱动及Session作用域 -->
    <mvc:annotation-driven>
        <!--不使用默认消息转换器 -->
        <mvc:message-converters register-defaults="false">
            <!--spring消息转换器 -->
            <bean class="org.springframework.http.converter.ByteArrayHttpMessageConverter"/>
            <bean class="org.springframework.http.converter.BufferedImageHttpMessageConverter"/>
            <!--解决@Responcebody中文乱码问题 -->
            <!-- 将StringHttpMessageConverter的默认编码设为UTF-8 -->
            <bean class="org.springframework.http.converter.StringHttpMessageConverter">
                <constructor-arg value="UTF-8"/>
            </bean>
            <!--配合fastjson支持 -->
            <bean class="com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter">
                <property name="defaultCharset" value="UTF-8"/>
                <property name="supportedMediaTypes">
                    <list>
                        <!--顺序保持这样,避免IE下载出错 -->
                        <value>text/html;charset=UTF-8</value>
                        <value>application/json</value>
                    </list>
                </property>
                <property name="fastJsonConfig" ref="fastJsonConfig"/>
            </bean>
        </mvc:message-converters>
    </mvc:annotation-driven>

    <!--配置这里只需要编码-->
    <bean id="fastJsonConfig" class="com.alibaba.fastjson.support.config.FastJsonConfig">
        <!--默认编码格式 -->
        <property name="charset" value="UTF-8"/>
        <property name="serializerFeatures">
            <list>
                <value>WriteNullListAsEmpty</value>
                <value>WriteDateUseDateFormat</value>
                <value>PrettyFormat</value>
                <value>WriteMapNullValue</value>
                <value>WriteNullStringAsEmpty</value>
                <value>WriteNullListAsEmpty</value>
                <value>DisableCircularReferenceDetect</value>
            </list>
        </property>
    </bean>

    <!-- 视图解析器 -->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>
        <property name="prefix" value="/web/"/>
        <property name="suffix" value=".jsp"/>
        <property name="contentType" value="text/html;charset=UTF-8"/>
    </bean>

    <!-- 数据库连接池 -->
    <!-- 查看监控 http://localhost:8080/spring4.x/druid/index.html -->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"
          init-method="init" destroy-method="close">
        <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
        <!-- 基本属性 url、user、password -->
        <property name="url" value="jdbc:mysql://localhost:3306/spring-tellsea?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&useSSL=false"/>
        <property name="username" value="root"/>
        <property name="password" value="123456"/>
        <!-- 配置初始化大小、最小、最大 -->
        <property name="initialSize" value="30"/>
        <property name="minIdle" value="50"/>
        <property name="maxActive" value="300"/>
        <!-- 配置获取连接等待超时的时间 -->
        <property name="maxWait" value="60000"/>
        <!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 -->
        <property name="timeBetweenEvictionRunsMillis" value="60000"/>
        <!-- 配置一个连接在池中最小生存的时间,单位是毫秒 -->
        <property name="minEvictableIdleTimeMillis" value="300000"/>
        <property name="validationQuery" value="SELECT 'x'"/>
        <property name="testWhileIdle" value="true"/>
        <property name="testOnBorrow" value="false"/>
        <property name="testOnReturn" value="false"/>
        <!-- 打开PSCache,并且指定每个连接上PSCache的大小 -->
        <property name="poolPreparedStatements" value="true"/>
        <property name="maxPoolPreparedStatementPerConnectionSize" value="20"/>
        <!-- 配置监控统计拦截的filters,去掉后监控界面sql无法统计 -->
        <property name="filters" value="stat"/>
        <!-- 控制sql注入 -->
        <property name="proxyFilters">
            <list>
                <ref bean="wall-filter"/>
            </list>
        </property>
    </bean>

    <!-- 给 jdbcTemplate 注入 dataSource-->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"/>
    </bean>
    
    <!-- 防止sql注入 -->
    <bean id="wall-filter-config" class="com.alibaba.druid.wall.WallConfig" init-method="init">
        <!-- 指定配置装载的目录  -->
        <property name="dir" value="META-INF/druid/wall/mysql"/>
    </bean>

    <bean id="wall-filter" class="com.alibaba.druid.wall.WallFilter">
        <property name="dbType" value="mysql"/>
        <property name="config" ref="wall-filter-config"/>
    </bean>

    <!-- 事务管理器 -->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <!-- 声明式容器事务管理 ,transaction-manager指定事务管理器为transactionManager -->
    <tx:advice id="txAdvice" transaction-manager="transactionManager">
        <tx:attributes>
            <tx:method name="save*" propagation="REQUIRED"/>
            <tx:method name="add*" propagation="REQUIRED"/>
            <tx:method name="update*" propagation="REQUIRED"/>
            <tx:method name="delete*" propagation="REQUIRED"/>
            <tx:method name="lock*" propagation="REQUIRED"/>
            <tx:method name="enable*" propagation="REQUIRED"/>
            <tx:method name="exit*" propagation="REQUIRED"/>
            <tx:method name="*" read-only="true"/>
        </tx:attributes>
    </tx:advice>
    
    <!--数据逻辑事物管理-->
    <aop:config expose-proxy="true">
        <!-- 只对业务逻辑层实施事务 -->
        <aop:pointcut id="txPointcut" expression="execution(* cn.tellsea.service..*.*(..))"/>
        <!-- Advisor定义,切入点和通知分别为txPointcut、txAdvice -->
        <aop:advisor pointcut-ref="txPointcut" advice-ref="txAdvice"/>
    </aop:config>

    <!-- 文件上传 -->
    <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
        <property name="defaultEncoding" value="UTF-8"></property>
        <property name="resolveLazily" value="true"></property>
        <property name="maxInMemorySize" value="40960"></property>
        <property name="maxUploadSizePerFile" value="52428800"></property>
    </bean>

    <!-- Bean解析器,级别高于默认解析器,寻找bean对象进行二次处理 -->
    <bean id="beanNameViewResolver"
          class="org.springframework.web.servlet.view.BeanNameViewResolver" p:order="0">
    </bean>

    <!-- 拦截器配置 -->
    <mvc:interceptors>
        <mvc:interceptor>
            <mvc:mapping path="/**"/>
            <bean class="cn.tellsea.interceptor.MyInterceptor"/>
        </mvc:interceptor>
    </mvc:interceptors>
    
    <!-- 获取Spring Context -->
    <bean class="cn.tellsea.util.SpringContextUtils" id="SpringContextUtils"/>

    <!-- 异常处理 -->
    <bean id="exceptionResolver" class="cn.tellsea.handle.GlobalExceptionHandle"/>

    <!-- 允许swagger界面-->
    <mvc:resources location="classpath:/META-INF/resources/" mapping="swagger-ui.html"/>
    <mvc:resources location="classpath:/META-INF/resources/webjars/" mapping="/webjars/**"/>
</beans>

(2)spring注解驱动开发

spring注解驱动开发的典型代表springboot,全部采用注解的形式进各种配置信息的注入等操作

注解驱动开发就是使用注解的方式,达到扫描、注入、定义等操作,以下两种方式等效

<?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="person" class="com.ldc.bean.Person">
		<property name="age" value="18"></property>
		<property name="name" value="张三"></property>
	</bean>
</beans>
/**
 * @Description 配置类就等同以前的配置文件
 */
@Configuration //告诉Spring这是一个配置类
public class MainConfig {

    //相当于xml配置文件中的<bean>标签,告诉容器注册一个bean
    //之前xml文件中<bean>标签有bean的class类型,那么现在注解方式的类型当然也就是返回值的类型
    //之前xml文件中<bean>标签有bean的id,现在注解的方式默认用的是方法名来作为bean的id
    @Bean
    public Person person() {
        return new Person("lisi",20);
    }

}

(3)Spring注解驱动开发核心知识图

在这里插入图片描述

2、传统开发转为注解驱动开发(配置文件分析)

下面,我们通过案例,来体现怎么将传统开发转为注解驱动开发, 案例的核心分析为实际项目中最常见的相关xml配置信息

(1)案例:spring包扫描配置

传统开发

    <!-- 自动扫描bean,把所有注解类转换成bean -->
    <context:component-scan base-package="cn.tellsea"/>

注解驱动开发

import org.springframework.context.annotation.ComponentScan;

/**
 * @author Tellsea
 * @date 2020-8-13
 */
@ComponentScan("cn.tellsea")
public class ComponentScanConfig {
}

(2)案例:定制http消息转换器为FastJson

传统开发

    <!-- 加载驱动及Session作用域 -->
    <mvc:annotation-driven>
        <!--不使用默认消息转换器 -->
        <mvc:message-converters register-defaults="false">
            <!--spring消息转换器 -->
            <bean class="org.springframework.http.converter.ByteArrayHttpMessageConverter"/>
            <bean class="org.springframework.http.converter.BufferedImageHttpMessageConverter"/>
            <!--解决@Responcebody中文乱码问题 -->
            <!-- 将StringHttpMessageConverter的默认编码设为UTF-8 -->
            <bean class="org.springframework.http.converter.StringHttpMessageConverter">
                <constructor-arg value="UTF-8"/>
            </bean>
            <!--配合fastjson支持 -->
            <bean class="com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter">
                <property name="defaultCharset" value="UTF-8"/>
                <property name="supportedMediaTypes">
                    <list>
                        <!--顺序保持这样,避免IE下载出错 -->
                        <value>text/html;charset=UTF-8</value>
                        <value>application/json</value>
                    </list>
                </property>
                <property name="fastJsonConfig" ref="fastJsonConfig"/>
            </bean>
        </mvc:message-converters>
    </mvc:annotation-driven>

    <!--配置这里只需要编码-->
    <bean id="fastJsonConfig" class="com.alibaba.fastjson.support.config.FastJsonConfig">
        <!--默认编码格式 -->
        <property name="charset" value="UTF-8"/>
        <property name="serializerFeatures">
            <list>
                <value>WriteNullListAsEmpty</value>
                <value>WriteDateUseDateFormat</value>
                <value>PrettyFormat</value>
                <value>WriteMapNullValue</value>
                <value>WriteNullStringAsEmpty</value>
                <value>WriteNullListAsEmpty</value>
                <value>DisableCircularReferenceDetect</value>
            </list>
        </property>
    </bean>

注解驱动开发

import com.alibaba.fastjson.serializer.SerializerFeature;
import com.alibaba.fastjson.support.config.FastJsonConfig;
import com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter;
import org.springframework.boot.autoconfigure.http.HttpMessageConverters;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;

import java.util.ArrayList;
import java.util.List;

/**
 * Http FastJSON 转换器
 *
 * @author Tellsea
 * @date 2019/8/13
 */
@Configuration
public class HttpConverterConfig {

    @Bean
    public HttpMessageConverters fastJsonHttpMessageConverters() {
        // 1.定义一个converters转换消息的对象
        FastJsonHttpMessageConverter fastConverter = new FastJsonHttpMessageConverter();
        // 2.添加fastjson的配置信息,比如: 是否需要格式化返回的json数据
        FastJsonConfig fastJsonConfig = new FastJsonConfig();
        fastJsonConfig.setSerializerFeatures(
                SerializerFeature.WriteNullListAsEmpty,
                SerializerFeature.WriteDateUseDateFormat,
                SerializerFeature.PrettyFormat,
                SerializerFeature.WriteMapNullValue,
                SerializerFeature.WriteNullStringAsEmpty,
                SerializerFeature.WriteNullListAsEmpty,
                SerializerFeature.DisableCircularReferenceDetect
        );
        // 3.在converter中添加配置信息
        fastConverter.setFastJsonConfig(fastJsonConfig);
        // 4.处理中文乱码问题
        List<MediaType> fastMediaTypes = new ArrayList<>();
        fastMediaTypes.add(MediaType.APPLICATION_JSON_UTF8);
        fastConverter.setSupportedMediaTypes(fastMediaTypes);
        // 5.将converter赋值给HttpMessageConverter
        HttpMessageConverter<?> converter = fastConverter;
        // 6.返回HttpMessageConverters对象
        return new HttpMessageConverters(converter);
    }
}

类中使用了springboot的消息转换器HttpMessageConverters,当然也可以使用spring原生的方式改写,我这里以实际使用的案例为准

(3)案例:配置视图解析器

传统开发

    <!-- 视图解析器 -->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>
        <property name="prefix" value="/web/"/>
        <property name="suffix" value=".jsp"/>
        <property name="contentType" value="text/html;charset=UTF-8"/>
    </bean>

注解驱动开发

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.view.InternalResourceViewResolver;
import org.springframework.web.servlet.view.JstlView;

/**
 * @author Tellsea
 * @date 2020-8-13
 */
@Configuration
public class SpringMvcConfig {
 
    @Bean
    public InternalResourceViewResolver internalResourceViewResolver() {
        InternalResourceViewResolver resolver = new InternalResourceViewResolver();
        resolver.setViewClass(JstlView.class);
        resolver.setPrefix("/web/");
        resolver.setSuffix(".jsp");
        resolver.setContentType("text/html;charset=UTF-8");
        return resolver;
    }
}

在springboot中,配置视图解析器可以直接通过application.yml文件进行配置

(4)案例:配置防止sql注入

传统开发

    <!-- 防止sql注入 -->
    <bean id="wall-filter-config" class="com.alibaba.druid.wall.WallConfig" init-method="init">
        <!-- 指定配置装载的目录  -->
        <property name="dir" value="META-INF/druid/wall/mysql"/>
    </bean>

    <bean id="wall-filter" class="com.alibaba.druid.wall.WallFilter">
        <property name="dbType" value="mysql"/>
        <property name="config" ref="wall-filter-config"/>
    </bean>

注解驱动开发

import com.alibaba.druid.wall.WallConfig;
import com.alibaba.druid.wall.WallFilter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @author Tellsea
 * @date 2020-8-13
 */
@Configuration
public class DruidDataSourceConfig {

    @Bean("wall-filter-config")
    public WallConfig wallConfig() {
        WallConfig wallConfig = new WallConfig();
        wallConfig.setDir("META-INF/druid/wall/mysql");
        return wallConfig;
    }

    @Bean("wall-filter")
    public WallFilter wallFilter() {
        WallFilter wallFilter = new WallFilter();
        wallFilter.setDbType("mysql");
        wallFilter.setConfig(wallConfig());
        return wallFilter;
    }
}

(5)案例:配置数据库连接池

传统开发

    <!-- 数据库连接池 -->
    <!-- 查看监控 http://localhost:8080/spring4.x/druid/index.html -->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"
          init-method="init" destroy-method="close">
        <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
        <!-- 基本属性 url、user、password -->
        <property name="url" value="jdbc:mysql://localhost:3306/spring-tellsea?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&useSSL=false"/>
        <property name="username" value="root"/>
        <property name="password" value="123456"/>
        <!-- 配置初始化大小、最小、最大 -->
        <property name="initialSize" value="30"/>
        <property name="minIdle" value="50"/>
        <property name="maxActive" value="300"/>
        <!-- 配置获取连接等待超时的时间 -->
        <property name="maxWait" value="60000"/>
        <!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 -->
        <property name="timeBetweenEvictionRunsMillis" value="60000"/>
        <!-- 配置一个连接在池中最小生存的时间,单位是毫秒 -->
        <property name="minEvictableIdleTimeMillis" value="300000"/>
        <property name="validationQuery" value="SELECT 'x'"/>
        <property name="testWhileIdle" value="true"/>
        <property name="testOnBorrow" value="false"/>
        <property name="testOnReturn" value="false"/>
        <!-- 打开PSCache,并且指定每个连接上PSCache的大小 -->
        <property name="poolPreparedStatements" value="true"/>
        <property name="maxPoolPreparedStatementPerConnectionSize" value="20"/>
        <!-- 配置监控统计拦截的filters,去掉后监控界面sql无法统计 -->
        <property name="filters" value="stat"/>
        <!-- 控制sql注入 -->
        <property name="proxyFilters">
            <list>
                <ref bean="wall-filter"/>
            </list>
        </property>
    </bean>

注解驱动开发

import com.alibaba.druid.filter.Filter;
import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.wall.WallConfig;
import com.alibaba.druid.wall.WallFilter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

/**
 * @author Tellsea
 * @date 2020-8-13
 */
@Configuration
public class DruidDataSourceConfig {

    @Bean("wall-filter-config")
    public WallConfig wallConfig() {
        WallConfig wallConfig = new WallConfig();
        wallConfig.setDir("META-INF/druid/wall/mysql");
        return wallConfig;
    }

    @Bean("wall-filter")
    public WallFilter wallFilter() {
        WallFilter wallFilter = new WallFilter();
        wallFilter.setDbType("mysql");
        wallFilter.setConfig(wallConfig());
        return wallFilter;
    }

    @Bean("dataSource")
    public DataSource dataSource() throws SQLException {
        DruidDataSource druidDataSource = new DruidDataSource();
        druidDataSource.setDriverClassName("com.mysql.jdbc.Driver");
        druidDataSource.setUrl("jdbc:mysql://localhost:3306/zhengwu?useSSL=false");
        druidDataSource.setUsername("root");
        druidDataSource.setPassword("123456");
        druidDataSource.setInitialSize(30);
        druidDataSource.setMinIdle(50);
        druidDataSource.setMaxActive(300);
        druidDataSource.setMaxWait(60000);
        druidDataSource.setTimeBetweenEvictionRunsMillis(60000);
        druidDataSource.setMinEvictableIdleTimeMillis(300000);
        druidDataSource.setValidationQuery("SELECT 'x'");
        druidDataSource.setTestWhileIdle(true);
        druidDataSource.setTestOnBorrow(false);
        druidDataSource.setTestOnReturn(false);
        druidDataSource.setPoolPreparedStatements(true);
        druidDataSource.setMaxPoolPreparedStatementPerConnectionSize(20);
        druidDataSource.setFilters("stat");
        List<Filter> filters = new ArrayList<>();
        filters.add(wallFilter());
        druidDataSource.setProxyFilters(filters);
        return druidDataSource;
    }
}

(6)案例:配置JdbcTemplate注入数据源

传统开发

    <!-- 给 jdbcTemplate 注入 dataSource-->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"/>
    </bean>

注解驱动开发

    @Bean
    public JdbcTemplate jdbcTemplate() throws SQLException {
        JdbcTemplate jdbcTemplate = new JdbcTemplate();
        jdbcTemplate.setDataSource(dataSource());
        return jdbcTemplate;
    }

在配置数据库连接池的配置类中,增加JdbcTemplate的Bean配置

(7)案例:配置事务管理器

传统开发

    <!-- 事务管理器 -->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <!-- 声明式容器事务管理 ,transaction-manager指定事务管理器为transactionManager -->
    <tx:advice id="txAdvice" transaction-manager="transactionManager">
        <tx:attributes>
            <tx:method name="save*" propagation="REQUIRED"/>
            <tx:method name="add*" propagation="REQUIRED"/>
            <tx:method name="update*" propagation="REQUIRED"/>
            <tx:method name="delete*" propagation="REQUIRED"/>
            <tx:method name="lock*" propagation="REQUIRED"/>
            <tx:method name="enable*" propagation="REQUIRED"/>
            <tx:method name="exit*" propagation="REQUIRED"/>
            <tx:method name="*" read-only="true"/>
        </tx:attributes>
    </tx:advice>
    
    <!--数据逻辑事物管理-->
    <aop:config expose-proxy="true">
        <!-- 只对业务逻辑层实施事务 -->
        <aop:pointcut id="txPointcut" expression="execution(* cn.tellsea.service..*.*(..))"/>
        <!-- Advisor定义,切入点和通知分别为txPointcut、txAdvice -->
        <aop:advisor pointcut-ref="txPointcut" advice-ref="txAdvice"/>
    </aop:config>

注解驱动开发

import org.aspectj.lang.annotation.Aspect;
import org.springframework.aop.Advisor;
import org.springframework.aop.aspectj.AspectJExpressionPointcut;
import org.springframework.aop.support.DefaultPointcutAdvisor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.interceptor.*;

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

/**
 * 全局事物配置
 * <p>
 * REQUIRED :如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。
 * SUPPORTS :如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
 * MANDATORY :如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。
 * REQUIRES_NEW :创建一个新的事务,如果当前存在事务,则把当前事务挂起。
 * NOT_SUPPORTED :以非事务方式运行,如果当前存在事务,则把当前事务挂起。
 * NEVER :以非事务方式运行,如果当前存在事务,则抛出异常。
 * NESTED :如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于 REQUIRED 。
 * 指定方法:通过使用 propagation 属性设置,例如:@Transactional(propagation = Propagation.REQUIRED)
 *
 * @author Tellsea
 * @date 2020/3/7
 */
@Aspect
@Configuration
public class TransactionAdviceConfig {

    /**
     * 配置方法过期时间,默认-1,永不超时
     */
    private final static int TX_METHOD_TIME_OUT = 10;

    /**
     * 配置切入点表达式,这里解释一下表达式的含义
     * 1.execution(): 表达式主体
     * 2.第一个*号:表示返回类型,*号表示所有的类型
     * 3.com.schcilin.goods.service表示切入点的包名
     * 4.第二个*号:表示实现包
     * 5.*(..)*号表示所有方法名,..表示所有类型的参数
     */
    private static final String POITCUT_EXPRESSION = "execution(* cn.tellsea.service..*.*(..))";

    @Autowired
    private PlatformTransactionManager platformTransactionManager;

    @Bean
    public TransactionInterceptor txadvice() {
        /* 配置事务管理规则,声明具备管理事务方法名.这里使用public void addTransactionalMethod(String methodName, TransactionAttribute attr)*/
        NameMatchTransactionAttributeSource source = new NameMatchTransactionAttributeSource();
        Map<String, TransactionAttribute> nameMap = new HashMap<>(16);
        /*只读事物、不做更新删除等*/
        /*事务管理规则*/
        RuleBasedTransactionAttribute readOnlyRule = new RuleBasedTransactionAttribute();
        /*设置当前事务是否为只读事务,true为只读*/
        readOnlyRule.setReadOnly(true);
        /* transactiondefinition 定义事务的隔离级别;
         *  PROPAGATION_REQUIRED 如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中*/
        readOnlyRule.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
        RuleBasedTransactionAttribute requireRule = new RuleBasedTransactionAttribute();
        /*抛出异常后执行切点回滚*/
        requireRule.setRollbackRules(Collections.singletonList(new RollbackRuleAttribute(Exception.class)));
        /*PROPAGATION_REQUIRED:事务隔离性为1,若当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。这是默认值。 */
        requireRule.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
        /*设置事务失效时间,超过10秒,可根据hytrix,则回滚事务*/
        requireRule.setTimeout(TX_METHOD_TIME_OUT);

        nameMap.put("add*", requireRule);
        nameMap.put("save*", requireRule);
        nameMap.put("create*", requireRule);
        nameMap.put("insert*", requireRule);
        nameMap.put("update*", requireRule);
        nameMap.put("delete*", requireRule);
        nameMap.put("remove*", requireRule);
        /*进行批量操作时*/
        nameMap.put("batch*", requireRule);
        nameMap.put("get*", readOnlyRule);
        nameMap.put("query*", readOnlyRule);
        nameMap.put("find*", readOnlyRule);
        nameMap.put("select*", readOnlyRule);
        nameMap.put("count*", readOnlyRule);
        source.setNameMap(nameMap);
        return new TransactionInterceptor(platformTransactionManager, source);
    }

    /**
     * 设置切面=切点pointcut+通知TxAdvice
     *
     * @return
     */
    @Bean
    public Advisor txAdviceAdvisorByBusiness() {
        AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
        pointcut.setExpression(POITCUT_EXPRESSION);
        return new DefaultPointcutAdvisor(pointcut, txadvice());
    }
}

(8)案例:注入CommonsMultipartResolver处理文件上传

传统开发

    <!-- 文件上传 -->
    <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
        <property name="defaultEncoding" value="UTF-8"></property>
        <property name="resolveLazily" value="true"></property>
        <property name="maxInMemorySize" value="40960"></property>
        <property name="maxUploadSizePerFile" value="52428800"></property>
    </bean>

注解驱动开发

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.multipart.MultipartResolver;
import org.springframework.web.multipart.commons.CommonsMultipartResolver;

/**
 * @author Tellsea
 * @date 2020-8-13
 */
@Configuration
public class MultipartResolverConfig {

    @Bean(name = "multipartResolver")
    public MultipartResolver multipartResolver() {
        CommonsMultipartResolver resolver = new CommonsMultipartResolver();
        resolver.setDefaultEncoding("UTF-8");
        //resolveLazily属性启用是为了推迟文件解析,以在在UploadAction中捕获文件大小异常
        resolver.setResolveLazily(true);
        resolver.setMaxInMemorySize(40960);
        //上传文件大小 50M 50*1024*1024 bytes
        resolver.setMaxUploadSize(50 * 1024 * 1024);
        return resolver;
    }
}

springmvc中将MultipartFile转为file,需要注入CommonsMultipartResolver,否则报错

java.lang.ClassCastException: org.springframework.web.multipart.support.StandardMultipartHttpServletRequest$StandardMultipartFile cannot be cast to org.springframework.web.multipart.commons.CommonsMultipartFile

springboot中的相关配置为

spring:
  servlet:
    multipart:
      max-file-size: 20MB
      max-request-size: 100MB
      enabled: true
      resolve-lazily: false

(9)案例:配置Bean解析器的处理级别

传统开发

    <!-- Bean解析器,级别高于默认解析器,寻找bean对象进行二次处理 -->
    <bean id="beanNameViewResolver"
          class="org.springframework.web.servlet.view.BeanNameViewResolver" p:order="0">
    </bean>

注解驱动开发

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.web.servlet.view.BeanNameViewResolver;

/**
 * @author Tellsea
 * @date 2020-8-13
 */
@Configuration
public class BeanNameViewResolverConfig {

    @Bean
    @Order(0)
    public BeanNameViewResolver beanNameViewResolver() {
        return new BeanNameViewResolver();
    }
}

(10)案例:配置自定义拦截器

传统开发

    <!-- 拦截器配置 -->
    <mvc:interceptors>
        <mvc:interceptor>
            <mvc:mapping path="/**"/>
            <bean class="cn.tellsea.interceptor.MyInterceptor"/>
        </mvc:interceptor>
    </mvc:interceptors>

注解驱动开发
首先编写自定义拦截器,然后将拦截器注册到SpringMVC中

import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * @author Tellsea
 * @date 2020-8-13
 */
public class MyInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("在业务处理器处理请求之前被调用...");
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("在业务处理器处理完请求后被调用...");
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("在DispatcherServlet完全处理完请求后被调用...");
    }
}

注册自定义拦截器

import cn.tellsea.interceptor.MyInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

/**
 * @author Tellsea
 * @date 2020-8-13
 */
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new MyInterceptor()).addPathPatterns("/**");
    }
}

(11)案例:配置获取ApplicationContext工具类

传统开发

    <!-- 获取Spring Context -->
    <bean class="cn.tellsea.util.SpringContextUtils" id="SpringContextUtils"/>

注解驱动开发

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.util.Objects;

/**
 * @author Tellsea
 * @date 2020-8-13
 */
@Component
public class SpringContextUtils implements ApplicationContextAware {

    private static ApplicationContext applicationContext;

    public static HttpServletRequest getHttpServletRequest() {
        return ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        if (SpringContextUtils.applicationContext == null) {
            SpringContextUtils.applicationContext = applicationContext;
        }
        System.out.println("SpringUtils: {" + applicationContext + "}");
    }

    /**
     * 获取applicationContext
     *
     * @return
     */
    public static ApplicationContext getApplicationContext() {
        return applicationContext;
    }

    /**
     * 通过name获取 Bean
     *
     * @param name
     * @return
     */
    public static Object getBean(String name) {
        return getApplicationContext().getBean(name);
    }

    /**
     * 通过class获取 Bean
     *
     * @param clazz
     * @param <T>
     * @return
     */
    public static <T> T getBean(Class<T> clazz) {
        return getApplicationContext().getBean(clazz);
    }

    /**
     * 通过name,以及Clazz返回指定的Bean
     *
     * @param name
     * @param clazz
     * @param <T>
     * @return
     */
    public static <T> T getBean(String name, Class<T> clazz) {
        return getApplicationContext().getBean(name, clazz);
    }
}

(12)案例:配置全局异常处理类

传统开发

    <!-- 异常处理 -->
    <bean id="exceptionResolver" class="cn.tellsea.handle.GlobalExceptionHandle"/>

注解驱动开发

import lombok.extern.slf4j.Slf4j;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;

/**
 * @author Tellsea
 * @date 2020-8-13
 */
@Slf4j
@ControllerAdvice
@Order(value = Ordered.HIGHEST_PRECEDENCE)
public class GlobalExceptionHandle {

    @ExceptionHandler(value = Exception.class)
    public String exception(Exception e, Model model) {
        model.addAttribute("code", e.getClass());
        model.addAttribute("info", e.getMessage());
        log.error("【错误原因】{}", e.getClass());
        log.error("【错误描述】{}", e.getMessage());
        e.printStackTrace();
        return "admin/error";
    }
}

(13)案例:配置swagger静态资源访问放行

传统开发

    <!-- 允许swagger界面-->
    <mvc:resources location="classpath:/META-INF/resources/" mapping="swagger-ui.html"/>
    <mvc:resources location="classpath:/META-INF/resources/webjars/" mapping="/webjars/**"/>

注解驱动开发

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

/**
 * @author Tellsea
 * @date 2020-8-13
 */
@Configuration
public class SwaggerConfig implements WebMvcConfigurer {

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("swagger-ui.html").addResourceLocations("classpath:/META-INF/resources/");
        registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/");
    }
}

3、测试类的入口方式转变

下面以读取IOC容器中所有bean为例,检查测试类是否可用

传统开发

import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * @author Tellsea
 * @date 2020-8-13
 */
public class XmlTest {

    public static void main(String[] args) {
        ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
        String[] definitionNames = applicationContext.getBeanDefinitionNames();
        for (String name : definitionNames) {
            System.out.println(name);
        }
    }
}

注解驱动开发
需要先定一个配置扫描类,然后注解驱动加载这个扫描类得到IOC容器

import org.springframework.context.annotation.ComponentScan;

/**
 * @author Tellsea
 * @date 2020-8-13
 */
@ComponentScan("cn.tellsea")
public class AppConfig {
}
import cn.tellsea.AppConfig;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

/**
 * @author Tellsea
 * @date 2020-8-13
 */
public class AnnonTest {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
        String[] definitionNames = applicationContext.getBeanDefinitionNames();
        for (String name : definitionNames) {
            System.out.println(name);
        }
    }
}

4、更多细节学习

技术分享区

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Tellsea

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值