Spring开发介绍

Spring开发介绍

Maven 项目依赖添加

pom.xml文件增加Spring依赖包

        <!--引入spring-core核心包-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>5.3.6</version>
        </dependency>

        <!--引入Spring-Bean包-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-beans</artifactId>
            <version>5.3.6</version>
        </dependency>

        <!--引入Spring-context包-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.3.6</version>
        </dependency>

        <!--引入spring-expression表达式jar包-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-expression</artifactId>
            <version>5.3.6</version>
        </dependency>
        <!--引入日志依赖-->
        <dependency>
            <groupId>commons-logging</groupId>
            <artifactId>commons-logging</artifactId>
            <version>1.2</version>
        </dependency>
        <!--引入测试包-->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>

Spring容器初始化,并获取对象

从Spring容器中获取对象可以使用getBean方法,根据对象id或者类字节码获取对象。在xml配置开发中常使用id方式获取对象,在注解开发中常使用接口类字节码方式获取实现类的对象。
初始化容器并获取对象,分为如下模式:
1)xml配置文件模式

   public void testUser(){
        ApplicationContext context = new ClassPathXmlApplicationContext(
        "application.xml");
        User user= (User) context.getBean("user");
        user.say();
    }

2)注解模式

    @Test
    public void testSpringTx(){
        ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
        /**
         * Spring 中规定:如果传入的是接口的类型,则自动查找/注入 该接口的实现类
         *              该接口只能有一个实现类
         * 注入接口的原理:
         *          if(getBean( isInterface ) ){
         *              class targetClass = interface.getImpl();
         *              根据实现类类型,动态获取对象
         *              return 对象;
         *          }
         */
        UserService userService= (UserService) context.getBean("userServiceImpl");
        //向上造型,使用接口接收实现类对象
        UserService userService1=context.getBean(UserService.class);
        User user= new User();
        user.setId(101);
        user.setName("SpringAOP测试入门案例");
        userService1.addUser(user);
    }

关于Spring工厂模式说明

1)Spring源码中创建对象都是采用工厂模式 接口:BeanFactory(顶级接口)
2)Spring开发中需要手动的创建对象时,一般采用FactoryBean(业务接口)

Spring 对象加载方式

Spring中默认被管理对象一般为单例模式,可以通过bean标签的scope属性开启多例模式。
单例模式下默认被管理对象加载方式为立即加载,即容器创建则立即加载;多例模式中只能是懒加载方式,当获取对象时才会创建对象。

在这里插入图片描述

Spring对象生命周期

单例对象当容器销毁时对象销毁,多例对象需要手动销毁对象。
实现类代码:

public class User {
    private  String conn;  //数据库链接
    //1.初始化方法
    public void init(){
        this.conn="获取数据库链接";
        System.out.println(this.conn);
    }
    //2.用户调用方法
    public void say(){
        System.out.println("我是用户");
    }
    //3.销毁方法
    public void destory(){
        this.conn=null;
        System.out.println("释放资源["+this.conn+"]=====");
    }
}

application.xml 配置文件如下:

    <bean id="user" class="com.jt.pojo.User" init-method="init" destroy-method="destory" scope="singleton"></bean>
    <!--<bean id="user" class="com.jt.pojo.User" init-method="init" destroy-method="destory" scope="prototype"></bean>-->

测试类代码:

    @Test
    public void testLife(){
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("application.xml");
        User user=(User)context.getBean("user");
        context.getBean("user");
        //调用方法
        user.say();
        user.destory();  //多例对象需要手动调用编写的销毁方法
        //单例对象只要容器关闭,则对象销毁
        context.close();
    }

Spring注入方式介绍

注入方式分为两种:
1、set注入
2、构造函数注入

set注入介绍

set注入是指通过调用对象的set方法对属性进行赋值。
1、将application.xml配置文件中name属性指定的值,进行首字母大写,例如属性名字为map,转换后为Map
2、通过反射查找set+转换后的name属性查找对应的set方法进行赋值,例如setMap

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

public class User {  //首选set注入方式
    private Integer id;
    private String  name;
    // 为集合赋值
    private List list;
    private Set  set;
    private Map map;
    private Properties pro;  //内部只能保存String类型数据

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", list=" + list +
                ", set=" + set +
                ", map=" + map +
                ", pro=" + pro +
                '}';
    }

    public Properties getPro() {
        return pro;
    }

    public void setPro(Properties pro) {
        this.pro = pro;
    }

    public List getList() {
        return list;
    }

    public void setList(List list) {
        this.list = list;
    }

    public Set getSet() {
        return set;
    }

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

    public Map getMap() {
        return map;
    }

    public void setMap(Map map) {
        this.map = map;
    }

    public Integer getId() {
        return id;
    }

    public User() {
    }

    public User(Integer id, String name) {
        this.id = id;
        this.name = name;
    }

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

    public String getName() {
        return name;
    }

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

application.xml 配置文件如下:

    <!--为集合赋值-->
    <bean id="user" class="com.jt.pojo.User">
        <property name="id" value="101"></property>
        <property name="name" value="李元芳"></property>
        <property name="list">
            <list>
                <value>张三</value>
                <value>李四</value>
                <value>王五</value>
            </list>
        </property>
        <property name="set">
            <set>
                <value>1</value>
                <value>2</value>
                <value>3</value>
            </set>
        </property>
        <property name="map">
            <map>
                <entry key="id" value="1000"></entry>
                <entry key="name" value="Tomcat猫"></entry>

            </map>
        </property>
        <property name="pro">
            <props>
                <prop key="proId" >110</prop>
                <prop key="proName" >米老鼠</prop>
            </props>
        </property>
    </bean>
    <!--使用公共标签的方式赋值Map集合的值-->
    <bean id="person" class="com.jt.pojo.Person">
        <property name="map">
            <map>
                <entry key="id" value="100"></entry>
                <entry key="name" value="testName"></entry>
            </map>
        </property>
    </bean>

构造函数注入介绍

构造函数注入是指对象创建时通过构造函数进行属性赋值。
代码参考set注入
application.xml 配置文件如下:

<bean id="user" class="com.jt.pojo.User">
        <constructor-arg name="id" value="102" index="0"></constructor-arg>
        <constructor-arg name="name" value="兰陵王" index="1"></constructor-arg>
    </bean>

Spring配置文件

Spring容器基于Map的方式管理对象,其中key为id属性,value存储实例化后的对象
Spring配置文件名称一般为application.xml,常用标签如下:
beans:用于管理Spring实例化后的对象集合
bean: 用于管理单个Spring实例化后的对象

Spring bean标签介绍

bean标签主要用于通过Spring容器实例化对象的管理
application.xml 配置文件如下:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean id="user" class="com.jt.pojo.User"></bean>
</beans>

Spring bean标签属性介绍

id:用于标识被管理对象的唯一标识,不能重复
class:用于指示被管理对象的类全路径名字
factory-method:用户指示获取实例化对象的方法
scope:用于指示单例模式或者多例模式,默认为单例模式,若为多例模式则编码时需要考虑对象销毁问题

<!--通过scope属性控制对象的单例或者多例
	scope=“prototype" 多例模式
	scope=“singleton" 单例模式,默认为单例模式一般不写
	init-method:配置初始化方法
	destroy-method:配置销毁方法,单例模式时容器销毁时自动调用销毁方法,多例模式时需要手动销毁
-->
<bean id="user" class="com.jt.pojo.User" init-method="init" destroy-method="destory" scope="prototype"></bean>

init-method:用于指示对象初始化的方法
destroy-method:用于指示对象销毁的方法,单例模式时容器销毁时自动调用销毁方法,多例模式时需要手动销毁
lazy-init:用于指示是否开启懒加载

<!--lazy-init="true" 开启懒加载
	lazy-init="false"/lazy-init=“default" 懒加载不生效
	原则:只要是多例对象都是懒加载,懒加载只对单例有效
	关于懒加载说明:一般服务器对象应该先行创建,用户直接使用即可
	多例对象:用户使用时创建,同时将对象的生命周期交给使用者管理,Sping不负责维护对象的生命周期
	(随用随销毁)
-->
<bean id="user" class="com.jt.pojo.User" lazy-init="true"></bean>

constructor-arg:构造函数注入时使用
name:用于指示属性名字
index:用于指示参数位置,即第几个参数

<bean id="user" class="com.jt.pojo.User">
        <constructor-arg name="id" value="102" index="0"></constructor-arg>
        <constructor-arg name="name" value="兰陵王" index="1"></constructor-arg>
    </bean>

property:set注入时设置属性的值

    <bean id="user" class="com.jt.pojo.User">
        <property name="id" value="101"></property>
        <property name="name" value="李元芳"></property>
        <property name="list">
            <list>
                <value>张三</value>
                <value>李四</value>
                <value>王五</value>
            </list>
        </property>
        <property name="set">
            <set>
                <value>1</value>
                <value>2</value>
                <value>3</value>
            </set>
        </property>
        <property name="map">
            <map>
                <entry key="id" value="1000"></entry>
                <entry key="name" value="Tomcat猫"></entry>

            </map>
        </property>
        <property name="pro">
            <props>
                <prop key="proId" >110</prop>
                <prop key="proName" >米老鼠</prop>
            </props>
        </property>
    </bean>

ref:引用配置文件中的对象或者其他属性

    <bean id="user" class="com.jt.pojo.User">
        <property name="id" value="100"></property>
        <!--<property name="name" value="&lt;SpringMVC设计模式&gt;"></property>-->
        <property name="name">
            <value><![CDATA[<SpringMVC>]]></value>
        </property>
    </bean>
    <!--2.构建Dao对象 面向接口编程
        根据面向接口编程
        ID:写的是接口的名称
        class:实现类的名称(包含包路径)
    -->
    <bean id="userDao" class="com.jt.dao.UserDaoImpl"></bean>
    <!--3.构建Service-->
    <bean id="userService" class="com.jt.service.UserServiceImpl">
        <property name="userDao" ref="userDao"></property>
    </bean>
    <bean id="userController" class="com.jt.controller.UserController">
        <property name="user" ref="user"></property>
        <property name="userService" ref="userService"></property>
    </bean>

autowire:自动注入配置
byName:按照属性名字自动注入
byType:按照类型自动注入

XML配置文件中特殊字符转换

特殊万能转换方式:

        <property name="name">
            <value><![CDATA[<SpringMVC>]]></value>
        </property>

Spring工厂–通过静态方法获取对象

通过编写静态get方法获取对象
代码如下:


import java.util.Calendar;

public class StaticFactory {
    /*通过静态方式创建对象*/
    public static Calendar getCalendar(){
        System.out.println("静态工厂创建对象");
        return Calendar.getInstance();
    }
}

application.xml 中配置文件如下:

<bean id="calendar" class="com.jt.factory.StaticFactory" factory-method="getCalendar"></bean>

Spring工厂–通过普通方法获取对象

通过编写普通get方法获取对象
代码如下:


import java.util.Calendar;

public class InstanceFactory {
    /*通过普通方法获取对象 */
    public Calendar getCalendar(){
        System.out.println("通过工厂普通方法获取对象");
        return Calendar.getInstance();
    }
}

application.xml 中配置文件如下:

    <!--普通实例化工厂方式:1、将工厂交给Sping管理 2、通过对象调用方法-->
    <bean id="instaceFactory" class="com.jt.factory.InstanceFactory"></bean>
    <bean id="calendar2" factory-bean="instaceFactory" factory-method="getCalendar"></bean>

Spring 设置配置文件公共标签

application.xml 增加util约束:

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:util="http://www.springframework.org/schema/util"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
                           http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd
        ">

application.xml 增加公共标签:

    <util:map id="map">
        <entry key="id" value="1000"></entry>
        <entry key="name" value="11Tomcat猫"></entry>
    </util:map>

Spring MVC 编码-配置文件

