Spring

Spring

1. 介绍

Spring:春天 绿色的

赞美者 胶水语言

Spring框架不属于任何一层,Spring框架的思想旨在让其他框架专注于自身专业的问题解决,而不需要关注自身无关的问题处理。

  • Spring框架做了哪些事(目前代码中存在的问题)?
    • 回顾之前我们使用Mybatis框架,MyBatis框架是一个持久层框架,主要负责数据库的CRUD,但是目前我们还需要让mybatis处理事务以及日志的操作,这样不是很合理,所以以后 这样的"杂活"可以交给"专人"来处理,就是Spring框架

以上问题,Spring框架可以解决~

在实际开发中,将很少见到new关键字

2. Spring家族

3. 架构

  • Core核心部分
  • Bean 负责创建对象 IOC容器
  • Context Spring应用程序上下文
  • SpEL Spring提供的表达式
  • AOP Aspectes(Aspect Oriented Programming)面向切面编程,是指在不影响原业务逻辑代码的情况,实现业务逻辑的增强
  • Transaction 事务的处理
  • JDBC 连接数据库
  • ORM 关系映射
  • Web
    • Servlet SpringMVC对Servlet的封装
    • Web SpringMVC对web项目支持
  • Test Spring提供有对junit测试的支持

4. 特点

  • 轻量级 是指Spring框架将每一个功能都单独划分为一个小组件 可以按需取用
  • IOC inversion of control 控制反转
  • AOP Aspect Oriented Programming 面向切面编程

5. IOC

  • IOC称之为控制反转
    • 1.控制了什么?控制了创建对象的主权,以前new对象,但是会增加耦合度,现在使用spring框架不需要new对象了
    • 2.反转了什么?反转了创建对象这个事,以前自己new,现在由Spring框架创建
    • 3.如何实现控制反转?通过IOC容器,帮我们实现控制反转
  • DI dependicy injection 依赖注入,依赖IOC容器给创建的对象属性赋值,这个操作就是DI

6. 创建项目

6.1 添加依赖

 <!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.2.18.RELEASE</version>
        </dependency>

6.2 编写配置文件

  • 编写文件头 添加到idea模板
<?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">
</beansn>
<?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">

    <bean id="bHornDate" class="java.util.Date">
       <property name="year" value="120"/>
        <property name="month" value="10"/>
        <property name="hours" value="10"/>
    </bean>

    <bean id="hero1" class="com.qfedu.entity.Hero">
        <property name="hId" value="1"/>
        <property name="hName" value="张辽"/>
        <property name="hStory" value="关羽好兄弟"/>
        <property name="imgPath" value="abc"/>
        <property name="countryId" value="1"/>
        <property name="hBornDate" ref="bHornDate"/>
        <property name="country" ref="country"></property>
    </bean>

    <bean id="hero2" class="com.qfedu.entity.Hero">
        <property name="hId" value="2"/>
        <property name="hName" value="曹植"/>
        <property name="hStory" value="七步成诗"/>
        <property name="imgPath" value="abc"/>
        <property name="countryId" value="1"/>
        <property name="hBornDate" ref="bHornDate"/>
        <property name="country">
            <bean class="com.qfedu.entity.Country">
                <property name="cId" value="1"/>
                <property name="cName" value="魏国"/>
            </bean>
        </property>
    </bean>


    <bean id="country" class="com.qfedu.entity.Country">
        <property name="cId" value="1"/>
        <property name="cName" value="魏国"/>
    </bean>

</beans>

6.3 编写测试

getBean() 方法

可以传入bean标签中id属性值来获取对象

也可以再传入一个Class对象类指定创建对象的类型

import com.qfedu.entity.Country;
import com.qfedu.entity.Hero;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * @author WHD
 * @date 2021/11/5 11:11
 */
public class TestHero {
    public static void main(String[] args) {
//        Hero hero = new Hero();
        // 创建context对象 用于加载配置文件
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");

        // 通过context对象 根据bean标签 id属性来获取对象
        Object obj = context.getBean("hero1");
        if(obj instanceof  Hero){
            Hero hero = (Hero) obj;
            System.out.println(hero);
        }

        Country country = context.getBean("country", Country.class);
        System.out.println(country);

        Hero hero2 = context.getBean("hero2", Hero.class);
        System.out.println(hero2);


    }
}

6.4 注意

当前我们使用的方式是setter注入,那么必须给需要注入值的属性添加setter方法,否则注入失败,报错

