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

Spring

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

Spring

一、Spring 1.1 简介

Spring是一个轻量级控制反转(IoC)和面向切面(AOP)的容器框架

用于整合现有的技术,使各技术使用更加便捷。

官网:https://spring.io/projects/spring-framework

官方下载地址:https://repo.spring.io/libs-release-local/org/springframework/spring/

GitHub:https://github.com/spring-projects/spring-framework

  • 两个重要的依赖包

    org.springframework
    spring-webmvc
    5.2.12.RELEASE



    org.springframework
    spring-jdbc
    5.2.12.RELEASE


1.2 优点
  • Spring是一个轻量级的、非入侵式的框架;
  • Spring是一个开源的免费的框架(容器);
  • 控制反转(Ioc)、面向切面编程(AOP);
  • 支持事务的处理,对框架整合的支持。
1.3 组成

Spring七大模块

  • 核心容器:核心容器提供 Spring 框架的基本功能。核心容器的主要组件是 BeanFactory,它是工厂模式的实现。BeanFactory 使用控制反转(IOC) 模式将应用程序的配置和依赖性规范与实际的应用程序代码分开。
  • Spring 上下文:Spring 上下文是一个配置文件,向 Spring 框架提供上下文信息。Spring 上下文包括企业服务,例如 JNDI、EJB、电子邮件、国际化、校验和调度功能。
  • Spring AOP:通过配置管理特性,Spring AOP 模块直接将面向切面的编程功能 , 集成到了 Spring 框架中。所以,可以很容易地使 Spring 框架管理任何支持 AOP的对象。Spring AOP 模块为基于 Spring 的应用程序中的对象提供了事务管理服务。通过使用 Spring AOP,不用依赖组件,就可以将声明性事务管理集成到应用程序中。
  • Spring DAO:JDBC DAO 抽象层提供了有意义的异常层次结构,可用该结构来管理异常处理和不同数据库供应商抛出的错误消息。异常层次结构简化了错误处理,并且极大地降低了需要编写的异常代码数量(例如打开和关闭连接)。Spring DAO 的面向 JDBC 的异常遵从通用的 DAO 异常层次结构。
  • Spring ORM:Spring 框架插入了若干个 ORM 框架,从而提供了 ORM 的对象关系工具,其中包括 JDO、Hibernate 和 iBatis SQL Map。所有这些都遵从 Spring 的通用事务和 DAO 异常层次结构。
  • Spring Web 模块:Web 上下文模块建立在应用程序上下文模块之上,为基于 Web 的应用程序提供了上下文。所以,Spring 框架支持与 Jakarta Struts 的集成。Web 模块还简化了处理多部分请求以及将请求参数绑定到域对象的工作。
  • Spring MVC 框架:MVC 框架是一个全功能的构建 Web 应用程序的 MVC 实现。通过策略接口,MVC 框架变成为高度可配置的,MVC 容纳了大量视图技术,其中包括 JSP、Velocity、Tiles、iText 和 POI。
1.4 拓展
  1. SpringBoot
  • 一个快速开发的脚手架
  • 基于SpringBoot可以快速的开发单个微服务
  • 约定大于配置
  1. Spring Cloud
  • Spring Cloud是基于SpringBoot实现的

学习SpringBoot的前提是,完全掌握Spring和SpringMVC

二、IOC容器

控制反转IoC(Inversion of Control)可以用来减低计算机代码之间的耦合度。其中最常见的方式叫做**依赖注入(Dependency Injection,简称DI**),还有一种方式叫“依赖查找”(Dependency Lookup)。通过控制反转,对象在被创建的时候,由一个调控系统内所有对象的外界实体将其所依赖的对象的引用传递给它。也可以说,依赖被注入到对象中,将对象的创建转移给第三方。

2.1 IOC理论推导

下面注意看代码中接口调用的区别

  • 首先看使用IOC之前的方法:

    • UserDao 接口
    public interface UserDao {
        void getUser();
    }
    
    • UserDaoimpl 实现类
    public class UserDaoimpl implements UserDao{
        public void getUser(){
            System.out.println("默认获取用户的数据");
        }
    }
    
    • UserService 业务接口
    public interface UserService {
        void getUser();
    }
    
    • UserServicelmpl 业务实现类
    public class UserServiceimpl implements UserService {
    
        private UserDao userDao = new UserDaoimpl();
    
        //实际上调用的是接口层的方法
        public void getUser(){
            userDao.getUser();
        }
    }
    
    • 测试类
    public class MyTest {
        public static void main(String[] args) {
    
            //用户实际调用的是业务层,dao层不需要接触
            UserServiceimpl userServiceimpl = new UserServiceimpl();
            userServiceimpl.getUser();
        }
    }
    