Spring MVC代码分层

  目前一般采用面向接口开发模式,Controller层接收简单实体类对象进行处理前端传输数据,Controller层通过Spring框架获取Service层对象实现业务逻辑处理,Service层通过Spring框架获取Dao层对象实现数据持久化。

1、Controller层:用于接收前端界面用户数据
2、Service层:用于业务逻辑处理
3、Dao层:用于数据持久化
4、pojo目录:用于存放简单实体类
1、

Spring MVC调用路径

  MVC开发过程中一般采用面向接口方式进行开发,所以Controller层创建Service层接口属性,并通过Spring框架获取Service层实体对象,调用Service层处理业务逻辑;Service层创建Dao层接口属性,并通过Spring框架获取Dao层对象,调用Dao层方法实现数据持久化。

在这里插入图片描述

Spring MVC配置文件编写规则

一般bean标签中id指向接口名字,class指向实现类

    <bean id="user" class="com.jt.pojo.User">
        <property name="id" value="100"></property>
        <!--<property name="name" value="&lt;SpringMVC设计模式&gt;"></property>-->
        <property name="name">
            <value><![CDATA[<SpringMVC>]]></value>
        </property>
    </bean>
    <!--2.构建Dao对象 面向接口编程
        根据面向接口编程
        ID:写的是接口的名称
        class:实现类的名称(包含包路径)
    -->
    <bean id="userDao" class="com.jt.dao.UserDaoImpl"></bean>
    <!--3.构建Service-->
    <bean id="userService" class="com.jt.service.UserServiceImpl">
        <property name="userDao" ref="userDao"></property>
    </bean>
    <bean id="userController" class="com.jt.controller.UserController">
        <property name="user" ref="user"></property>
        <property name="userService" ref="userService"></property>
    </bean>

自动装配-配置文件

自动装配分为两种模式:
1、按照属性名字进行自动装配
2、按照类型进行装配
注:自动装配不能注入简单属性,例如Integer String 等

自动装配-按照属性名字-配置文件

    <!--3.构建Service
        自动装配:程序无需手动的编辑perperty
        autowire=”byName" 根据属性的名称进行注入
        1、找到对象的所有set方法 setUserDao方法
        2、setUserDao方法~~~~set去掉~~~~UserDao~~~~首字母小写~~~~userDao
        3、Spring会根据对象的属性查找自己维护的Map集合,根据userDao名称,查找Map中的Key与之对应,
           如果匹配成功,则自动调用set方法实现set注入(必须有set方法)
        autowire=”byTYpe"
          1、找到对象的所有set方法  setUserDao方法
          2、根据set方法找到方法中参数的类型 UserDao.class
          3、Spring根据自己维护对象的Class进行匹配,如果匹配成功则实现注入(set方法)
    -->
    <bean id="userService" class="com.jt.service.UserServiceImpl" autowire="byName">
<!--        <property name="userDao" ref="userDao"></property>-->
    </bean>
    <bean id="userController" class="com.jt.controller.UserController" autowire="byName">
<!--        <property name="user" ref="user"></property>
        <property name="userService" ref="userService"></property>-->
    </bean>

自动装配-按照类型进行-配置文件

    <bean id="userDao" class="com.jt.dao.UserDaoImpl"></bean>
    <!--3.构建Service
        自动装配:程序无需手动的编辑perperty
        autowire=”byName" 根据属性的名称进行注入
        1、找到对象的所有set方法 setUserDao方法
        2、setUserDao方法~~~~set去掉~~~~UserDao~~~~首字母小写~~~~userDao
        3、Spring会根据对象的属性查找自己维护的Map集合,根据userDao名称,查找Map中的Key与之对应,
           如果匹配成功,则自动调用set方法实现set注入(必须有set方法)
        autowire=”byType"
          1、找到对象的所有set方法  setUserDao方法
          2、根据set方法找到方法中参数的类型 UserDao.class
          3、Spring根据自己维护对象的Class进行匹配,如果匹配成功则实现注入(set方法)
    -->
    <bean id="userService" class="com.jt.service.UserServiceImpl" autowire="byType">
          <!-- <property name="userDao" ref="userDao"></property>-->
    </bean>
    <bean id="userController" class="com.jt.controller.UserController" autowire="byType">
   <!-- <property name="user" ref="user"></property>
        <property name="userService" ref="userService"></property>-->
    </bean>

注解生效,开启包扫描

application.xml中修改beas标签增加context约束

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
                           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

application.xml中开启包注解扫描

    <!--业务需求:不扫描@Controller注解,但是扫描其他注解
        属性1:use-default-filters="false" 按照指定的注解进行加载
             use-default-filters="true"   默认规则:表示可以扫描所有注解
    -->
    <context:component-scan base-package="com.jt" use-default-filters="false">
        <!--expression:表达式 可以写包路径-->
        <!--当前的包扫描指定注解-->
        <context:include-filter type="annotation" expression="org.springframework.stereotype.Service"/>
        <context:include-filter type="annotation" expression="org.springframework.stereotype.Repository"/>
        <context:include-filter type="annotation" expression="org.springframework.stereotype.Component"/>
        <!--当前的包不扫描指定注解-->
        <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
    </context:component-scan>

类注解的使用原理

/*
DAO层Spring注解
等价xml配置文件中使用Spring标识:<bean id="类名首字母小写~~userDaoImpl" class="UserDaoImpl.class" />
类添加注解后,id规则为类名首字母小写,即userDaoImpl
如果需要修改bean的id则手动添加value属性即可,一般不写
@Repository(value="userDao")
 */
@Repository
public class UserDaoImpl implements UserDao{
    @Override
    public void addUser(User user) {
        System.out.println("链接数据库执行insert into:"+user);
    }
}

Spring注解介绍

注解加载

1).当程序启动Spring容器时 AnnotationConfigApplicationContext 利用beanFactory实例化对象
2).根据配置类中的包扫描开始加载指定的注解. 根据配置文件的顺序依次进行加载
在这里插入图片描述