7 .依赖(DI)注入方式

  • setter方法注入 必须给属性添加setter方法

  • 构造器注入

    • <!--约定大于配置  是spring一大特点  不写index  和 name 将默认按照构造器的属性顺序赋值-->
         <bean  id="country2" class="com.qfedu.entity.Country">
             <constructor-arg value="蜀国" index="1" name="cName"/>
             <constructor-arg value="2" index="0" name="cId"/>
         </bean>
      
  • p命名空间注入

    • xmlns:p="http://www.springframework.org/schema/p"
      <!--p命名空间 也是根据setter方法注入的 如果不写setter方法 报错-->
          <bean id="country3" class="com.qfedu.entity.Country" p:cId="3" p:cName="吴国"></bean>
      
          <bean id="hero3" class="com.qfedu.entity.Hero" p:country-ref="country3" ></bean>
      
  • 注解方式注入

    • @Component 任何类上都可以使用 通常用在工具类,或者实体类 因为其他的有特殊含义的类有固定的注解来描述
    • @Service注解 表示用于在Service实现类上
    • @Repository注解 表示用于在DAO实现类上 可以实现生成bean
    • @Controller注解 表示用于控制器上 实现生成bean
    • @Value注解 表示给属性注入值 引用数据类型 使用 value = "#{bean id}" 这种方式类实现注入
    • @Autowired 表示自动装配 按照类型找到一个对应的javabean进行注入
    • @Qualifier 表示可以指定具体beanid 通常结合Autowired来使用
  • 使用注解的方式不要忘记在主配置文件中加上对应的文件头 以及扫描注解

    • xmlns:context="http://www.springframework.org/schema/context"
      xsi:schemaLocation="
            http://www.springframework.org/schema/context
            https://www.springframework.org/schema/context/spring-context.xsd"
      
    • <context:component-scan base-package="com.qfedu.entity"></context:component-scan>
      

7.1 复杂参数注入

各种集合类型的数据

package com.qfedu.entity;

import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * @author WHD
 * @date 2021/11/5 16:26
 */
public class TestParam {
    private List<String> list1;
    private List<Country> list2;
    private Set<Country> set;
    private Map<String,String> map;

    public List<String> getList1() {
        return list1;
    }

    public List<Country> getList2() {
        return list2;
    }

    public Set<Country> getSet() {
        return set;
    }

    public Map<String, String> getMap() {
        return map;
    }

    public void setList1(List<String> list1) {
        this.list1 = list1;
    }

    public void setList2(List<Country> list2) {
        this.list2 = list2;
    }

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

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

<?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">


    <bean id="testParam" class="com.qfedu.entity.TestParam">
        <property name="list1" value="a,b,c,d,e" />
        <property name="list2">
            <list>
                <bean class="com.qfedu.entity.Country">
                    <property name="cId" value="1"/>
                    <property name="cName" value="魏国"/>
                </bean>
                <bean class="com.qfedu.entity.Country">
                    <property name="cId" value="2"/>
                    <property name="cName" value="蜀国"/>
                </bean>
                <bean class="com.qfedu.entity.Country">
                    <property name="cId" value="3"/>
                    <property name="cName" value="吴国"/>
                </bean>
                <bean class="com.qfedu.entity.Country">
                    <property name="cId" value="4"/>
                    <property name="cName" value="中立"/>
                </bean>
            </list>
        </property>
        <property name="set">
            <set>
                <bean class="com.qfedu.entity.Country">
                    <property name="cId" value="1"/>
                    <property name="cName" value="魏国"/>
                </bean>

                <bean class="com.qfedu.entity.Country">
                    <property name="cId" value="1"/>
                    <property name="cName" value="魏国"/>
                </bean>

                <bean class="com.qfedu.entity.Country">
                    <property name="cId" value="1"/>
                    <property name="cName" value="魏国"/>
                </bean>
            </set>
        </property>

        <property name="map">
            <map>
                <entry>
                    <key><value>"1"</value></key>
                    <value>"赵云"</value>
                </entry>

                <entry>
                    <key><value>"2"</value></key>
                    <value>"赵四"</value>
                </entry>

                <entry>
                    <key><value>"3"</value></key>
                    <value>"赵信"</value>
                </entry>
            </map>
        </property>
    </bean>

</beans>

8. IOC补充

8.1 主配置文件autowire属性

常用值为

byName:即根据名字来装配bean,会装配属性名和id名相同的b,并且类型相同bean,如果属性名和id不一致,则不能装配,属性值为null,不会报错

此时可以使用byType来装配,即根据类型

byType:即根据类型来装配,不再关注bean id和属性名是否一致,只会 找类型相同的bean,如果找到多个,将报错

expected single matching bean but found 2: c,country1 表示根据类型应该匹配一个,但是找到两个ioc容器不知道使用哪个

  <bean id="hero" class="com.qfedu.entity.Hero" autowire="byName | byType">
     
  </bean>

8.2 init-method和destory-method方法

这两个属性分别可以指定bean在生成之前,和bean在销毁之前执行的操作

只需要指定对应的方法名即可

    <bean id="c" class="com.qfedu.entity.Country" init-method="init" destroy-method="destory">
        <property name="cId" value="1"/>
        <property name="cName" value="魏国"/>
    </bean>
package com.qfedu.entity;

/**
 * @author WHD
 * @date 2021/11/8 9:12
 */
public class Country {
    private int cId;
    private String cName;