现在dao层只存在一个实现类,后续如果需要存在多个实现类,当用户调用不同接口时,要随时修改代码。

  • 现在使用set注入:

    • 首先创建另一个实现类mysqlDaoimpl
    public class UserDaoimpl implements UserDao{
        public void getUser(){
            System.out.println("默认获取用户的数据");
        }
    }
    
    • 然后进行测试调用
    public class MyTest {
        public static void main(String[] args) {
    
            //用户实际调用的是业务层,dao层不需要接触
            UserServiceimpl serviceimpl = new UserServiceimpl();
            serviceimpl.setUserDao(new UserDaoimpl());
            
            serviceimpl.getUser();
        }
    }
    

这里程序提供接口,用户去调用接口就可以了,从控制上发生了根本性的转变。

2.2 HelloSpring 2.2.1 创建POJOs实体类Hello
public class Hello {
    private String str;

    public String getStr() {
        return str;
    }

    public void setStr(String str) {
        this.str = str;
    }

    @Override
    public String toString() {
        return "Hello{" +
                "str='" + str + ''' +
                '}';
    }
}
2.2.2 配置元数据
  • 基于 XML 的配置元数据的基本结构:

    
    
    
        
            
        
    
        
    
    
    
    • id属性是标识单个 bean 定义的字符串。
    • class属性定义 bean 的类型并使用完全限定的类名。
  • 创建applicationContext.xml

    
    
    
        
        
              
        
    
    
    
2.2.3 实例化容器

提供给ApplicationContext构造函数的位置路径是资源字符串,这些资源字符串使容器可以从各种外部资源(例如本地文件系统,Java CLASSPATH等)加载配置元数据。

public class MyTest {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("ApplicationContext.xml");
    }
}

这里是将IoC容器实例化,而在Spring容器中配置元数据,其实是创建、实例化、设置属性值的一个过程


     

 

进行测试

public class MyTest {
    public static void main(String[] args) {
        //Spring容器
        ApplicationContext context = new ClassPathXmlApplicationContext("ApplicationContext.xml");
        Hello hello = (Hello) context.getBean("hello");
        System.out.println(hello.toString());
    }
}
  • 总结:

Spring创建对象、设置属性值,需要实现不同的操作,只需要在xml配置文件中进行。

实际上就是利用Spring的IoC容器Management对象

2.3 IOC创建对象的方式
  1. 使用无参构造创建对象

    
        
    
    
  2. 使用有参构造创建对象

    
        
    
    
    
    
        
    
    
    
    
        
    
    

**总结:**配置文件加载的时候,容器(ApplicationContext context)中管理的对象就已经被初始化了

2.4 Spring配置说明 Beans配置

    







三、依赖注入DI 3.1 构造器注入

第2.2、2.3节的方法

3.2 Set注入

依赖:Bean对象的创建依赖于容器

注入:Bean对象中所有的属性,由容器注入

  1. Student实体类

    package com.rainlx.pojo;
    
    import java.util.*;
    
    public class Student {
        private String name;        //value赋值
        private Address address;    //ref赋值
        private String[] books;
        private List hobby;
        private Map card;
        private Set games;
        private Properties info;
        private String wife;
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public Address getAddress() {
            return address;
        }
    
        public void setAddress(Address address) {
            this.address = address;
        }
    
        public String[] getBooks() {
            return books;
        }
    
        public void setBooks(String[] books) {
            this.books = books;
        }
    
        public List getHobby() {
            return hobby;
        }
    
        public void setHobby(List hobby) {
            this.hobby = hobby;
        }
    
        public Map getCard() {
            return card;
        }
    
        public void setCard(Map card) {
            this.card = card;
        }
    
        public Set getGames() {
            return games;
        }
    
        public void setGames(Set games) {
            this.games = games;
        }
    
        public Properties getInfo() {
            return info;
        }
    
        public void setInfo(Properties info) {
            this.info = info;
        }
    
        public String getWife() {
            return wife;
        }
    
        public void setWife(String wife) {
            this.wife = wife;
        }
    
        @Override
        public String toString() {
            return "Student{" +
                    "name='" + name + ''' +
                    ", address=" + address +
                    ", books=" + Arrays.toString(books) +
                    ", hobby=" + hobby +
                    ", card=" + card +
                    ", games=" + games +
                    ", info=" + info +
                    ", wife='" + wife + ''' +
                    '}';
        }
    }
    
  2. Address实体类

    package com.rainlx.pojo;
    
    public class Address {
        private String address;
    
        public String getAddress() {
            return address;
        }
    
        public void setAddress(String address) {
            this.address = address;
        }
    
        @Override
        public String toString() {
            return "Address{" +
                    "address='" + address + ''' +
                    '}';
        }
    }
    
  3. applicationContext.xml

    
    
    
        
            
            
    
            
            
    
            
            
                
                    Java入门
                    Java进阶
                    Java入神
                
            
    
            
            
                
                    打球
                    画画
                    看书
                
            
    
            
            
                
                    
                    
                
            
    
            
            
                
                    泡泡堂
                    王者荣耀
                
            
    
            
            
                
                    888888
                    1001
                
            
    
            
            
                
            
        
    
        
            
        
    
    
  4. 测试类

    import com.rainlx.pojo.Student;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    
    public class MyTest {
        public static void main(String[] args) {
            ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
            Student student = (Student) context.getBean("student");
            System.out.println(student.toString());
        }
    }
    

    输出结果:

    Student{
    	name='rain', 
    	address=Address{address='中国'}, 
    	books=[Java入门, Java进阶, Java入神],
        hobby=[打球, 画画, 看书], 
        card={
        	银行卡=888888888888888, 
        	电话卡=18888888888
        }, 
        games=[泡泡堂, 王者荣耀], 
        info={
        	学号=888888, 
        	班级=1001
        },
        wife='null'
    }
    
