Spring---IOC、AOP学习

Spring学习

IOC---控制反转(反转控制)

IOC是Java把创建和调用对象的工作交给Spring容器来进行处理,目的是为了降低耦合度

底层

底层有三部分xml文件解析、工厂模式、反射( 获取Java的字节码文件【即Java编译后的 .class文件】,得到其中的方法和变量进行调用 )

 

工厂模式---就是建立一个类做工厂,然后将想要创建的类的对象都在里面实现使用只要调用这个工厂类的方法就行了,但是耦合度还是很高

 

IOC过程

1、创建xml配置文件,在配置文件时将想要创建、调用的类都装进去

2、创建一个工厂类,在里面解析xml文件,通过反射创建对象,得到文件的编码文件

String classValue = 解析配置文件
Class cls = Class.forName(classValue)

 

3、创建类的实例对象并返回该值

return (类型转换)cls.newInstance();

 

IOC思想和功能实现接口

IOC思想基于IOC容器,IOC底层实际就是对象工厂

IOC提供了两个接口用来实现IOC容器

1)BeanFactory,是Spring自带的接口,他只会在类需要被用到时才会创建对象,不然就不会创建对象,不推荐在开发时使用

2)ApplicationContext,BeanFactory的子接口是一个功能更加强大的接口,也能够解析获取xml文件中的类,并创建对象不管需不需要调用。平时开发时使用,因为Spring经常会跟Web项目一起进行工作,需要在服务器启动时就完成各项消耗资源的工作

3)ApplicationFactory接口有两个实现类ClassPathXmlApplicationContext和FileSystemXmlApplicationContext

ClassPathXmlApplicationContext实现类解析xml文件是在src目录下需要使用时

FileSystemXmlApplicationContext实现类使用时需要写上xml文件在磁盘上的准确目录:D:\hi\hello...

Bean管理操作有两种方式

1)基于xml文件管理

2)基于注解管理

 

IOC管理xml方式创建对象和注入属性

<1>配置对象创建
    <bean id="add" class="xlw.com.test.Add"></bean>

 


 

通过bean标签添加对应的属性值是实现对象的创建,bean中有两个属性值需要注意id 和 name

id是具有唯一性,用于区别创建的对象,里面的值不能添加符号别的。

name可以在里面添加特殊符号,这个属性本来是提供给status1使用的,但是因为技术原因不再使用这个技术

class属性是用来指明想要创建对象的类,类全路径

在创建对象时通过调用无参构造方法创建类的对象,当创建了有参构造方法后,也要创建无参构造方法

A <2>注入属性 DI(依赖注入、属性注入) set方法

创建类和方法、变量

public class ToBean {
​
    public String name;
    public String author;
​
    public void setName(String name) {
        this.name = name;
    }
​
    public void setAuthor(String author) {
        this.author = author;
    }
​
    public void store(){
        System.out.println(name+"--"+author);
    }
}

 


在xml文件 中使用bean标签实现对象创建

其中注入属性就是字在bean标签对中通过property标签进行注入属性

在property边里有name 、value属性对应起来进行注入属性

 <bean id="toBean" class="xlw.com.test.ToBean">
        <property name="name" value="九阴白骨爪"></property>
        <property name="author" value="殷素素"></property>
    </bean>

 

<3>解析xml文件
    @Test
    public void toBean(){
        //解析xml文件
        ApplicationContext application = new ClassPathXmlApplicationContext("hi.xml");
        
       //获取对象
        ToBean toBean = application.getBean("toBean", ToBean.class);
        toBean.store();
    }

 

<4>获取想要调用的类对象

<5>使用对象

P 名称空间(实际使用名称空间实现) 注入

1、首先对xml文件beans标签中的内容进行修改,添加 xmls:p 实现添加名称空间

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

 


2、使用

<bean id="toBean" class="xlw.com.test.ToBean" p:name="降龙十八掌" p:author="洪七公">    </bean>

 

在bean标签中使用 p名称空间,输入后就能发现可以对class指向的类中的变量进行赋值(属性注入),这个方法适用于set方法注入属性(简化代码)

