(三十六)Spring框架详细概述大家庭第二集(DI注解、IoC注解、动态静态代理)

Spring(二)

1.DI注解
   @Autowired注解细节
   @Resource注解细节
 
2.IoC注解
   Scope 和 PostConstruct 以及 PreDestroy 注解

 
3.使用DI注解与IoC注解简化模拟实例
   Spring容器可以自己存自己,为什么这样设计?
 
4.代理模式
   静态代理
   动态代理
    JDK动态代理
     CGLIB 动态代理


DI注解

@Value:注入常量值,贴字段或者setter方法

@Autowired:注入bean,贴字段或者setter方法

@Resource:找bean的方式跟Autowired不一样


@Autowired注解细节

  • 可以让Spring自动把属性或字段需要的对象找出,并注入到属性或字段上
  • 可以贴在字段或者setter方法上面,贴在字段相当于提供了(setter)
  • 可以同时注入多个对象
  • 可以注入一些 Spring 内置的重要对象,比如 BeanFactory,ApplicationContext 等
  • 默认情况下 Autowired 注解必须要能找到对应的对象,否则报错。通过 required=false 来避免这 个问题:@Autowired(required=false)
  • Autowired 注解寻找 bean 的方式
1.首先按照依赖对象找,找到,使用setter或者字段注入
2.如果在Spring上下文(ApplicationContext) 中找到多个匹配的类型,再按照名字寻找,没有匹配报错
3. 使用@Autowire自动注入的时候,加上@Qualifier(“other”)可以指定注入哪个对象;


@Resource注解细节

基本同@Autowired,找bean的方式不一样

1.首先按照名字去找,如果找到,就使用 setter 或者字段注入。
2.若按照名字找不到,再按照类型去找,但如果找到多个匹配类型,报错。
3.可以直接使用 name 属性指定 bean 的名称(@Resource(name=""));但若指定的
name,就只能按照 name 去找,若找不到,就不会再按照类型去。    

使用@Autowired注解相当于简化了xml匹配文件中的这一段话

<property name="" ref=""/>

注意:
@Setter仅仅用来生成set方法,没有注入的效果,不会给对象设置属性值或者字段值
    要想Spring给对象设置属性值或者字段值,写两种配置
    XML配置
    <property name="属性名" value="属性值"|ref="容器另外一个bean名称"/>
    注解配置
    可以贴字段或者setter方法,若是贴字段上,是可以不用提供setter方法,@Value一定要提供setter方法


IoC注解

使用IoC注解可以简化xml配置文件中的bean配置

常用的有:

  • @Repository:用于标注数据访问组件,即 DAO 实现类上。
  • @Service:用于标注业务层实现类上。
  • @Controller:用于标注控制层类上(如 SpringMVC 的 Controller)
  • @Component:当不是以上的话,可以使用这个注解进行标注。


Scope 和 PostConstruct 以及 PreDestroy 注解

  • @Scope:贴在类上,标明 bean 的作用域。
  • @PostConstruct:贴在方法上,标明 bean 创建完后调用此方法。
  • @PreDestroy:贴在方法上,标明容器销毁时调用此方法。


使用DI注解与IoC注解简化模拟实例

  • DAO层
public interface IUserDAO {
    void save(String username,String password)throws Exception;}
  • DAO实现类
@Repository //dao层实现类使用 @Repository
public class UserDAOImpl implements IUserDAO {
    private DataSource dataSource;

    //使用@Autowired注入属性
    @Autowired
    public void setDataSource(DataSource dataSource) {
        this.dataSource = dataSource;
    }

    @Override
    public void save(String username, String password) throws Exception {
        @Cleanup  //lombook的注解,关闭资源
        Connection connection = dataSource.getConnection();
        @Cleanup
        PreparedStatement preparedStatement =
        connection.prepareStatement("insert into user(username, password) VALUES (?,?)");
        preparedStatement.setObject(1,username);
        preparedStatement.setObject(2,password);
        preparedStatement.executeUpdate();
    }
}
  • Service层
public interface IUserService {
   void register(String username,String password)throws Exception;}
  • service实现类
@Service //业务层实现类使用 @Service
public class UserServiceImpl implements IUserService {
    private IUserDAO userDAO;

    @Autowired
    public void setUserDAO(IUserDAO userDAO) {
        this.userDAO = userDAO;
    }
    @Override
    public void register(String username, String password) throws Exception {
        userDAO.save(username,password);}}
  • applicationContext.xml配置文件
//头省略
    <!--引入db.properties配置文件-->