    public void init(){
        System.out.println("country init方法开始执行");
    }


    public void destory(){
        System.out.println("country destory方法开始执行");
    }

}

8.3 @PostConstruct和@PreDestory

这两个注解可以实现与8.2配置文件相同的效果

package com.qfedu.entity;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;

/**
 * @author WHD
 * @date 2021/11/8 9:12
 */
public class Country {
    private int cId;
    private String cName;

    @PostConstruct
    public void init(){
        System.out.println("country init方法开始执行");
    }

    @PreDestroy
    public void destory(){
        System.out.println("country destory方法开始执行");
    }
}

8.3 @AutoWired注解

  • 默认先根据类型装配,如果找到对应的bean ,则完成装配
  • 如果找不到与属性名一致的beanid,且找到多个同类型的,则报错
  • 如果没有 找到与属性名一致的beanid,但是只有一个类型相同的bean,也能完成装配
  • required属性默认为true,表示必须装配,改为false,则表示不是必须装配
  • @Autowired 如果存在多个相同类型,则优先匹配beanid和属性名一致的bean
  • 通常结合@Qulifier注解结合使用

8.4 @Resource注解

  • 默认按照与属性名对应的beanid来装配,也可以通过name属性来指定其他的beanid

  • 如果只有一个类型相同,且名字不一致,也能实现自动装配

    @Resource(name="country2")
     private Country country;
    

8.5 @Resource和@Autowired的区别

1.@Resources是Java提供的注解 @AutoWired是Spring提供的注解

2.@Resource默认是按照名字装配,即属性名和beanid一致则完成装配

@AutoWired默认按照类型来装配,即只要类型符合则完成装配

3.@Resource可以指定name属性和type属性,如果都指定表示寻找一个唯匹配的类型装配

只指定name,则按照名字查找

只指定type,则按照类型并且加上默认属性名为id查找

4.@Autowired 如果存在多个相同类型,则优先匹配beanid和属性名一致的bean

9. Bean的作用域

作用域
单例模式,默认为饿汉单例,可以通过@Lazy注解实现懒汉单例
@Scope("prototype")注解设置为prototype表示为非单例模式
@Scope("request") 表示在同一个请求中使用同一个对象
@Scope("session") 表示 在同一个session回话中使用同一个对象
@Scope("global session") 全局session

10. 代理模式

10.1 静态代理

静态代理只能代理固定的类,不易于程序的扩展

package com.qfedu.proxy;

/**
 * @author WHD
 * @date 2021/11/8 11:44
 */
public interface GeneralDao {
    void add();

    void del();

    void modify();
}
package com.qfedu.proxy;

/**
 * @author WHD
 * @date 2021/11/8 11:43
 */
public class CountryDaoImpl implements  GeneralDao{
   public void add(){
        System.out.println("添加country成功");
    }

    public void del(){
        System.out.println("删除country成功");
    }

    public void modify(){
        System.out.println("修改country成功");
    }
}

package com.qfedu.proxy;

/**
 * @author WHD
 * @date 2021/11/8 11:36
 */
public class HeroDaoImpl implements  GeneralDao{
   public void add(){
        System.out.println("添加hero成功");
    }

    public void del(){
        System.out.println("删除hero成功");
    }

    public void modify(){
        System.out.println("修改hero成功");
    }

}
package com.qfedu.proxy;

/**
 * @author WHD
 * @date 2021/11/8 11:36
 */
public class StaticProxy {
    private GeneralDao generalDao;

    public StaticProxy(GeneralDao generalDao) {
        this.generalDao = generalDao;
    }

    void add(){
       begin();
        generalDao.add();
       commit();

    }

    void del(){
        begin();
        generalDao.del();
        commit();
    }

    void modify(){
        begin();
        generalDao.modify();
        commit();
    }


    public void begin(){
        System.out.println("-----开启事务-----");
    }

    public void commit(){
        System.out.println("-----提交事务-----");
    }

}
package com.qfedu.proxy;

import com.qfedu.entity.Country;

/**
 * @author WHD
 * @date 2021/11/8 11:41
 */
public class Test {
    public static void main(String[] args) {
        CountryDaoImpl countryDaoImpl = new CountryDaoImpl();
        StaticProxy staticProxy = new StaticProxy(countryDaoImpl);
        staticProxy.add();

        staticProxy.del();

        staticProxy.modify();
    }
}

10.2 动态代理

  • JDK动态代理
    • 要求:
    • 1.被代理对象必须实现接口
    • 好处:
    • 灵活性,任何接口实现类都可以被代理,解决静态代理的局限性
package com.qfedu.proxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 * @author WHD
 * @date 2021/11/8 15:17
 */
public class DynamicProxy implements InvocationHandler {
    // 被代理对象
    private Object objProxy;