3.3 拓展方式注入 3.3.1 p命名空间
xmlns:p="http://www.springframework.org/schema/p"

p-namespace 允许您使用bean元素的属性(而不是嵌套的元素)来描述协作 Bean 的属性值,或同时使用这两者



    
    
        
    

    

注:使用p命名空间需要有无参构造

3.3.2 c命名空间

c-namespace 允许使用内联属性来配置构造函数参数,而不是嵌套的constructor-arg元素。



    
    

    
    
        
        
        
    

    


注:使用p命名空间要求实体类中有有参构造

3.4 bean的作用域
ScopeDescription
singleton(默认)将每个 Spring IoC 容器的单个 bean 定义范围限定为单个对象实例。
prototype将单个 bean 定义的作用域限定为任意数量的对象实例。
request将单个 bean 定义的范围限定为单个 HTTP 请求的生命周期。也就是说,每个 HTTP 请求都有一个在单个 bean 定义后面创建的 bean 实例。仅在可感知网络的 Spring ApplicationContext中有效。
session将单个 bean 定义的范围限定为 HTTP Session的生命周期。仅在可感知网络的 Spring ApplicationContext上下文中有效。
application将单个 bean 定义的范围限定为ServletContext的生命周期。仅在可感知网络的 Spring ApplicationContext上下文中有效。
websocket将单个 bean 定义的范围限定为WebSocket的生命周期。仅在可感知网络的 Spring ApplicationContext上下文中有效。
  • singleton单例模式(Spring默认)

    
    
  • prototype原型模式

    每次从容器中get的时候,都会生成一个新的对象

    
    
四、Bean自动装配

自动装配是Spring满足bean依赖的一种方式

Spring会在上下文中自动寻找,并自动给bean装配属性