B 注入属性 DI 有参构造方法
 <bean id="toBean" class="xlw.com.test.ToBean">
        <constructor-arg name="name" value="冰魄银针"></constructor-arg>
        <constructor-arg name="author" value="你好"></constructor-arg>
    </bean>

 

在通过有参构造方法进行实现属性注入时,使用constructor-arg标签实现,跟property差不多

其中也有index、value通过索引参数实现属性注入

调用一样

    @Test
    public void toBean(){
        ApplicationContext application = new ClassPathXmlApplicationContext("hi.xml");
        ToBean toBean = application.getBean("toBean", ToBean.class);
        toBean.store();
    }

 

IOC操作Bean,xml注入其他类型属性

字面量:就是在类中定义的变量被直接赋值,或者在xml文件中对通过属性注入(name=value)方式赋值,设置使用固定值就是字面量。

设置null值
<bean id="toBean" class="xlw.com.test.ToBean">
        <property name="name" value="九阴白骨爪"></property>
        <property name="author">
            <null/>
        </property>
    </bean>

 

设置特殊符号使用转义符或CDATA

<value>标签中的所有内容都是值

<bean id="toBean" class="xlw.com.test.ToBean">
    <property name="name" value="九阴白骨爪"></property>
    <property name="author">
        <value>
        <![CDATA[ 特殊符号 ]]>
        </value>
    </property>
</bean>

 

IOC管理---外部Bean

外部Bean注入就是在实际开发中,会分很多层,上层调用下层方法时要通过实例化对象实现,而现在来说就是通过外部Bean注入。

在AddUser中
public class AddUser {
//    将类对象做为类内部属性,通过set方法实现属性注入
    private UserDao userDao;

    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }
    public void addUser(){
        userDao.addUser();
    }
}

 

在xml文件里

首先创建上层类(AddUser)对象,由于使用了set方法进行属性注入,所以采用property标签以name=ref的方式进行注入,因为类中的属性是一个类对象,所以不能用value,要用ref

其次要注意的是创建UerDao的对象,由于并没有在service层中的类AddUser中实例化它,在使用中要使用到他的方法,创建它的对象时,使用bean标签,class要指向它的实现类,id值与 注入的属性值(ref)一致

//   创建属性的对象时,属性的id值与注入属性的值(ref)一致
    <bean  id="userDaoImpl" class="xlw.com.dao.Impl.UserImpl"></bean>

    <bean id="addUser" class="xlw.com.service.AddUser">
        <property name="userDao" ref="userDaoImpl"></property>
    </bean>

 

这就叫外部Bean注入

IOC管理---内部Bean和级联赋值

内部bean的属性注入

bean类中

里面有另一个类( Student )作为内部属性

public class Student {
    private String stuName;
    private String hobby;
    private School school;

    public void setSchool(School school) {
        this.school = school;
    }

    public void setStuName(String stuName) {
        this.stuName = stuName;
    }

    public void setHobby(String hobby) {
        this.hobby = hobby;
    }
    }

 

 

xml文件中

在xml文件中对bean类进行属性注入,由于student中有一个类对象做属性,所以就可以在属性注入时采取 内部bean注入,即在property标签中嵌套<bean>标签,再在bean标签中嵌套<property>标签实现属性注入。这就叫 内部bean注入

 <bean id="student" class="xlw.com.bean.Student">
        <property name="stuName" value="马牛逼"></property>
        <property name="hobby" value="乱入尴尬"></property>
        <property name="school">
            <bean id="school111dasda" class="xlw.com.bean.School">
                <property name="name" value="牛逼学校"></property>
            </bean>
        </property>
    </bean>

 

第二种 外部bean注入

外部注入就是在进行注入属性时,使用property标签以name=ref进行注入,将对象类中的实体对象属性在外面重新用bean标签创建对象并用property注入属性,再用ref获取创建对象的id实现属性注入

这里注意 ref的值是要等与外部bean创建对象的id值,不是id值等于ref值