3).当程序实例化Controller时,由于缺少Service对象,所以挂起线程 继续执行后续逻辑.
当构建Service时,由于缺少Dao对象,所以挂起线程 继续执行后续逻辑.
当实例化Dao成功时,保存到Spring所维护的Map集合中. 执行之前挂起的线程.
所以以此类推 所有对象实现封装.最终容器启动成功
在这里插入图片描述

4). 根据指定的注解/注入指定的对象.之后统一交给Spring容器进行管理.最终程序启动成功.

注解模式说明

Spring为了简化XML配置文件方式,则研发注解模式
Spring为了程序更加严谨,通过不同的注解标识不同的层级,但是注解的功能一样
注解的作用:复杂的程序以丢耦合度的方式进行调用

java元注解介绍:

	@Target  标注注解对谁有效  type:类 method:方法有效
	```java
	ElementType.TYPE 应用于类的元素
	ElementType.METHOD 应用于方法级
	ElementType.FIELD 应用于字段或属性(成员变量)
	ElementType.ANNOTATION_TYPE 应用于注解类型
	ElementType.CONSTRUCTOR 应用于构造函数
	ElementType.LOCAL_VARIABLE 应用于局部变量
	ElementType.PACKAGE 应用于包声明
	ElementType.PARAMETER 应用于方法的参数
	```
	@Retention 运行期有效(大,向下兼容)
	```java
	RetentionPolicy.SOURCE 在源文件中有效(即源文件保留)
	RetentionPolicy.CLASS 在class文件中有效(即class保留)
	RetentionPolicy.RUNTIME 在运行时有效(即运行时保留)
	```
	@Documented  该注解编译到API文档中
	@Inherited 允许子注解继承
	@Repeatable注解为可重复类型注解,可以在同一个地方多次使用,不常用

由谁加载:由Spring内部源码进行调用

Spring注解:

1)@Controller 用于表示Controller层的代码,相当于将对象交给Spring管理
2)@Service 用来标识Service层代码
3)@Repository 用来表示DAO持久层代码
4)@Component 万能注解,Controller、Service、Repository调用Component注解
5)@Configuration:用于标识Spring配置类
6)@ComponentScan(“com.jt”) :设定包扫描的路径
7)@PropertySource:加载指定的properties配置文件,将数据加载到Spring容器中,为解决中文乱码问题需要指定配置文件的格式encoding,例如:@PropertySource(value = “classpath:/user.properties”, encoding = “UTF-8”)
8)@Aspect: 标识SpringAOP切面类
9)@EnableAspectJAutoProxy:标识Spring配置类启动AOP注解,创建代理对象,默认启用JDK动态代理,目标对象没有实现接口时,则采用CGLib动态代理
10)@Order:控制切面类的执行顺序,值越小,越先执行
属性注解:
1)
@Autowired
:可以根据类型/属性名称进行注入,首先按照类型进行注入
2)@Qualifier:如果按照名称注入名称进行注入,则需要额外添加本注解
3)@Resource:( type=“XXX.class”, name=“属性名字” ),java原生注解,不建议使用
4)@Bean:可以将业务数据实例化,需要在配置类中实现
5)@Value:用于从Spring容器中获取对应属性值,需要使用${user.id}

类属性注解

1、@Autowired:可以根据类型/属性名称进行注入,首先按照类型进行注入, 如果类型注入失败则按照属性名字注入,代码中需要使用@Service注解增加被注解类id名字
2、@Qualifier:如果按照名称注入名称进行注入,则需要额外添加本注解
3、@Resource( type=“XXX.class”, name=“属性名字” )
注1:由于@Resource注解 是java原生提供,不是Spring官方提供,所以不建议使用
注2:以上注解在调用时自动封装了set方法,所以set方法可以省略不写。

    @Autowired
    @Qualifier
    //@Resource(type="UserDao.class",name="userDao")
    private UserDao userDao;  //基于Spring注入  面向接口编程

Spring MVC 编码-纯注解

配置类介绍

  随着软件技术发展,xml配置文件显得臃肿,不便于操作,所以spring后期提出了配置类的思想。将所有的配置文件中的内容,写到java类中。

Spring配置类代码:

import com.jt.pojo.User;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;

@Configuration  //标识我是一个配置类,相当于application.xml
@ComponentScan("com.jt")  //如果注解中只有value属性,可以省略
@PropertySource(value = "classpath:/user.properties", encoding = "UTF-8")  //作用:加载指定的properties配置文件,将数据保存到Spring容器中
public class SpringConfig {
    /**
     * 定义对象属性 准备接收数据
     * 在Spring容器中查找key=user.id的数据时通过${}标识
     */
    @Value("${user.id}")
    private Integer id;
    @Value("${user.username}")
    private String username;
    /**
     * Spring <bean id="方法名称" class=“返回值的类型 />
     * 执行@Bean的方法,将方法名称当做id,返回值的对象直接保存到Map集合中
     */
    @Bean
    public User user(){
        User user = new User();
        user.setId( id );
        user.setUsername( username);
        return user;
    }
}

Spring容器管理业务数据

Spring管理业务数据需要使用@Bean注解,通过该注解可以将业务数据实例化后,交给Spring容器管理,但是@Bean注解应该写到配置类中。

Spring 配置代码:

import com.jt.pojo.User;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;

@Configuration  //标识我是一个配置类,相当于application.xml
@ComponentScan("com.jt")  //如果注解中只有value属性,可以省略
@PropertySource(value = "classpath:/user.properties", encoding = "UTF-8")  //作用:加载指定的properties配置文件,将数据保存到Spring容器中
public class SpringConfig {
    /**
     * 定义对象属性 准备接收数据
     * 在Spring容器中查找key=user.id的数据时通过${}标识
     */
    @Value("${user.id}")
    private Integer id;
    @Value("${user.username}")
    private String username;
    /**
     * Spring <bean id="方法名称" class=“返回值的类型 />
     * 执行@Bean的方法,将方法名称当做id,返回值的对象直接保存到Map集合中
     */
    @Bean
    public User user(){
        User user = new User();
        user.setId( id );
        user.setUsername( username);
        return user;
    }
}