4.1 环境搭建
  • Dog实体类

    package com.rainlx.pojo;
    
    public class Dog {
        public void bark(){
            System.out.println("汪~");
        }
    }
    
  • Cat实体类

    package com.rainlx.pojo;
    
    public class Cat {
        public void bark(){
            System.out.println("喵~");
        }
    }
    
  • People实体类

    package com.rainlx.pojo;
    
    public class People {
        private Cat cat;
        private Dog dog;
        private String name;
    
        @Override
        public String toString() {
            return "People{" +
                    "cat=" + cat +
                    ", dog=" + dog +
                    ", name='" + name + ''' +
                    '}';
        }
    
        public Cat getCat() {
            return cat;
        }
    
        public void setCat(Cat cat) {
            this.cat = cat;
        }
    
        public Dog getDog() {
            return dog;
        }
    
        public void setDog(Dog dog) {
            this.dog = dog;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    }
    
4.2 byName、byType





    



    

解释:

`id="people"`这个设置了byName自动装配,并且People实体类中包含了setName(..)方法,那么spring将自动查找一个名为people的bean定义并使用它来设置属性

在本例中,People实体类包含了setDog( )、setCat( )方法,在`id="people"`的bean中设置自动装配后,就会自动查找`id="dog"`和`id="cat"`的bean,完成自动装配。
4.3 注解自动装配

使用注解条件:

  1. 导入约束

    包含context名称空间

  2. 配置注解的支持

    
    
    
        
    
    
    
4.3.1 @Autowired

@Autowired可以直接在属性上使用,也可以在set方法上使用

使用@Autowired可以没有set方法,但要有get,前提是自动装配的属性在IOC容器中存在,且名字一致




    

    
    
    

  • 实体类
package com.rainlx.pojo;

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

public class People {

    @Autowired
    private Cat cat;
    @Autowired
    private Dog dog;

    @Override
    public String toString() {
        return "People{" +
                "cat=" + cat +
                ", dog=" + dog +
                '}';
    }

    public Cat getCat() {
        return cat;
    }

    public Dog getDog() {
        return dog;
    }

}
  • 测试
import com.rainlx.pojo.People;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class MyTest {

    @Test
    public void test(){
        ApplicationContext Context = new ClassPathXmlApplicationContext("applicationContext.xml");
        People people = Context.getBean("people", People.class);
        people.getCat().bark();
        people.getDog().bark();
    }
}
  • 如果显式定义了Autowired的required属性为false,说明这个对象可以为null,否则不允许为空

  • 如果@Autowired自动装配的环境比较复杂,自动装配无法通过一个注解完成的时候,可以使用@Qualifier(value=“xxx”)去配合@Autowired的使用,指定一个唯一的bean对象注入。

    
    
    
    //在xml中配置了id=cat和id=cat01的bean,通过使用@Qualifier来指定bean对象注入
    @Autowired
    @Qualifier(value = "cat01")
    private Cat cat;
    
4.3.2**@Resource**



public class People(){
    @Resource(name = "cat01")	
    private Cat cat;
    @Resource
    private Dog dog;
    
}

总结:

  • @Resource和@Autowired都是用来自动装配的,都可以放在属性字段上

  • @Autowired默认通过byType实现,然后通过byName实现

  • @Resource默认通过byName实现,如果找不到名字,通过byType实现,两个都找不到则报错

注解汇总
  • @Autowired:自动装配,先类型,后名字

    如果@Autowired不能唯一装配属性,则需要通过@Qualifier(value=“xxx”)指定唯一的bean

  • @Nullable:字段标记了这个注解,说明这个字段可以为null

  • @Resource:自动装配,先名字,后类型

  • @Component:组件,放在类上,说明这个类被Spring管理装配成bean了

  • @value:放在属性/属性的set()方法上,用来给属性字段赋值

五、Spring注解开发

使用注解开发,首先要进行一些配置

  1. 保证aop的包导入

  2. 导入context约束和支持




    
    
    

5.1 属性注入
//等价于
//Component组件
@Component
public class User {

    //相当于
    @Value("rainlx")
    public String name;
}
5.2 衍生的注解

@Component

在web开发中,按照mvc三层架构分成:

  • dao【@Repository】

  • service【@Service】

  • controller【@Controller】

    这四个注解的功能是一样的,都是将某个类注册到Spring中,装配bean

5.3 作用域

@Scope注解用来限定作用域,主要有以下几个基本作用域:

  • singleton单例模式:全局有且仅有一个实例
  • prototype原型模式:每次获取Bean的时候会有一个新的实例
  • Web(reqeust、session、globalsession)
    • request:针对每一次HTTP请求都会产生一个新的bean,同时该bean仅在当前HTTP request内有效
    • session:针对每一次HTTP请求都会产生一个新的bean,同时该bean仅在当前HTTP session内有效
    • globalsession:类似于标准的HTTP Session作用域,不过它仅仅在基于portlet的web应用中才有意义

总结:

  • xml与注解:
    • xml更加万能,适用于任何场合,维护简便
    • 注解,只能使用自己的类,维护复杂
  • xml与注解最佳实践:
    • xml用来管理bean
    • 注解负责完成属性注入
六、Java配置Spring 6.1 基本概念

使用Java配置代替xml配置Spring,主要使用@Configuration注解的类和@Bean注解的方法

  • 使用@Configuration的类,表示这个类是一个配置类,等同于ApplicationContext.xml文件

  • @Bean注解用于指示方法实例化,配置和初始化要由 Spring IoC 容器 Management 的新对象

用一个简单的例子说明如何使用java配置Spring

  • 实体类

    //@Component注解:将下面的类注册到容器中
    @Component
    public class User {
        private String name;
    
        public String getName() {
            return name;
        }
    
        @Value("rainlx")
        public void setName(String name) {
            this.name = name;
        }
    
        @Override
        public String toString() {
            return "User{" +
                    "name='" + name + ''' +
                    '}';
        }
    }
    
  • 新建一个Config.java的配置文件

    //下面的类注册到容器中被Management
    @Configuration
    public class ProConfig {
    
        //@Bean注解与元素具有相同的作用
        //方法名就是xml配置文件bean标签中的id属性
        //方法的返回值就是bean标签中的class属性
        @Bean
        public User getUser(){
            return new User();
        }
    }
    
  • 测试类

    public class MyTest {
        public static void main(String[] args) {
            //如果完全使用java配置类,就只能通过 AnnotationConfig 上下文获取容器,通过配置类的class对象加载。
            AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ProConfig.class);
            User user = context.getBean("getUser",User.class);
            System.out.println(user.getName());
        }
    }
    