<context:property-placeholder location="db.properties"/>
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"
      init-method="init" destroy-method="close">
        <property name="driverClassName" value="${db.driverClassName}"/>
        <property name="url" value="${db.url}"/>
        <property name="username" value="${db.username}"/>
        <property name="password" value="${db.password}"/>
    </bean>
    <!--配置好IoC DI注解解析器
        让Spring 帮我们创建业务对象,DAO对象,并设置好属性值
    -->
    <context:component-scan base-package="cn.k"/>
</beans>
  • 测试类
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class UserServiceTest {
    @Autowired
    private IUserService userService;
    @Test
    public void testUser() throws Exception {
        userService.register("111","123");
    }
}

注意:记得第三方配置

 <context:component-scan base-package="贴了上面注解的类所在包路径"/>


思考

Spring容器可以自己存自己,为什么这样设计?
比如我从容器获取某种特征对象,比如贴@Controller注解所有的对象,怎么实现这个需求?
只要获取到容器对象,就可以拿到所有具有某种特征对象
    
 @Autowrited
   //容器或者应用上下文,容器获取到了容器,代表容器存到了自己 
    private ApplicationContext ctx;
    //获取容器中所有贴Controller注解的对象
    ctx.getBeansWithAnnotation(Controller.class);


代理模式

**为什么会有代理模式的理念?**例如在开发中,我们需要代码责任分离,如果一个业务层掺杂了事务的各种代码,会导致代码结构重复,责任不重复.

代理分类

  • 静态代理:程序运行前已经存在代理类(代理类自己创建)字节码文件,代理对象和真实对象的关系前运行前就确定(代理类和对象都要自己创建)

  • 动态代理:代理类是在程序运行期间由 JVM 通过反射等机制动态的生成的,所以不存在代理类的 字节码文件,动态生成字节码对象,代理对象和真实对象的关系是在程序运行时期才确定的。(即 代理类及对象不要我们自己创建)


静态代理

需求:给业务类方法添加模拟事务

  • 业务层接口与实现类
public interface IUserService {
    void save(String username,String password);
}
--------------------------------------------------------
  //真实类 真实对象 
public class UserServiceImpl implements IUserService {
    //模拟
    @Override
    public void save(String username, String password) {
        System.out.println("保存了"+username+""+password);
    }
}
  • 创建代理类
//代理类 代理对象 
public class UserServiceProxy implements IUserService {
    //与真实类保持联系
    private IUserService target;
    @Autowired
    public void setTarget(IUserService target) {
        this.target = target;}
    
    private MyTransactionManager tx;
    @Autowired
    public void setTx(MyTransactionManager tx) {
        this.tx = tx;}

    @Override
    public void save(String username, String password) {
        try {
            tx.begin();
            //调用真实对象的方法
            target.save(username,password);
            tx.commit();
        } catch (Exception e) {
            tx.rollback();}}}
  • 把事务代码抽取出来 tx
//封装事务相关的操作
public class MyTransactionManager {
    public void begin(){
        System.out.println("开启事务");
    }
    public void commit(){
        System.out.println("提交事务");
    }
    public void rollback(){
        System.out.println("回滚事务");
    }
}
  • 配置applicationContext.xml
<bean id="tx" class="cn.k.tx.MyTransactionManager"/>
    <bean id="proxy" class="cn.k.service.impl.UserServiceProxy">
        <!--配置真实对象,藏起来-->
        <property name="target">
            <bean class="cn.k.service.impl.UserServiceImpl"/>
        </property>
        <property name="tx" ref="tx"/>
    </bean>
  • 测试类
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class UserServiceTest {

    @Autowired
    private IUserService Proxy;//这里注意使用xml配置对应实现类的名字,不然会找不到,因为这里使用的是多态接口
    @Test
    public void save() {
       /* //创建房东
        IUserService userService = new UserServiceImpl();
        //创建中介
        UserServiceProxy proxy = new UserServiceProxy();
        //中介为上面房东做服务
       proxy.setTarget(userService);
       proxy.setTx(new MyTransactionManager());*/
        Proxy.save("456","4444");
    }
}


动态代理

实现方式:

  • 针对真实类有接口使用JDK动态代理
  • 针对真实类没实现接口使用CGLIB或者Javassist组件

实现机制,静态代理还是需要java文件经过编译,产生字节码文件(二进制)再通过类加载器加载到java虚拟机中,而动态代理,我们在运行期期间,遵循java编译.class文件的格式和结构,生成对应的二进制数据,直接将二进制转换成对应的类(大概理解其实就是跳过了编译生成字节码文件,直接写二进制转成对应的类)


JDK动态代理


JDK动态代理API

  • java.lang.reflect.Proxy:Java 动态代理机制生成的所有动态代理类的父类
//为指定类加载器、一组接口及调用处理器生成动态代理类实例。
public static Object newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler hanlder)
    