Spring动态获取外部数据

编辑properties文件

user.properties配置文件

# 规则:properties文件
#数据结构类型:k-v结构
#存储数据类型:只能保存String类型,数字按照字符串存储
#加载时编码格式:默认采用ISO-8859-1格式解析,中文必然乱码
user.id=1001
#Spring容器获取的当前计算机的名字,所以慎用user.name
#user.name=你好哈哈哈
user.username=鲁班7

Spring配置文件类java代码:

import com.jt.pojo.User;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;

@Configuration  //标识我是一个配置类,相当于application.xml
@ComponentScan("com.jt")  //如果注解中只有value属性,可以省略
@PropertySource(value = "classpath:/user.properties", encoding = "UTF-8")  //作用:加载指定的properties配置文件,将数据保存到Spring容器中
public class SpringConfig {
    /**
     * 定义对象属性 准备接收数据
     * 在Spring容器中查找key=user.id的数据时通过${}标识
     */
    @Value("${user.id}")
    private Integer id;
    @Value("${user.username}")
    private String username;
    /**
     * Spring <bean id="方法名称" class=“返回值的类型 />
     * 执行@Bean的方法,将方法名称当做id,返回值的对象直接保存到Map集合中
     */
    @Bean
    public User user(){
        User user = new User();
        user.setId( id );
        user.setUsername( username);
        return user;
    }
}

编辑properties文件乱码

1)加载配置文件时指定配置文件格式

@PropertySource(value = "classpath:/user.properties", encoding = "UTF-8")  //作用:加载指定的properties配置文件,将数据保存到Spring容器中
public class SpringConfig {
}

2)idea配置新建porperties文件编码
在这里插入图片描述

Spring 中场景问题

接口多实现类情况

原则上Spring中规定一个接口最好只有一个实现类,编码时遇到某个接口提供多个实现类

解决方案1-Service注解时增加被注解类id:

Controller层代码:

import com.jt.pojo.User;
import com.jt.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Controller;

@Controller
public class UserController {
    /**
     * @Autowired:
     *   首先根据属性的类型进行注入
     *   如果类型不能匹配,则根据属性的名称进行注入
     *   如果增加了@Qualifier则根据属性的名称注入
     */
    @Autowired
    private UserService userService;

    public void addUser(){
        User user=new User();  //手动创对象
        user.setId(101);
        user.setName("不知火舞|王昭君");
        this.userService.addUser(user);
    }
}

Service A实现类代码:

import com.jt.dao.UserDao;
import com.jt.pojo.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;

@Service("userService")  //使用Service注解时增加被注解类id名字
public class UserServiceImpl implements UserService{

    @Autowired
    //@Qualifier("userDao") //必须按照名称进行匹配,一般不写
    private UserDao userDao;  //基于Spring注入  面向接口编程

    @Override
    public void addUser(User user) {
        this.userDao.addUser(user);
    }
}

Service B实现类

import com.jt.dao.UserDao;
import com.jt.pojo.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service("userServiceB")
public class UserServiceImpB implements UserService{
    @Autowired
    private UserDao userDao;

    @Override
    public void addUser(User user) {
        System.out.println("实现类2完成业务调用");
        userDao.addUser(user);
    }
}

测试代码:

    @Test
    public void testAnno(){
        ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
        UserController userController=(UserController) context.getBean(UserController.class);
        userController.addUser();
    }
解决方案2-使用@Qualifier注解指定被调用的Service对象:

Controller层代码优化如下:

import com.jt.pojo.User;
import com.jt.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Controller;

@Controller
public class UserController {
    /**
     * @Autowired:
     *   首先根据属性的类型进行注入
     *   如果类型不能匹配,则根据属性的名称进行注入
     *   如果增加了@Qualifier则根据属性的名称注入,如果名称注入失败则报错退出
     */
    @Autowired
    @Qualifier("userServiceB")
    private UserService userService;

    public void addUser(){
        User user=new User();  //手动创对象
        user.setId(101);
        user.setName("不知火舞|王昭君");
        this.userService.addUser(user);
    }
}

Spring 事务控制

代码中进行事务控制分析

在这里插入图片描述

生活中代理模式分析

房屋中介代理模式:
1.房东: 自己手里有房子 需要出租换钱
2.中介机构 1.本职工作 带客户看房/出租房屋 2.收取中介费(服务费)
3.租客: 满足自身需求 租房子

代码思维建模:
1.暴露一个公共的接口(租房子)
2.客户与中介机构进行沟通,中介看起来和房东功能一致.
(代理看起来就是真实的对象)
3.完成用户额外的操作(收取中介费)

代理模式

1).组成部分
1.要求代理者实现与被代理者相同的接口
2.在代理方法中实现功能的扩展
3.用户调用代理对象完成功能(用户认为代理就是目标对象)
1)代理模式调用流程:
在这里插入图片描述

静态代理模式

静态代理模式弊端分析:
1).静态代理只针对于某个接口 不能实现所有接口的代理 实用性较差
在这里插入图片描述

2).静态代理中所有的方法,都需要手动的添加事务开始/事务提交代码 代码冗余 不够简洁.
在这里插入图片描述

实体类:

import com.jt.pojo.User;

public interface UserService {
    void addUser(User user);
}

Mapper接口:

import com.jt.pojo.User;

public interface UserMapper {
    public void addUser(User user);
}

Mapper类:

import com.jt.pojo.User;
import org.springframework.stereotype.Repository;

@Repository
public class UserMapperImpl implements UserMapper{

    @Override
    public void addUser(User user) {
        System.out.println("用户入库:"+ user);
    }
}

接口:

import com.jt.pojo.User;

public interface UserService {
    void addUser(User user);
}

目标对象:

import com.jt.mapper.UserMapper;
import com.jt.pojo.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service("target")
public class UserServiceImpl implements UserService{
    @Autowired
    private UserMapper userMapper;

    public void addUser(User user) {
        userMapper.addUser(user);
    }
}

