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

Spring5

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Spring5

Spring5

导航
  • Spring5
    • 概述
    • 一、IoC(Inversion of Control)
      • 1.1 什么是IoC
      • 1.2 初识Spring
      • 1.3 Hello Spring
      • 1.4 IoC创建对象的方式
      • 1.5 Spring配置
      • 1.6 依赖注入
        • 1.6.1 测试环境搭建
        • 1.6.2 构造器注入
        • 1.6.3 set方法注入(重点)
        • 1.6.4 拓展方式注入
      • 1.7 bean作用域
      • 1.8 Bean自动装配
        • 1.8.1 环境搭建
        • 1.8.1 自动装配xml配置
    • 二、 使用注解
      • 2.1 注解实现自动装配
      • 2.2 使用注解开发
      • 2.3 注解与xml
      • 2.4 使用Java配置xml
    • 三、 代理模式
      • 3.1 静态代理
      • 3.2 动态代理
      • 3.3 动态代理模拟代理UserService(动态代理练习)
    • 四、AOP(Aspect Oriented Programming)
      • 4.1 什么是AOP
      • 4.2 Spring种使用AOP
        • 方式一:使用Spring的API接口
        • 方式二:自定义切面类实现AOP
      • 4.3 使用注解实现AOP
    • 五、Spring整合Mybatis
      • 5.1 xml整合Mybatis
      • 5.2 注解整合Mybatis
    • 六、事务
      • 方式一:注解
      • 方式二: xml配置

概述

Spring是一个轻量级,非入侵式的控制反转(IOC)和面向切面(AOP)编程的免费框架。

此话一出,我当时就只看得懂两个字:免费!

好吧,我们先看看什么是IoC

一、IoC(Inversion of Control) 1.1 什么是IoC

控制反转:我理解的控制反转就是控制权反转。

说理论难理解,直接上代码就明白了,我们先来模拟一下很久以前是怎么开发的:

先创建一个maven项目,然后目录结构为maven的经典结构:

假设有一个实体类User,依次创建Dao层和Service的接口和实现类。

UserDao:

public interface UserDao {
    List getAllUsers();
}

UserDaoImpl:

public class UserDaoImpl implements UserDao {
    @Override
    public List getAllUsers() {
        System.out.println("从数据库获取数据");
        return null;
    }
}

UserService:

public interface UserService {
    List getAllUsers();
}

UserServiceImpl:

public class UserServiceImpl implements UserService {
    private UserDao userImpl = new UserDaoImpl();

    @Override
    public List getAllUsers() {
        userImpl.getAllUsers();
        return null;
    }
}

测试:

@Test
public void test01() {
    UserService userService = new UserServiceImpl();
    
    userService.getAllUsers();
}

输出:

现在思考一下,假如我们现在有了新的需求,如模拟从不同数据库中获取数据,于是我们又写了一个dao层的实现类:

public class UserDaoMysqlImpl implements UserDao {
    @Override
    public List getAllUsers() {
        System.out.println("从MySQL中获取数据");
        return null;
    }
}

如果我们需要使用它,那么还需要修改service层:

public class UserServiceImpl implements UserService {
    private UserDao userImpl = new UserDaoMysqlImpl();

    @Override
    public List getAllUsers() {
        userImpl.getAllUsers();
        return null;
    }
}

测试:

    @Test
    public void test01() {
        UserService userService = new UserServiceImpl();
        
        userService.getAllUsers();
    }

结果:

那要是我们再增加一个需求,模拟从Oracle中获取数据:

public class UserDaoOracleImpl implements UserDao {
    @Override
    public List getAllUsers() {
        System.out.println("从Oracle中获取数据");
        return null;
    }
}

那么还是一样的要去修改service层…………

然后再多一个需求…………

这样就很麻烦,这个时候是程序主动创建对象,控制权在程序员手中,耦合度很高。

解决办法:

给service层添加一个set方法,让程序被动接受对象,控制权就在用户手中了:

UerService:

public interface UserService {
    List getAllUsers();
    void setUserImpl(UserDao userImpl);
}

UserServiceImpl:

public class UserServiceImpl implements UserService {
    private UserDao userImpl = new UserDaoImpl();

    public void setUserImpl(UserDao userImpl) {
        this.userImpl = userImpl;
    }

