SpringCore

反射Reflection

Student.java

public class Student {
    public String name;
    protected String gender;
    String id;
    private int grade;

    public Student(){

    }

    public Student(String name) {
        this.name = name;
    }

    private Student(String name, String gender){
        this.name = name;
        this.gender = gender;
    }

    public void study(){
        System.out.println("Studying...");
    }

    public void study(int hours){
        System.out.println("Studying " + hours + "hours");
    }

    private void sleep(){
        System.out.println("Sleeping...");
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", gender='" + gender + '\'' +
                ", id='" + id + '\'' +
                ", grade=" + grade +
                '}';
    }
}

在这里插入图片描述

public class ClassTest {
    public static void main(String[] args) throws Exception {
        /**
         * 三种方式
         * forName(String className)
         * 类名.class
         * 对象.getClass()
         */

        //forName(String className)
        Class<?> aClass = Class.forName("Note.JavaEE.reflect.Student");

        //类名.class
        Class<Student> studentClass = Student.class;

        //对象.getClass()
        Student mark = new Student("Mark");
        Class<? extends Student> aClass1 = mark.getClass();

        System.out.println(aClass == studentClass);
        System.out.println(aClass == aClass1);

    }
}

在这里插入图片描述
注意:

  • 无参构造方法可直接用Class对象的newInstance()方法进行构造
  • 当获取到的访问权限不是public时,需要使用setAccessible(true)进行暴力反射
  • 成员变量使用get,set进行取值和赋值
  • 构造方法采用newInstance()使用
  • 方法使用通过invoke()完成
public class FieldTest {
    public static void main(String[] args) throws Exception {
        Class<Student> studentClass = Student.class;
        /**
         * getFields()
         * 获取所有public属性
         */
        Field[] fields = studentClass.getFields();
        for(Field field : fields){
            System.out.println(field);
        }

        /**
         * getDeclaredFields()
         * 获取所有属性
         */
        Field[] declaredFields = studentClass.getDeclaredFields();
        for(Field field : declaredFields){
            System.out.println(field);
        }

        /**
         * getField(String name)
         * 获取指定public属性
         */
        Student student = new Student("Mark");
        Field name = studentClass.getField("name");
        Object o = name.get(student);
        System.out.println(o);

        /**
         * getDeclaredField(String name)  获取指定属性
         * setAccessible(true)  忽略安全检查(暴力反射)
         */
        Field age = studentClass.getDeclaredField("gender");
        age.setAccessible(true);
        Object o1 = age.get(student);
        System.out.println(o1);

        /**
         * set(Object o, Object value)  更改指定属性值
         */
        name.set(student, "Mike");
        System.out.println(student);
    }
}
public class ConstructorTest {
    public static void main(String[] args) throws Exception {

        Class<Student> studentClass = Student.class;
        
        //获取所有public构造器
        Constructor<?>[] constructors = studentClass.getConstructors();
        
        //获取所有构造器
        Constructor<?>[] declaredConstructors = studentClass.getDeclaredConstructors();
        
        //获取指定public构造器,传入参数类型和个数
        Constructor<Student> constructor = studentClass.getConstructor(String.class);
        Student mark = constructor.newInstance("Mark");
        
        //获取指定构造器,传入参数类型和个数
        Constructor<Student> declaredConstructor = studentClass.getDeclaredConstructor(String.class, String.class);
        declaredConstructor.setAccessible(true);
        Student student = declaredConstructor.newInstance("Mark", "Female");

        //使用无参构造器(必须是public,否则需要使用暴力反射)
        Student student1 = studentClass.newInstance();
    }
}
public class MethodTest {
    public static void main(String[] args) throws Exception {
        Class<Student> studentClass = Student.class;

        //获取所有public方法
        Method[] methods = studentClass.getMethods();

        //获取所有方法
        Method[] declaredMethods = studentClass.getDeclaredMethods();

        Student mark = new Student("Mark");
        //通过方法名以及参数获取指定public方法并使用
        Method study = studentClass.getMethod("study");
        study.invoke(mark);
        Method study1 = studentClass.getDeclaredMethod("study", int.class);
        study1.invoke(mark, 10);

        //通过方法名以及参数获取任意指定方法并使用
        Method sleep = studentClass.getDeclaredMethod("sleep");
        sleep.setAccessible(true);
        sleep.invoke(mark);

    }
}

在这里插入图片描述
配置文件 reflectTest.Properties