如果有多个config.java配置文件,可以使用@import注解将所有配置文件导入到同一个文件中

@import(****.class)
七、代理模式

代理模式的分类:

  • 静态代理

  • 动态代理

7.1 静态代理1

角色分析:

  • 抽象角色:一般用接口或抽象类来解决

  • 真实角色:被代理的角色

  • 代理角色:代理真实角色

  • 客户:访问代理对象的人

用一个实例说明静态代理各个角色及之间的关系

  1. 接口

    package com.rainlx.demo01;
    
    //提供一个“租房”的业务
    public interface Rent {
    }
    
  2. 真实角色

    package com.rainlx.demo01;
    
    //房东要参与“租房”业务,所以要继承这个业务
    public class Host implements Rent{
    
        public void rent(){
            System.out.println("房东出租房屋");
        }
    }
    
  3. 代理角色

    package com.rainlx.demo01;
    
    //中介代理了房东的业务,所以也要继承
    public class Proxy implements Rent{
    
        private Host host;      //代理角色(中介)代理了真实角色(房东)
    
        public Proxy() {
        }
    
        public Proxy(Host host) {
            this.host = host;
        }
    
        public void rent(){
            host.rent();        //代理角色拥有了真实角色的方法,这里代理代替房东去出租房屋
            seeHouse();
            sign();
        }
    
        //代理角色还可以提供公共业务
        public void seeHouse(){
            System.out.println("看房子");
        }
        public void sign(){
            System.out.println("签合同");
        }
    }
    
  4. 客户

    package com.rainlx.demo01;
    
    public class Client {
        public static void main(String[] args) {
            Host host = new Host();
            Proxy proxy = new Proxy(host);      //方法中的对象是代理角色代理的真实对象
            proxy.rent();
        }
    }
    

代理模式的好处:

  • 使真实角色不用去关注一些公共的业务
  • 所有的公共业务交给了代理角色
  • 公共业务发生扩展的时候,方便集中管理

缺点:

  • 一个真实角色就会有一个代理角色,代码量会翻倍,降低开发效率
7.2 静态代理2

例子:一个具有增删改查功能的业务,现在要在没个功能前加一个日志功能