    @Override
    public List getAllUsers() {
        userImpl.getAllUsers();
        return null;
    }
}

测试:

public class TestDemo {
    @Test
    public void test01() {
        UserService userService = new UserServiceImpl();

        userService.setUserImpl(new UserDaoOracleImpl());
        userService.getAllUsers();
    }
}

结果:

新增再多功能都不怕了,这个时候我们想set什么就set什么:

public class UserDaoHeadImpl implements UserDao {
    @Override
    public List getAllUsers() {
        System.out.println("从脑子里中获取数据");
        return null;
    }
}
public class TestDemo {
    @Test
    public void test01() {
        UserService userService = new UserServiceImpl();

        userService.setUserImpl(new UserDaoHeadImpl());
        userService.getAllUsers();
    }
}

这就是控制反转的一种实现方式DI(依赖注入)。

1.2 初识Spring 1.3 Hello Spring

我们先看看如何用用Spring写一个Hello Spring,再看看如何利用Spring简化上一节的操作。

  1. 导包:

    
        org.springframework
        spring-webmvc
        5.3.19
    
    
        org.projectlombok
        lombok
        1.16.20
    
    
        junit
        junit
        4.13.2
        test
    
    
  2. 创建一个Hello类,给他一个set方法(必须要,依赖注入就是靠set方法)

    @Data
    public class Hello {
        private String str;
    }
    
  3. 写 XML 的配置元数据

    
    
    
        
            
        
    
    
  4. 使用

    @Test
    public void test01() {
        // 获取Spring的上下文对象
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        // 从Spring容器中获取对象
        Hello hello = (Hello) context.getBean("hello");
        System.out.println(hello.getStr());
    }
    

    结果:

这就是Spring最简单的使用。

来看看怎么优化第一节的程序:

1,2步不用变,只需要把这个程序套到Spring里面去就行了。

  1. 编写xml配置元数据

    
    
        
        
        
    
        
         		   
            
        
    
    
  2. 使用

    @Test
    public void test02() {
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
    
        UserServiceImpl userServiceImpl = (UserServiceImpl) context.getBean("userServiceImpl");
        userServiceImpl.getAllUsers();
    }
    

    结果:

    如果要换成MySQL,只需要在配置文件中将第12行左右property中ref中的值改为mysqlImpl即可。

    降低了耦合度,我们的程序不需要再做改变,只需要修改配置文件即可,对象由Spring来创建,管理,装配!程序由主动创建对象变为被动接收对象,体现了IoC的思想。

1.4 IoC创建对象的方式

我们做点试验,把刚刚的Hello类改一下:

@Data
public class Hello {
    private String str;
    public Hello() {
        System.out.println("无参构造  Hello被创建");
    }
    
    public Hello(String str) {
        System.out.println("有参构造 Hello被创建");
        this.str = str;
    }
}

再来一个Hello02类:

@Data
public class Hello2 {
    private String str;
    public Hello2() {
        System.out.println("无参构造  Hello被创建");
    }
}

applicationContext.xml:




    
        
    

    

测试:

    @Test
    public void test01() {
        // 获取Spring的上下文对象
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        // 从Spring容器中获取对象
        Hello hello = (Hello) context.getBean("hello");
        Hello hello1 = (Hello) context.getBean("hello");
        
        System.out.println(hello.getStr());
        
        System.out.println(hello == hello1);
    }

结果:

结论:

  1. 默认使用无参构造,并且在get其中一个bean时,所有的bean实例都会创建。

  2. 使用有参构造来创建(三种方式)

    • 通过参数位置(下标)来创建

      
          
      
      
    • 通过参数类型创建

      
          
      
      
    • 通过参数名创建

      
          
      
      
  3. 一个bean不管get多少次,在容器中都只有一个。

1.5 Spring配置

还是刚刚的程序

  1. 取别名:

    alias:

    
    

    使用:

    Hello hello = (Hello) context.getBean("abc");
    

    name:

    不仅可以用alias标签取别名,还可以用bean的name属性取别名,一次可以取多个,可以用空格,逗号,分号分隔

    
    
  2. import:

    将其他配置文件引入。

    虽然classPathXmlApplicationContext可以一次读取多个配置文件,但是把所有的beans导入一个applicationContext.xml更方便:

    beans.xml:

    
    
        
            
            
        
    
    

    applicationContext.xml:

    
    
    
            
                
            
        
        
        
    
    