className=Note.JavaEE.reflect.Student
methodName =study
public class Test {
    public static void main(String[] args) throws Exception {
        Properties properties = new Properties();
        ClassLoader classLoader = Test.class.getClassLoader();
        InputStream resourceAsStream = classLoader.getResourceAsStream("reflectTest.properties");
        //加载配置文件
        properties.load(resourceAsStream);
        //获取配置文件中的内容
        String className = properties.getProperty("className");
        String methodName = properties.getProperty("methodName");
        //反射
        Class<?> aClass = Class.forName(className);
        Method method = aClass.getMethod(methodName);
        method.invoke(aClass.newInstance());
    }
}

问题1:
如何使用配置文件完成对有参数的方法的调用?
答案1:自己创建一个转换方法

private static Class getPrimitiveClass(String str) throws ClassNotFoundException {
        switch (str) {
            case "int":
                return int.class;
            case "double":
                return double.class;
            case "float":
                return float.class;
            case "char":
                return char.class;
            case "String":
                return String.class;
            default:
                return Class.forName(str);
        }
    }

注解Annotation

在这里插入图片描述
在这里插入图片描述

@SuppressWarnings("all")
public class SimpleAnnotationTest {
    @Override
    public String toString() {
        return super.toString();
    }
    @Deprecated
    public void show1(){
    }
    public void say(){
        Date date = new Date();
    }
}

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface MyAnnotation {
    //基本数据类型
    String className();
    String methodName();
    int value() default 0;
    //枚举类型
    MyEnum myEnum();
    //注解类型
    MyAnnotation2 myAnnotation();
    //以上类型数组
    int[] numbers();
}

在这里插入图片描述

@MyAnnotation(className = "Note.JavaEE.reflect.Student",methodName = "study",myEnum = MyEnum.S1,numbers = {1,2})
public class MyAnnotationTest {
    public static void main(String[] args) throws Exception {
        //采用注解替代配置文件获取类名和方法名
        Class<MyAnnotationTest> myAnnotationTestClass = MyAnnotationTest.class;
        MyAnnotation annotation = myAnnotationTestClass.getAnnotation(MyAnnotation.class);
        String className = annotation.className();
        String methodName = annotation.methodName();
        //反射
        Class<?> aClass = Class.forName(className);
        Method method = aClass.getMethod(methodName);
        method.invoke(aClass.newInstance());
    }
}

在这里插入图片描述
问题2:
如何理解解析程序?
答案2:
下面的代码中,CheckExceptionTest就是一个解析程序

public class Calculator {
    @CheckException
    public String add(){
        String s = null;
        return s.substring(1,2);
    }
    @CheckException
    public void sub(){

    }
    @CheckException
    public void multiply(){

    }
    @CheckException
    public int divide(){
        return 2/0;
    }
}
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface CheckException {
}
public class CheckExceptionTest {
    public static void main(String[] args) {
        //异常次数
        int count = 0;
        Calculator calculator = new Calculator();
        Class<Calculator> calculatorClass = Calculator.class;
        Method[] methods = calculatorClass.getMethods();
        for (Method method : methods) {
            if(method.isAnnotationPresent(CheckException.class)){
                try{
                    method.invoke(calculator);
                }catch (Exception e){
                    System.out.println(e.getCause().getMessage());
                    count++;
                }
            }
        }
        System.out.println("一共出现"+count+"次异常");
    }
}

JDBC

在这里插入图片描述
在这里插入图片描述

Tips:不要忘记Add as Library在这里插入图片描述
在这里插入图片描述

public class JDBCDemo {
    public static void main(String[] args) {
        Connection connection = null;
        PreparedStatement preparedStatement = null;
        ResultSet resultSet = null;
        try {
            Class.forName("com.mysql.jdbc.Driver");
            connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf-8","root", "root");
            String sql = "select * from user where username = ?";
            preparedStatement = connection.prepareStatement(sql);
            preparedStatement.setString(1,"Joe");
            resultSet = preparedStatement.executeQuery();
            while(resultSet.next()){
                System.out.println(resultSet.getString("id")+"  "+resultSet.getString("username"));
            }
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            if(resultSet != null){
                try {
                    resultSet.close();
                } catch (SQLException throwables) {
                    throwables.printStackTrace();
                }
            }
            if(preparedStatement!=null){
                try {
                    preparedStatement.close();
                } catch (SQLException throwables) {
                    throwables.printStackTrace();
                }
            }
            if(connection!=null){
                try {
                    connection.close();
                } catch (SQLException throwables) {
                    throwables.printStackTrace();
                }
            }
        }
    }
}
2  Joe

Process finished with exit code 0

数据库链接池
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