代理类:代理者实现与被代理者相同的接口,并在代理方法中扩展被代理类方法功能

@Service("userService")
public class StaticProxy implements UserService {
    //要引入目标对象
    @Autowired   //首先 byType,其次 byName
    //@Qualifier("target")
    private UserService target;
    @Override
    public void addUser(User user) {
        try {
            System.out.println("事务开始");
            target.addUser(user);
            System.out.println("事务结束");
        } catch ( Exception e){
            e.printStackTrace();
            System.out.println("事务回滚");
        }
    }
}

测试代码:用户调用代理对象完成功能(用户认为代理就是目标对象)

    @Test
    public void testStatic() {
        ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
        UserService userService = (UserService) context.getBean("userService");
        User user = new User();
        user.setId(100001);
        user.setName("测试代理机制");
        userService.addUser(user);
    }

动态代理模式

动态代理的优势:
将公共的部分写到动态代理中,之后其他的业务类调用即可

动态代理分类:
1、JDK动态代理
要求: 要求目标对象必须实现接口
代理要求: 代理对象也必须实现目标对象的接口
目标对象/代理关系: 目标对象与代理对象兄弟关系.
2、CGlib代理
要求: 不管目标对象是否有接口,都可以为其创建代理对象
代理要求: 要求代理对象必须继承目标对象
目标对象/代理关系: 目标对象与代理对象是父子关系
在这里插入图片描述

动态代理执行过程

在这里插入图片描述

JDK动态代理

创建JDK动态代理工厂类:
1)创建JDK动态代理工厂类
2)定义静态方法用于获取代理对象
3)通过动态代理创建对象(Proxy.newProxyInstance)
4)调用动态代理对象方法,实现被代理对象功能扩展
在这里插入图片描述
JDK代理工厂类代码:

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

public class JDKProxyFactory {
    /**
     * 匿名内部类用法说明:匿名内部类引用外部参数,要求参数必须是final
     */
    public static Object getProxy(final Object target) {
        /**
         * Java 实现动态代理:
         * Proxy.newProxyInstance()
         *   参数分析:3个参数
         *   1、ClassLoader loader,类加载器(获取目标对象的Class)
         *   2、Class<?>[] interfaces:JDK代理要求,必须要有接口
         *   3、InvocationHandler h:对目标方法进行扩展
         */
        //1.获取目标对象的类加载器
        ClassLoader classLoader=target.getClass().getClassLoader();
        //2.获取目标对象的接口数组
        Class[] interfaces=target.getClass().getInterfaces();
        //3.通过动态代理创建对象
        Object proxy=Proxy.newProxyInstance(classLoader, interfaces, new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            	//被代理对象方法扩展,打印方法执行时间
                Long starttime = System.currentTimeMillis();
                Thread.sleep(2000);
                Object result=method.invoke(target, args);
                Long endttime = System.currentTimeMillis();
                System.out.println("方法:"+method.getName()+"执行:"+(endttime-starttime)+"毫秒");
                return result;
            }
        });
        return proxy;
    }
}

测试代码:

    @Test
    public void test01(){
        ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
        //1.获取目标对象
        UserService target=(UserService) context.getBean("target");
        //2.获取代理对象
        UserService proxy = (UserService) JDKProxyFactory.getProxy(target);
        System.out.println(proxy.getClass());
        //3.调用业务方法
        proxy.addUser();
    }
CGLIB动态代理

对于动态代理,如果没有定义代理类接口,而只是定义了代理类,那么此时JDK动态代理就无法使用了,因为JDK动态代理要求必须定义接口,它是对接口进行代理的,现在没有定义接口,那么自然就不能使用JDK动态代理了。这时,我们就可以使用CGLIB动态代理来实现。CGLIB是一个功能强大,高性能的代码生成包(CGLIB就是用来生成代码的,它也是在内存中动态的去生成的)
CGLIB是第三方提供的包,需要在pom文件中添加依赖:

        <dependency>
            <groupId>cglib</groupId>
            <artifactId>cglib</artifactId>
            <version>3.3.0</version>
        </dependency>

实现代理工厂类:

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

import java.lang.reflect.Method;

public class CGlibProxyFactory<T>  implements MethodInterceptor {
    private T obj;
    public CGlibProxyFactory(T obj) {
        this.obj = obj;
    }

    /**
     * 获取被代理对象子类,即代理类
     */
    public T getProxy(){
        // 1. 创建Enhancer类对象,它类似于咱们JDK动态代理中的Proxy类,该类就是用来获取代理对象的
        Enhancer enhancer = new Enhancer();
        // 2. 设置父类的字节码对象。为啥子要这样做呢?因为使用CGLIB生成的代理类是属于目标类的子类的,也就是说代理类是要继承自目标类的
        enhancer.setSuperclass(obj.getClass());
        // 3. 设置回调函数
        enhancer.setCallback(this);
        // 4. 创建代理对象
        return (T) enhancer.create();
    }

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        Object result=null;
        if (method.getName().equals("deleteUser")){
            System.out.println("删除用户事务开始");
            result = method.invoke(obj, objects);
            System.out.println("删除用户事务结束");
        }
        return result;
    }
}

测试代码:

    @Test
    public void testCGLib(){
        ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
        //1.获取目标对象
        UserService target=(UserService) context.getBean("target");
        //2.根据实现类获取代理对象子类
        UserServiceImpl proxy = new CGlibProxyFactory<UserServiceImpl>((UserServiceImpl) target).getProxy();
        System.out.println(proxy.getClass());
        //3.调用业务方法
        proxy.deleteUser();
    }

Spring AOP

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

总结:AOP主要目的是利用动态代理的模式,降低程序的耦合度,扩展业务功能方法。

关于AOP名词介绍

1).连接点: 类代码中可以被扩展的方法
2).切入点:满足切入点表达式时调用被扩展的方法
3).通知: 扩展方法的具体实现
4).切面: 将通知应用到切入点的过程

通知类型(必会)