    public DynamicProxy(Object objProxy) {
        this.objProxy = objProxy;
    }


    // 获取代理对象
    // JDK动态代理
    public Object getAgent(){
        // 第一个参数 类加载器
        // 第二个参数 被代理的类实现的接口
        // 第三个参数 invocationHandler 拦截器
        ClassLoader classLoader = objProxy.getClass().getClassLoader();
        Class<?>[] interfaces = objProxy.getClass().getInterfaces();
        Object agent = Proxy.newProxyInstance(classLoader, interfaces, this);
        return agent;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        begin();
//        System.out.println("hello dynamic");
        Object returnValue = method.invoke(objProxy,args);
        commit();
        return returnValue;
    }

    public void begin(){
        System.out.println("---开启事务---");
    }


    public void commit(){
        System.out.println("---提交事务---");
    }


}

package com.qfedu.proxy.test;

import com.qfedu.entity.Country;
import com.qfedu.proxy.CountryDaoImpl;
import com.qfedu.proxy.DynamicProxy;
import com.qfedu.proxy.GeneralDao;
import com.qfedu.proxy.HeroDaoImpl;
import org.junit.Test;

/**
 * @author WHD
 * @date 2021/11/8 15:26
 */
public class TestDynamic {
    @Test
    public void test1(){
        CountryDaoImpl countryDaoImpl = new CountryDaoImpl();
        DynamicProxy dynamicProxy = new DynamicProxy(countryDaoImpl);

        Object agent = dynamicProxy.getAgent();
        if(agent instanceof GeneralDao) {
            GeneralDao generalDao = (GeneralDao) agent;
            generalDao.modify();
        }else{
            System.out.println("类型不匹配,不能转换");
        }
    }
}

  • CGLIb 动态代理

Code Generation Library 代码生成库

不要求被代理类实现接口,因为CGLIb是将被代理对象作为父类,生成一个代理对象子类

要求父类不能被final修饰

被final修饰的方法不能被代理

    <dependency>
            <groupId>cglib</groupId>
            <artifactId>cglib</artifactId>
            <version>3.3.0</version>
        </dependency>
package com.qfedu.proxy;

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

/**
 * @author WHD
 * @date 2021/11/8 16:24
 * CGLib动态代理 不要求代理类必须实现接口
 * CGLib的实现思想是将被代理对象作为父类
 * 如果被代理类是final修饰的 将 不能代理
 */
public class CGLibDynamicProxy implements MethodInterceptor {
    // 被代理对象
    private Object objProxy;

    public CGLibDynamicProxy(Object objProxy) {
        this.objProxy = objProxy;
    }

    public Object getAgent(){
        // 创建一个增强对象
        Enhancer enhancer = new Enhancer();
        // 设置父类
        enhancer.setSuperclass(objProxy.getClass());
        // callBack表示拦截父类的方法 由本类执行
        enhancer.setCallback(this);
        // 创建一个子类对象
        return enhancer.create();
    }


    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        begin();
        Object returnValue = method.invoke(objProxy, objects);
        commit();
        return returnValue;
    }


    public void begin(){
        System.out.println("---开启事务---");
    }


    public void commit(){
        System.out.println("---提交事务---");
    }
}
package com.qfedu.proxy.test;

import com.qfedu.proxy.CGLibDynamicProxy;
import com.qfedu.proxy.CountryDaoImpl;
import org.junit.Test;

/**
 * @author WHD
 * @date 2021/11/8 16:33
 */
public class TestCGLib {
    @Test
    public void test1(){
        CountryDaoImpl countryDaoImpl = new CountryDaoImpl();
        CGLibDynamicProxy cgLibDynamicProxy = new CGLibDynamicProxy(countryDaoImpl);

        Object agent = cgLibDynamicProxy.getAgent();
        if(agent instanceof  CountryDaoImpl){
            CountryDaoImpl countryDao = (CountryDaoImpl) agent;
            countryDao.del();
        }

    }
}

11. AOP

11.1 添加依赖

 <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.3.12</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aop</artifactId>
            <version>5.3.12</version>
        </dependency>


        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aspects</artifactId>
            <version>5.3.12</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>
    </dependencies>

11.2 编写Java类

package com.qfedu.dao;

/**
 * @author WHD
 * @date 2021/11/8 17:44
 */
public interface GeneralDao {
    void add();

    void del();

    void modify();
}

package com.qfedu.dao.impl;

import com.qfedu.dao.GeneralDao;

/**
 * @author WHD
 * @date 2021/11/8 17:45
 */
public class HeroDaoImpl implements GeneralDao {
    @Override
    public void add() {
        System.out.println("添加hero");
    }

    @Override
    public void del() {
        System.out.println("删除hero");
    }

    @Override
    public void modify() {
        System.out.println("修改hero");
    }
}

package com.qfedu.util;

/**
 * @author WHD
 * @date 2021/11/8 17:44
 */