<c3p0-config>
    <default-config>
        <property name="driverClass">com.mysql.jdbc.Driver</property>
        <property name="jdbcUrl">jdbc:mysql://localhost:3306/mybatis</property>
        <property name="user">root</property>
        <property name="password">root</property>

        <property name="initialPoolSize">5</property>
        <property name="maxPoolSize">10</property>
        <property name="checkoutTimeout">3000</property>
    </default-config>
    <named-config name="tododb">
        <property name="driverClass">com.mysql.jdbc.Driver</property>
        <property name="jdbcUrl">jdbc:mysql://localhost:3306/tododb</property>
        <property name="user">root</property>
        <property name="password">root</property>

        <property name="initialPoolSize">5</property>
        <property name="maxPoolSize">8</property>
        <property name="checkoutTimeout">1000</property>
    </named-config>
</c3p0-config>

其中如下部分配置可以缺省,存在默认值

<property name="initialPoolSize">5</property>
<property name="maxPoolSize">8</property>
<property name="checkoutTimeout">1000</property>

#初始化时获取连接数,取值应在minPoolSize与maxPoolSize之间。默认为: 3
c3p0.initialPoolSize=5
#连接池中保留的最小连接数,默认为:3
c3p0.minPoolSize=3
#连接池中保留的最大连接数。默认值: 15
c3p0.maxPoolSize=8
#获取一个connection需要的时间,单位毫秒。超过这个时间则超时。
c3p0.checkoutTimeout=3000

public class c3p0Demo {
    public static void main(String[] args) throws SQLException {
        //如果configName为空,或者不存在名字,则创建默认链接
        DataSource ds = new ComboPooledDataSource("tododb");
        Connection conn;
        for (int i=0; i<=11; i++){
            conn = ds.getConnection();
            System.out.println(conn);
            if(i==5){
                conn.close();
            }
        }
    }
}

输出:第6、8个connection是一样的,因为在第6个close归还了
由于配置文件中设置最大连接数为8,所以共有8个不同的连接,第9个超时

com.mchange.v2.c3p0.impl.NewProxyConnection@5fe5c6f [wrapping: com.mysql.jdbc.JDBC4Connection@6979e8cb]
com.mchange.v2.c3p0.impl.NewProxyConnection@5c0369c4 [wrapping: com.mysql.jdbc.JDBC4Connection@2be94b0f]
com.mchange.v2.c3p0.impl.NewProxyConnection@17ed40e0 [wrapping: com.mysql.jdbc.JDBC4Connection@50675690]
com.mchange.v2.c3p0.impl.NewProxyConnection@3ac42916 [wrapping: com.mysql.jdbc.JDBC4Connection@47d384ee]
com.mchange.v2.c3p0.impl.NewProxyConnection@22a71081 [wrapping: com.mysql.jdbc.JDBC4Connection@3930015a]
com.mchange.v2.c3p0.impl.NewProxyConnection@1bc6a36e [wrapping: com.mysql.jdbc.JDBC4Connection@1ff8b8f]
com.mchange.v2.c3p0.impl.NewProxyConnection@71e7a66b [wrapping: com.mysql.jdbc.JDBC4Connection@2ac1fdc4]
com.mchange.v2.c3p0.impl.NewProxyConnection@1c53fd30 [wrapping: com.mysql.jdbc.JDBC4Connection@1ff8b8f]
com.mchange.v2.c3p0.impl.NewProxyConnection@75412c2f [wrapping: com.mysql.jdbc.JDBC4Connection@282ba1e]
Exception in thread "main" java.sql.SQLException: An attempt by a client to checkout a Connection has timed out.
	at com.mchange.v2.sql.SqlUtils.toSQLException(SqlUtils.java:118)
	at com.mchange.v2.sql.SqlUtils.toSQLException(SqlUtils.java:77)
	at com.mchange.v2.c3p0.impl.C3P0PooledConnectionPool.checkoutPooledConnection(C3P0PooledConnectionPool.java:690)
	at com.mchange.v2.c3p0.impl.AbstractPoolBackedDataSource.getConnection(AbstractPoolBackedDataSource.java:140)
	at c3p0Demo.main(c3p0Demo.java:14)
Caused by: com.mchange.v2.resourcepool.TimeoutException: A client timed out while waiting to acquire a resource from com.mchange.v2.resourcepool.BasicResourcePool@6f79caec -- timeout at awaitAvailable()
	at com.mchange.v2.resourcepool.BasicResourcePool.awaitAvailable(BasicResourcePool.java:1505)
	at com.mchange.v2.resourcepool.BasicResourcePool.prelimCheckoutResource(BasicResourcePool.java:644)
	at com.mchange.v2.resourcepool.BasicResourcePool.checkoutResource(BasicResourcePool.java:554)
	at com.mchange.v2.c3p0.impl.C3P0PooledConnectionPool.checkoutAndMarkConnectionInUse(C3P0PooledConnectionPool.java:758)
	at com.mchange.v2.c3p0.impl.C3P0PooledConnectionPool.checkoutPooledConnection(C3P0PooledConnectionPool.java:685)
	... 2 more