1)before: 在目标方法执行之前执行
2)afterReturning: 在目标方法执行之后返回时执行
3)afterThrowing: 在目标方法执行之后,抛出异常时执行
4)after: 无论程序是否执行成功,都要最后执行的通知
5)around: 在目标方法执行前后 都要执行的通知(完美体现了动态代理模式)
功能最为强大 只有环绕通知可以控制目标方法的执行

关于通知方法总结:
1.环绕通知是处理业务的首选. 可以修改程序的执行轨迹
2.另外的四大通知一般用来做程序的监控.(监控系统) 只做记录

通知执行顺序

1.执行around开始
2.执行before
3.执行目标方法
4.执行afterReturning
5.执行afterThrowing
6.执行after
7,执行around通知结束

切入点表达式

概念:当程序满足切入点表达式,才能进入切面,执行通知方法.

1.bean(“bean的ID”) 根据beanId进行拦截 只能匹配一个
2.within(“包名.类名”) 可以使用通配符*? 能匹配多个.
粒度: 上述的切入点表达式 粒度是类级别的. 粗粒度.
3.execution(返回值类型 包名.类名.方法名(参数列表…))
粒度: 控制的是方法参数级别. 所以粒度较细. 最常用的.
4.@annotation(包名.注解名) 只拦截注解.
粒度: 注解是一种标记 根据规则标识某个方法/属性/类 细粒度

切入点表达式详解

.:通配当前目录下的一级目录所有内容
:代表多级目录下的所有内容
:代表多级目录下的所有类

     1within( com.jt.*.DeptServiceImpl )   一级包下的类
     2within( com.jt..*.DeptServiceImpl )   ..代表多级包下的类
     3within( com.jt..* )   匹配包下的所有类

定义公共的切入点表达式:

    /**
     * 定义公共切入点表达式
     */
    @Pointcut("@annotation(com.jt.anno.Cache)")
    public void pointcut(){

    }

    //通过调用方法调用公共切入点表达式
    @AfterReturning("pointcut()")
    public void afterReturning_pub(){
        System.out.println("我是afterReturning_pub通知");
    }

Aop切面=切入点表达式 + 通知方法

AOP依赖

pom.xml 依赖:spring-aop、spring-aspects、org.aspectj

    <dependencies>

        <!--Spring核心包-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>5.3.6</version>
        </dependency>

        <!--引入SpringBean-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-beans</artifactId>
            <version>5.3.6</version>
        </dependency>

        <!--引入context包-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.3.6</version>
        </dependency>

        <!--引入表达式jar包-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-expression</artifactId>
            <version>5.3.6</version>
        </dependency>

        <!--引入日志依赖-->
        <dependency>
            <groupId>commons-logging</groupId>
            <artifactId>commons-logging</artifactId>
            <version>1.2</version>
        </dependency>

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

        <!--引入AOP包-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aop</artifactId>
            <version>5.3.6</version>
        </dependency>
        <!--引入切面包-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aspects</artifactId>
            <version>5.3.6</version>
        </dependency>
        <!--引入切面包的依赖包-->
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjrt</artifactId>
            <version>1.9.6</version>
        </dependency>
        <!--当下所有的jar包都需要手工添加依赖,并且需要确定依赖的关系,对于初学则不友好-->
    </dependencies>

AOP 切面案例1

Spring配置类,启动AOP代理类创建对象功能:

@Configuration
@ComponentScan("com.jt")
/**
 * 启动AOP注解 Spring容器自动创建代理对象
 * proxyTargetClass = true: 强制使用CGlib
 * proxyTargetClass = false  默认启动JDK动态代理,目标对象没有接口时则采用CGLib
 * 注:JDK代理创建速度快,运行时稍慢,CGLib创建时速度慢,运行时快
 */
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class SpringConfig {
}

注解:用于测试注解表达式:

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

//控制注解的生命周期
@Retention(RetentionPolicy.RUNTIME)
//注解的作用对象, 方法有效,类有效 ,属性有效
@Target({ElementType.METHOD,ElementType.TYPE,ElementType.FIELD})
public @interface Cache {
}

创建AOP切面类:
切面 = 切入点表达式 + 通知方法
Before1.java 用于测试Order执行顺序:

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

@Component
@Aspect
@Order(2)
public class Before1 {
    @Before("@annotation(com.jt.anno.Cache)")
    public void before(){
        System.out.println("我是切面1执行");
    }
}

Before2.java 用于测试Order执行顺序:

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

@Component
@Aspect
@Order(1)   //控制程序的执行顺序,通过数字进行控制,值越小越小执行。
public class Before2 {
    @Before("@annotation(com.jt.anno.Cache)")
    public void before(){
        System.out.println("我是切面2执行");
    }
}

用于测试AOP的5大通知方法及切面表达式:

package com.jt.aop;

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

import java.util.Arrays;

/**
 * 1、Aop需要交给Spring容器管理
 * 2、标识该类为AOP切面
 */
@Component  //将类交给Spring容器管理
@Aspect   //标记为AOP切面类,Spring容器默认不能识别切面注解,需要手动配置
public class SpringAop {
    //1.定义before通知方法,bean拦截指定Spring id属性对象
    @Before("bean(deptServiceImpl)")
    public void before_bean(){
        System.out.println("我是before_bean的通知");
    }

    /**
     * 1、within( com.jt.*.DeptServiceImpl )   一级包下的类
     * 2、within( com.jt..*.DeptServiceImpl )   ..代表多级包下的类
     * 3、within( com.jt..* )   匹配包下的所有类
     */
    @Before("within(com.jt.*.DeptServiceImpl)")
    public void before_within(){
        System.out.println("我是before_within的通知");
    }

    /**
     * execution( 返回类型 包名.类名.方面名(参数列表)
     * 1、execution(* com.jt..*.DeptServiceImpl.add*())
     *   注释:返回类型为任意的,com.jt下所有包中的DeptServiceImpl的类add开头的方法,且没有参数
     * 2、execution(* com.jt..*.*(..))
     * 注释:返回值类型任意,com.jt包下的所有包的所有类的所有方法,且参数任意
     * 3、execution(int com.jt..*.*(int))
     *    execution(Integer com.jt..*.*(Integer))
     * 强调:在Spring表达式中没有自动拆装箱功能!!!
     */
    @Before("execution(* com.jt..*(..))")
    public void before_execution(){
        System.out.println("我是before_execution的通知");
    }