<bean id="student" class="xlw.com.bean.Student">
    <property name="stuName" value="摇摆阳"></property>
    <property name="hobby" value="摇摆"></property>
    <property name="school" ref="schoolijiojll"></property>
</bean>
<bean id="schoolijiojll" class="xlw.com.bean.School">
    <property name="name" value="gun!"></property>
</bean>

 

第三种 级联赋值

级联赋值是在外面创建bean对象,在内部通过property标签以name=value的方式进行属性注入,用 .运算符取到内部实体类属性的属性并赋值

这种 外部bean属性注入要注意,该对象的类中要有这个实体类属性的get方法,在进行属性注入 之前要先以name=ref给内部实体类属性进行属性注入。并且,我发现对同一个属性进行注入,这种方式的优先级大于外部bean标签内注入属性

    <bean id="student" class="xlw.com.bean.Student">
        <property name="stuName" value="渣渣辉"></property>
        <property name="hobby" value="认兄弟来砍我"></property>
        <property name="school" ref="school"></property>
        <property name="school.name" value="贪玩蓝月"></property>
    </bean>
    <bean id="school" class="xlw.com.bean.School">
        <property name="name" value="dhaskjdhkasj" ></property>
     </bean>

 

IOC管理---对集合类型的属性进行属性注入

在bean类中
public class Collection {
    private String[] arrayList;
    private List list;
    private Map<String,String> map;
    private Set<String> set;

    public void setArrayList(String[] arrayList) {
        this.arrayList = arrayList;
    }

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

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

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

    @Override
    public String toString() {
        return "Collection{" +
                "arrayList=" + Arrays.toString(arrayList) +
                ", list=" + list +
                ", map=" + map +
                ", set=" + set +
                '}';
    }
}

 

在xml文件中

在对集合类属性进行属性注入时,在property标签中使用对应类型的标签对,在标签对内部通过value标签对list进行属性注入多个值。当然还是一个属性一个property标签

注意:map属性进行属性注入时使用entry标签,并在标签里面通过key=value的方式注入,通过多个entry标签实现注入多个值,这个vlue值一样就输出一个

<bean id="collection" class="xlw.com.bean.Collection">
    <property name="arrayList">
        <array>
            <value>心态</value>
            <value>身体</value>
            <value>学习</value>
        </array>
    </property>
    <property name="list">
        <list>
            <value>快乐</value>
            <value>健康</value>
            <value>循序渐进</value>
        </list>
    </property>
    <property name="map">
        <map>
            <entry key="1" value="A"></entry>
            <entry key="2" value="A"></entry>
            <entry key="3" value="A"></entry>
        </map>
    </property>
    <property name="set">
        <set>
            <value>good</value>
            <value>god</value>
            <value>gd</value>
        </set>
    </property>
</bean>

 

当集合类型中的参数是对象类型时
<property name="schools">
      <list>
          <ref bean="school"></ref>
          <ref bean="school1"></ref>
          <ref bean="school2"></ref>
      </list>
    </property>

<bean id="school1" class="xlw.com.bean.School">
    <property name="name" value="嘿嘿"></property>
</bean>
<bean id="school2" class="xlw.com.bean.School">
    <property name="name" value="哈哈"></property>
</bean>

 

 

在注入属性时使用property标签以name->list->ref进行注入,这样就能注入多个对象,ref标签里面通过bean指定要注入的类对象

提取list属性注入公共代码

在bean中

public class Course {
    private List<String> list;

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

    @Override
    public String toString() {
        return "Course{" +
                "list=" + list +
                '}';
    }
}

 

在xml文件中

做公共代码块时需要先添加名称空间util,对beans标签里面的内容做修改如下

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:p="http://www.springframework.org/schema/p"
       xmlns:util="http://www.springframework.org/schema/util"
       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">

 

 

然后通过调用命名空间标签uil:list做出公共list注入代码,设置一个id,在有对象需要进行list类型属性注入时就可以通过ref属性引用公共代码块

可以发现ref负责引入对象类属性的,list属性注入 公共代码块也是一个对象,有自己的id