在这里插入图片描述
配置文件
在这里插入图片描述
在这里插入图片描述
创建DBUtils工具类
创建成员属性DataSource ds,将加载配置文件放在静态代码块内对ds赋值,创建静态方法getConnection()、close()、getDataSource()
在这里插入图片描述
在这里插入图片描述
释放资源还可以重载一个方法,当需要prepareStatement时,此处省略
在这里插入图片描述
在这里插入图片描述
JDBCTemplate
将不再需要手动释放资源,提供了方便的数据库操作的接口,只需要将sql作为参数传递即可
在这里插入图片描述
JDBCTemplate详细示例

IOC

bean的作用范围:
默认为单例
scope属性:
指定bean的作用范围
singleton:单例
prototype:多例
request: 作用于web应用的请求范围
session: 作用于web应用的会话范围
global-session:作用域集群环境的会话范围,当不是集群环境,就是session
bean对象的生命周期:
单例:
生命周期和容器相同
多例:
出生:使用对象时才创建
活着:对象在使用过程中一直活着
死亡:垃圾回收

DI

基于注解的IOC

XML注解
配置简单,维护方便 (我们找到类,就相当于找到了对应的配置)修改时,不用改源码。不涉及重新编译和部署

目标:
通过注解达到和XML一样的配置效果

XML配置的基本格式:

<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl"
scope="" init-method="" destroy-method="">
	<property name="" value="" | ref=""></property>
</bean>
创建对象依赖注入作用范围生命周期
XMLbean标签property标签scope属性init/destroy-method
Annotation@Component @Repository @Controller @Service@Autowired @Qualifier @Resource @Value@Scope@PostConstruct @PreDestroy

创建对象:
@Component
作用:把当前类对象存入spring容器
属性:value=”“ 指定bean的id,默认为类名首字母小写
出现位置:类的开头

  • 如果只存在有参构造方法,参数会按照@Autowired方式赋值
  • 如果同时存在有参和无参构造方法,默认调用无参构造方法

以下三个注解与Component功能一样,用于区分三层结构
@Controller:表现层
@Service:业务层
@Repository:持久层

数据注入:
@Autowired
作用:按照类型注入,容器中有唯一的对象与注入类型相同,则注入成功
支持类型:其他bean类型
出现位置:属性之前

@Qualifier
作用:当容器中有多个相同类型的bean时,可指定id
支持类型:其他bean类型
属性:value:指定bean的id
出现位置:

  • 属性之前:必须与@Autowired搭配使用
  • 方法参数:可单独存在

@Resource
作用:直接按照 Bean 的 id 注入。
支持类型:其他bean类型
属性:name:指定 bean 的 id。
出现位置:类之前

  • 该注解并非spring框架中的
  • import javax.annotation.Resource;

@Value
作用:注入基本数据类型和 String 类型数据的关键字
属性:value:用于指定值
出现位置:

  • 属性之前
  • 方法参数

作用范围:

@Scope
作用:指定bean的作用范围
属性:value:指定范围的值

  • singleton
  • prototype
  • request
  • session
  • global-session

出现位置:类之前

生命周期:

@PostConstruct
作用:指定初始化方法
出现位置:方法之前
执行时间:构造方法之后

@PreDestroy
作用:指定销毁方法
出现位置:方法之前
执行时间:对象销毁之前

纯注解配置

上述基于注解的IOC仍存在bean.xml配置文件,并且在数据库配置时也不得不使用XML

bean.xml

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

数据库配置

<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
	<property name="driverClass">com.mysql.jdbc.Driver</property>
    <property name="jdbcUrl">jdbc:mysql://localhost:3306/mybatis</property>
    <property name="user">root</property>
    <property name="password">root</property>
</bean>

我们希望可以不依赖于bean.xml,完全采用注解来完成全部的配置。

XMLbean.xmlbean标签<context:component-scan base-package=“com.itheima”></context:component-scan>
注解@Configuration@Bean@ComponentScan

@Configuration
作用:指定当前类是一个 spring 配置类
位置:类之前

@ComponentScan
作用:指定 spring 在初始化容器时要扫描的包
属性:basePackages/value:指定要扫描的包
位置:类之前

@Bean
作用:创建一个对象,并且放入 spring 容器。
属性:name:指定bean的id
位置:方法之前

@PropertySource
作用:加载.properties文件中的配置
属性:指定 properties 文件位置,如果是在类路径下,需要写上"classpath:"
位置:类之前

@Import
作用:加载其他java文件中的配置(都写在一个配置文件中太长)
属性:value:指定其他java配置类模板
位置:类之前