1.6 依赖注入

Dependency Injection

实现IoC思想的一种方式。

1.6.1 测试环境搭建

我们现在已经会了一点,但是在复杂的环境下,还有很多需要注意的点。

  1. 创建一个maven项目,把包建好。

  2. 创建一个Student实体类,需要设置set方法。

    @Data
    public class Student {
        private String id;
        private String name;
        private Address address;
        private String[] books;
        private List hobbies;
        private Map scores;
        private Set friends;
        private Properties info;
    }
    
    @Data
    class Address {
        private String address;
    }
    
1.6.2 构造器注入

也就是1.4说过的那三种方法,不再赘述。

1.6.3 set方法注入(重点)

简单的set注入我们已经会了:




    
        
    

测试:

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

    Student student = context.getBean("student", Student.class);
    System.out.println(student.getName());
}

结果:

那Map、Set、Address……这些怎么注入呢?

直接看答案:




    
        
            
        
        
        
        
        
        
            
                语文书
                数学书
                历史书
            
        
        
        
            
                敲代码
                玩游戏
                打篮球
            
        

        
            
                
                
                
            
        

        
            
                张三
                李四
                王五
            
        

        
            
                班长
                男
            
        
    

    
        
    

测试:

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

    Student student = context.getBean("student", Student.class);
    System.out.println(student);
}

结果:

Student(id=null, name=set方法注入, address=Address(address=四川省), books=[语文书, 数学书, 历史书], hobbies=[敲代码, 玩游戏, 打篮球], scores={语文=120, 数学=150, 历史=90}, friends=[张三, 李四, 王五], info={性别=男, 职务=班长})

1.6.4 拓展方式注入

使用之前要导入xml约束,如:

xmlns:p="http://www.springframework.org/schema/p"
xmlns:c="http://www.springframework.org/schema/c"

p命名空间注入:

p指的就是property

拿之前的Hello类来做实验:

@Data
public class Hello {
    private String str;
    public Hello() {
        System.out.println("无参构造  Hello被创建");
    }
    public Hello(String str) {
        System.out.println("有参构造 Hello被创建");
        this.str = str;
    }
}

xml前后对比:



	
    
        
    
    
    


c命名空间注入:

c指的就是构造器constructor



	
    
        
    
    
    


可以测试一下

@Test
public void test01() {
    ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
    Hello hello = context.getBean("hello", Hello.class);

    System.out.println(hello.getStr());
}
1.7 bean作用域

看看官网的图:

  1. singleton 单例模式:

    Spring默认为单例模式,即这个bean对象只有一个

    
    
  2. prototype 原型模式:

    即每次get都会创建一个新的对象

    
    

其余的几个是在web开发中使用的,日后再议。

1.8 Bean自动装配 1.8.1 环境搭建

为了方便,就没有写规范:

类:

@Data
public class People {
    private Dog dog;
    private Cat cat;
    private String name;
}
@Data
class Dog {
    private String name;
}
@Data
class Cat {
    private String name;
}

xml:




    
    

    


test:

@Test
public void test() {
    ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
    People people = context.getBean("people", People.class);

    System.out.println(people);
}

结果:

People(dog=Dog(name=dog), cat=Cat(name=cat), name=people)

1.8.1 自动装配xml配置

刚刚的代码,People里面还有Dog和Cat,一个个设置值太麻烦,于是,自动装配出现了。




    
    




    
        
    
    

通过容器中对象的id自动装配,但id必须与被装配对象中的set后面的名字相同才行。

还有第二种方式,通过类型自动装配:




    
    








    
        
    

通过容器中对象的类型自动装配,但类型必须与被装配对象中的属性类型相同才行。

用注解更方便。

二、 使用注解 2.1 注解实现自动装配

接着上回,我们还可以使用注解进行自动装配。

步骤:

  1. 导入约束

    
    
    	
        
        
    
    
  2. 配置注解

​



    
	

    
    

    


  1. 写注解:

    注解可以写在属性上,可以写在setter上,还可以写在构造方法上,一般写在属性上:

    @Data
    public class People {
        @Autowired
        private Dog dog;
        @Autowired
        private Cat cat;
        private String name;
    }
    
  2. test

    @Test
    public void test() {
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        People people = context.getBean("people", People.class);
    
        System.out.println(people);
    }
    

    输出:

    People(dog=Dog(name=dog), cat=Cat(name=cat), name=people)