<util:list id="bean">
    <value>1</value>
    <value>2</value>
    <value>3</value>
    <value>4</value>
</util:list>

<bean id="course" class="xlw.com.bean.Course">
    <property name="list" ref="bean"></property>
</bean>

 

这样的提取集合类型的属性注入公共代码块可以同样应用于map和set集合

IOC管理---FactoryBean

普通bean--在获取它的对象时返回的跟定义时的对象一样

FactoryBean--在获取对象时返回的值可以跟定义的时的值不一样。用过实现FactoryBean这个接口实现

在xml文件中

定义的bean是testbean

<bean id="testBean" class="xlw.com.bean.TestBean"></bean>

 

 

在TestBean中

通过FactoryBean后的参数指定返回的bean类型,通过getObject方法实现对返回bean对象的操作

public class TestBean implements FactoryBean<School> {
    @Override
    public School getObject() throws Exception {
        School school = new School();
        school.setName("哈哈大笑");
        return school;
    }

    @Override
    public Class<?> getObjectType() {
        return null;
    }
}

 

IOC管理---单实例和多实例(Bean的作用域)

单实例---默认情况下创建bean对象是单实例,在调用这个bean对象时不管调用几个都会得到一样的bean对象

多实例---使用bean标签中的属性scope,设置值为prototype,在调用这个bean对象时会得到不同的bean对象

在xml文件中
<bean id="course" class="xlw.com.bean.Course" scope="prototype"></bean>

 

在使用多实例时,不在解析xml文件时创建对象,而是在使用geBean方法时创建对象,实现创建多个不同对象

此外scope属性还有request和session两个属性,request是在一次请求中创建的对象,会把bean对象放进请求中

session是会将创建的bean对象放进一次会话中

IOC管理---bean的生命周期

bean会经历bean对象的创建( 通过set方法注入属性 )、bean的初始化方法之前、bean的初始化方法、bean的初始化方法后、bean对象的获取、bean对象的销毁( 销毁方法要将解析xml文件的对象转换为ClassPathXmlApplicationContext类型调用close方法实现销毁 )

在xml文件中

文件中配置了一个类prosess类用来做初始化前后的方法的实现

在想要使用初始化和销毁后的功能时,要自己声明bean中的哪一个类是初始化方法,哪一个类是销毁方法。用来实现对应功能

当在一个xml文件中添加了后置前置处理器会给当前文件中的所有bean都添加后置前置处理器

<bean id="pro" class="xlw.com.bean.Prosess"></bean>
<bean id="link" class="xlw.com.bean.Link" init-method="init_method" destroy-method="destroyMethod"></bean>

 

 

在prosess文件中

实现BeanPostProcess接口,调用了里面的初始化前置方法和初始化后置方法

public class Prosess implements BeanPostProcessor {
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("初始化之前");
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("初始化之后");
        return bean;
    }
}

 

 

IOC管理---自动装配

自动装配就是不用手动注入,通过autowire属性的两个值( byName、byType ),实现自动匹配注入

在xml文件中
<!--    通过autowire属性设置byName和byType 指定自动装配的类型
使用byName时创建的实力对象的id要跟类里面的变量名一样
使用byType时只能创建一个bean对象
-->
<bean id="da" class="xlw.com.bean.Woman" autowire="byName"></bean>
<bean id="home" class="xlw.com.bean.Home"></bean>

 

 

IOC管理---引入配置文件

在xml文件中

使用时要导入包,记住DruidDataSource类的路径在pool包下

<!--    
外部依赖文件引入时,首先添加命名空间context,然后使用标签<context:property-override>以属性location调用外部依赖文件
在对DruidDataSource进行属性注入时,value的值要用Spring表达式引用外部属性文件的key来获取值
-->
    
    <context:property-override location="classpath:jdbc.properties"></context:property-override>

    <bean id="jdbc" class="com.alibaba.druid.pool.DruidDataSource" >
        <property name="driverClassName" value="${druid.DriverClassname }"></property>
        <property name="url" value="${druid.url }"></property>
        <property name="username" value="${druid.username }"></property>
        <property name="password" value="${druid.password }"></property>
    </bean>


 

 