思路:在不改动原来代码的条件下,增加一个代理原业务的角色,然后在代理角色中增加新的日志功能

  1. 接口

    package com.rainlx.demo02;
    
    public interface UserService {
        public void add();
        public void delete();
        public void update();
        public void query();
    }
    
  2. 原业务类

    package com.rainlx.demo02;
    
    public class UserServiceImpl implements UserService{
        @Override
        public void add() {
            System.out.println("增加用户");
        }
    
        @Override
        public void delete() {
            System.out.println("删除用户");
        }
    
        @Override
        public void update() {
            System.out.println("更新用户");
        }
    
        @Override
        public void query() {
            System.out.println("查询用户");
        }
    }
    
  3. 代理业务

    package com.rainlx.demo02;
    
    //代理也要有原有的增删改查功能业务
    public class UserServiceProxy implements UserService{
    
        //代理原业务UserServiceImpl
        private UserServiceImpl userService;
    
        //将原业务对象userService注入到代理对象中,要用set方法,
        public void setUserService(UserServiceImpl userService) {
            this.userService = userService;
        }
    
        //重写原业务的方法,并调用新的日志功能
        @Override
        public void add() {
            log("add");
            userService.add();
        }
    
        @Override
        public void delete() {
            log("delete");
            userService.delete();
        }
    
        @Override
        public void update() {
            log("update");
            userService.update();
        }
    
        @Override
        public void query() {
            log("query");
            userService.query();
        }
    
        //新功能:日志
        public void log(String msg){
            System.out.println("【DEBUG】使用了"+msg+"方法");
        }
    }
    
  4. 调用业务类

    package com.rainlx.demo02;
    
    public class Client {
        public static void main(String[] args) {
            UserServiceImpl userService = new UserServiceImpl();
            UserServiceProxy proxy = new UserServiceProxy();
            proxy.setUserService(userService);	//将原业务对象userService注入到代理对象中
    
            proxy.add();
            proxy.delete();
        }
    }
    
    运行结果:
    【DEBUG】使用了add方法
    增加用户
    【DEBUG】使用了delete方法
    删除用户
    
7.3 动态代理1
  • 动态代理的代理类是动态生成的,不是直接写好的
  • 动态代理分两大类
    • 基于接口——JDK动态代理
    • 基于类——cglib

还是通过租房子的例子说明动态代理中的各个角色

  1. 接口

    package com.rainlx.demo03;
    
    public interface Rent {
        public void rent();
    }
    
  2. 真实角色

    package com.rainlx.demo03;
    
    public class Host implements Rent {
    
        public void rent(){
            System.out.println("房东出租房屋");
        }
    }
    
  3. 程序处理角色

    package com.rainlx.demo03;
    
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.lang.reflect.Proxy;
    
    //通过这个类自动生成代理类
    public class ProxyInvocationHandler implements InvocationHandler {
    
        //被代理的接口
        private Rent rent;
    
        public void setRent(Rent rent) {
            this.rent = rent;
        }
    
        
        public Object getProxy(){
            return Proxy.newProxyInstance(this.getClass().getClassLoader(), rent.getClass().getInterfaces(), this);
        }
    
    
        //处理代理实例,并返回结果
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    
            //动态代理的本质就是使用反射机制实现
            Object result = method.invoke(rent, args);
            return result;
        }
    }
    
  4. 客户

    package com.rainlx.demo03;
    
    
    
    public class Client {
        public static void main(String[] args) {
            Host host = new Host();
            ProxyInvocationHandler handler = new ProxyInvocationHandler();
            handler.setRent(host);      //通过”程序处理角色“的set方法将要代理的对象注入到handler中
    
            Rent proxy = (Rent) handler.getProxy();
            proxy.rent();
        }
    }
    
7.4 动态代理2

例子:设置一个万能的动态代理,代理demo02中的真实对象

  1. 程序处理角色

    package com.rainlx.demo04;
    
    import com.rainlx.demo03.Rent;
    
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.lang.reflect.Proxy;
    
    //通过这个类自动生成代理类
    public class ProxyInvocationHandler implements InvocationHandler {
    
        //被代理的接口
        private Object target;
    
        public void setTarget(Object target) {
            this.target = target;
        }
    
        //生成代理类
        public Object getProxy(){
            return Proxy.newProxyInstance(this.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
        }
    
    
        //处理代理实例,并返回结果
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            log(method.getName());      //getName自动获取执行方法的名称
            Object result = method.invoke(target, args);
            return result;
        }
    
        public void log(String msg){
            System.out.println("执行了"+msg+"方法");
        }
    }
    
  2. 客户

    package com.rainlx.demo04;
    
    import com.rainlx.demo02.UserService;
    import com.rainlx.demo02.UserServiceImpl;
    
    public class Client {
        public static void main(String[] args) {
            //真实角色
            UserServiceImpl userService = new UserServiceImpl();
            ProxyInvocationHandler handler = new ProxyInvocationHandler();
            handler.setTarget(userService);
    
            UserService proxy = (UserService) handler.getProxy();
            proxy.add();
            proxy.delete();
        }
    }
    运行结果:
    执行了add方法
    增加用户
    执行了delete方法
    删除用户
    