补充:

  • @Autowired有一个参数required,是布尔值,@Autowired(required=true)默认为true

    为true时表示注入时该bean必须存在,不然报错

    为false时即使不存在也不报错

  • 会先通过类型搜索bean,不行就通过id搜索bean

  • 配合@Qualifier()使用:

    @Data
    public class People {
        @Autowired
        @Qualifier("dog123")
        private Dog dog;
        @Autowired
        private Cat cat;
        private String name;
    }
    

    表示通过byName的方式自动装配,Qualifier里面的值对应beans里面的id。

  • 还可以用@Resource注解来自动装配,效果一样,但@Resource是先byName再byType,还可以指定name或者type或者都指定。

    @Data
    public class People {
        @Resource(name = "dog123",type = Dog.class)
        private Dog dog;
        @Autowired
        private Cat cat;
        private String name;
    }
    
2.2 使用注解开发
  1. 先看看结构:

  2. application.xml

    
    
    
        
    
    

    扫描vip/yangsf包下的所有component以及隐式启用

  3. 把类放入beans:

    之前的做法(User为例)

    
        
        
        
    
    

    使用注解(get/set方法自行添加):

    @Component
    public class User {
        @Value("123")
        private int id;
        @Value("root")
        private String name;
        @Value("123456")
        private String pwd;
    }
    

    @Component的作用就是,

    相同作用不同语义的还有三个

    dao层的@Repository

    @Repository
    public class UserDaoImpl implements UserDao {
        @Override
        public List getUsers() {
            return null;
        }
    }
    

    service层的@Service

    @Service
    public class UserServiceImpl implements UserService {
        @Override
        public List getUsers() {
            return null;
        }
    }
    

    controller层的@Controller

    @Controller
    public class UserController {
        
    }
    

    作用都是把类注册到Spring中。

  4. 作用域

    @Component
    @Scope("singleton")
    @Data
    public class User {
        @Value("123")
        private int id;
        @Value("root")
        private String name;
        @Value("123456")
        private String pwd;
    }
    

    用@Scope(“singleton”)设置单例模式,当然也可以@Scope(“prototype”)。

2.3 注解与xml
  • xml更万能,易于维护,但比较繁琐。
  • 注解更方便,但不够万能。

最佳实践:

  • xml用来管理bean
  • 注解用来注入属性(@Value)
2.4 使用Java配置xml

完全可以用一些注解来代替xml的配置。

把刚刚的xml删掉,换成配置类。

  1. 新建一个Config类:

    @Configuration
    @ComponentScan("vip.yangsf")
    public class MyConfig {
        @Bean
        public String getString() {
            return "hello annotation";
        }
    }
    

    Configuration表示这是一个配置类,并且这个注解还有和Component一样的效果,会在Spring中注册一个bean。

    @ComponentScan(“xxx”)会扫描xxx路径下的Component,相当于

    @Bean就是在Spring中注册了一个bean ,id就是方法名,class就是返回类型。

    当然,既然要替代xml,其他的注解也能用,比如@import等等

  2. 测试

    @Test
    public void test() {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyConfig.class);
        System.out.println(context.getBean("user"));
        System.out.println(context.getBean("getString"));
    }
    

    获取Spring上下文对象由ClassPathXmlApplicationContext变成了AnnotationConfigApplicationContext,其他的没啥区别

    结果:

三、 代理模式

不直接访问某个对象,而是通过代理类来提供某个对象的访问。相当于一个中介。

3.1 静态代理

代理类和委托类在运行前就写死了,就是静态代理。