在properties文件中

把该加的都加上,在key命名时最好加个前缀,便于区分和避免出错

druid.DriverClassname=com.mysql.cj.jdbc.Driver
druid.url=jdbc:mysql://localhost:3308/book?characterEncoding=utf8&useSSL=true&serverTimezone=UTC
druid.username=root
druid.password=123456

 

 

IOC管理---基于注解方式实现创建对象和属性注入

首先引入依赖【spring-aop-5.2.9.RELEASE.jar】

注解就是java代码中有特殊意义的符号,目的是为了简化xml配置文件的配置

创建对象的注解:【@Component】、【@Service】、【@Cotroller】、【@Repository】。功能一样,用哪个都行在那用都可以,但是建议在service层中用@Service,在Web层用@Cotroller,在持久层用@Repository

注解可以在方法上面、类上面、变量上面引用

在xml文件中

添加组件扫描

在使用前先对<beans>标签进行修改,添加命名空间context,然后使用context:component-scan标签中的base-package属性设置要扫描的使用了Spring注解的包

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:p="http://www.springframework.org/schema/p"
       xmlns:util="http://www.springframework.org/schema/util"
       xmlns:context="http://www.springframework.org/schema/context"
       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
                           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

<!--
在属性base-package中可以指定某一个包,在需要指定多个包时,可以用逗号隔开包跟包,也可以直接让Spring扫描全部包
-->
    <context:component-scan base-package="xlw.com"></context:component-scan>

 

 

组件扫描的细节

将属性use-default-filters的值定义为false就告诉Spring不用自带的filter,自己设置过滤内容

context:include-filter的意思是包含这些filter条件的就执行,扫描包含条件的包、类

context:exclude-filter的意思是不包含这些filter条件的就执行,扫描不包含条件的包和类

<!--    
在前使用时不指定扫描那些包,就会自动扫描全部,通过将use-default-filters设置为false自己定义filter
在<context:component-scan>标签里使用 <context:include-filter>标签可以通过expression属性指定扫描那些有特定注解的包
在<context:exclude-filter>中使用expression属性指定不扫描那些包含特定注解的包
-->
    <context:component-scan base-package="xlw.com" use-default-filters="false">
        <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
    </context:component-scan>
    <context:component-scan base-package="xlw.com" use-default-filters="false">
        <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
    </context:component-scan>

 

 

IOC管理---注解实现属性注入

使用注解进行属性注入时,不用set方法,Spring内部封装了

实现属性注入的注解有:【@AutoWired】、【@Qualifier】、【@Value】、【@Resource】

 

在service代码中

首先要对定义的属性进行注解创建bean对象【@Repository】

然后在对类进行注解创建bean对象【@Service】、属性注入【@AutoWired】, 他遵守的规则是根据属性类型进行注入,当bean属性只有一个bean对象时

当bean属性有多个bean对象时,需要使用注解【@Qualifier】他根据bean对象的名称进行注入,要先有注解【@AutoWired】,@Qualifier注解作用就像在xml文件中外部bean注入时能选择注入bean对象,ref = bean对象的id

@Service
public class UserService {

//    在属性上方使用@AutoWired注解完成属性注入,这个注解只能对bean类型的属性进行注入
//    @Qualifier注解是跟@AutoWired属性配合起来使用的,它用来声明注入的属性是哪一个类的bean对象
    @Autowired
    @Qualifier(value = "userImpl")
    private UserImpl user;
    public void faa(){
        user.addUser();
        System.out.println(user);
    }
}

 

 

@Resource注解既能进行类型注入【bean属性只有一个bean对象】,也能进行名称匹配注入

但是这个注解【@Resource】是Java扩展包Javax的,实际开发中不建议使用

import javax.annotation.Resource;