示例:

#jdbcConfig.properties
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/eesy
jdbc.username=root
jdbc.password=1234
/**
 * 和spring连接数据库相关的配置类
 */
public class JdbcConfig {

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

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

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

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

    /**
     * 用于创建一个QueryRunner对象
     * @param dataSource
     * @return
     */
    @Bean(name="runner")
    @Scope("prototype")
    public QueryRunner createQueryRunner(@Qualifier("ds2") DataSource dataSource){
        return new QueryRunner(dataSource);
    }

    /**
     * 创建数据源对象
     * @return
     */
    @Bean(name="ds2")
    public DataSource createDataSource(){
        try {
            ComboPooledDataSource ds = new ComboPooledDataSource();
            ds.setDriverClass(driver);
            ds.setJdbcUrl(url);
            ds.setUser(username);
            ds.setPassword(password);
            return ds;
        }catch (Exception e){
            throw new RuntimeException(e);
        }
    }

    @Bean(name="ds1")
    public DataSource createDataSource1(){
        try {
            ComboPooledDataSource ds = new ComboPooledDataSource();
            ds.setDriverClass(driver);
            ds.setJdbcUrl("jdbc:mysql://localhost:3306/eesy02");
            ds.setUser(username);
            ds.setPassword(password);
            return ds;
        }catch (Exception e){
            throw new RuntimeException(e);
        }
    }
}
@Configuration
@ComponentScan("com.itheima")
@Import(JdbcConfig.class)
@PropertySource("classpath:jdbcConfig.properties")
public class SpringConfiguration {


}

获取容器:

ApplicationContext ac = new AnnotationConfigApplicationContext(SpringConfiguration.class);

注意:在实际写代码的过程中,我们发现即使省略@Configuration注解也能正常运行,但实际上是有区别的,具体情况可参考https://cloud.tencent.com/developer/article/1657369

Spring 整合 Junit

经过了上述的各种测试后,我们会发现测试方法中总会出现这样一段代码:

ApplicationContext ac = new AnnotationConfigApplicationContext(JdbcConfig.class);
as  = ac.getBean("accountServiceImpl", IAccountService.class);

对于测试者来说,这段代码不应该由他们来完成,这应该是开发者的职责。因此Spring框架提供了一个运行器,可以读取配置文件(或注解)来创建容器。

1. 导入依赖

		<dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>5.0.2.RELEASE</version>
        </dependency>

2. 使用@RunWith 注解替换原有运行器

在测试类的前面加上这段注解:

@RunWith(SpringJUnit4ClassRunner.class)

3. 使用@ContextConfiguration 指定 spring 配置文件或注解的位置

在测试类前面加上这段注解:

配置文件

@ContextConfiguration(locations = "classpath:bean.xml")

注解

@ContextConfiguration(classes = JdbcConfig.class)

4. 使用@Autowired 给测试类中的变量注入数据

示例:

@RunWith(SpringJUnit4ClassRunner.class)
//@ContextConfiguration(locations = "classpath:bean.xml")
@ContextConfiguration(classes = JdbcConfig.class)
public class AccountServiceTest {
    @Autowired
    private IAccountService as;

    @Test
    public void testFindAll() throws SQLException {
        as.findAll();
    }
}

代理

代理模式在 Java 开发中是一种比较常见的设计模式。设计目的旨在为服务类与客户类之间插入其他功能,插入的功能对于调用者是透明的,起到伪装控制的作用。如租房的例子:房客、中介、房东。对应于代理模式中即:客户类、代理类 、委托类(被代理类)。

  • JAVA静态代理

是指由程序员创建或工具生成的代理类,这个类在编译期就已经是确定了的,存在的。

  • JAVA动态代理

运行期利用JVM的反射机制生成代理类,这里是直接生成类的字节码,然后通过类加载器载入JAVA虚拟机执行。

相比于静态代理,动态代理在创建代理对象上更加的灵活,动态代理类的字节码在程序运行时,由Java反射机制动态产生。它会根据需要,通过反射机制在程序运行期,动态的为目标对象创建代理对象,无需程序员手动编写它的源代码。动态代理不仅简化了编程工作,而且提高了软件系统的可扩展性,因为反射机制可以生成任意类型的动态代理类。代理的行为可以代理多个方法,即满足生产需要的同时又达到代码通用的目的。

图例:
在这里插入图片描述

JDK动态代理

基于接口的动态代理;被代理对象至少存在一个接口

  1. 创建被代理对象接口以及被代理对象
public interface IProducer {

    /**
     * 销售
     * @param money
     */
    public void saleProduct(float money);

    /**
     * 售后
     * @param money
     */
    public void afterService(float money);
}
public class Producer implements IProducer{