public class AOPUtil {
    public void begin(){
        System.out.println("开启事务");
    }


    public void commit(){
        System.out.println("提交事务");
    }

}
import com.qfedu.dao.GeneralDao;
import com.qfedu.dao.impl.HeroDaoImpl;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * @author WHD
 * @date 2021/11/8 17:54
 */
public class TestAOP {
    @Test
    public void test1(){
        ApplicationContext context = new ClassPathXmlApplicationContext("application.xml");

        HeroDaoImpl heroDaoImpl = context.getBean("heroDaoImpl",HeroDaoImpl.class);

        heroDaoImpl.add();


    }
}

11.3 编写配置文件

<?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
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd
        ">

    <bean id="heroDaoImpl" class="com.qfedu.dao.impl.HeroDaoImpl"></bean>

    <bean id="aopUtil" class="com.qfedu.util.AOPUtil"></bean>

    <!--
       proxy-target-class="true" 不写或者false都默认使用JDK动态代理
       写为true表示使用CGLib动态代理
    -->
    <aop:config proxy-target-class="true">
        <!--
            第一个* 表示所有的返回值
            包名.类名
            (..) 表示所有参数个数、类型的参数
        -->
        <aop:pointcut id="pointCut" expression="execution(* com.qfedu.dao.impl.*.*(..))"/>

        <aop:aspect ref="aopUtil">
            <aop:before method="begin" pointcut-ref="pointCut"></aop:before>
            <aop:after method="commit" pointcut-ref="pointCut"></aop:after>
        </aop:aspect>
    </aop:config>

</beans>

11.4 测试

import com.qfedu.dao.GeneralDao;
import com.qfedu.dao.impl.HeroDaoImpl;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * @author WHD
 * @date 2021/11/8 17:54
 */
public class TestAOP {
    @Test
    public void test1(){
        ApplicationContext context = new ClassPathXmlApplicationContext("application.xml");

        HeroDaoImpl heroDaoImpl = context.getBean("heroDaoImpl",HeroDaoImpl.class);

        heroDaoImpl.add();


    }
}

12. AOP的切入点

切入点可以灵活的配置

<!--表示所有impl包下的所有类中的所有方法都将作为切入点-->
<aop:pointcut id="pointCut" expression="execution(* com.qfedu.dao.impl.*.*(..))"/>

 <!--表示只在HeroDaoImpl类中的返回值类型为void的add方法作为切入点-->
<aop:pointcut id="pointCut1" expression="execution(void com.qfedu.dao.impl.HeroDaoImpl.add(..))"/>

<!--表示只在HeroDaoImpl类中的返回值类型为int的无参del作为去切入点-->
<aop:pointcut id="pointCut2" expression="execution(int com.qfedu.dao.impl.HeroDaoImpl.del())"/>

13. AOP的增强

  • 前置增强
  • 后置增强
  • 返回值以后增强
  • 抛出异常以后增强
  • 环绕增强

13.1 AsceptJ

AsceptJ是一个单独的框架,Spring对其进行了集成,可以用于来实现AOP

接下来我们使用AsceptJ框架来实现日志的增强

  • 第一步 导入依赖
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.3.12</version>
</dependency>

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aop</artifactId>
    <version>5.3.12</version>
</dependency>


<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aspects</artifactId>
    <version>5.3.12</version>
</dependency>
<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
</dependency>

<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.9.7</version>
</dependency>

<dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    <version>1.2.12</version>
</dependency>
  • 第二步 编写日志工具类

    package com.qfedu.util;
    
    import org.apache.log4j.Logger;
    import org.aspectj.lang.JoinPoint;
    import org.aspectj.lang.ProceedingJoinPoint;
    
    
    /**
     * @author WHD
     * @date 2021/11/9 9:50
     */
    public class LogUtil {
        // 这行代码表示创建一个日志对象 用于人为的控制日志打印
        Logger logger = Logger.getLogger(LogUtil.class);
    
        public void before(JoinPoint joinPoint){
            logger.info("前置增强:");
            logger.info(joinPoint.getTarget().getClass().getName() + "类的" + joinPoint.getSignature().getName() + "准备执行……");
        }
    
        public void after(JoinPoint joinPoint){
            logger.info("后置增强:");
            logger.info(joinPoint.getTarget().getClass().getName() + "类的" + joinPoint.getSignature().getName() + "执行完毕");
        }
    
        public Object afterReturning(JoinPoint joinPoint,Object returnValue){
            logger.info("返回值以后增强:");
            logger.info(joinPoint.getTarget().getClass().getName() + "类的" + joinPoint.getSignature().getName() + "执行完毕");
            logger.info("返回值是" + returnValue);
            return returnValue;
        }
    
    
        public void afterThrowing(JoinPoint joinPoint,Exception ex){
            logger.info("抛出异常以后增强:");
            logger.info(joinPoint.getTarget().getClass().getName() + "类的" + joinPoint.getSignature().getName() + "执行完毕");
            logger.info(ex.getMessage());
        }
    
        public Object around(ProceedingJoinPoint proceedingJoinPoint){
            logger.info("环绕增强开始:");
            Object returnValue = null;
            try {
                returnValue = proceedingJoinPoint.proceed();
            } catch (Throwable throwable) {
                throwable.printStackTrace();
                logger.info("环绕增强异常信息:" + throwable.getMessage());
            }
            logger.info("环绕增强的返回值:" + returnValue);
            return returnValue;
        }
    
    }
    
    
  • 第三步 编写主配置文件

    <bean  id="logUtil" class="com.qfedu.util.LogUtil"></bean>
    
    <aop:config>
        <aop:pointcut id="pointCut" expression="execution(* com.qfedu.dao.impl.*.*(..))"/>
    
        <aop:aspect ref="logUtil">
            <aop:before method="before" pointcut-ref="pointCut"/>
            <aop:after method="after" pointcut-ref="pointCut"/>
            <!--returning属性值和 对应方法形参名称保持一致-->
            <aop:after-returning method="afterReturning" pointcut-ref="pointCut" returning="returnValue"/>
            <!--throwing属性值与对应方法形参名保持一致-->
            <aop:after-throwing method="afterThrowing" pointcut-ref="pointCut" throwing="ex"/>
            <aop:around method="around" pointcut-ref="pointCut"/>
        </aop:aspect>
    </aop:config>
    
    
  • 不要忘记添加log4j.properties文件

    log4j.rootLogger=DEBUG,Console 
    log4j.appender.Console=org.apache.log4j.ConsoleAppender
    log4j.appender.Console.layout=org.apache.log4j.PatternLayout
    log4j.appender.Console.layout.ConversionPattern=%d [%t] %-5p [%c] - %m%n
    log4j.logger.org.apache=INFO
    log4j.logger.org.springframework=ON
    log4j.logger.org.apache.struts2=OFF  
    log4j.logger.com.opensymphony.xwork2=OFF  
    log4j.logger.com.ibatis=ON  
    log4j.logger.org.hibernate=ON
    

13.2 注解实现日志增强

  • 第一步将原配置文件注释

  • 第二步修改LogUtil工具类

    package com.qfedu.util;
    
    import org.apache.log4j.Logger;
    import org.aspectj.lang.JoinPoint;
    import org.aspectj.lang.ProceedingJoinPoint;
    import org.aspectj.lang.annotation.*;
    import org.springframework.stereotype.Component;
    
    
    /**
     * @author WHD
     * @date 2021/11/9 9:50
     */
    @Component
    @Aspect // 表示当前类为一个切面
    public class LogUtil {
        // 这行代码表示创建一个日志对象 用于人为的控制日志打印
        Logger logger = Logger.getLogger(LogUtil.class);
    
        @Pointcut("execution(* com.qfedu.dao.impl.*.*(..))")
        public void pointCut(){}
    
        @Before("pointCut()")
        public void before(JoinPoint joinPoint){
            logger.info("前置增强:");
            logger.info(joinPoint.getTarget().getClass().getName() + "类的" + joinPoint.getSignature().getName() + "准备执行……");
        }
    
        @After("pointCut()")
        public void after(JoinPoint joinPoint){
            logger.info("后置增强:");
            logger.info(joinPoint.getTarget().getClass().getName() + "类的" + joinPoint.getSignature().getName() + "执行完毕");
        }
    
        @AfterReturning(pointcut = "pointCut()",returning = "returnValue")
        public Object afterReturning(JoinPoint joinPoint,Object returnValue){
            logger.info("返回值以后增强:");
            logger.info(joinPoint.getTarget().getClass().getName() + "类的" + joinPoint.getSignature().getName() + "执行完毕");
            logger.info("返回值是" + returnValue);
            return returnValue;
        }
    
    
        @AfterThrowing(pointcut = "pointCut()",throwing = "ex")
        public void afterThrowing(JoinPoint joinPoint,Exception ex){
            logger.info("抛出异常以后增强:");
            logger.info(joinPoint.getTarget().getClass().getName() + "类的" + joinPoint.getSignature().getName() + "执行完毕");
            logger.info(ex.getMessage());
        }
    
        // 这里是value属性 直接写值 表示 切入点
        @Around("pointCut()")
        public Object around(ProceedingJoinPoint proceedingJoinPoint){
            logger.info("环绕增强开始:");
            Object returnValue = null;
            try {
                returnValue = proceedingJoinPoint.proceed();
            } catch (Throwable throwable) {
                throwable.printStackTrace();
                logger.info("环绕增强异常信息:" + throwable.getMessage());
            }
            logger.info("环绕增强的返回值:" + returnValue);
            return returnValue;
        }
    }
    
  • 第三步 在主配置文件中开启扫描注解(添加contex文件头)

    <!--这表示扫描com.qfedu包下的所有的注解-->
    <context:component-scan base-package="com.qfedu"/>
    
  • 第四步 开启aspectj自动代理

    <!--表示开启aspectj自动代理 true-->
    <aop:aspectj-autoproxy proxy-target-class="true"/>
    