@Service
public class UserService {

//    在属性上方使用@AutoWired注解完成属性注入,这个注解只能对bean类型的属性进行注入
//    @Qualifier注解是跟@AutoWired属性配合起来使用的,它用来声明注入的属性是哪一个类的bean对象
//    @Autowired
//    @Qualifier(value = "userImplT")
    @Resource(name = "userImplT")
    private UserDao user;
    public void faa(){
        user.addUser();
        System.out.println(user);
    }
}

 

 

@value的使用

@Value("name值")
private String name;

 

 

IOC管理---完全注解开发

使用配置类代替xml文件,取消xml文件设置,简化文件结构,完全注解开发

在配置类中
//注解@Configuration的作用就是声明当前类为配置类等同与xml问价的作用
@Configuration
//注解@ConponentScan的作用就是声明z扫描的范围,作用跟标签<context component-scan>作用一样
@ComponentScan(value = "xlw.com")
public class ConfigUtil {
}

 

 

在test类中

使用配置类后没有了xml文件,所以要通过AnnotationConfigApplicationContext类代替ClassPathXmlApplicationContext类来解析配置类

@Test
public void testUserImpl(){
    ApplicationContext context = new AnnotationConfigApplicationContext(ConfigUtil.class);
    UserService userService = context.getBean("userService", UserService.class);
    userService.faa(
    );
}

 

 

AOP---面向切面

大致理解

面向切面【AOP】就是在不更改源代码的前提实现对内部功能的添加和更新,降低代码耦合性、提高代码复用性

 

AOP底层

1、AOP底层用了动态代理实现,动态代理有两种方式:【有接口的动态代理】、【无接口的动态代理】

A)、有接口的动态代理实现过程【JDK动态代理】

现有一个接口和一个对应的实现方法,想要对接口的方法进行增强功功能,平时会重新创建一个方法,然后添加功能。使用动态代理就能通过创建接口的实现类的代理对象,功能相同但是省去了创建【new】新的对象的过程

B)、无接口的动态代理实现过程【CGLIB动态代理】

现有一个类,希望不改动他的前提下增强功能和修改相关功能,普通方法是,创建一个子类继承,super.方法名重写方法更改和添加功能。使用动态代理,创建一个子类的代理对象。

实现有接口的实现类的方法增强【JDK动态代理】

创建一个类A和实现类AImpl,通过Proxy接口中的【newProxyInstance】方法实现建立需要增强的A的代理对象

 

在代码中

当需要对AImpl类的方法进行增强时,使用Proxy的【newProxyInstance】方法创建动态代理对象。参数介绍

ClassLoader loader【类加载器】, Class<?>[] interfaces【AImpl实现类实现的接口数组】, InvocationHandler h【接口对象】。

 

后面使用method.invoke得到要增强的类对象【AImpl】和需要用到的参数。在执行这一个代码的前后对方法进行更新增强内容。

 

这个【newProxyInstance】方法最终返回对象类型变量,将它强转为A类型的对象【a】。通过这个【a】对象调用A中的方法,填写参数,实现方法继续跑还能添加别的内容.

在method.invoke方法中可以获取方法名所以也能根据方法名做判断对应增强功能

public class JDKProxy {
    public static void main(String[] args) {
//        在newProxyInstance方法中有三个参数,第一个参数是类加载器,得到当前类
//        第二个参数是一个实现类类型的数组,它里面是需要增强的方法所在类实现的接口,可以有多个
//        第三个参数就是一个InvocationHandler接口,需要进行实现,在里面创建代理对象
        Class[] studentClass = {Student.class};
        Student o = (Student) Proxy.newProxyInstance(JDKProxy.class.getClassLoader(), studentClass, new InvocationHandler(){
            StudentImpl studenti = new StudentImpl();

            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println("在增强方法前实现的功能,待增强方法名:" + method.getName());
                Object invoke = method.invoke(studenti, args);
                System.out.println("在增强方法后实现的功能");
                return invoke;
            }
        });
        int i = o.addAchievement(1, 2);
        String name = "名字";
        o.name(name);
        System.out.println(i+name);
    }
}

 