模拟一个静态代理,需要以下几步:

  1. 抽象角色,一般为接口或者抽象类。

    // 租房
    public interface Rent {
        void rent();
    }
    
  2. 真实角色,具体被代理的角色

    public class Host implements Rent{
    
        @Override
        public void rent() {
            System.out.println("房东卖房");
        }
    }
    
  3. 代理角色,代理真实角色,在代理前后我们会对其做一些处理

    public class Proxy implements Rent {
        Rent rent;
        // 默认代理自己
        public Proxy() {
            this.rent = new Proxy();
        }
    
        public Proxy(Rent rent) {
            this.rent = rent;
        }
    
        @Override
        public void rent() {
            seeHouse();
            rent.rent();
            hetong();
        }
    
        public void seeHouse() {
            System.out.println("中介带你看房");
        }
        public void hetong() {
            System.out.println("中介和你签合同");
        }
    }
    
  4. 客户端访问

    public class Client {
        public static void main(String[] args) {
            Rent host = new Host();
            Proxy proxy = new Proxy(host);
            proxy.rent();
        }
    }
    

    整个过程我们没有接触到房东,一直都是和中介接触。

3.2 动态代理

动态代理就比刚刚的静态代理牛逼多了,静态代理只能写死了代理一个接口,而动态代理的代理类是动态生成的,可以代理一切。

我们可以根据不同的接口,生成不同的代理类。运用Java的反射机制,我们可以做到这一点:

  1. 抽象角色,被代理的接口,只能是接口。

    还是先用刚刚的例子

    // 租房
    public interface Rent {
        void rent();
    }
    
  2. 被代理的真实类

    public class Host implements Rent {
        @Override
        public void rent() {
            System.out.println("房东卖房");
        }
    }
    
  3. 代理类我们动态生成,如何根据不同的接口生成不同的代理类?

    可以通过java.lang.reflect.Proxy类下的

    方法来生成代理类。

    ClassLoader loader:类加载器,用来加载被代理的类。

    Class[] interfaces:被代理对象的所有接口,即要生成的代理类需要实现的接口。

    InvocationHandler h:调用的策略,需要传入一个实现了InvocationHandler接口的类,设置代理类调用方法的策略。

    假如我要代理房东,那么房东的代理类这样生成:

    Rent host = new Host();
    Proxy.newProxyInstance(host.getClass().getClassLoader(),host.getClass().getInterfaces(), 策略??);
    

    我们倒是知道了要用rent的类加载器来加载被代理的类,也知道代理类要实现Rent接口,但调用策略如何创建?

    public class ProxyInvocationHandler implements InvocationHandler{
        private Object target;
    
        public void setTarget(Object target) {
            this.target = target;
        }
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            Object result = method.invoke(target, args);
            return result;
        }
    }
    

    Object proxy:刚刚生成的代理类。

    Method method:调用的方法。

    Object[] args:调用的方法的参数。

    target:被代理的对象。

    于是:

    Rent host = new Host();
    ProxyInvocationHandler proxyInvocationHandler = new ProxyInvocationHandler();
    proxyInvocationHandler.setTarget(host);
    
    // 有了策略过后
    Rent proxy = Proxy.newProxyInstance(rent.getClass().getClassLoader(),rent.getClass().getInterfaces(), proxyInvocationHandler);
    //就获得了代理类
    
  4. 把获得代理类方法和调用策略类合并成一个工具类(完整代码):

    public class ProxyInvocationHandler implements InvocationHandler {
        // 被代理的类
        private Object target;
    	// 动态设置被代理的类
        public void setTarget(Object target) {
            this.target = target;
        }
    	// 动态生成代理类
        public Object getProxy() {
            return Proxy.newProxyInstance(target.getClass().getClassLoader(),
                    target.getClass().getInterfaces(), this);
        }
    	// 调用策略
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            log(method.getName());
            Object result = method.invoke(target, args);
            return result;
        }
    	// 添加一个自制日志
        public void log(String s) {
            System.out.println("调用:" + s + " 方法。");
        }
    }
    
  5. 客户端访问

    public class Client {
        public static void main(String[] args) {
            Rent host = new Host();
    	
            ProxyInvocationHandler pih = new ProxyInvocationHandler();
            pih.setTarget(host);
    
            Rent proxy = (Rent) pih.getProxy();
            proxy.rent();
        }
    }
    

    结果:

3.3 动态代理模拟代理UserService(动态代理练习)
  1. 被代理的接口

    public interface UserService {
        // 增
        int addUser();
        // 删
        int delete();
        // 改
        int update();
        // 查
        List select();
    }
    
  2. 被代理的真实类

    public class UserServiceImpl implements UserService{
        @Override
        public int addUser() {
            System.out.println("增");
            return 0;
        }
    
        @Override
        public int delete() {
            System.out.println("删");
            return 0;
        }
    
        @Override
        public int update() {
            System.out.println("改");
            return 0;
        }
    
        @Override
        public List select() {
            System.out.println("查");
            return null;
        }
    }
    
  3. 工具类不变,还是刚才那个

  4. 客户端访问

    public class Client {
        public static void main(String[] args) {
            UserService userService = new UserServiceImpl();
            ProxyInvocationHandler proxyInvocationHandler = new ProxyInvocationHandler();
            proxyInvocationHandler.setTarget(userService);
    
            UserService proxy = (UserService) proxyInvocationHandler.getProxy();
            proxy.addUser();
            proxy.delete();
            proxy.update();
            proxy.select();
        }
    }
    

    结果:

有了代理模式的基础,走向AOP就容易的多。

四、AOP(Aspect Oriented Programming) 4.1 什么是AOP

面向切面编程,通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。

----百度百科

再来一波概念:

  • Aspect:切面,即一个横跨多个核心逻辑的功能,或者称之为系统关注点;
  • Joinpoint:连接点,即定义在应用程序流程的何处插入切面的执行;
  • Pointcut:切入点,即一组连接点的集合;
  • Advice:通知,指特定连接点上执行的动作;
  • Introduction:引介,指为一个已有的Java对象动态地增加新的接口;
  • Weaving:织入,指将切面整合到程序的执行流程中;
  • Interceptor:拦截器,是一种实现通知的方式;
  • Target Object:目标对象,即真正执行业务的核心逻辑对象;
  • AOP Proxy:AOP代理,是客户端持有的通知后的对象引用。

直接懵逼,AOP概念就是很抽象,实际上很简单,就是刚刚实现的动态代理,AOP就是简化了动态代理。

我们先来一个不用Spring的:

就拿刚刚的UserService举例

被代理接口,实现类就是刚刚的UserService和UserServiceImpl,我们先手写一个传说中的切面(模拟):

public class AfterLog {
    public void after() {
        System.out.println("==== after ====");
    }
}
public class AfterLog {
    public void after() {
        System.out.println("==== after ====");
    }
}

再次手敲一个工具类:

public class ProxyInvocationHandler implements InvocationHandler {
    // 被代理的类
    private Object target;
    // 为了方便就这样导入切面了
    AfterLog afterLog = new AfterLog();
    BeforeLog beforeLog = new BeforeLog();
    // 动态设置被代理的类
    public void setTarget(Object target) {
        this.target = target;
    }
    // 动态生成代理类
    public Object getProxy() {
        return Proxy.newProxyInstance(target.getClass().getClassLoader(),
                target.getClass().getInterfaces(), this);
    }
    // 调用策略
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        beforeLog.before();
        Object result = method.invoke(target, args);
        afterLog.after();
        return result;
    }
}

当我们运行时,调用方法前都会执行before() 调用完后都会执行 after()

我们把切面 切入了 切入点。

降低了代码的耦合度,核心业务不需要再做修改,添加功能只需横向开发,添加切面。

那用Spring呢?

4.2 Spring种使用AOP

工具类不再需要,service和其实现类不变,现在将我们的切面变成真正的Spring中的切面:

先导个包包:


    org.aspectj
    aspectjweaver
    1.9.4

通知(advice):五种通知方式:

于是:

方式一:使用Spring的API接口
public class AfterLog implements AfterReturningAdvice {
    @Override
    public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
        System.out.println(target.getClass().getName() + "的" + method.getName() + "方法执行完毕,返回了:" + returnValue);
    }
}

实现AfterReturningAdvice接口表示在调用方法后执行。

public class BeforeLog implements MethodBeforeAdvice {
    @Override
    public void before(Method method, Object[] args, Object target) throws Throwable {
        System.out.println(target.getClass().getName() + "的" + method.getName() + "方法被执行");
    }
}

实现MethodBeforeAdvice接口表示在调用方法前执行。

然后去Spring上下文中注册:



    
    
    
    

    
        
        
        
    


注意点:

  1. 引入aop的xml约束
  2. 注册bean不必多说
  3. aop配置 pointcut 表示切入点,也就是可以被切面切入的地方, expression后面表示可以被切入的方法,第一个*表示返回值,然后是类,然后第二个*是方法名,后面的(…)表示参数。
  4. advisor表示环绕,pointcut-ref引入到哪个切入点,advice-ref引入切面
