栏目分类:
子分类:
返回
终身学习网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
终身学习网 > IT > 软件开发 > 后端开发 > Java

Spring

Java 更新时间:发布时间: 百科书网 趣学号
一、现有阶段存在的问题
  • 以二阶段项目为例
1、类与类之间的耦合度过高

业务层与持久层的耦合度太高

  • 业务层实现必须要有持久层接口及实现才能编译通过,那么代表业务层实现跟持久层接口及实现都存在高耦合
    • 业务层实现在编译期间,代码是否OK跟具体的持久层实现没有一点关系
      • 但是现在如果把持久层实现去掉,连同业务层实现也会编译通不过
2、方法之间有耦合度过高

业务层实现跟事务的耦合度太高

  • 每个业务方法都需要控制事务,而控制事务的代码都是一层不变的。那么每个业务方法中控制事务的代码过于冗余。我们程序员写代码应该把重点放在业务上,而不是每次写业务都需要写一些没有意义的代码【控制事务】
  • 而且事务代码是否成功跟我们业务代码是否成功,理论上是没有直接关系。但是现在因为写在一个方法中,就会造成如果事务因为某些原因失败了,就会导致整个业务方法失败,那这明显是有问题的
二、解决方案
  • 使用工厂模式就可以解决类与类之间的耦合度过高
  • 使用代理模式就可以解决方法之间有耦合度过高
三、工厂模式 1、概述
  • 是由工厂把创建对象的细节隐藏起来,对外暴露获取对象的接口即可。用户无需关心对象是如何创建的
  • 分类
    • 实例工厂
    • 静态工厂
2、工厂实现
  • bean.properties
#在这个配置文件中,只需把Dao放在前面,就可以实现有序了
foodTypeDao=com.qf.java2108.dao.impl.FoodTypeDaoImpl
foodTypeService=com.qf.java2108.service.impl.FoodTypeServiceImpl
  • BeanFactory.java
package com.qfedu.factory;
import java.util.*;

public class BeanFactory {
    private static Properties properties = new Properties();
    private static Map ioc = new HashMap();

    static {
        try {
            properties.load(BeanFactory.class.getClassLoader().getResourceAsStream("bean.properties"));
            //获取所有属性名
            Set names = properties.stringPropertyNames();
            System.out.println(names);
            for (String name : names) {
                //获取全类名
                String clsName= (String) properties.get(name);
                Class clazz=Class.forName(clsName);
                if (!Objects.isNull(clazz)){
                    //反射构建对象
                   Object object=clazz.getConstructor().newInstance();
                   // 存储到map中
                   ioc.put(name,object);
                }


            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    public  static Object getBean(String beanName){
        return ioc.get(beanName);
    }

    public static void main(String[] args) {
        Object obj= getBean("foodTypeDao");
        System.out.println(obj);
    }
}

四、Spring介绍 1、概述
  • 官网:https://spring.io/projects/spring-framework

  • Spring

    • 一站式JavaEE框架

      • 使用Spring可以独立开发一个完整的JavaEE项目【JDBCTemplate + SpringIOC/AOP + SpringMVC】
    • 一个容器框架

      • 可以无缝集成第三方技术【Mybatis、Redis、RabbitMQ、Elasticsearch…】
    • 特点

      • 安全、使用简单、快
2、架构

Spring架构由诸多模块组成,可分类为

  • 核心技术:依赖注入,控制反转,AOP。

  • 数据访问:提供对 JDBC,ORM框架支持,包括事务管理。

  • WEB支持:Spring MVC。

  • 集成:整合管理第三方框架,整合mybais等。

3、两大核心技术
  • IOC
  • AOP
五、IOC 1、概述
  • 控制反转【Inversion of Control】
    • 控制:对象的创建权
    • 反转:以前我们自己创建对象,现在由Spring来创建和管理对象
  • 是一种编程思想
    • SpringIOC是对这种思想提供了相应的实现
  • 作用
    • 降低程序之间的耦合性
2、快速入门 2.1 导入依赖

    org.springframework
    spring-context
    5.1.6.RELEASE


    junit
    junit
    4.12

2.2 准备接口和实现
//接口
public interface FoodTypeService {
    int save();
}


//实现
public class FoodTypeServiceImpl implements FoodTypeService {
    @Override
    public int save() {
        System.out.println("FoodTypeServiceImpl save running ----->");
        return 1;
    }
}
2.3 编写Spring配置文件



    


2.4 测试
@Test
public void iocQuickTest() throws Exception {
    //1.创建容器
    ApplicationContext ioc = new ClassPathXmlApplicationContext("applicationContext.xml");

    //2.从容器获取组件
    FoodTypeService foodTypeService = (FoodTypeService) ioc.getBean("foodTypeService");

    //3.使用组件
    int row = foodTypeService.save();
    System.out.println("row = " + row);

}
3、Spring继承体系
  • BeanFactory:是Spring容器的顶层接口【对容器中的组件使用的是延迟加载】
    • ApplicationContext:是Spring容器的子接口,一般都会使用这个【对容器中的组件使用的是即时加载】
      • ClassPathXmlApplicationContext:是Spring容器的实现类,是基于类路径下的XML配置
      • FileSystemXmlApplicationContext:是Spring容器的实现类,是基于文件路径下的XML配置【一般不用】
      • AnnotationConfigApplicationContext::是Spring容器的实现类,是基于注解配置【SpringBoot采用的是全注解】
Spring继承体系
六、DI 1、问题
  • Spring能够管理业务层实现,也可以管理持久层实现,那么如何让Spring来实现在业务层中使用持久层的对象?
2、概述
  • 依赖注入【Dependency Injection】
    • 在创建对象的同时,为其属性赋值
    • 前提:依赖的组件和被依赖的组件必须在同一个容器中
  • 是IOC的一种具体的表现形式
3、分类 3.1 set注入

通过set方法完成属性的注入

  • 注入引用类型【大多用于自己写的类】

FoodTypeDao

public class FoodTypeDaoImpl implements FoodTypeDao {
    @Override
    public int save() {
        System.out.println("FoodTypeDaoImpl save --->");
        return 1;
    }
}
FoodServiceImpl
注入引用类型【使用的是ref】
  • 基本数据类型和String【自己跟第三方类库】

实体类

public class Employee {

    private Integer id;
    private String username;
    private Date birth;
    private Double salary;
    private Date createTime;

	//setter/getter方法
}
基本数据类型和String【使用的是value】
  • 注入集合属性【一般都是第三方类库】

测试实体:Demo

public class Demo {

    private String[] strs;
    private List stringList;
    private Set nums;
    private Map map;
    private Properties properties;

	//setter/getter方法
}

    
        
            AA
            BB
            CC
        
    
    
        
            aa
            bb
        
    
    
        
            11
            22
            33
            44
        
    
    
        
            
            
            
                
            
        
    
    
        
            jdbc:mysql://localhost:3306/java2108
            root
        
    

3.2 p注入【看得懂】

底层使用的就是set注入

需要导入约束
xmlns:p="http://www.springframework.org/schema/p"
p注入
3.3 构造器注入【一般用于第三方组件】

通过构造器完成属性的注入



    
    
        
            
            ]]>
        
    
    

如果在注入的属性值有特殊符号的情况下,可以使用CDATA区完成注入

3.4 自动注入【看得懂】

配置文件形式:了解

注解形式:掌握

自动注入【配置文件形式】
3.5 工厂注入【看得懂】

准备工作

package com.qf.java2108.pojo;


public class Car {