14. MyBatis-Spring整合

14.1 分析

  • 这两个框架分别实现了什么操作?
    • MyBatis:
      • CRUD
      • 获取数据源DataSource
      • 创建SqlSessionFactory
      • 生成SqlSession,通过openSession()方法
      • 加载Mapper文件,通过代理生成实现类
      • 起别名,自动(全局、局部)映射,配置数据源
      • 日志、事务的处理
    • Spring框架不属于任何一层,Spring框架的思想旨在让其他框架专注于自身专业的问题解决,而不需要关注自身无关的问题处理。所以,当我们Spring框架和Mybatis整合完了以后,MyBatis就真正能实现只专注于自身的功能实现,只做CRUD
    • Spring:
      • IOC:负责Bean的管理
      • AOP:负责日志以及事务的处理
    • 经过分析对比,我们发现除了CRUD这项工作Spring无法实现,其他的都可以实现

14.2 (1)需要哪些依赖?

<!-- mybatis依赖 -->
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.5.7</version>
</dependency>


<!--spring相关依赖-->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.3.12</version>
</dependency>

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-beans</artifactId>
    <version>5.3.12</version>
</dependency>

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aop</artifactId>
    <version>5.3.12</version>
</dependency>

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aspects</artifactId>
    <version>5.3.12</version>
</dependency>


<!--测试-->
<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
</dependency>


<!--日志-->
<dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    <version>1.2.12</version>
</dependency>


<!--aspectj aop框架-->
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.9.7</version>
    <scope>runtime</scope>
</dependency>


<!--mysql-->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.45</version>
</dependency>


<!-- 数据库连接池 -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.2.8</version>
</dependency>

除了以上我们之前使用的依赖之外,我们还需要引入一个新的依赖

就是Mybaits和Spring联合的依赖

这个依赖是Mybatis出的

mybatis-spring

<!-- mybatis和Spring联合的依赖 -->
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis-spring</artifactId>
    <version>2.0.0</version>
</dependency>

以上依赖是不支持web开发的mybatis结合spring

如果要添加web支持需要导入

servlet

jsp

jstl

spring-web 等依赖

14.3 (2) 编写配置文件

两个框架原来都需要一些配置文件,所以现在依然将两个框架的配置文件添加···

14.4 (3) 编写数据库连接信息文件

druid.url=jdbc:mysql://localhost:3306/tkm
druid.driver=com.mysql.jdbc.Driver
druid.username=root
druid.password=9999

druid.initSize=5
druid.maxActive=20
druid.minIdel=5
druid.maxWait=3000

14.5(4) 编写spring主配置文件

  • 1.加载druid.properties文件,获取数据源
  • 2.创建SqlSessionFactory
  • 3.编写实体类,DAO,以及Mapper文件,引入lombok依赖,以及spring-jdbc依赖
  • 4.配置MapperScannerConfigure
    • 1.将SqlSessionFactoryBeanName作为属性赋值
    • 将需要生成实现类的接口所在包作为属性赋值,以帮我们生成实现类
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-jdbc</artifactId>
    <version>5.3.12</version>
</dependency>

<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.20</version>
    <scope>provided</scope>
</dependency>
<?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
        https://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">

    <!--
        加载druid.properties文件
        classpath是Spring家族产品中经常使用的一个固定的标识
        表示类路径 即classes文件夹下
    -->
    <context:property-placeholder location="classpath:druid.properties"/>

    <!--第一步 配置数据源-->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="username" value="${druid.username}"/>
        <property name="password" value="${druid.password}"/>
        <property name="url" value="${druid.url}"/>
        <!--driver属于是对象类型 这里应该引入一个javabean-->
        <!--所以我们如果使用druid.properties文件中的driver信息 则使用driverName属性-->
        <property name="driverClassName" value="${druid.driver}"/>

        <!--数据库连接池信息-->
        <property name="initialSize" value="${druid.initSize}"/>
        <property name="maxActive" value="${druid.maxActive}"/>
        <property name="minIdle" value="${druid.minIdel}"/>
        <property name="maxWait" value="${druid.maxWait}"/>
    </bean>

    <!--第二步 -->
    <bean id="sessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource"/>
        <property name="typeAliasesPackage" value="com.qfedu.entity"/>
        <property name="mapperLocations" value="classpath:mappers/*Mapper.xml"/>
        <property name="configLocation" value="classpath:mybatis-config.xml"></property>
    </bean>


    <!--第三步 编写entity实体类 dao层接口和Mapper文件  注意 换战场了 -->

    <!--使用以上步骤可以已经可以实现CRUD了,但是如果这样每次都是自己获取SqlSession
      那么意味着事务依然需要自己处理 所以 我们将SqlSession也交给Spring来管理-->
    <!--第四步 将SqlSession创建 和 DAO实现类创建 交给Spring-->

    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="sqlSessionFactoryBeanName" value="sessionFactory"/>
        <!--此包下所有的接口都会帮我们生成实现类 注册为bean  且bean id是?????-->
        <property name="basePackage" value="com.qfedu.dao"/>
    </bean>