方式二:自定义切面类实现AOP

定义一个切面:

public class DiyPointcut {
    public void before() {
        System.out.println("======before======");
    }

    public void after() {
        System.out.println("======after======");
    }
}

xml中配置:




    
        
        
        
    

先注册切面,然后配置切面。

测试代码和刚刚一样:

虽然第二种实现获取方法信息那些不好获取,但是更容易理解。

手动配置了这俩例后,相信已经对AOP有一定的理解了,下一步,注解!

4.3 使用注解实现AOP

XML已经配置烦了,来用注解放双手。

包还是刚才那个包,不用导其他的了。

先来观察一下我们的配置文件:



    
    
    

    
        
        
        
    

    
    
    
        
            
            
        
    


我们有两个切面和一个切入点,两个切面,现在我们用注解做这件事情:

  1. 定义一个config类:

    @Configuration
    @EnableAspectJAutoProxy
    @ComponentScan("vip.yangsf")
    public class MyConfig {
    }
    

    @Configuration:表示这是一个配置类

    @EnableAspectJAutoProxy:开启注解版的AOP功能

    @ComponentScan(“vip.yangsf”):扫描这个包下面的所有@Component

  2. 定义切面:

    先观察原来的切面:

    public class BeforeLog implements MethodBeforeAdvice {
        @Override
        public void before(Method method, Object[] args, Object target) throws Throwable {
            System.out.println(target.getClass().getName() + "的" + method.getName() + "方法被执行");
        }
    }
    
    public class AfterLog implements AfterReturningAdvice {
        @Override
        public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
            System.out.println(target.getClass().getName() + "的" + method.getName() + "方法执行完毕,返回了:" + returnValue);
        }
    }
    
    public class DiyPointcut {
        public void before() {
            System.out.println("======before======");
        }
    
        public void after() {
            System.out.println("======after======");
        }
    }
    

    然后我们进行改造:

    有以下注解可以选择:

    • @Before:这种拦截器先执行拦截代码,再执行目标代码。如果拦截器抛异常,那么目标代码就不执行了;
    • @After:这种拦截器先执行目标代码,再执行拦截器代码。无论目标代码是否抛异常,拦截器代码都会执行;
    • @AfterReturning:和@After不同的是,只有当目标代码正常返回时,才执行拦截器代码;
    • @AfterThrowing:和@After不同的是,只有当目标代码抛出了异常时,才执行拦截器代码;
    • @Around:能完全控制目标代码是否执行,并可以在执行前后、抛异常后执行任意拦截代码,可以说是包含了上面所有功能。

    使用Around最省事:

    @Aspect
    @Component
    public class DiyPointcut {
        @Around("execution(* vip.yangsf.service.impl.UserServiceImpl.*(..))")
        public Object around(ProceedingJoinPoint pjp) throws Throwable {
            System.out.println(pjp.getTarget().getClass().getName() + "的" + pjp.getSignature().getName() + "方法被执行");
            System.out.println("======before======");
    		// 执行方法,获得返回值
            Object proceed = pjp.proceed();
            
            System.out.println("======after======");
            System.out.println(pjp.getTarget().getClass().getName() + "的" + pjp.getSignature().getName() + "方法执行完毕,返回了:" + proceed);
            
            return proceed;
        }
    }
    

    测试访问一下:

    @Test
    public void test01() {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyConfig.class);
        UserService userServiceImpl = context.getBean("userServiceImpl", UserService.class);
        userServiceImpl.addUser();
    }
    

    结果:

成功!

五、Spring整合Mybatis

这里介绍两种方式:xml整合Mybatis和注解整合Mybatis

首先要导包,根据自己的版本导入相应的包:

我的环境:

jdk:8

mysql:8.0.28


    org.mybatis
    mybatis
    3.5.9



    mysql
    mysql-connector-java
    8.0.28



    org.springframework
    spring-jdbc
    5.3.19



    org.mybatis
    mybatis-spring
    2.0.7



    org.aspectj
    aspectjweaver
    1.9.9.1



    org.springframework
    spring-webmvc
    5.3.19



    org.projectlombok
    lombok
    1.16.20



    junit
    junit
    4.13.2
    test