    /**
     * @annotation:根据注解扫描切入点
     */
    @Before("@annotation(com.jt.anno.Cache)")
    public void before_anno(){
        System.out.println("我是before_anno的通知");
    }

    /**
     * 定义公共切入点表达式
     */
    @Pointcut("@annotation(com.jt.anno.Cache)")
    public void pointcut(){

    }

    @Before("pointcut()")
    public void before_func(JoinPoint joinPoint){
        System.out.println("获取目标对象的类型:"+joinPoint.getTarget().getClass());
        System.out.println("获取目标对象类名:"+joinPoint.getSignature().getDeclaringTypeName());
        System.out.println("获取目标对象方法名:"+joinPoint.getSignature().getName());
        System.out.println("获取目标对象方法的参数:"+ Arrays.toString(joinPoint.getArgs()));
        System.out.println("我是before_func的通知");
    }
    //通过调用方法调用公共切入点表达式

    /**
     * 记录方法的返回值:
     * pointcut:切入点表达式
     * returning:将方法的返回值,通过result进行传递
     * 注意事项:如果参数中需要添加JoinPoint 对象时参数必须位于第一位,
     * Spring在进行参数赋值时,采用index[0]下标方式赋值
     * 报错:error at ::0 formal unbound in pointcut
     */
    @AfterReturning(pointcut = "pointcut()",returning = "result")
    public void afterReturning_pub(JoinPoint joinPoint, Object result ){
        System.out.println("我是afterReturning_pub通知");
        System.out.println("方法返回结果:"+result);
    }
    //通过调用方法调用公共切入点表达式
    @AfterThrowing(pointcut = "pointcut()", throwing = "e")
    public void afterThrowing_pub(Exception e){
        System.out.println("获取异常信息:"+e.getMessage());
        System.out.println("获取异常的类型:"+e.getClass());
        System.out.println("我是afterThrowing_pub通知");
    }
    //通过调用方法调用公共切入点表达式
    @After("pointcut()")
    public void after_pub(){
        System.out.println("我是after_pub通知");
    }

    /**
     * 环绕通知
     * 作用:可以控制目标方式是否执行
     * 参数:ProceedingJoinPoint 通过proceed方法控制目标方法执行。
     * 注意事项:ProceedingJoinPoint 只能支持@Around通知方法
     */
    @Around("pointcut()")
    public Object around(ProceedingJoinPoint joinPoint){
        Object result =null;
        System.out.println("环绕通知开始");
        //1.执行下一个通知  2.执行目标方法
        try {
            Long startTime=System.currentTimeMillis();
            System.out.println("开始时间:"+startTime);
            result=joinPoint.proceed();
            Long endTime=System.currentTimeMillis();
            System.out.println("结束时间:"+endTime);
            System.out.println("耗时:"+(endTime-startTime));
        } catch (Throwable e) {
            e.printStackTrace();
        }
        System.out.println("环绕通知结束");
        return null;
    }

}

Service接口类:

public interface DeptService {
    public void addDept();
    public void updateDept();
    //AOP中测试方法
    public String after( Integer id);
    public void afterThrow();
    void doAround();
    void doOrder();
}

Service实现类:

import com.jt.anno.Cache;
import org.springframework.stereotype.Service;

@Service
public class DeptServiceImpl implements DeptService{
    @Override
    public void addDept() {
        System.out.println("添加部门信息");
    }

    @Override
    @Cache   //Spring Aop 被注解标识
    public void updateDept() {
        System.out.println("更新部门");
    }

    @Override
    @Cache
    public String after(Integer id) {
        return "Spring通知测试";
    }

    @Override
    @Cache
    public void afterThrow() {
        System.out.println("用户执行目标方法时抛出异常");
        int a = 1/0;
    }

    @Override
    @Cache
    public void doAround() {
        System.out.println("实现用户数据的入库操作");
    }

    @Override
    @Cache
    public void doOrder() {
        System.out.println("测试程序执行的顺序");
    }

}

测试AOP切面:

import com.jt.config.SpringConfig;
import com.jt.service.DeptService;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class TestSpring {
    @Test
    public void testAop(){
        ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
        DeptService deptService=context.getBean(DeptService.class);
        System.out.println(deptService.getClass());
        deptService.addDept();
        System.out.println("------------------------------------");
        deptService.updateDept();
    }
    @Test
    public void testAopRet(){
        ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
        DeptService deptService=context.getBean(DeptService.class);
        System.out.println(deptService.getClass());
        String result=deptService.after(12);
        System.out.println("接收到返回值:"+result);
    }
    @Test
    public void testAopThrow(){
        ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
        DeptService deptService=context.getBean(DeptService.class);
        System.out.println(deptService.getClass());
        deptService.afterThrow();
    }
    @Test
    public void testAopDoAround(){
        ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
        DeptService deptService=context.getBean(DeptService.class);
        System.out.println(deptService.getClass());
        deptService.doAround();
    }

    @Test
    public void testAopOrder(){
        ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
        DeptService deptService=context.getBean(DeptService.class);
        System.out.println(deptService.getClass());
        deptService.doOrder();
    }
}

常见报错1-Spring 通知函数若使用JoinPoint 对象时必须在参数第一位

报错信息:

警告: Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'springConfig': Initialization of bean failed; nested exception is java.lang.IllegalArgumentException: error at ::0 formal unbound in pointcut 

org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'springConfig': Initialization of bean failed; nested exception is java.lang.IllegalArgumentException: error at ::0 formal unbound in pointcut 

解决方案:
如果参数中需要添加JoinPoint 对象时参数必须位于第一位,Spring在进行参数赋值时,采用index[0]下标方式赋值
查找通知方法中JoinPoint 形参是否放在第一个位置,并调整JoinPoint 对象位置

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值