代理模式的好处:

  • 使真实角色不用去关注一些公共的业务
  • 所有的公共业务交给了代理角色
  • 公共业务发生扩展的时候,方便集中管理
  • 一个动态代理类代理的是一个接口,即对应的一类业务
  • 一个动态代理类可以代理多个类,只要是实现同一个接口即可
八、AOP 8.1 什么是AOP

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

AOP中的概念

  • Aspect(切面): Aspect 声明类似于 Java 中的类声明,在 Aspect 中会包含着一些 Pointcut 以及相应的 Advice。是一个类
  • Joint point(连接点):表示在程序中明确定义的点,典型的包括方法调用,对类成员的访问以及异常处理程序块的执行等等,它自身还可以嵌套其它 joint point。
  • Pointcut(切入点):表示一组 joint point,这些 joint point 或是通过逻辑关系组合起来,或是通过通配、正则表达式等方式集中起来,它定义了相应的 Advice 将要发生的地方。要织入的位置
  • Advice(增强):Advice 定义了在 Pointcut 里面定义的程序点具体要做的操作,它通过 before、after 和 around 来区别是在每个 joint point 之前、之后还是代替执行的代码。是类中的一个方法
  • Target(目标对象):织入 Advice 的目标对象。要织入的对象
  • Weaving(织入):将 Aspect 和其他对象连接起来, 并创建 Adviced object 的过程
8.2 Spring实现AOP

使用AOP,首先需要导入一个依赖包


    
        org.aspectj
        aspectjweaver
        1.9.5
    

8.2.1 方式一:使用Spring API接口

现在希望实现一个在CRUD方法的前后插入日志的功能,设计方式如下:

  • 先写好接口接口实现类、日志类(要织入的对象
  • 然后通过Spring API接口的方式实现AOP,在applicationContext.xml配置文件中进行AOP织入
  • 在配置文件中说明,切入点:需要在哪个地方执行代码;增强:执行的是哪一段代码。
    • PS:标记的几项内容是AOP必备的
  1. 接口

    package com.rainlx.service;
    
    public interface UserService {
        public void add();
        public void delete();
        public void update();
        public void select();
    }
    
  2. 接口实现类

    package com.rainlx.service;
    
    public class UseServiceImpl implements UserService{
        @Override
        public void add() {
            System.out.println("增加用户");
        }
    
        @Override
        public void delete() {
            System.out.println("删除用户");
        }
    
        @Override
        public void update() {
            System.out.println("更新用户");
        }
    
        @Override
        public void select() {
            System.out.println("查询用户");
        }
    }
    
  3. before日志类

    package com.rainlx.log;
    
    
    import org.springframework.aop.MethodBeforeAdvice;
    
    import java.lang.reflect.Method;
    
    public class Log implements MethodBeforeAdvice {
    
        
        @Override
        public void before(Method method, Object[] args, Object target) throws Throwable {
            System.out.println(target.getClass().getName()+"的"+method.getName()+"方法被执行了");
        }
    }
    
  4. after日志类

    package com.rainlx.log;
    
    import org.springframework.aop.AfterReturningAdvice;
    
    import java.lang.reflect.Method;
    
    public class AfterLog implements AfterReturningAdvice {
    
        @Override
        public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
            System.out.println("执行了"+method.getName()+"方法,结果为"+returnValue);
        }
    }
    
  5. 配置文件(在这里配置pointcut)

    
    
    
        
        
        
        
    
        
        
        
            
            
    
            
            
            
        
    
    
  6. 测试类

    import com.rainlx.service.UserService;
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    
    public class MyTest {
        public static void main(String[] args) {
            ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
            //动态代理,代理的是接口
            UserService userService = context.getBean("userService", UserService.class);
            userService.add();
        }
    }
    运行结果:
    com.rainlx.service.UseServiceImpl的add方法被执行了
    增加用户
    执行了add方法,结果为null
    
    • AOP中的pointcut expression表达式:
    任意公共方法的执行:
    execution(public * *(..))
    
    任何一个以“set”开始的方法的执行:
    execution(* set*(..))
    
    AccountService 接口的任意方法的执行:
    execution(* com.xyz.service.AccountService.*(..))
    
    定义在service包里的任意方法的执行:
    execution(* com.xyz.service.*.*(..))
    
    定义在service包和所有子包里的任意类的任意方法的执行:
    execution(* com.xyz.service..*.*(..))
    
    定义在pointcutexp包和所有子包里的JoinPointObjP2类的任意方法的执行:
    execution(* com.test.spring.aop.pointcutexp..JoinPointObjP2.*(..))
    
    PS:最靠近(..)的为方法名,靠近.*(..))的为类名或者接口名,如上例的JoinPointObjP2.*(..))
    

    execution(* com.xyz.service..*.*(..))表达式包括5部分:

    • execution():表达式主体
    • 第一个*号:表示返回类型, *号表示所有的类型
    • 包名:表示需要拦截的包名,后面的两个点表示当前包和当前包的所有子包,com.xyz.service包、子孙包下所有类的方法
    • 第二个*号:表示类名, *号表示所有的类
    • *(…):最后这个星号表示方法名, *号表示所有的方法,后面括弧里面表示方法的参数,两个句点表示任何参数