-----------------分割线---------结果-------
在增强方法前实现的功能,待增强方法名:addAchievement
这是需要被加强的方法,执行中........
在增强方法后实现的功能
在增强方法前实现的功能,待增强方法名:name
名字这是另一个方法
在增强方法后实现的功能
3名字

 

也可以将InvocationHandler单独用一个类实现,并在类中使用method.invoke对AImpl方法进行增强
代码中
        main(

        Class[] studentClass = {Student.class};
        StudentImpl studentImpl = new StudentImpl();
        Student o = (Student) Proxy.newProxyInstance(JDKProxy.class.getClassLoader(), studentClass, new Invocation(studentImpl));
        int i = o.addAchievement(1, 2);
        String name = "名字";
        o.name(name);
        System.out.println(i+name);
    }
}
class Invocation implements InvocationHandler{

    Object object = new Object();

//    通过有参构造方法传递需要增强的方法所属类
    public Invocation(Object object) {
        this.object = object;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("这是被增强代码执行之前做的工作,增强的方法是:"+method.getName());
        Object invoke = method.invoke(object, args);
        System.out.println("这是被增强代码执行之后做的工作,增强的方法是:"+method.getName());
        return invoke;
    }
}
 

 

AOP相关术语

假设有接口A和实现类AImpl

1、连接点

Aimpl中可以被增强到的模块方法就是连接点

2、切入点

Aimpl中真正被增强的方法叫切入点

3、通知【增强】

真正被增强到的业务逻辑部分就叫通知,假如需要增强一个功能进行身份识别,这个增强进去的功能就是通知

通知有五种:

【前置通知】、在切入点之前执行的通知【增强的功能】

【后置通知】、在切入点之后执行的通知

【环绕通知】、切入点前后都有的通知

【异常通知】、当切入点出问题抛异常的通知

【最终通知】、在切入点代码块一切都执行完后【包括异常抛出】,最后也会执行这个通知。就像是try-catch-finally,finally总会在业务代码执行完后执行。

4、切面

切面是一个动作,指的是把通知应用到切入点的过程

AOP的准备工作

1、基于AspectJ实现AOP操作
2、什么是AspectJ?

他是一个独立的AOP框架,在一般情况下把AspectJ和Spring一起用进行AOP操作,有两种操作方法:注解方式和xml文件方式

3、导包

【com.springsource.net.sf.cglib-2.2.0.jar】、【com.springsource.org.aopalliance-1.0.0.jar】、【com.springsource.org.aspectj.weaver-1.6.4.RELEASE.jar】、【spring-aspects-5.2.9.RELEASE.jar】

4、切入点表达式

他的作用是告诉Spring具体增强哪一个切入点【方法】

语法结构:

execution( [权限修饰符] [返回值] [类路径] 方法名 )

AOP通过 注解方式实现

A)创建类和方法并确定切入点【要增强的方法】

B)引入命名空间,开启注解扫描

在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"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       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
       http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">


    <context:component-scan base-package="xlw.com.bean"></context:component-scan>


<!--    开启自动生成代理对象,实际上就是去扫描的组件中查看有没有注解@Aspect,有就生成代理对象-->

    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>

 

 

C)编写通知,添加注解生成bean对象和代理对象

在java类中
@Component
public class Order {
    public void page() {
        System.out.println("这是要被增强的page方法");
    }

    public int paga(int a,int b) {
        System.out.println("这是要被增强的page方法");
        return a+b;
    }
}
 

@Component
//生成代理对象
@Aspect
public class Aopanno {
//    前置通知
    @Before("execution(* xlw.com.bean.Order.page(..))")
    public void before(){
        System.out.println("这是前置通知");
    }
//    最终通知
//    ..代表参数列表
    @After("execution(* xlw.com.bean.Order.paga(..))")
    public void after(){
        System.out.println("这是最终通知");
    }
}

 


--------------------------结果---------------------------
这是要被增强的page方法
这是最终通知
3
这是前置通知
这是要被增强的page方法

 

D)开启Aspect自动生成代理对象

<!--    开启自动生成代理对象,实际上就是去扫描的组件中查看有没有注解@Aspect,有就生成代理对象-->

    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>

 

 