    private Integer id;
    private String name;
    private Double price;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Double getPrice() {
        return price;
    }

    public void setPrice(Double price) {
        this.price = price;
    }

    public Car() {
    }

    public Car(Integer id, String name, Double price) {
        this.id = id;
        this.name = name;
        this.price = price;
    }

    @Override
    public String toString() {
        return "Car{" +
                "id=" + id +
                ", name='" + name + ''' +
                ", price=" + price +
                '}';
    }
}
  • 实例工厂注入
public class CarInstanceFactory {

    public Car getCar(){
        return new Car(111, "BMW", 500000D);
    }

}



  • 静态工厂注入
public class CarStaticFactory {

    public static Car getCar(){
        return new Car(222, "Audi", 350000D);
    }
}


七、Bean的细节 1、Spring的Bean默认是单例的
  • 创建时机:默认是在容器初始化时,就创建对象

  • 可以通过设置scope属性为prototype为实现对象是多例的,以此来保证线程的安全问题

    • scope的取值

      • singleton:默认值,表示单例
      • prototype:原型,也就是多例
      • 以下取值,基本不用
      request:WEB环境下,表示一次请求创建一个对象
      session:WEB环境下,表示一次会话创建一个对象
      
      
  • 由于Spring的Bean默认是单例的,所以我们在使用Spring管理这些bean实例时,往往都是选择配置像Dao、Service、Controller这种类型的bean,而不会选择配置实体类型

2、初始化时机和销毁时机
  • init-method:初始化bean时会去调用的方法
  • destroy-method:销毁bean时会去调用的方法
初始化与销毁
3、bean生命周期
  • 单例bean
Spring容器初始化【构造器 ---> set方法 ---> init-method指定的方法】 ---> 使用bean ---> 销毁bean【Spring容器销毁】
  • 多例bean
Spring容器初始化 ---> 第一次获取bean时【构造器 ---> set方法 ---> init-method指定的方法】 ---> 使用bean ---> 销毁bean【由GC控制】
4、创建复杂Bean 4.1、举例
  • JDBC中Connection
Class.forName("com.mysql.jdbc.Driver");
Connection connection = DriverManager.getConnection(url, username, password);
4.2、FactoryBean

是Spring用来管理复杂Bean的一个接口,提供一个getObject方法来创建和返回该复杂对象对应的Bean

  • 导入依赖

    mysql
    mysql-connector-java
    5.1.6

  • 创建一个类【ConnectionFactoryBean】实现FactoryBean接口,重写抽象方法
package com.qf.java2108.factorybean;

import org.springframework.beans.factory.FactoryBean;

import java.sql.Connection;
import java.sql.DriverManager;


public class ConnectionFactoryBean implements FactoryBean {

    
    @Override
    public Connection getObject() throws Exception {
        Class.forName("com.mysql.jdbc.Driver");
        String url = "jdbc:mysql:///java2108";
        String username = "root";
        String password = "root";
        return DriverManager.getConnection(url, username, password);
    }

    
    @Override
    public Class getObjectType() {
        return Connection.class;
    }

    
    @Override
    public boolean isSingleton() {
        return true;
    }
}

  • Spring配置文件


  • 测试代码
@Test
public void factoryBeanTest() throws Exception {
    ApplicationContext ioc = new ClassPathXmlApplicationContext("applicationContext-info.xml");

    Connection connection = (Connection) ioc.getBean("connection");
    //得到的是一个com.mysql.jdbc.JDBC4Connection@7674f035
    System.out.println("connection = " + connection);  

}

Mybatis中用来连接和操作数据库的核心对象SqlSession也是通过这种方式来管理的

5、getBean方法

是BeanFactory提供用来获取容器中bean的方法,有很多重载

  • getBean(String beanName)
    • 根据bean名称获取bean,需要强转
  • getBean(String beanName, Class beanClass)
    • 根据bean名称及其类型来获取bean,不需要强转
  • getBean(Class beanClass)
    • 根据bean的类型来获取bean,不需要强转,要求容器有且只一个该类型的bean
@Test
public void factoryBeanTest() throws Exception {
    ApplicationContext ioc = new ClassPathXmlApplicationContext("applicationContext-info.xml");

    Connection connection = (Connection) ioc.getBean("connection");
    System.out.println("connection = " + connection);

    Connection connection1 = ioc.getBean("connection", Connection.class);

    Connection connection2 = ioc.getBean(Connection.class);

    System.out.println(connection == connection1);
    System.out.println(connection == connection2);

}

八、其他标签 1、需求:配置一个数据源【druid】
  • 导入依赖

    com.alibaba
    druid
    1.1.10

  • 配置文件





    
    
    
    
    
    

  • 测试
@Test
public void dataSourceTest() throws Exception {
    ApplicationContext ioc = new ClassPathXmlApplicationContext("applicationContext-dataSource.xml");
    DataSource dataSource = (DataSource) ioc.getBean("dataSource");
    System.out.println(dataSource.getConnection());

}
2、 context:property-placeholder
  • 用于加载外部配置文件




3、import
  • 导入其他Spring的配置文件【一般情况下用于多配置文件】




    

多配置文件读取测试

@Test
public void manyIocTest() throws Exception {
    ApplicationContext ioc = new ClassPathXmlApplicationContext("applicationContext-dataSource.xml", "applicationContext-dao.xml");
    FoodTypeDao foodTypeDao = (FoodTypeDao) ioc.getBean("foodTypeDao");
    foodTypeDao.save();
}
九、IOC相关注解 1、创建Bean相关 1.1 用于创建自己写的类的对象
  • @Component:标记在类上
    • 衍生注解,功能跟@Component一样
      • @Repository:标记在Dao上
      • @Service:标记在Service上
      • @Controller:标记在Controller上

要想注解生效,需要配置组件扫描【包扫描器】---- Spring配置文件中












@Component
//等同于:
//@Component("ft")
//等同于:
public class FoodType {

    private Integer id;
    private String name;

	//getter/setter
}
@Repository
public class FoodTypeDaoImpl implements FoodTypeDao {

    private DataSource dataSource;

    public void setDataSource(DataSource dataSource) {
        this.dataSource = dataSource;
    }

    @Override
    public int save() throws SQLException {
        System.out.println("FoodTypeDaoImpl save");
        //System.out.println(dataSource);

        return 1;
    }
}
1.2 一般用于第三方的组件
  • @Bean:标记在方法上【默认bean的id为方法名】

    把当前方法的返回值注入到Spring容器中

    • 要配套@Component或者@Configuration注解一起使用
    • @Configuration:代表当前类是Spring的一个配置类

如:数据源

@Configuration   //代表是Spring的一个配置类
public class DataSourceConfig {

    @Bean
    public DataSource dataSource(){
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setDriverClassName("com.mysql.jdbc.Driver");
        dataSource.setUrl("jdbc:mysql://localhost:3306/java2108?useSSL=false&serverTimezone=UTC&characterEncoding=UTF8&useUnicode=true");
        dataSource.setUsername("root");
        dataSource.setPassword("root");
        dataSource.setMaxActive(3000);
        dataSource.setMinIdle(5);
        return dataSource;
    }

}

当前这个类必须被组件扫描到

2、注入属性相关的
  • @Value:注入基本数据类型和String
@Component
public class FoodType {

    @Value("100")
    private Integer id;

    @Value("zhangsan")
    private String name;

	//getter/setter
}
  • @Autowired:自动注入

    • 按照类型自动注入
      • 如果找到多个同类型的bean,那么会把属性作为bean的名字再次匹配
        • 如果匹配到,注入成功
        • 如果匹配不到,则注入失败
      • 如果只找到一个该类型的bean,那么就直接注入
      • 如果找不到就报错了
    • 配套@Qualifier使用,@Qualifier仅用于指定bean的名称,没有自动注入的功能
  • @Resource

    • 按名称注入,可以使用name属性来指定bean的名称
@Service("foodTypeService")
public class FoodTypeServiceImpl implements FoodTypeService {

    //@Autowired   //按照类型自动注入,一般适用于容器只有一个bean的情况
    //@Qualifier("foodTypeDao2")   //配套@Autowired使用,用于指定bean的名称
    @Resource(name = "foodTypeDao2")   //等同于 @Autowired + @Qualifier
    private FoodTypeDao foodTypeDao;

    @Override
    public int save() throws SQLException {
        int row = foodTypeDao.save();
        return row;
    }
}
3、生命周期相关【看得懂】
  • @Scope:标记在类上,作用跟配置文件中的bean标签的scope属性一样
    • singleton:默认值,表示单例
    • prototype:原型,表示多例
  • @PostConstruct:标记在方法上,作用跟配置文件中的bean标签的init-method属性一样
  • @PreDestroy:标记在方法上,作用跟配置文件中的bean标签的destroy-method属性一样
@Component
//@Scope("prototype")
public class FoodType {

    @Value("100")

    private Integer id;

    @Value("zhangsan")
    private String name;

    @PostConstruct
    public void init(){
        System.out.println("FoodType init.....");
    }

    @PreDestroy
    public void destroy(){
        System.out.println("FoodType destroy.....");
    }
    
    //getter/setter
    
}
4、其他注解 4.1 @Configuration

表示当前类是一个配置类,作用等同于Spring的配置文件

4.2 @Import

导入其他配置类,作用等同于Spring的配置文件的import标签

4.3 @PropertySource

加载外部配置文件,作用等同于Spring的配置文件的context:property-placeholder标签

4.4 @ComponentScan

组件扫描,作用等同于Spring的配置文件的context:component-scan标签

@Configuration   //表示当前类是一个配置类
@Import(DataSourceConfig.class)   //导入其他配置类
//@ComponentScan(basePackages = "com.qf.java2108")
//@ComponentScan(basePackages = {"com.qf.java2108"})
@ComponentScan("com.qf.java2108")  //组件扫描
public class SpringConfig {
}
@Configuration   //代表是Spring的一个配置类
//@PropertySource("classpath:jdbc.properties")
@PropertySources({
        @PropertySource("classpath:jdbc.properties"),   //加载外部配置文件
        @PropertySource("classpath:redis.properties")
})
public class DataSourceConfig {

    @Value("${jdbc.url}")
    String url;

    @Bean
    public DataSource dataSource(){
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setDriverClassName("com.mysql.jdbc.Driver");
        //dataSource.setUrl("jdbc:mysql://localhost:3306/java2108?useSSL=false&serverTimezone=UTC&characterEncoding=UTF8&useUnicode=true");
        dataSource.setUrl(url);
        dataSource.setUsername("root");
        dataSource.setPassword("root");
        dataSource.setMaxActive(3000);
        dataSource.setMinIdle(5);
        return dataSource;
    }

}
  • 基于纯注解测试
@Test
public void annoTest() throws Exception {
    ApplicationContext ioc = new AnnotationConfigApplicationContext(SpringConfig.class);
    FoodTypeService foodTypeService = ioc.getBean(FoodTypeService.class);
    foodTypeService.save();
}
十、代理模式 1、概述
  • 23种设计模式
  • 创建一个代理对象,让代理对象去完成目标对象想要完成的功能 + 增强功能
  • 分类
    • 静态代理:事先创建好代理对象,然后让代理对象去完成目标对象要完成的功能
    • 动态代理:在程序运行期间,动态的生成代理对象,然后让代理对象去完成目标对象要完成的功能
2、动态代理分类
  • JDK动态代理

    • 基于接口创建代理对象
    目标跟代理之间的关系【兄弟】
  • CGLIB动态代理

    • 基于父类来创建代理对象
目标跟代理之间的关系【父子】
3、JDK动态代理 3.1 准备工作
  • 接口:UserService
package com.qf.java2108.service;


public interface UserService {

    int save(int i);

    int update(int i);

    int findAll();

}
  • 实现:UserServiceImpl
package com.qf.java2108.service.impl;

import com.qf.java2108.service.UserService;


public class UserServiceImpl implements UserService {
    @Override
    public int save(int i) {
        //int m = 1/0;
        System.out.println("UserServiceImpl save方法----->" + i);
        return 1;
    }

    @Override
    public int update(int i) {
        System.out.println("UserServiceImpl update方法----->" + i);
        return 1;
    }

    @Override
    public int findAll() {
        System.out.println("UserServiceImpl findAll方法----->");
        return 1;
    }
}
3.2 编写动态创建代理对象的底层代码
  • 手动创建代理对象
package com.qf.java2108.test;

import com.qf.java2108.factory.jdkproxy.InstanceProxyFactory;
import com.qf.java2108.service.UserService;
import com.qf.java2108.service.impl.UserServiceImpl;
import org.junit.Test;


public class UserServiceTest {

    
    @Test
    public void jdkDynamicProxyTest() throws Exception {

        //target:目标对象
        UserService target = new UserServiceImpl();

        //1.创建一个代理对象
        
        UserService proxy = (UserService)Proxy.newProxyInstance(
                target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),
                new InvocationHandler() {
                    */
public class InstanceProxyFactory {

    private Object target;

    public InstanceProxyFactory(Object target){
        this.target = target;
    }

    public Object getProxy(){
        return Proxy.newProxyInstance(
                target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),
                new InvocationHandler() {
                    
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        Object result = null;
                        try {
                            if(method.getName() != null && method.getName().startsWith("find")) {
                                result = method.invoke(target, args);
                            } else {
                                System.out.println("开启事务----------------");
                                //调用目标方法
                                result = method.invoke(target, args);
                                System.out.println("提交事务----------------");
                            }
                        } catch (Exception e) {
                            //e.printStackTrace();
                            System.out.println("回滚事务----------------");
                        }
                        return result;
                    }
                });
    }
}
@Test
public void jdkDynamicProxyTest() throws Exception {

    //target:目标对象
    UserService target = new UserServiceImpl();

    //创建一个代理对象
	InstanceProxyFactory instanceProxyFactory = new InstanceProxyFactory(target);
    UserService proxy = (UserService)instanceProxyFactory.getProxy();
    proxy.save(10);
    proxy.update(100);
    proxy.findAll();

}
4、CGLIB动态代理 4.1 准备工作
  • UserServiceImpl
4.2 编写动态创建代理对象的底层代码
  • 创建代理对象的工厂
package com.qf.java2108.factory.cglibproxy;

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

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


public class CglibInstanceProxyFactory {

    private Object target;

    public CglibInstanceProxyFactory(Object target){
        this.target = target;
    }

    public Object getProxy(){

        
        return Enhancer.create(
                target.getClass(),
                new MethodInterceptor() {
                    
                    @Override
                    public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
                        Object result = null;
                        try {
                            if(method.getName() != null && method.getName().startsWith("find")) {
                                result = method.invoke(target, args);
                            } else {
                                System.out.println("开启事务----------------");
                                //调用目标方法
                                result = method.invoke(target, args);
                                System.out.println("提交事务----------------");
                            }
                        } catch (Exception e) {
                            //e.printStackTrace();
                            System.out.println("回滚事务----------------");
                        }
                        return result;
                    }
                });
    }
}
  • 测试代码
@Test
public void cglibDynamicProxyTest() throws Exception {
    UserServiceImpl target = new UserServiceImpl();
    CglibInstanceProxyFactory factory = new CglibInstanceProxyFactory(target);
    UserServiceImpl proxy = (UserServiceImpl) factory.getProxy();
    proxy.findAll();
    proxy.save(100);
}
十一、AOP 1、概述
  • 面向切面编程,是一种基于横切关注点的编程思想
    • 使用到AOP的技术
      • WEB阶段的Filter
      • 分页插件【拦截器】
  • SpringAOP就是对AOP进行了实现
    • aspectj【增强】
    • 声明式事务
  • 底层就是动态代理
2、术语
  1. 连接点(Joinpoint):程序执行的某个特定位置:如类某个方法调用前、调用后、方法抛出异常后等。连接点由两个信息确定:方法表示的程序执行点;相对点表示的方位。
  • ==目标类中可以被增强功能的方法 > 就是连接点(Joinpoint)
    • 譬如:业务层每个方法都可以加事务【事务就是被增强的功能】
  1. 切点(pointcut):每个类都拥有多个连接点:例如 类中所有方法实际上都是连接点,即连接点是程序类中客观存在的事务。AOP 通过切点定位到特定的连接点。类比:连接点相当于数据库中的记录,切点相当于查询条件。切点和连接点不是一对一的关系,一个切点匹配多个连接点,切点通过 org.springframework.aop.Pointcut 接口进行描述,它使用类和方法作为连接点的查询条件。(你想切入到指定目标类的哪些方法上面)
    譬如: 你只想让其中的几个,在调用这几个方法之前,之后或者抛出异常时干点什么,那么就用切点来定义这几个方法,让切点来筛选连接点,选中哪几个你想要的方法。
  • ==目标类中真正被增强功能的方法 > 就是切点
    • 譬如:一个类有增删改查四个方法,都可以被增强事务功能,但是实际上只给增跟删方法增强事务功能,那么增跟删就被称为切点
  1. 通知(Advice): (又称增强,切点中增强功能的代码)切面必须要完成的工作 (需求:日志,事务,验证等)
  • 譬如现在要给方法增加事务功能,那么这个功能就被称为叫 通知/增强
  1. 切面(Aspect): 横切关注点(跨越应用程序多个模块的功能)被模块化的特殊对象 (通知和切入点的结合)
  • 通知/增强 + 切点 = 切面
  1. 目标(Target): 被通知的对象 (被追踪的目标类,也就是真正的业务逻辑)
  • 要增强功能的类的对象
  1. 织入(Weaving)
  • 把 增强 应用到 目标 的过程
  1. 代理(Proxy): 向目标对象应用通知之后创建的对象
  • 一个类被织入增强后,产生的结果就是一个代理(代理对象)
  1. 引介(Introduction)
  • 一种特殊的通知/增强
  • 听不懂没关系,不影响我们使用
3、入门

使用SpringAOP来完成代理模式【增加事务】的实现

3.1 具体实现
  • 依赖

    org.springframework
    spring-context
    5.1.6.RELEASE



    org.springframework
    spring-aspects
    5.1.6.RELEASE



    junit
    junit
    4.12

  • 业务层接口与实现
UserService
UserServiceImpl
  • 事务通知/增强类【控制事务】
public class TxAdvice {

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

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

    public void rollback(){
        System.out.println("回滚事务-------------->");
    }

    public void release(){
        System.out.println("释放资源-------------->");
    }

}
  • Spring配置文件



    
    

    

    
    
        
        
        
        
            
            
            
            
            
            
            
            
        
    


3.2 增强分类
  • 前置增强:在目标方法执行之前执行
  • 后置增强:在目标方法正确执行之后执行
  • 异常增强:在目标方法出现异常时执行
  • 最终增强:无论目标方法是否正常执行,都会执行
  • 环绕增强:上述四个一起使用

在使用增强类型时,采用能用小的就用小的。

​ 后置增强跟异常增强只会执行一个

后置增强跟异常增强是可以得到结果的
3.3 连接点
  • JoinPoint
    • 可以在增强类中为方法加入JoinPoint类型的形参,以此获取当前连接点的信息
public void begin(JoinPoint joinPoint){
    //可以通过连接点获取相应的目标信息
    String methodName = joinPoint.getSignature().getName();
    Object[] args = joinPoint.getArgs();
    List params = args == null ? null : Arrays.asList(args);
    System.out.println(methodName + "开启事务--------------> 参数:" + params);
}


public void commit(JoinPoint joinPoint, Object result){
    String methodName = joinPoint.getSignature().getName();
    Object[] args = joinPoint.getArgs();
    List params = args == null ? null : Arrays.asList(args);
    System.out.println(methodName + "提交事务--------------> 结果:" + result + ", 参数:" + params);
}
 
3.4 环绕增强 
  • 增强类增加环绕增强的方法
public Object around(ProceedingJoinPoint pjp){
    //返回值
    Object result = null;

    try {
        String methodName = pjp.getSignature().getName();
        Object[] args = pjp.getArgs();
        List params = args == null ? null : Arrays.asList(args);
        System.out.println(methodName + "开启事务--------------> 参数:" + params);

        //调用目标方法
        result = pjp.proceed();

        System.out.println(methodName + "提交事务--------------> 结果:" + result + ", 参数:" + params);
    } catch (Throwable throwable) {
        //throwable.printStackTrace();
        System.out.println("回滚事务-------------->" + throwable.getMessage());
    } finally {
        System.out.println("释放资源-------------->");
    }

    return result;
}
 
  • 配置文件
配置文件
4、多增强配置

需要在业务层再增加日志功能【日志功能就是一个增强功能】

  • 创建日志通知类
public class LogAdvice {

    public void beforeLog(JoinPoint joinPoint){
        //可以通过连接点获取相应的目标信息
        String methodName = joinPoint.getSignature().getName();
        Object[] args = joinPoint.getArgs();
        List params = args == null ? null : Arrays.asList(args);
        System.out.println(methodName + "方法开始执行了,参数:" + params);
    }

}
 
  • Spring配置文件
Spring配置文件
5、切点表达式
execution (public int com.qf.java2108.service.impl.UserServiceImpl.save(int))
  • 一般都会使用通配的方式以满足需求
    • 如:正常情况下,业务层的每个方法基本都要加日志,除查询外都要加事务
execution (* com.qf.java2108.service.impl.*.*(..))

#上述配置代表,com.qf.java2108.service.impl包下,所有的类,所有的方法都需要进行功能增强
#*: 代表通配
#..: 写在方法参数的地方,代表方法不计类型,不计个数
修改如下,发现业务层的所有方法都进行了功能增强
6、纯注解AOP开发

@Aspect:标记在类上,表示当前类是一个切面

@Order:标记当前切面的执行时机

@Pointput:标记在方法上,表示当前方法是一个切点

@Before:前置通知

@AfterReturning:后置通知

@AfterThrowing: 异常通知

@After:最终通知

@Around: 环绕通知

@EnableAspectJAutoProxy:开启注解AOP

  • 主配置类
@Configuration
@ComponentScan(basePackages = {"com.qf.java2108"})
@EnableAspectJAutoProxy  //开启注解AOP
public class SpringConfig {
}
  • 事务通知类
package com.qf.java2108.advice;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import java.util.Arrays;
import java.util.List;


@Component
@Aspect   //这是一个切面类
@Order(1)
public class TxAdvice {

    @Pointcut("execution (* com.qf.java2108.service.impl.*.*(..))")
    public void pt(){}

    @Before("pt()")
    public void begin(JoinPoint joinPoint){
        //可以通过连接点获取相应的目标信息
        String methodName = joinPoint.getSignature().getName();
        Object[] args = joinPoint.getArgs();
        List params = args == null ? null : Arrays.asList(args);
        System.out.println(methodName + "开启事务--------------> 参数:" + params);
    }

    
    @AfterReturning(value = "pt()", returning = "result")
    public void commit(JoinPoint joinPoint, Object result){
        String methodName = joinPoint.getSignature().getName();
        Object[] args = joinPoint.getArgs();
        List params = args == null ? null : Arrays.asList(args);
        System.out.println(methodName + "提交事务--------------> 结果:" + result + ", 参数:" + params);
        System.out.println("释放资源-------------->");
    }

    @AfterThrowing(value = "pt()", throwing = "ex")
    public void rollback(Exception ex){
        System.out.println("回滚事务-------------->" + ex.getMessage());
        System.out.println("释放资源-------------->");
    }

    //@After("pt()")
    public void release(){
        System.out.println("释放资源-------------->");
    }

    //@Around("pt()")
    public Object around(ProceedingJoinPoint pjp){
        //返回值
        Object result = null;

        try {
            String methodName = pjp.getSignature().getName();
            Object[] args = pjp.getArgs();
            List params = args == null ? null : Arrays.asList(args);
            System.out.println(methodName + "开启事务--------------> 参数:" + params);

            //调用目标方法
            result = pjp.proceed();

            System.out.println(methodName + "提交事务--------------> 结果:" + result + ", 参数:" + params);
        } catch (Throwable throwable) {
            //throwable.printStackTrace();
            System.out.println("回滚事务-------------->" + throwable.getMessage());
        } finally {
            System.out.println("释放资源-------------->");
        }

        return result;
    }

}
 
  • 日志通知类
package com.qf.java2108.advice;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import java.util.Arrays;
import java.util.List;


@Component
@Aspect   //这是一个切面类
@Order(2)
public class LogAdvice {

    @Pointcut("execution (* com.qf.java2108.service.impl.*.*(..))")
    public void pt(){}

    @Before("pt()")
    public void beforeLog(JoinPoint joinPoint){
        //可以通过连接点获取相应的目标信息
        String methodName = joinPoint.getSignature().getName();
        Object[] args = joinPoint.getArgs();
        List params = args == null ? null : Arrays.asList(args);
        System.out.println(methodName + "方法开始执行了,参数:" + params);
    }

}

 
  • 业务层实现
package com.qf.java2108.service.impl;

import com.qf.java2108.service.UserService;
import org.springframework.stereotype.Service;


@Service("userService")
public class UserServiceImpl implements UserService {
    @Override
    public int save(int i) {
        //int m = 1/0;
        System.out.println("UserServiceImpl save方法----->" + i);
        return 1;
    }

    @Override
    public int update(int i) {
        System.out.println("UserServiceImpl update方法----->" + i);
        return 1;
    }

    @Override
    public int findAll() {
        System.out.println("UserServiceImpl findAll方法----->");
        return 1;
    }
}

  • 测试代码
    @Test
    public void aopTest() throws Exception {
        ApplicationContext ioc = new AnnotationConfigApplicationContext(SpringConfig.class);
        UserService userService = (UserService) ioc.getBean("userService");
        int row = userService.save(110);
        System.out.println("save row = " + row);
    }

Spring注解AOP开发会存在一个问题:

  • 增强的执行顺序有问题:最终通知会在增强通知或者异常增强执行之前执行。
    • 解决方案
      • 使用环绕增强
      • 把最终增强要实现的功能写在后置增强和异常增强的里面的最后部分
7、SpringAOP对代理类型的选择
DefaultProxyFactory
  • 如果有接口,则选择JDK动态代理
  • 如果没有接口,则选择CGLIB动态代理
  • 如果有接口,但是强制指定了一个属性proxy-target-,那么就采用CGLIB动态代理
8、后置处理器
  • BeanPostProcessor

    该接口我们也叫后置处理器,作用是在Bean对象在实例化和依赖注入完毕后,在显示调用初始化方法的前后添加我们自己的逻辑。注意是Bean实例化完毕后及依赖注入完成后触发的。接口的源码如下

    //
    // Source code recreated from a .class file by IntelliJ IDEA
    // (powered by FernFlower decompiler)
    //
    
    package org.springframework.beans.factory.config;
    
    import org.springframework.beans.BeansException;
    import org.springframework.lang.Nullable;
    
    public interface BeanPostProcessor {
        @Nullable
        default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
            return bean;
        }
    
        @Nullable
        default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
            return bean;
        }
    }
    
    方法说明
    postProcessBeforeInitialization实例化、依赖注入完毕, 在调用显示的初始化之前完成一些定制的初始化任务
    postProcessAfterInitialization实例化、依赖注入、初始化完毕时执行
  • Spring提供了很多后置处理器,用于在创建Bean的过程中对Bean进行再加工,可以作出对Bean的修改和增强等

  • 是Spring提供一个接口【BeanPostProcessor】。

Spring接管了对象的创建后,不是直接把对象当做一个bean。而是会经过各种验证、各种封装、各种加强,最后才会变成一个Spring容器中的bean。

8.1 自定义后置处理器
package com.qf.java2108.processor;

import com.qf.java2108.advice.LogAdvice;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.stereotype.Component;


@Component
public class MyBeanPostProcessor implements BeanPostProcessor {

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println(bean);
        System.out.println(beanName);
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println(bean);
        System.out.println(beanName);
        if(bean instanceof LogAdvice) {
            LogAdvice logAdvice = (LogAdvice) bean;
            //修改LogAdvice的name属性的值
            logAdvice.setName("测试");
        }
        return bean;
    }
}
十二、整合Mybatis 1、为什么要整合?怎么整合? 1.1 Spring容器【IOC】
  • 让Spring来管理Mybatis的SqlSessionFactory对象
  • 让Spring来生成和管理Mapper接口代理对象
1.2 让Mybatis用上Spring的声明式事务【AOP】
  • 纯用Mybatis,有没有事务?
    • 有:事务在持久层
  • 开发时,应该让事务在业务层来进行控制
    • Spring的声明式事务就可以做到在业务层来进行控制事务

整合后,mybatis全局配置文件可以没有

2、具体实现 2.1 准备工作
  • Mybatis

    • 数据库表
    CREATE TABLE `t_account` (
      `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',
      `account_num` varchar(30) NOT NULL COMMENT '账号',
      `user_id` int(11) DEFAULT NULL COMMENT '持有人',
      `balance` int(20) DEFAULT NULL COMMENT '余额',
      PRIMARY KEY (`id`)
    ) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;
    
    insert  into `t_account`(`id`,`account_num`,`user_id`,`balance`) values 
    (1,'1001',1,1000),
    (2,'1002',2,1000),
    (3,'1003',3,1);
    
    • 实体
    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public class Account implements Serializable {
    
        private Integer id;
        private String accountNum;
        private Integer userId;
        private Long balance;
    
    }
    
    • Mapper
    public interface AccountMapper {
    
       List findAll();
    
       int save(Account account);
    
    }
    
    • Mapper.xml
    
    
    
    
    
        
            
            
            
            
        
    
        
        
            SELECT id, account_num, user_id, balance FROM t_account
        
    
        
        
            INSERT INTO t_account (account_num, user_id, balance)
            VALUES (#{accountNum}, #{userId}, #{balance})
        
        
    
    
  • Spring

    • 配置数据源
    • 配置SqlSessionFactory
    • 配置Mapper接口代理对象
2.2 导入依赖
    
        
        
            mysql
            mysql-connector-java
            5.1.6
        
        
            org.mybatis
            mybatis
            3.5.2
        
        
            com.github.pagehelper
            pagehelper
            5.1.10
        
        
            log4j
            log4j
            1.2.17
        
        
            org.slf4j
            slf4j-log4j12
            1.7.7
        
        
            org.projectlombok
            lombok
            1.18.22
            provided
        
        
            junit
            junit
            4.12
        

        
        
            com.alibaba
            druid
            1.1.10
        
        
            org.springframework
            spring-context
            5.1.6.RELEASE
        
        
            org.springframework
            spring-aspects
            5.1.6.RELEASE
        
        
            org.springframework
            spring-jdbc
            5.1.6.RELEASE
        
        
            org.springframework
            spring-test
            5.1.6.RELEASE
        

        
        
            org.mybatis
            mybatis-spring
            2.0.1
        
    

2.3 写代码 2.4 Spring配置



    

    
    
        
        
        
        
    

    
    
        
        
        
        
            throw new RuntimeException("账户不存在:输入正确的收款账户");
        }

        //转账方的余额是否足够?够:则往下执行;  不够:提示 余额不足,请先存钱
        if(fromAccount.getBalance() < money) {
            throw new RuntimeException("余额不足,请先存钱");
        }

        //转账方 - 转账金额。成功:则往下执行;  不成功:未知错误
        fromAccount.setBalance( fromAccount.getBalance() - money);
        accountMapper.updateAccountBalance(fromAccount);

        //int i = 1/0;

        //收款方 + 转账金额。成功:完成转账;  不成功:未知错误
        toAccount.setBalance( toAccount.getBalance() + money);
        accountMapper.updateAccountBalance(toAccount);

        return true;
    }
}
  • 测试方法
@Test
public void Test() throws Exception {
    ApplicationContext ioc = new ClassPathXmlApplicationContext("spring/spring-dao.xml", "spring/spring-service.xml", "spring/spring-tx.xml");
    AccountService accountService = (AccountService) ioc.getBean("accountService");
    Boolean result = accountService.transfer("1003", "1002", 1000);
    System.out.println("result = " + result);
}
  • 经过测试:有事务,但是业务层的事务没有控制住
2、配置声明式事务
  • spring/spring-tx.xml



    
    
        
    

    
    
        
        
            
        
    

    
    
        
        
    

  • 加入如上配置后,再次测试,发现事务控制住了
3、事务属性
  • 面试题
    • 事务传播行为
    • 事务隔离级别


    
    
        
        
        
        
        
        
    

4、注解方式声明式事务

半配置文件半注解

  • 自己写的类,使用注解
  • 第三方的,使用配置文件
  • 事务配置【Spring配置文件】



    
    
        
    

    
    

  • 业务层实现类上标记一个注解@Transactional
package com.qf.java2108.service.impl;

import com.qf.java2108.mapper.AccountMapper;
import com.qf.java2108.pojo.Account;
import com.qf.java2108.service.AccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;


@Service("accountService")
@Transactional   //这个类中的所有方法都使用了读写型事务
public class AccountServiceImpl implements AccountService {

    @Autowired
    private AccountMapper accountMapper;

    @Override
    public Boolean transfer(String fromAccountNum, String toAccountNum, Integer money) {

        //.....

        return true;
    }

    //在方法上也标记了@Transactional,那么方法级别的注解优先生效
    @Transactional(propagation = Propagation.SUPPORTS, readOnly = true)
    @Override
    public List findAll() {
        return null;
    }
}

十三、整合Junit 1、为什么要整合?
  • 简化使用Spring容器中的组件来进行功能测试
2、怎么整合?
  • 导入依赖

    org.springframework
    spring-test
    5.1.6.RELEASE

  • 在测试类上标记两个注解
@RunWith(SpringJUnit4ClassRunner.class)    //更换Junit的运行器
@ContextConfiguration(locations = {"classpath:spring/spring-*.xml"})    //加载Spring配置【配置文件】
//@ContextConfiguration(classes = {SpringConfig.class})    //加载Spring配置【注解】
  • 在测试类中直接注入要使用的Bean即可

    @Autowired
    AccountService accountService;

3、测试代码
package com.qf.java2108.test;

import com.qf.java2108.mapper.AccountMapper;
import com.qf.java2108.pojo.Account;
import com.qf.java2108.service.AccountService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import java.util.List;


@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath*:spring/spring-*.xml"})
public class SpringAndJunitTest {

    @Autowired
    AccountService accountService;

    @Autowired
    AccountMapper accountMapper;

    
    @Test
    public void annoTxTest() throws Exception {
        Boolean result = accountService.transfer("1003", "1002", 100);
        System.out.println("result = " + result);
    }

    
    @Test
    public void mapperTest() throws Exception {
        List list = accountMapper.findAll();
        for (Account account : list) {
            System.out.println("account = " + account);
        }
    }

}
转载请注明:文章转载自 www.051e.com
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 ©2023-2025 051e.com

ICP备案号:京ICP备12030808号