</beans>
package com.qfedu.entity;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * @author WHD
 * @date 2021/11/9 14:32
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Country {
    private int cId;
    private String cName;
}

package com.qfedu.dao;

import com.qfedu.entity.Country;

import java.util.List;

/**
 * @author WHD
 * @date 2021/11/9 14:34
 */
public interface CountryDao {
    List<Country> getAllCountries();
}

<?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.qfedu.dao.CountryDao">
    <select id="getAllCountries" resultType="Country">
        select * from country
    </select>

</mapper>
<?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="autoMappingBehavior" value="FULL"/>
        <setting name="cacheEnabled" value="true"/>
    </settings>
    
    
</configuration>
package com.qfedu.dao;

import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import static org.junit.Assert.*;

/**
 * @author WHD
 * @date 2021/11/9 14:37
 */
public class CountryDaoTest {

    @Test
    public void getAllCountries() {
        ApplicationContext context = new ClassPathXmlApplicationContext("application.xml");

//        SqlSessionFactory sessionFactory = context.getBean("sessionFactory", SqlSessionFactory.class);
//
//        SqlSession sqlSession = sessionFactory.openSession(true);
//
//        CountryDao countryMapper = sqlSession.getMapper(CountryDao.class);
//
//        System.out.println(countryMapper.getAllCountries());

        CountryDao countryDao = context.getBean("countryDao", CountryDao.class);
        System.out.println(countryDao.getAllCountries());

    }
}

14.6(5) 将事务交给Spring管理

隔离级别 幻读 不可重复读 脏读
SERIALIZABLE NO NO NO
REPEATABLE_READ YES NO NO
READ_COMMITTED YES YES NO
READ_UNCOMMITTED YES YES YES
  • 事务的传播机制
事务传播机制 描述
REQUIRED 必须使用事务,如果没有则新开启事务
SUPPORTS 支持事务,如果当前有事务则继续使用,没有则使用数据库默认事务
NOT_SUPPORTED 不支持,如果当前有事务,则挂起
 <!--第五步 将事务交给Spring管理-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <!-- 处理事务的配置  需要使用到新的标签 tx  在上方文件头加入 xmlns:tx-->
    <!--transaction-manager="transactionManager" 不需要单独写 因为默认值就是这个值-->
    <tx:advice id="advice" >
        <tx:attributes>
            <!--
                rollback-for="java.lang.Exception" 表示遇到这种异常将会回滚
                isolation 事务的隔离级别
                propagation 事务的传播机制
            -->
            <tx:method name="get*" isolation="REPEATABLE_READ" propagation="REQUIRED" rollback-for="java.lang.Exception"/>
            <tx:method name="add*" isolation="REPEATABLE_READ" propagation="SUPPORTS" rollback-for="java.lang.Exception"/>
            <tx:method name="modify*" isolation="REPEATABLE_READ" propagation="NOT_SUPPORTED" rollback-for="java.lang.Exception"/>
            <tx:method name="del*" isolation="REPEATABLE_READ" propagation="REQUIRED" rollback-for="java.lang.Exception"/>
        </tx:attributes>
    </tx:advice>
    
    <aop:config>
        <aop:pointcut id="pointCut" expression="execution(* com.qfedu.service.impl.*.*(..))"/>
        <aop:advisor advice-ref="advice" pointcut-ref="pointCut"></aop:advisor>
    </aop:config>

14.7(6) 补充

  • 将service层使用注解实现
package com.qfedu.service;

import com.qfedu.entity.Country;

import java.util.List;

/**
 * @author WHD
 * @date 2021/11/9 16:35
 */
public interface CountryService {
    List<Country> getAllCountries();
}

package com.qfedu.service.impl;

import com.qfedu.dao.CountryDao;
import com.qfedu.entity.Country;
import com.qfedu.service.CountryService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

/**
 * @author WHD
 * @date 2021/11/9 16:36
 */
@Service
public class CountryServiceImpl implements CountryService {

    @Autowired
    private CountryDao countryDao;

    @Override
    public List<Country> getAllCountries() {
        return countryDao.getAllCountries();
    }
}

15. 后续内容

  • 使用注解完成事务的配置
  • 使用注解的方式实现AOP日志增强
  • 将锋迷网整合为Spring + Mybatis + Servlet + JSP + Tomcat
posted @ 2021-12-28 21:11  牛亚龙  阅读(74)  评论(1编辑  收藏  举报