E)用注解设置通知类型

//    前置通知
    @Before("execution(* xlw.com.bean.Order.page(..))")
    public void before(){
        System.out.println("这是前置通知");
    }
//    最终通知
//    ..代表参数列表
    @After("execution(* xlw.com.bean.Order.paga(..))")
    public void after(){
        System.out.println("这是最终通知");
    }

 

其他通知的应用
//    环绕通知
@Around("execution(* xlw.com.bean.Order.read(..))")
public void around(ProceedingJoinPoint proceedingJoinPoint) {
    System.out.println("这是环绕通知");
    System.out.println("环绕之前");
    try {
        proceedingJoinPoint.proceed();
    } catch (Throwable throwable) {
        throwable.printStackTrace();
    }
    System.out.println("环绕之后");
}

//    错误通知
@AfterThrowing("execution(* xlw.com.bean.Order.read(..))")
public void afterThrowing() {
    System.out.println("这是代码出错通知");
}
//    后置通知
@AfterReturning("execution(* xlw.com.bean.Order.read(..))")
public void afterReturning() {
    System.out.println("这是后置通知");
}

 


--------------------------结果---------------------
这是环绕通知
环绕之前
这是实例方法,切入点
这是后置通知
环绕之后
------------------------出现异常时-------------------
这是环绕通知
环绕之前
java.lang.ArithmeticException: / by zero
这是代码出错通知
---------------------总结-------------------
可以发现在切入点执行到一半时出现了错误,在将错误抛出后执行了错误通知。并且后置通知和环绕通知的后半部分没有执行。但是最终通知会执行



AOP操作的细节

1、抽取公共的插入点表达式,使用注解【@Pointcut】

//    使用注解Pointcut可以在注解中写入公共的切入点,在添加通知的时候只需要调用注解@Piontcut下的方法就可以了
    @Pointcut("execution(* xlw.com.bean.Order.page(..))")
    public void piont(){
        
    }
    

 

2、当有多个类对同一个方法进行增强时设置优先级【@Order( 参数 )】,参数越小优先级越高

先创建bean对象,再声明要创建动态代理类,再说优先级,最后在类里面写通知类型和增强方法

@Component
@Aspect
//用来处理对于同一个切入点有多个通知时的执行顺序优先级问题,注解内加的数字越小优先级越高
@Order(-1)
public class Aopanno2 {
@Before("execution(* xlw.com.bean.Order.read())")
    public void jkl(){
        System.out.println("第二个增强类");
    }
}

 

 

使用xml文件方式实现AOP配置通知

在使用xml文件进行AOP操作时

1、创建增强类和被增强类的对象

2、以标签aop:config进行aop配置

3、以标签aop:poioncut设置切入点路径

4、在aop:aspect标签中设置通知方法和对应的切入点,aop:aspect标签属性ref用来设置增强类的路径,对应增强类的id

5、在aop:aspect标签里,设置通知的不同类型,都有标签

    <bean id="order2" class="xlw.com.bean.Order"></bean>
    <bean id="aopanno22" class="xlw.com.bean.Aopanno2"></bean>
<!--进行aop配置-->
    <aop:config>
<!--设置切入点路径-->
        <aop:pointcut id="po" expression="execution(* xlw.com.bean.Order.paga(..))"/>
<!--设置切面【将通知应用到切入点】-->
        <aop:aspect ref="aopanno22">
<!--声明需要加入的通知和要切入的切入点-->
            <aop:before method="Ggb" pointcut-ref="po"></aop:before>
        </aop:aspect>
    </aop:config>

 

完全注解开发【配置类】

使用配置类后,解析的就是配置类用【AnnotationConfigApplicationContext】

//声明为注解类
@Configuration
//开启组件扫描
@ComponentScan(basePackages = {"xlw.com.bean"})
//开启AspectJ的代理类生成
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class AOPUtil {
}

 

 

posted @ 2021-11-12 20:12  优质水  阅读(42)  评论(0)    收藏  举报