    /**
     * 销售
     * @param money
     */
    public void saleProduct(float money){
        System.out.println("销售产品,并拿到钱:"+money);
    }

    /**
     * 售后
     * @param money
     */
    public void afterService(float money){
        System.out.println("提供售后服务,并拿到钱:"+money);
    }
}
  1. 创建代理对象,并实现匿名内部类
public class Client {

    public static void main(String[] args) {
        final Producer producer = new Producer();
       IProducer proxyProducer = (IProducer) Proxy.newProxyInstance(producer.getClass().getClassLoader(),
                producer.getClass().getInterfaces(),
                new InvocationHandler() {
                    /**
                     * 作用:执行被代理对象的任何接口方法都会经过该方法
                     * 方法参数的含义
                     * @param proxy   代理对象的引用
                     * @param method  当前执行的方法
                     * @param args    当前执行方法所需的参数
                     * @return        和被代理对象方法有相同的返回值
                     * @throws Throwable
                     */
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        //提供增强的代码
                        Object returnValue = null;

                        //1.获取方法执行的参数
                        Float money = (Float)args[0];
                        //2.判断当前方法是不是销售
                        if("saleProduct".equals(method.getName())) {
                            returnValue = method.invoke(producer, money*0.8f);
                        }
                        return returnValue;
                    }
                });
        proxyProducer.saleProduct(10000f);
    }
}

CGLIB动态代理

cglib是针对类来实现代理的,它的原理是对指定的目标类生成一个子类,并覆盖其中方法实现增强,但因为采用的是继承,所以不能对final修饰的类进行代理。

  1. 导入cglib坐标
    由于使用的是第三方库cglib,需要maven坐标导入
<dependency>
            <groupId>cglib</groupId>
            <artifactId>cglib</artifactId>
            <version>2.1_3</version>
</dependency>
  1. 动态创建代理对象,采用匿名内部类
public class Client {

    public static void main(String[] args) {
        final Producer producer = new Producer();
        Producer cglibProducer = (Producer)Enhancer.create(producer.getClass(), new MethodInterceptor() {
            /**
             * 执行被代理的任何方法都会经过该方法
             * @param proxy
             * @param method
             * @param args
             *    以上三个参数和基于接口的动态代理中invoke方法的参数是一样的
             * @param methodProxy :当前执行方法的代理对象
             * @return
             * @throws Throwable
             */
            @Override
            public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
                //提供增强的代码
                Object returnValue = null;

                //1.获取方法执行的参数
                Float money = (Float)args[0];
                //2.判断当前方法是不是销售
                if("saleProduct".equals(method.getName())) {
                    returnValue = method.invoke(producer, money*0.8f);
                }
                return returnValue;
            }
        });
        cglibProducer.saleProduct(10000f);
    }
}

两种动态代理的区别:

JDK动态代理(目标对象存在接口时)执行效率高于Cglib
如果目标对象有接口实现,选择JDK代理,如果没有接口实现选择Cglib代理

JDK与CGLIB效率对比以及分析 https://blog.csdn.net/bibiboyx/article/details/109045613

我们来分析一下两种动态代理的底层实现逻辑

  • JDK动态代理
    动态生成的代理类$Proxy0.class继承自Proxy.class,Proxy类的构造方法对成员变量invocationHandler进行初始化,而自定义的匿名内部类对象对其进行初始化。
    而$Proxy0.class在创建时利用的是producer.getClass().getInterfaces(),即接口的类模板。
    也就是说动态创建的代理对象是该接口的一个实现类。
    由此我们可以猜测,在动态创建代理时,接口的每个方法实现了以下这段代码:
invocationHandler.invoke(this,当前执行方法,参数);

为了验证这个猜想,我们可以反编译$Proxy0.class:
在这里插入图片描述
具体详细内容可参考https://cloud.tencent.com/developer/article/1638472
这篇文章把两种动态代理讲得很清楚!!!

  • CGLIB动态代理
    生成一个被代理对象的子类,该子类的每一个方法调用MethodInterceptor对象的intercept方法

MethodProxy是intercept()方法中的第四个参数的类型,它是实际类方法的代理引用,使用methodProxy比使用jdk自身的method在效率上会有提升

MethodProxy的使用可以参考https://blog.csdn.net/psd0503/article/details/107116881

此外,cglib可以采用回调过滤器 CallbackFilter,它在拦截目标对象的方法时,可以有选择性的执行方法拦截,也就是选择被代理方法的增强处理。

public class TargetCallbackFilter implements CallbackFilter {
    @Override
    public int accept(Method method) {
        if ("hobby".equals(method.getName()))
            return 1;
        else
            return 0;
    }
}

具体详细内容可参考https://cloud.tencent.com/developer/article/1638472