整合mybatis很重要的一个包:mybatis-spring 这里我是Spring5所以导入mybatis-spring2.0以上版本。

先来看看不用spring,mybatis应该怎么写:

  1. 首先有个表和实体类,使用mybatis还要一个mybatis的配置文件(官网就有模板):

    mybatis-config.xml:

    
    
    
        
        
            
        
        
            
                
                
                    
                    
                    
                    
                
            
        
        
            
        
    
    

    user表:

    实体类:

    @Data
    public class User {
        private int id;
        private String name;
        private String pwd;
    }
    
  2. 然后写接口

    public interface UserMapper {
        List getUsers();
    }
    
  3. 写UserMapper.xml

    
    
    
        
            select * from mybatis.user
        
    
        
            insert into mybatis.user(id, name, pwd) VALUES (#{id}, #{name}, #{pwd})
        
    
        
            deletes from mybatis.user where id=#{id}
        
    
    

    这里delete语句故意写错,让事务回滚。

    @Service
    public class UserMapperImpl implements UserMapper{
        @Autowired
        private SqlSessionTemplate sqlSession;
        
        public void setSqlSession(SqlSessionTemplate sqlSession) {
            this.sqlSession = sqlSession;
        }
        @Override
        public int addUser(User user) {
            return sqlSession.getMapper(UserMapper.class).addUser(user);
        }
    
        @Override
        public int deleteUser(int id) {
            return sqlSession.getMapper(UserMapper.class).deleteUser(id);
        }
    
        @Override
        public List getUsers() {
            return sqlSession.getMapper(UserMapper.class).getUsers();
        }
    
        @Override
        public List selectUser() {
            addUser(new User(123, "abb", "123456"));
            deleteUser(3);
    
            return getUsers();
        }
    }
    

    实现类,模拟service层

    一些要注册的bean:

    @Configuration
    @ComponentScan("com.lq")
    public class LqConfig {
        @Bean
        public DriverManagerDataSource datasource() {
            DriverManagerDataSource dataSource = new DriverManagerDataSource();
            dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
            dataSource.setUsername("root");
            dataSource.setPassword("123456");
            dataSource.setUrl("jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8");
            return dataSource;
        }
    
        @Bean
        public SqlSessionFactory sqlSessionFactory() throws Exception {
            SqlSessionFactoryBean factoryBean  = new SqlSessionFactoryBean();
            factoryBean .setDataSource(datasource());
            factoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath*:com/lq/mapper/*.xml"));
            factoryBean.setTypeAliasesPackage("com.lq.pojo");
            return factoryBean.getObject();
        }
    
        @Bean
        public SqlSessionTemplate sqlSession() throws Exception {
            return new SqlSessionTemplate(sqlSessionFactory());
        }
    
        @Bean
        public DataSourceTransactionManager transactionManager() {
            return new DataSourceTransactionManager(datasource());
        }
    }
    

    最重要的就是最后一个

    DataSourceTransactionManager

    然后再spring配置中开启注解支持(需要导入tx的xml约束)

    
    
        
        
        
        
    
    

    如果不开启事务:

    @Test
    public void lqTest01() {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring-transaction.xml");
        UserMapper mapper = context.getBean("userMapperImpl", UserMapper.class);
        List users = mapper.selectUser();
        users.forEach(System.out::println);
    }
    

    调用selectUser方法,即使delete方法报错,delete方法之前的add方法也会执行成功,数据会成功添加。

    这个时候只需要在UserMapperImpl中的selectUser方法上添加一个@Transactional注解。

    @Transactional
    public List selectUser() {
    
        addUser(new User(123, "abb", "123456"));
        deleteUser(3);
    
        return getUsers();
    }
    

    完美解决问题。

    方式二: xml配置

    把@Configuration配置类中的那些bean在xml中配置无须多说。关键是如何用xml代替@Transactional注解:

    
    
        
            
            
            
            
        
    
        
            
            
            
        
    
        
            
        
    
    
        
            
        
    
        
            
        
    
        
            
                
            
        
    
        
            
            
        
    
    
    

    最重要的是这段

    
        
            
        
    
    
    
        
        
    
    

    其实还是aop的思想,在不改变原来代码的情况下,开启事务。

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/890612.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号