8.2.2 方式二:使用自定义类

主要过程是定义切面,切面要包括切入点、增强

  1. 自定义日志类

    package com.rainlx.diy;
    
    public class DiyPointCut {
        public void before(){
            System.out.println("执行前的日志:*******");
        }
        public void after(){
            System.out.println("执行后的日志:*******");
        }
    }
    
  2. 配置文件(配置切入点、切面)

    
    
    
        
        
        
        
            
            
                
                
                
                
                
    
            
        
    
    
  3. 测试类

    import com.rainlx.service.UserService;
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    
    public class MyTest {
        public static void main(String[] args) {
            ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
            UserService userService = context.getBean("userService",UserService.class);
            userService.add();
        }
    }
    运行结果:
    执行前的日志:*******
    增加用户
    执行后的日志:*******
    
8.2.3 方式三:使用注解

接口类、接口实现类还是上面例子中的。

  1. 自定义一个类

    package com.rainlx.diy;
    
    import org.aspectj.lang.ProceedingJoinPoint;
    import org.aspectj.lang.annotation.After;
    import org.aspectj.lang.annotation.Around;
    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.annotation.Before;
    
    @Aspect	//标注这个类是一个切面
    public class AnnotationPointCut {
    
        @Before("execution(* com.rainlx.service.UserServiceImpl.*(..))")
        public void before(){
            System.out.println("====方法执行前====");
        }
    
        @After("execution(* com.rainlx.service.UserServiceImpl.*(..))")
        public void after(){
            System.out.println("====方法执行后====");
        }
    
        @Around("execution(* com.rainlx.service.UserServiceImpl.*(..))")
        //在环绕通知中,可以给定一个参数,用于启动目标方法执行的
        public void around(ProceedingJoinPoint joinPoint) throws Throwable {
            System.out.println("环绕开始");				  //前置通知
            Object proceed = joinPoint.proceed();		 //目标方法
            System.out.println("环绕结束");				  //后置通知
        }
    }
    
  2. 在配置文件中注册bean,并添加AOP注解支持

    
    
    
        
        
        
        
    
    
  3. 测试类

    import com.rainlx.service.UserService;
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    
    public class MyTest {
        public static void main(String[] args) {
            ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
            UserService userService = context.getBean("userService",UserService.class);
            userService.add();
        }
    }
    运行结果:
    环绕开始
    ====方法执行前====
    增加用户
    ====方法执行后====
    环绕结束
    
九、整合MyBatis

erviceImpl.*(…))")
public void after(){
System.out.println(“方法执行后”);
}

   @Around("execution(* com.rainlx.service.UserServiceImpl.*(..))")
   //在环绕通知中,可以给定一个参数,用于启动目标方法执行的
   public void around(ProceedingJoinPoint joinPoint) throws Throwable {
       System.out.println("环绕开始");				  //前置通知
       Object proceed = joinPoint.proceed();		 //目标方法
       System.out.println("环绕结束");				  //后置通知
   }

}

2. 在配置文件中注册bean,并添加AOP注解支持

```xml



    
    
    
    

  1. 测试类

    import com.rainlx.service.UserService;
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    
    public class MyTest {
        public static void main(String[] args) {
            ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
            UserService userService = context.getBean("userService",UserService.class);
            userService.add();
        }
    }
    运行结果:
    环绕开始
    ====方法执行前====
    增加用户
    ====方法执行后====
    环绕结束
    
转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/285315.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

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

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