loader :类加载器
interfaces:代理类需要实现的接口    
handler:代理执行处理器,想让代理对象帮我们做什么自己定义
返回:创建的代理对象。
  • java.lang.reflect.InvocationHandler
//让使用者自定义做什么事情
public Object invoke(Object proxy, Method method, Object[] args)
    
proxy :生成的代理对象;
method:当前调用的真实方法对象;
args :当前调用方法的实参。
返回:真实方法的返回结果。

代码实现:

  • 创建业务层接口是实现类
public interface IUserService {
    void save(String username,String password);}
---------------------------------------
    public class UserServiceImpl implements IUserService {
    //模拟
    @Override
    public void save(String username, String password) {
        System.out.println("保存了"+username+""+password);}}
  • 把事务代码抽取出来 ,跟上述静态一样

  • 定义TransactionInvocationHandler实现InvocationHandler接口

java.lang.reflect //注意接口是这个包
public class TransactionInvocationHandler implements InvocationHandler {
    //在这里告诉生成类的对象做什么事情,下面这个方法编写由开发者根据具体需求来决定
    //调用真实对象的业务方法之前,开启事务,正常执行提交事务,若出现异常,回滚事务

    //定义存真实对象的引用
    private Object target;

    public void setTarget(Object target) {
        this.target = target;
    }

    public Object getTarget() {
        return target;
    }

    private MyTransactionManager tx;

    public void setTx(MyTransactionManager tx) {
        this.tx = tx;
    }

    @Override
    public Object invoke(Object o, Method method, Object[] args) throws Throwable {
        //反射调用真实对象的方法
        Object reVal =null;
        try {
            tx.begin();
            // 调用真实对象的方法
            reVal=method.invoke(target, args);
            tx.commit();
        } catch (Exception e) {
            tx.rollback();
        }
        return reVal;
    }
}
  • 修改applicationContext.xml配置文件
<bean id="tx" class="cn.k.tx.MyTransactionManager"/>
    <bean id="UserHandler" class="cn.k.handle.TransactionInvocationHandler">
     <property name="tx" ref="tx"/>
     <property name="target">
      <bean class="cn.k.service.impl.UserServiceImpl"/>
     </property>
    </bean>
  • 测试类
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class UserServiceTest {
    @Autowired
    private TransactionInvocationHandler UserHandler;

    @Test
    public void save() {
       /* TransactionInvocationHandler UserHandler = new TransactionInvocationHandler();
        UserHandler.setTx(new MyTransactionManager());
        UserHandler.setTarget(new UserServiceImpl()); //为哪个真实对象做代理*/

        //帮我们动态生成代理类,并创建这个类的对象,最终底层会把我们从容器中获取到UserHandler对象
        IUserService proxy = (IUserService)Proxy.newProxyInstance(
            this.getClass().getClassLoader(),//类加载器
                //生成类要实现的接口
                UserHandler.getTarget().getClass().getInterfaces(),//获取真实类实现的接口,传递过去那么生成类就要实现相同的接口
                UserHandler //指定生成类到底能做什么
        );
        //上面代码把实现类对象赋值给父类接口,下面调用方法会产生多态
        proxy.save("456","789");

    }
}

CGLIB 动态代理

API:

  • org.springframework.cglib.proxy.Enhancer:类似 JDK 中 Proxy,用来生成代理类创建代理对象的。
  • org.springframework.cglib.proxy.InvocationHandler:类似 JDK 中 InvocationHandler,让使用者自定义做什么事情

CGLIB:代理类继承真实类

实现:

  • 修改TransactionInvocationHandler类中的实现接口包名,改为 org.springframework.cglib.proxy.InvocationHandler;其他不变
  • 修改测试类
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class UserServiceTest {
    @Autowired
    private TransactionInvocationHandler UserHandler;
    @Test
    public void save() {
        Enhancer enhancer = new Enhancer();
        //设置生成代理类继承的类
        enhancer.setSuperclass(UserHandler.getTarget().getClass());
        //设置生成代理类对象
        enhancer.setCallback(UserHandler);
        //生成代理类,创建代理对象
        UserServiceImpl proxy = (UserServiceImpl)enhancer.create();
        proxy.save("456","789");
    }
}

注意与选用

JDK代理:代理类与真实类共同实现一个接口的。

CGLIB代理:代理类是继承真实类

JDK 动态代理是基于实现接口的,CGLIB 和 Javassit 是基于继承真实类的。

性能:Javassit > CGLIB > JDK。


关于JDK动态代理的原理

版本JDK11查看不了源码了,JDK8可以,ProxyGenerator 在 JDK 11 已经不是公有的了,所有emmm,我看不了,想想办法先

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值