CGLIB 动态代理是通过配置目标类信息,然后利用 ASM 字节码框架进行生成目标类的子类。
ASM字节码操纵框架https://www.jianshu.com/p/a1e6b3abd789

学习完CGLIB,我们可以回头再去分析@configuration配置类https://blog.csdn.net/qq_15719169/article/details/119579612

Spring中的AOP

相关术语

  • 连接点

被代理类中可以被增强的方法

  • 切入点

被代理类中被增强的方法

  • 通知

拦截到切入点后要做的事情,即增强的内容 通知类型:前置通知,后置通知,异常通知,最终通知,环绕通知

  • 目标对象(Target)

被代理的对象

  • 织入(Weaving)

是指把增强应用到目标对象来创建新的代理对象的过程。

  • 代理(Proxy)

一个类被 AOP 织入增强后,就产生一个结果代理类。

  • 切面(Aspect)

是切入点和通知(引介)的结合

基于XML的AOP配置

  1. 创建接口和被代理类
public interface IAccountService {
   void saveAccount();
   void updateAccount(int i);
   int  deleteAccount();
}
public class AccountServiceImpl implements IAccountService{

    @Override
    public void saveAccount() {
        System.out.println("执行了保存");
    }

    @Override
    public void updateAccount(int i) {
        System.out.println("执行了更新"+i);

    }

    @Override
    public int deleteAccount() {
        System.out.println("执行了删除");
        return 0;
    }
}

通知类:

public class Logger {

    /**
     * 用于打印日志:计划让其在切入点方法执行之前执行(切入点方法就是业务层方法)
     */
    public  void printLog(){
        System.out.println("Logger类中的pringLog方法开始记录日志了。。。");
    }
}

  1. 导入maven坐标
		<dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.0.2.RELEASE</version>
        </dependency>

        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.8.7</version>
        </dependency>
  1. 配置bean.xml

命名空间

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

配置Ioc

    <bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl"></bean>

配置Aop

  1. 配置通知类
  2. 配置切面以及切入点
<!-- 配置Logger类 -->
    <bean id="logger" class="com.itheima.utils.Logger"></bean>

    <!--配置AOP-->
    <aop:config>
        <!--配置切面 -->
        <aop:aspect id="logAdvice" ref="logger">
            <!-- 配置通知的类型,并且建立通知方法和切入点方法的关联-->
            <aop:before method="printLog" pointcut="execution(* com.itheima.service.impl.*.*(..))"></aop:before>
        </aop:aspect>
    </aop:config>

使用aop:aspect标签表明配置切面
id属性:是给切面提供一个唯一标识
ref属性:是指定通知类bean的Id。

在aop:aspect标签的内部使用对应标签来配置通知的类型
我们现在示例是让printLog方法在切入点方法执行之前之前:所以是前置通知
aop:before:表示配置前置通知
method属性:用于指定Logger类中哪个方法是前置通知
pointcut属性:用于指定切入点表达式,该表达式的含义指的是对业务层中哪些方法增强

切入点表达式的写法:
关键字:execution(表达式)
表达式:
访问修饰符 返回值 包名.包名.包名…类名.方法名(参数列表)
标准的表达式写法:
public void com.itheima.service.impl.AccountServiceImpl.saveAccount()
访问修饰符可以省略
void com.itheima.service.impl.AccountServiceImpl.saveAccount()
返回值可以使用通配符,表示任意返回值
* com.itheima.service.impl.AccountServiceImpl.saveAccount()
包名可以使用通配符,表示任意包。但是有几级包,就需要写几个*.
* ....AccountServiceImpl.saveAccount())
包名可以使用…表示当前包及其子包
* …AccountServiceImpl.saveAccount()
类名和方法名都可以使用
来实现通配
* .()
参数列表:
可以直接写数据类型:
基本类型直接写名称 int
引用类型写包名.类名的方式 java.lang.String
可以使用通配符表示任意类型,但是必须有参数
可以使用…表示有无参数均可,有参数可以是任意类型
全通配写法:
* .
(…)
实际开发中切入点表达式的通常写法:
切到业务层实现类下的所有方法
* com.itheima.service.impl..(…)

其他通知类型切面配置

aop:pointcut:
作用:
用于配置切入点表达式。就是指定对哪些类的哪些方法进行增强。
属性:
expression:用于定义切入点表达式。
id:用于给切入点表达式提供一个唯一标识
<aop:pointcut expression=“execution(
public void com.itheima.service.impl.AccountServiceImpl.transfer(
java.lang.String, java.lang.String, java.lang.Float) )” id=“pt1”/>
aop:after-returning
作用:
用于配置后置通知
属性:
method:指定通知中方法的名称。
pointct:定义切入点表达式
pointcut-ref:指定切入点表达式的引用
执行时间点:
切入点方法正常执行之后。它和异常通知只能有一个执行
<aop:after-returning method=“commit” pointcut-ref=“pt1”/>
aop:after-throwing
作用:
用于配置异常通知
属性:
method:指定通知中方法的名称。
pointct:定义切入点表达式
pointcut-ref:指定切入点表达式的引用
执行时间点:
切入点方法执行产生异常后执行。它和后置通知只能执行一个
<aop:after-throwing method=“rollback” pointcut-ref=“pt1”/>
aop:after
作用:
用于配置最终通知
属性:
method:指定通知中方法的名称。
pointct:定义切入点表达式
pointcut-ref:指定切入点表达式的引用
执行时间点:
无论切入点方法执行时是否有异常,它都会在其后面执行。
<aop:after method=“release” pointcut-ref=“pt1”/>

环绕通知

<aop:around method="aroundPringLog" pointcut-ref="pt1"></aop:around>

环绕通知方法中手动插入代码,表明切入点所在位置:
接口ProceedingJoinPoint可以作为环绕通知的方法参数,并且通过proceed()方法表明切入点位置

public Object aroundPringLog(ProceedingJoinPoint pjp){
        Object rtValue = null;
        try{
            Object[] args = pjp.getArgs();//得到方法执行所需的参数

            System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。前置");

            rtValue = pjp.proceed(args);//明确调用业务层方法(切入点方法)

            System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。后置");

            return rtValue;
        }catch (Throwable t){
            System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。异常");
            throw new RuntimeException(t);
        }finally {
            System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。最终");
        }
    }
  1. 编写测试类
public class AOPTest {
    public static void main(String[] args) {
        ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
        IAccountService as = (IAccountService)ac.getBean("accountService");
        as.saveAccount();
        as.updateAccount(1);
        as.deleteAccount();
    }
}
  1. 运行结果
Logger类中的pringLog方法开始记录日志了。。。
执行了保存
Logger类中的pringLog方法开始记录日志了。。。
执行了更新1
Logger类中的pringLog方法开始记录日志了。。。
执行了删除

基于注解的AOP配置

  1. 导入maven坐标
		<dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.0.2.RELEASE</version>
        </dependency>

        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.8.7</version>
        </dependency>
  1. 配置beam.xml
    命名空间(aop,context)
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:aop="http://www.springframework.org/schema/aop"
       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/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd">

配置spring创建容器时要扫描的包

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

配置spring开启注解AOP的支持

    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
  1. 对被代理类添加注解配置
@Service("accountService")
public class AccountServiceImpl implements IAccountService{

    @Override
    public void saveAccount() {
        System.out.println("执行了保存");
        int i=1/0;
    }

    @Override
    public void updateAccount(int i) {
        System.out.println("执行了更新"+i);

    }

    @Override
    public int deleteAccount() {
        System.out.println("执行了删除");
        return 0;
    }
}
  1. 对通知类进行注解配置

    对类进行注解配置|对增强方法注解配置

@Component("logger")
@Aspect//表示当前类是一个切面类
public class Logger {

    @Pointcut("execution(* com.itheima.service.impl.*.*(..))")
    private void pt1(){}

//    @Before("pt1()")
    public  void beforePrintLog(){
        System.out.println("前置通知Logger类中的beforePrintLog方法开始记录日志了。。。");
    }

//    @AfterReturning("pt1()")
    public  void afterReturningPrintLog(){
        System.out.println("后置通知Logger类中的afterReturningPrintLog方法开始记录日志了。。。");
    }

//    @AfterThrowing("pt1()")
    public  void afterThrowingPrintLog(){
        System.out.println("异常通知Logger类中的afterThrowingPrintLog方法开始记录日志了。。。");
    }

//    @After("pt1()")
    public  void afterPrintLog(){
        System.out.println("最终通知Logger类中的afterPrintLog方法开始记录日志了。。。");
    }

    @Around("pt1()")
    public Object aroundPringLog(ProceedingJoinPoint pjp){
        Object rtValue = null;
        try{
            Object[] args = pjp.getArgs();//得到方法执行所需的参数

            System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。前置");

            rtValue = pjp.proceed(args);//明确调用业务层方法(切入点方法)

            System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。后置");

            return rtValue;
        }catch (Throwable t){
            System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。异常");
            throw new RuntimeException(t);
        }finally {
            System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。最终");
        }
    }
  1. 编写测试类
public class AOPTest {
    public static void main(String[] args) {
        ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
        IAccountService as = (IAccountService)ac.getBean("accountService");
        as.saveAccount();
    }
}

MyBatis

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值